246 lines
6.9 KiB
Vue
246 lines
6.9 KiB
Vue
|
|
<template>
|
||
|
|
<GridLayout
|
||
|
|
class="default-main"
|
||
|
|
v-model:layout="layout"
|
||
|
|
:row-height="rowHeight"
|
||
|
|
:is-resizable="false"
|
||
|
|
:is-draggable="false"
|
||
|
|
:responsive="false"
|
||
|
|
:vertical-compact="false"
|
||
|
|
prevent-collision
|
||
|
|
:col-num="12"
|
||
|
|
>
|
||
|
|
<template #item="{ item }">
|
||
|
|
<div class="box">
|
||
|
|
<div class="title">
|
||
|
|
<div style="display: flex; align-items: center">
|
||
|
|
<Icon class="HelpFilled" :name="item.icon" />
|
||
|
|
{{ item.name }}
|
||
|
|
</div>
|
||
|
|
<!-- <FullScreen class="HelpFilled" style="cursor: pointer" @click="zoom(item)" /> -->
|
||
|
|
<img :src="flag ? img : img1" style="cursor: pointer; height: 16px" @click="zoom(item)" />
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<component
|
||
|
|
:is="item.component"
|
||
|
|
v-if="item.component"
|
||
|
|
class="pd10"
|
||
|
|
:key="key"
|
||
|
|
:height="rowHeight * item.h - (item.h == 6 ? -20 : item.h == 2 ? 20 : 5) + 'px'"
|
||
|
|
:width="rowWidth * item.w - 5 + 'px'"
|
||
|
|
:timeKey="item.timeKey"
|
||
|
|
/>
|
||
|
|
<div v-else class="pd10">组件加载失败...</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
</GridLayout>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, reactive, onMounted, markRaw, onUnmounted, defineAsyncComponent, type Component } from 'vue'
|
||
|
|
import { GridLayout } from 'grid-layout-plus'
|
||
|
|
import { useDebounceFn } from '@vueuse/core'
|
||
|
|
import { queryActivatePage } from '@/api/system-boot/csstatisticalset'
|
||
|
|
import { HelpFilled, FullScreen } from '@element-plus/icons-vue'
|
||
|
|
// defineOptions({
|
||
|
|
// name: 'cockpit/homePage'
|
||
|
|
// })
|
||
|
|
// 定义类型
|
||
|
|
interface LayoutItem {
|
||
|
|
x: number
|
||
|
|
y: number
|
||
|
|
w: number
|
||
|
|
h: number
|
||
|
|
i: string | number
|
||
|
|
name: string
|
||
|
|
path: string
|
||
|
|
component?: Component | string
|
||
|
|
loading?: boolean
|
||
|
|
error?: any
|
||
|
|
}
|
||
|
|
const key = ref(0)
|
||
|
|
const img = new URL(`@/assets/imgs/amplify.png`, import.meta.url)
|
||
|
|
const img1 = new URL(`@/assets/imgs/reduce.png`, import.meta.url)
|
||
|
|
// 响应式数据
|
||
|
|
const rowHeight = ref(0)
|
||
|
|
const rowWidth = ref(0)
|
||
|
|
const layout = ref<LayoutItem[]>([
|
||
|
|
// {
|
||
|
|
// x: 4,
|
||
|
|
// y: 0,
|
||
|
|
// w: 4,
|
||
|
|
// h: 2,
|
||
|
|
// i: '1',
|
||
|
|
// name: '',
|
||
|
|
// path: '/src/views/pqs/runManage/assessment/components/uese/index.vue'
|
||
|
|
// },
|
||
|
|
])
|
||
|
|
const layoutCopy = ref<LayoutItem[]>([])
|
||
|
|
const flag = ref(true)
|
||
|
|
// 组件映射
|
||
|
|
const componentMap = reactive(new Map<string, Component | string>())
|
||
|
|
|
||
|
|
// 获取主内容区域高度
|
||
|
|
const getMainHeight = () => {
|
||
|
|
const elMain = document.querySelector('.el-main')
|
||
|
|
return (elMain?.offsetHeight || 0) - 70
|
||
|
|
}
|
||
|
|
// 获取主内容区域高度
|
||
|
|
const getMainWidth = () => {
|
||
|
|
const elMain = document.querySelector('.el-main')
|
||
|
|
return (elMain?.offsetWidth || 0) - 20
|
||
|
|
}
|
||
|
|
|
||
|
|
// 初始化行高
|
||
|
|
const initRowHeight = () => {
|
||
|
|
rowHeight.value = Math.max(0, (getMainHeight() - 20) / 6)
|
||
|
|
rowWidth.value = Math.max(0, getMainWidth() / 12)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 动态注册组件
|
||
|
|
const registerComponent = (path: string): Component | string | null => {
|
||
|
|
if (!path) return null
|
||
|
|
|
||
|
|
const cacheKey = path
|
||
|
|
|
||
|
|
// 使用缓存的组件
|
||
|
|
if (componentMap.has(cacheKey)) {
|
||
|
|
return componentMap.get(cacheKey)!
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 动态导入组件
|
||
|
|
const modules = import.meta.glob('@/views/**/*.vue')
|
||
|
|
if (!modules[path]) {
|
||
|
|
console.error(`组件加载失败: ${path}`)
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
const AsyncComponent = defineAsyncComponent({
|
||
|
|
loader: modules[path],
|
||
|
|
loadingComponent: () => h('div', '加载中...'),
|
||
|
|
errorComponent: ({ error }) => h('div', `加载错误: ${error.message}`),
|
||
|
|
delay: 200,
|
||
|
|
timeout: 10000
|
||
|
|
})
|
||
|
|
|
||
|
|
// 保存到映射中
|
||
|
|
componentMap.set(cacheKey, markRaw(AsyncComponent))
|
||
|
|
return AsyncComponent
|
||
|
|
} catch (error) {
|
||
|
|
console.error('注册组件失败:', error)
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// 缩放
|
||
|
|
const zoom = (value: any) => {
|
||
|
|
if (flag.value) {
|
||
|
|
layout.value = [{ ...value, x: 0, y: 0, w: 12, h: 6 }]
|
||
|
|
} else {
|
||
|
|
layout.value = layoutCopy.value.map((item, index) => ({
|
||
|
|
...item,
|
||
|
|
i: item.i || index, // 确保有唯一标识
|
||
|
|
component: registerComponent(item.path)
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
flag.value = !flag.value
|
||
|
|
key.value += 1
|
||
|
|
}
|
||
|
|
// 获取布局数据
|
||
|
|
const fetchLayoutData = async () => {
|
||
|
|
try {
|
||
|
|
const { data } = await queryActivatePage()
|
||
|
|
const parsedLayout = JSON.parse(data.containerConfig || '[]') as LayoutItem[]
|
||
|
|
// 处理布局数据
|
||
|
|
layout.value = parsedLayout.map((item, index) => ({
|
||
|
|
...item,
|
||
|
|
i: item.i || index, // 确保有唯一标识
|
||
|
|
component: registerComponent(item.path)
|
||
|
|
}))
|
||
|
|
layoutCopy.value = JSON.parse(JSON.stringify(layout.value))
|
||
|
|
} catch (error) {
|
||
|
|
console.error('获取布局数据失败:', error)
|
||
|
|
// 可以添加错误提示逻辑
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 窗口大小变化处理 - 使用防抖
|
||
|
|
const handleResize = useDebounceFn(() => {
|
||
|
|
initRowHeight()
|
||
|
|
key.value += 1
|
||
|
|
}, 200)
|
||
|
|
|
||
|
|
// 生命周期钩子
|
||
|
|
onMounted(() => {
|
||
|
|
initRowHeight()
|
||
|
|
fetchLayoutData()
|
||
|
|
|
||
|
|
// 添加窗口大小变化监听器
|
||
|
|
window.addEventListener('resize', handleResize)
|
||
|
|
})
|
||
|
|
|
||
|
|
onUnmounted(() => {
|
||
|
|
// 移除监听器防止内存泄漏
|
||
|
|
window.removeEventListener('resize', handleResize)
|
||
|
|
})
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
.vgl-layout {
|
||
|
|
background-color: #f8f9fa;
|
||
|
|
border-radius: 4px;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.vgl-item:not(.vgl-item--placeholder)) {
|
||
|
|
background-color: #ffffff;
|
||
|
|
border-radius: 4px;
|
||
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
||
|
|
transition: all 0.3s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.vgl-item:hover:not(.vgl-item--placeholder)) {
|
||
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.12);
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.vgl-item--static) {
|
||
|
|
background-color: #f0f2f5;
|
||
|
|
}
|
||
|
|
|
||
|
|
.text {
|
||
|
|
position: absolute;
|
||
|
|
inset: 0;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
font-size: 16px;
|
||
|
|
color: #606266;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.vgl-item) {
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
.box {
|
||
|
|
overflow: hidden;
|
||
|
|
.title {
|
||
|
|
border-bottom: 1px solid #000;
|
||
|
|
font-size: 14px;
|
||
|
|
height: 30px;
|
||
|
|
font-weight: 600;
|
||
|
|
padding: 0px 10px;
|
||
|
|
color: #fff;
|
||
|
|
background-color: var(--el-color-primary);
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
.HelpFilled {
|
||
|
|
height: 16px;
|
||
|
|
width: 16px;
|
||
|
|
color: #fff !important;
|
||
|
|
margin-right: 5px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|