优化驾驶舱页面

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 treRef = ref()
const changeDeviceType = (val: any, obj: any) => {
console.log("🚀 ~ changeDeviceType ~ val:", val,obj)
console.log('🚀 ~ changeDeviceType ~ val:', val, obj)
emit('deviceTypeChange', val, obj)
}
getDeviceTree().then(res => {
@@ -102,11 +102,12 @@ 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
nextTick(() => {
if (arr.length) {
setTimeout(() => {
if (arr.length > 0) {
treRef.value.treeRef1.setCurrentKey(arr[0].id)
// 注册父组件事件
emit('init', {
@@ -114,8 +115,7 @@ getDeviceTree().then(res => {
...arr[0]
})
return
}
if (arr2.length) {
} else if (arr2.length > 0) {
treRef.value.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件
emit('init', {
@@ -123,8 +123,7 @@ getDeviceTree().then(res => {
...arr2[0]
})
return
}
if (arr3.length) {
} else if (arr3.length > 0) {
treRef.value.treeRef3.setCurrentKey(arr3[0].id)
// 注册父组件事件
emit('init', {
@@ -132,11 +131,11 @@ getDeviceTree().then(res => {
...arr3[0]
})
return
}
else {
} else {
emit('init')
return
}
}, 500)
})
})
const handleCheckChange = (data: any, checked: any, indeterminate: any) => {

View File

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

View File

@@ -1,5 +1,6 @@
<template>
<div class="nav-tabs" ref="tabScrollbarRef">
<div
v-for="(item, idx) in navTabs.state.tabsView"
@click="onTab(item)"
@@ -71,8 +72,11 @@ const onTab = (menu: RouteLocationNormalized) => {
}
const onContextmenu = (menu: RouteLocationNormalized, el: MouseEvent) => {
// 禁用刷新
state.contextmenuItems[0].disabled = route.path !== menu.path
// 禁用关闭其他和关闭全部
state.contextmenuItems[4].disabled = state.contextmenuItems[3].disabled =
navTabs.state.tabsView.length == 1 ? true : false

View File

@@ -4,6 +4,7 @@ import { STORE_TAB_VIEW_CONFIG } from '@/stores/constant/cacheKey'
import type { NavTabs } from '@/stores/interface/index'
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
import { adminBaseRoutePath } from '@/router/static'
import { set } from 'lodash'
export const useNavTabs = defineStore(
'navTabs',
@@ -20,15 +21,17 @@ export const useNavTabs = defineStore(
// 从后台加载到的菜单路由列表
tabsViewRoutes: [],
// 按钮权限节点
authNode: new Map(),
authNode: new Map()
})
function addTab(route: RouteLocationNormalized) {
console.log('🚀 ~ addTab ~ route:', route)
if (!route.meta.addtab) return
for (const key in state.tabsView) {
if (state.tabsView[key].path === route.path) {
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
return
}
}
@@ -81,21 +84,79 @@ export const useNavTabs = defineStore(
state.tabFullScreen = fullScreen
}
return { state, addTab, closeTab, closeTabs, setActiveRoute, setTabsViewRoutes, setAuthNode, fillAuthNode, setFullScreen }
const refresh = () => {
// setTimeout(() => {
// console.log(123, state.tabsViewRoutes)
let list = matchAndReturnRouteData(state.tabsViewRoutes, state.tabsView)
state.tabsView = []
list.forEach(item => {
addTab(item)
})
// }, 1000)
}
return {
state,
addTab,
closeTab,
closeTabs,
setActiveRoute,
setTabsViewRoutes,
setAuthNode,
fillAuthNode,
setFullScreen,
refresh
}
},
{
persist: {
key: STORE_TAB_VIEW_CONFIG,
paths: ['state.tabFullScreen'],
},
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) => {
data.forEach(item => {
if (item.meta?.menu_type == 'iframe') {
item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path)
}

View File

@@ -839,6 +839,7 @@ const lineId: any = ref('')
const dataLevel: any = ref('')
const dataSource = ref([])
const nodeClick = async (e: anyObj) => {
console.log("🚀 ~ nodeClick ~ e:", e)
if (e == undefined || e.level == 2) {
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>
<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">
<el-button icon="el-icon-Plus" type="primary" @click="add">新增</el-button>
</div>
@@ -52,12 +52,13 @@ import { defaultAttribute } from '@/components/table/defaultAttribute'
import { getDashboardPageByUserId, deleteDashboard, activatePage } from '@/api/system-boot/csstatisticalset'
import { useAdminInfo } from '@/stores/adminInfo'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useNavTabs } from '@/stores/navTabs'
import { getMenu } from '@/utils/router'
const { push } = useRouter()
const dialogVisible = ref(false)
const route = useRouter()
const navTabs = useNavTabs()
const adminInfo = useAdminInfo()
const pageList: any = ref([])
@@ -92,7 +93,7 @@ const beforeChange = (row: any): Promise<boolean> => {
type: 'warning'
})
.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') {
ElMessage({
type: 'success',
@@ -101,7 +102,11 @@ const beforeChange = (row: any): Promise<boolean> => {
}
init()
resolve(true)
getMenu()
await getMenu()
await setTimeout(() => {
navTabs.refresh()
}, 1000)
})
})
.catch(() => {

View File

@@ -2,7 +2,7 @@
<div class="default-main">
<TableHeader :showSearch="false" v-show="flag">
<template v-slot:select>
<el-form-item label="日期">
<el-form-item label="日期" v-show="layout?.length != 1">
<DatePicker
ref="datePickerRef"
:nextFlag="false"
@@ -13,7 +13,14 @@
</template>
<template v-slot:operation>
<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>
</TableHeader>
<GridLayout
@@ -221,6 +228,10 @@ const fetchLayoutData = async () => {
component: registerComponent(item.path)
}))
layoutCopy.value = JSON.parse(JSON.stringify(layout.value))
if (layout.value.length == 1) {
setZoom(layout.value[0])
}
initRowHeight()
} catch (error) {
console.error('获取布局数据失败:', error)

View File

@@ -21,21 +21,30 @@
@load="onIframeLoad"
></iframe>
</div>
<el-card class="bottom-container" style="min-height: 230px">
</div>
<div class="bottom-container">
<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>
</div> -->
<div class="tableBox">
<!-- <Table ref="tableRef" height="100%"></Table> -->
<vxe-table border auto-resize height="100%" :data="tableData" ref="tableRef">
<transition name="table-fade">
<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 field="date" 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-table>
</div>
</el-card>
</transition>
</div>
</div>
</template>
@@ -49,7 +58,7 @@ import { mainHeight } from '@/utils/layout'
// const props = defineProps<{
// project: { id: string; name: string } | null
// }>()
const show = ref(false)
const prop = defineProps({
width: { type: [String, Number] },
height: { type: [String, Number] },
@@ -57,7 +66,9 @@ const prop = defineProps({
timeValue: { type: Object }
})
const tableData = ref()
const tableData = ref([
])
// 在父页面中添加事件监听器
window.addEventListener('message', function (event) {
@@ -195,10 +206,10 @@ const sendKeysToIframe = (keyList: string[]) => {
flex: 3.5;
}
:deep(.el-card__body) {
display: flex;
padding: 10px;
height: 100%;
.bottom-container {
position: absolute;
width: 100%;
bottom: 0px;
.buttonBox {
display: flex;
@@ -212,4 +223,39 @@ const sendKeysToIframe = (keyList: string[]) => {
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>

View File

@@ -17,11 +17,20 @@
<el-form-item label="页面排序" prop="sort">
<el-input-number style="width: 100%" v-model.trim="form.sort" :min="0" :max="10000" :step="1" />
</el-form-item>
<!-- <el-form-item label="绑定页面">
<el-select v-model="form.pagePath" filterable placeholder="请选择绑定页面" style="width: 100%" clearable>
<el-option v-for="item in pageList" :key="item.path" :label="item.name" :value="item.path" />
</el-select>
</el-form-item> -->
<el-form-item label="是否激活">
<el-switch
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-input
@@ -33,6 +42,7 @@
v-model.trim="form.remark"
></el-input>
</el-form-item>
<el-form-item>
<div style="width: 100%; display: flex; justify-content: end">
<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 { useRoute } from 'vue-router'
import { getMenu } from '@/utils/router'
import { useNavTabs } from '@/stores/navTabs'
// defineOptions({
// name: 'cockpit/popup'
// })
const { go } = useRouter()
const navTabs = useNavTabs()
const { query } = useRoute()
const router = useRouter()
const height = mainHeight(108)
const indicatorHeight = mainHeight(128)
const height = mainHeight(148)
const indicatorHeight = mainHeight(168)
const rowHeight = ref(0)
const pageList: any = ref([])
const GridHeight = ref(0)
@@ -153,7 +166,7 @@ const form: any = reactive({
containerConfig: [],
sort: '100',
id: '',
state: 1,
icon: '',
pagePath: '',
remark: '',
@@ -209,6 +222,7 @@ const info = () => {
form.sort = res.data.sort
form.remark = res.data.remark
form.id = res.data.id
form.state = res.data.state
form.icon = res.data.icon
})
} else {
@@ -367,22 +381,28 @@ const onSubmit = () => {
if (valid) {
if (form.id == '') {
addDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then(
(res: any) => {
await addDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then(
async (res: any) => {
ElMessage.success('新增页面成功!')
go(-1)
getMenu()
// go(-1)
await getMenu()
}
)
} else {
updateDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then(
(res: any) => {
await updateDashboard({ ...form, containerConfig: JSON.stringify(layout.value), thumbnail: url }).then(
async (res: any) => {
ElMessage.success('修改页面成功!')
go(-1)
getMenu()
// go(-1)
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;
.el-form-item {
display: flex;
flex: 1;
align-items: center;
// flex: 1;
// align-items: center;
width: 24%;
.el-form-item__content {
width: 100%;
flex: 1;