2025-07-22 16:40:46 +08:00
|
|
|
|
<template>
|
2025-08-05 14:23:32 +08:00
|
|
|
|
<div class="groupGraph-component">
|
|
|
|
|
|
<img src="@/assets/images/groupEvolution/graph-title.png" class="titleImage" />
|
|
|
|
|
|
<div class="container" id="container"></div>
|
2025-08-12 18:05:32 +08:00
|
|
|
|
<div class="legends" v-show="storeId == 'anomalousGroup'"></div>
|
2025-08-05 14:23:32 +08:00
|
|
|
|
<div class="timeList">
|
|
|
|
|
|
<TimeAxis
|
|
|
|
|
|
v-if="timeList.length"
|
|
|
|
|
|
:time-list="timeList"
|
2025-08-12 18:05:32 +08:00
|
|
|
|
:is-auto-play="isPlay"
|
2025-08-05 14:23:32 +08:00
|
|
|
|
:start-time="new Date(timeList[0])"
|
|
|
|
|
|
:end-time="new Date(timeList[timeList.length - 1])"
|
|
|
|
|
|
@click:pointerDown="handlePointerDown"
|
|
|
|
|
|
@slide:pointerUp="handlePointerDown"
|
|
|
|
|
|
></TimeAxis>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-07-22 16:40:46 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-08-05 14:23:32 +08:00
|
|
|
|
<script setup>
|
2025-08-12 14:12:01 +08:00
|
|
|
|
import { ref, toRaw, watch } from "vue"
|
2025-08-05 14:23:32 +08:00
|
|
|
|
import { storeToRefs } from "pinia"
|
|
|
|
|
|
import { convertToUtcIsoString } from "@/utils/transform"
|
2025-08-07 10:00:14 +08:00
|
|
|
|
import { paintNodeFunction, paintLineFunction } from "@/utils/customePaint"
|
|
|
|
|
|
import TimeAxis from "@/components/timeAxis.vue"
|
|
|
|
|
|
import GraphVis from "@/assets/package/graphvis.esm.min.js"
|
2025-08-08 17:41:30 +08:00
|
|
|
|
|
2025-08-05 14:23:32 +08:00
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
store: {
|
|
|
|
|
|
required: true
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
const emit = defineEmits(["click:pointerDownAndSlide"])
|
2025-08-06 15:01:12 +08:00
|
|
|
|
const { timeList, graph } = storeToRefs(props.store)
|
2025-08-07 20:13:33 +08:00
|
|
|
|
let isPlay = ref(false)
|
2025-08-07 10:00:14 +08:00
|
|
|
|
let graphVis = null
|
|
|
|
|
|
let forceSimulator = null
|
|
|
|
|
|
let currentSelectNode = ref(null)
|
2025-08-08 15:08:10 +08:00
|
|
|
|
const storeId = props.store.$id
|
2025-08-07 10:00:14 +08:00
|
|
|
|
const defaultConfig = {
|
|
|
|
|
|
node: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
font: "normal 14px KaiTi",
|
|
|
|
|
|
color: "250,250,250",
|
2025-08-08 09:47:19 +08:00
|
|
|
|
textPosition: "Middle_Center", //Middle_Center
|
2025-08-07 10:00:14 +08:00
|
|
|
|
textOffsetY: 10
|
|
|
|
|
|
},
|
|
|
|
|
|
shape: "circle",
|
2025-08-12 09:44:30 +08:00
|
|
|
|
size: storeId == "anomalousGroup" ? 15 : 40,
|
2025-08-07 10:00:14 +08:00
|
|
|
|
borderColor: "200,50,50",
|
|
|
|
|
|
borderWidth: 0,
|
|
|
|
|
|
selected: {
|
2025-08-11 15:36:41 +08:00
|
|
|
|
borderWidth: 5,
|
2025-08-07 10:00:14 +08:00
|
|
|
|
borderColor: "100,250,100",
|
|
|
|
|
|
showShadow: true, // 是否展示阴影
|
2025-08-12 11:50:27 +08:00
|
|
|
|
shadowBlur: 10, //阴影范围大小
|
2025-08-07 10:00:14 +08:00
|
|
|
|
shadowColor: "50,250,30" // 选中是的阴影颜色
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
link: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: false, // 是否显示
|
|
|
|
|
|
color: "245,245,245", // 字体颜色
|
|
|
|
|
|
font: "normal 11px KaiTi", // 字体大小及类型
|
|
|
|
|
|
background: "255,255,255" //文字背景色(设置后文字居中,一般与画布背景色一致)
|
|
|
|
|
|
},
|
2025-08-07 14:54:33 +08:00
|
|
|
|
lineType: "straight", // curver
|
2025-08-11 15:36:41 +08:00
|
|
|
|
showArrow: false,
|
|
|
|
|
|
lineWidth: 1,
|
2025-08-07 10:00:14 +08:00
|
|
|
|
colorType: "both",
|
|
|
|
|
|
color: "240,240,240",
|
|
|
|
|
|
selected: {
|
|
|
|
|
|
color: "100,250,100"
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
highLightNeiber: true // 相邻节点高亮开关
|
|
|
|
|
|
}
|
2025-08-12 09:44:30 +08:00
|
|
|
|
const registCustomePaintFunc = (curStoreId) => {
|
|
|
|
|
|
graphVis.definedNodePaintFunc(paintNodeFunction(curStoreId)) //自定义节点绘图方法
|
2025-08-07 10:00:14 +08:00
|
|
|
|
graphVis.definedLinkPaintFunc(paintLineFunction) //自定义关系绘图方法
|
|
|
|
|
|
}
|
2025-08-05 14:23:32 +08:00
|
|
|
|
// 处理时间轴点击事件和拉动
|
|
|
|
|
|
const handlePointerDown = (time) => {
|
|
|
|
|
|
const utcTime = convertToUtcIsoString(time)
|
|
|
|
|
|
emit("click:pointerDownAndSlide", utcTime)
|
|
|
|
|
|
}
|
2025-08-06 15:01:12 +08:00
|
|
|
|
|
2025-08-07 10:00:14 +08:00
|
|
|
|
const registEvents = () => {
|
2025-08-11 15:36:41 +08:00
|
|
|
|
const simulation = graphVis.getSimulationLayout()
|
|
|
|
|
|
forceSimulator = simulation.forceSimulation()
|
2025-08-07 10:00:14 +08:00
|
|
|
|
//全局记录包裹层元素
|
|
|
|
|
|
const containerDom = document.getElementById("container")
|
|
|
|
|
|
graphVis.registEventListener("node", "mouseOver", function (event, node) {
|
|
|
|
|
|
containerDom.style.cursor = "pointer"
|
|
|
|
|
|
})
|
|
|
|
|
|
graphVis.registEventListener("node", "mouseOut", function (event, node) {
|
|
|
|
|
|
containerDom.style.cursor = ""
|
|
|
|
|
|
})
|
|
|
|
|
|
//节点开始拖动
|
|
|
|
|
|
graphVis.registEventListener("node", "mousedrag", function (event, node) {
|
|
|
|
|
|
currentSelectNode.value = node
|
|
|
|
|
|
//开始拖动,必须要设置的属性
|
|
|
|
|
|
currentSelectNode.value.fx = node.x
|
|
|
|
|
|
currentSelectNode.value.fy = node.y
|
|
|
|
|
|
forceSimulator.alphaTarget(0.3).restart()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
graphVis.registEventListener("node", "dblClick", function (event, node) {
|
|
|
|
|
|
node.fx = null
|
|
|
|
|
|
node.fy = null
|
|
|
|
|
|
forceSimulator.alphaTarget(0.3).restart()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
//拖动中
|
|
|
|
|
|
graphVis.registEventListener("scene", "mouseDraging", function (event, client) {
|
|
|
|
|
|
if (currentSelectNode.value != null) {
|
|
|
|
|
|
currentSelectNode.value.fx = currentSelectNode.value.x
|
|
|
|
|
|
currentSelectNode.value.fy = currentSelectNode.value.y
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
graphVis.registEventListener("scene", "mouseDragEnd", function (event, client) {
|
|
|
|
|
|
if (currentSelectNode.value != null) {
|
|
|
|
|
|
forceSimulator.alphaTarget(0)
|
|
|
|
|
|
//如果拖动结束需要固定拖拽的节点,则注释下面两行,保留最后拖动的位置即可
|
2025-08-11 15:36:41 +08:00
|
|
|
|
currentSelectNode.value.fx = null
|
|
|
|
|
|
currentSelectNode.value.fy = null
|
2025-08-07 10:00:14 +08:00
|
|
|
|
currentSelectNode.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-08-11 15:36:41 +08:00
|
|
|
|
|
2025-08-11 17:05:47 +08:00
|
|
|
|
//公用连线或节点高亮函数
|
2025-08-11 15:36:41 +08:00
|
|
|
|
const highLightAboutNodesOrLinks = (type) => {
|
|
|
|
|
|
graphVis.cancelAllSelected()
|
2025-08-12 09:44:30 +08:00
|
|
|
|
const { newNodes, newLinks } = graph.value
|
2025-08-11 15:36:41 +08:00
|
|
|
|
if (type == "nodes") {
|
|
|
|
|
|
//实现高亮节点逻辑
|
2025-08-12 11:50:27 +08:00
|
|
|
|
console.log(graphVis.nodes)
|
|
|
|
|
|
|
2025-08-11 15:36:41 +08:00
|
|
|
|
graphVis.nodes.forEach((node) =>
|
|
|
|
|
|
newNodes.forEach((newNode) => {
|
|
|
|
|
|
if (node.id === newNode.name) {
|
|
|
|
|
|
node.selected = true
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
} else if (type == "links") {
|
|
|
|
|
|
//实现连线高亮逻辑
|
|
|
|
|
|
graphVis.links.forEach((link) =>
|
|
|
|
|
|
newLinks.forEach((newLink) => {
|
|
|
|
|
|
if (link.source.id === newLink.source && link.target.id === newLink.target) {
|
|
|
|
|
|
link.selected = true
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-08 15:08:10 +08:00
|
|
|
|
const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => {
|
|
|
|
|
|
function handleLayoutSuccess() {
|
|
|
|
|
|
//处理四个关系图的差异函数
|
|
|
|
|
|
const handleGroupDiscoveryDiff = () => {
|
2025-08-11 15:36:41 +08:00
|
|
|
|
return
|
2025-08-08 15:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleGroupStructureDiff = () => {
|
2025-08-11 15:36:41 +08:00
|
|
|
|
highLightAboutNodesOrLinks("links")
|
2025-08-08 15:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleGroupMemberDiff = () => {
|
2025-08-11 15:36:41 +08:00
|
|
|
|
highLightAboutNodesOrLinks("nodes")
|
2025-08-08 15:08:10 +08:00
|
|
|
|
}
|
2025-08-08 09:47:19 +08:00
|
|
|
|
|
2025-08-08 15:08:10 +08:00
|
|
|
|
const handleAnomalousGroup = () => {
|
2025-08-11 10:03:08 +08:00
|
|
|
|
// 当前时间从 store 取;没有就用 T0
|
|
|
|
|
|
const now = props.store.currentUtc
|
|
|
|
|
|
const TA = "2024-06-19T08:57:55Z" // A
|
|
|
|
|
|
const TB = "2024-06-19T10:58:03Z" // B
|
|
|
|
|
|
const TC = "2024-06-19T12:58:04Z" // C
|
|
|
|
|
|
// 组色(与你现有 colorMap 一致)
|
|
|
|
|
|
const GROUP_ALPHA = 0.3
|
|
|
|
|
|
const RED = "220,50,60"
|
|
|
|
|
|
// 时间门控:达到阈值就把各组异常节点染红
|
|
|
|
|
|
const shouldA = now >= TA
|
|
|
|
|
|
const shouldB = now >= TB
|
|
|
|
|
|
const shouldC = now >= TC
|
|
|
|
|
|
if (shouldA) {
|
|
|
|
|
|
graphVis.nodes = graphVis.nodes.map((n) => {
|
|
|
|
|
|
if (props.store.graphAbnormalData.groupA.includes(n.id)) {
|
|
|
|
|
|
n.fillColor = RED
|
|
|
|
|
|
}
|
|
|
|
|
|
return n
|
|
|
|
|
|
})
|
|
|
|
|
|
graphVis.addNodesInGroup(
|
|
|
|
|
|
graphVis.nodes.filter((n) => props.store.graphAbnormalData.groupA.includes(n.id)),
|
|
|
|
|
|
{
|
|
|
|
|
|
shape: "circle",
|
|
|
|
|
|
color: RED,
|
|
|
|
|
|
alpha: GROUP_ALPHA
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (shouldB) {
|
|
|
|
|
|
graphVis.nodes = graphVis.nodes.map((n) => {
|
|
|
|
|
|
if (props.store.graphAbnormalData.groupB.includes(n.id)) {
|
|
|
|
|
|
n.fillColor = RED
|
|
|
|
|
|
}
|
|
|
|
|
|
return n
|
|
|
|
|
|
})
|
|
|
|
|
|
graphVis.addNodesInGroup(
|
|
|
|
|
|
graphVis.nodes.filter((n) => props.store.graphAbnormalData.groupB.includes(n.id)),
|
|
|
|
|
|
{
|
|
|
|
|
|
shape: "circle",
|
|
|
|
|
|
color: RED,
|
|
|
|
|
|
alpha: GROUP_ALPHA
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (shouldC) {
|
|
|
|
|
|
graphVis.nodes = graphVis.nodes.map((n) => {
|
|
|
|
|
|
if (props.store.graphAbnormalData.groupC.includes(n.id)) {
|
|
|
|
|
|
n.fillColor = RED
|
|
|
|
|
|
}
|
|
|
|
|
|
return n
|
|
|
|
|
|
})
|
|
|
|
|
|
graphVis.addNodesInGroup(
|
|
|
|
|
|
graphVis.nodes.filter((n) => props.store.graphAbnormalData.groupC.includes(n.id)),
|
|
|
|
|
|
{
|
|
|
|
|
|
shape: "circle",
|
|
|
|
|
|
color: RED,
|
|
|
|
|
|
alpha: GROUP_ALPHA
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-08-11 15:36:41 +08:00
|
|
|
|
// graphVis.autoGroupLayout(graphVis.nodes)
|
2025-08-07 19:11:52 +08:00
|
|
|
|
}
|
2025-08-08 15:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
new Map([
|
|
|
|
|
|
["groupDiscovery", () => handleGroupDiscoveryDiff()],
|
|
|
|
|
|
["groupStructure", () => handleGroupStructureDiff()],
|
|
|
|
|
|
["groupMember", () => handleGroupMemberDiff()],
|
|
|
|
|
|
["anomalousGroup", () => handleAnomalousGroup()]
|
|
|
|
|
|
]).get(storeId)?.()
|
2025-08-12 18:05:32 +08:00
|
|
|
|
// isPlay.value = true
|
2025-08-08 15:08:10 +08:00
|
|
|
|
graphVis.zoomFit() //场景视图大小自适应缩放
|
|
|
|
|
|
}
|
|
|
|
|
|
graphVis.excuteWorkerLayout(graphVis.getGraphData(), layoutType, layoutConfig, isAsync, () =>
|
|
|
|
|
|
handleLayoutSuccess()
|
2025-08-07 19:11:52 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 根据节点的cluster属性进行分组
|
|
|
|
|
|
const clusterAnalyze = () => {
|
|
|
|
|
|
graphVis.removeAllGroup() // 清除原有分组
|
|
|
|
|
|
// 创建cluster到nodes的映射
|
|
|
|
|
|
const clusterNodesMap = new Map()
|
|
|
|
|
|
graphVis.nodes.forEach((node) => {
|
|
|
|
|
|
const cluster = parseInt(node.type)
|
|
|
|
|
|
if (!clusterNodesMap.has(cluster)) {
|
|
|
|
|
|
clusterNodesMap.set(cluster, [])
|
2025-08-07 10:20:33 +08:00
|
|
|
|
}
|
2025-08-07 19:11:52 +08:00
|
|
|
|
clusterNodesMap.get(cluster).push(node)
|
2025-08-07 10:00:14 +08:00
|
|
|
|
})
|
2025-08-08 17:41:30 +08:00
|
|
|
|
let colorMap = {
|
2025-08-11 17:49:31 +08:00
|
|
|
|
0: "75,241,184", // 绿色
|
|
|
|
|
|
1: "250,222,37", // 黄色
|
|
|
|
|
|
6: "69,192,242" // 蓝色
|
2025-08-07 19:11:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
clusterNodesMap.forEach((nodes, cluster) => {
|
|
|
|
|
|
const color = colorMap[cluster]
|
|
|
|
|
|
nodes.forEach((node) => {
|
|
|
|
|
|
node.fillColor = color
|
|
|
|
|
|
node.color = color
|
|
|
|
|
|
})
|
2025-08-11 15:51:02 +08:00
|
|
|
|
if (storeId !== "anomalousGroup") {
|
|
|
|
|
|
graphVis.addNodesInGroup(nodes, {
|
|
|
|
|
|
shape: "polygon", //circle|rect|polygon|bubbleset
|
|
|
|
|
|
color: color,
|
|
|
|
|
|
alpha: 0.2
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-08-07 10:00:14 +08:00
|
|
|
|
})
|
2025-08-11 15:36:41 +08:00
|
|
|
|
// graphVis.autoGroupLayout(graphVis.nodes)
|
2025-08-07 19:11:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 17:41:30 +08:00
|
|
|
|
// 仅对“异常群体模块”生效:时间变化时强制重绘一次
|
2025-08-11 09:20:57 +08:00
|
|
|
|
if (storeId === "anomalousGroup") {
|
2025-08-08 17:41:30 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.store.currentUtc,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
// 不改 graph 数据结构,直接用当前 graph 强制走一遍:addGraph → clusterAnalyze → runForceLayout
|
|
|
|
|
|
if (graphVis) {
|
2025-08-11 09:20:57 +08:00
|
|
|
|
updateChart(toRaw(graph.value))
|
2025-08-08 17:41:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-11 09:20:57 +08:00
|
|
|
|
)
|
2025-08-08 17:41:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 实例化 GraphVis、注册自定义绘制/事件
|
2025-08-07 19:11:52 +08:00
|
|
|
|
const createGraph = () => {
|
|
|
|
|
|
if (!graphVis) {
|
|
|
|
|
|
graphVis = new GraphVis({
|
|
|
|
|
|
container: document.getElementById("container"),
|
|
|
|
|
|
licenseKey: "hbsy",
|
|
|
|
|
|
config: defaultConfig
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
graphVis.setDragHideLine(false) //拖拽时隐藏连线
|
|
|
|
|
|
graphVis.setShowDetailScale(0.1) //展示细节的比例
|
|
|
|
|
|
graphVis.setZoomRange(0.1, 5) //缩放区间
|
2025-08-12 09:44:30 +08:00
|
|
|
|
registCustomePaintFunc(storeId) //注册自定义绘图方法
|
2025-08-07 19:11:52 +08:00
|
|
|
|
registEvents()
|
2025-08-07 10:00:14 +08:00
|
|
|
|
}
|
2025-08-07 19:11:52 +08:00
|
|
|
|
|
|
|
|
|
|
const initChart = () => {
|
|
|
|
|
|
createGraph()
|
|
|
|
|
|
graphVis.addGraph({ ...toRaw(graph.value) })
|
|
|
|
|
|
clusterAnalyze()
|
2025-08-08 15:08:10 +08:00
|
|
|
|
runDiffForceLayout({ strength: -500, ajustCluster: true }, "simulation", true)
|
2025-08-07 19:11:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 10:35:34 +08:00
|
|
|
|
// 添加更新图表的函数
|
|
|
|
|
|
const updateChart = (newGraphData) => {
|
|
|
|
|
|
if (!graphVis) {
|
2025-08-07 14:54:33 +08:00
|
|
|
|
initChart()
|
|
|
|
|
|
return
|
2025-08-07 10:35:34 +08:00
|
|
|
|
}
|
2025-08-07 14:54:33 +08:00
|
|
|
|
graphVis.clearAll()
|
|
|
|
|
|
graphVis.addGraph({ ...toRaw(newGraphData) })
|
2025-08-07 10:35:34 +08:00
|
|
|
|
// 重新运行力导向布局
|
2025-08-07 19:11:52 +08:00
|
|
|
|
clusterAnalyze()
|
2025-08-08 15:08:10 +08:00
|
|
|
|
runDiffForceLayout({ strength: -500, ajustCluster: true }, "simulation", true)
|
2025-08-06 15:01:12 +08:00
|
|
|
|
}
|
2025-08-07 14:54:33 +08:00
|
|
|
|
|
2025-08-07 10:35:34 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
graph,
|
|
|
|
|
|
(newValue) => {
|
2025-08-08 15:08:10 +08:00
|
|
|
|
if (newValue) {
|
2025-08-07 14:54:33 +08:00
|
|
|
|
updateChart(newValue)
|
2025-08-07 10:35:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-08-07 14:54:33 +08:00
|
|
|
|
{ deep: true }
|
|
|
|
|
|
)
|
2025-08-05 14:23:32 +08:00
|
|
|
|
</script>
|
2025-07-22 16:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
|
.groupGraph-component {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
2025-08-05 14:23:32 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
.titleImage {
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
}
|
2025-08-12 12:17:27 +08:00
|
|
|
|
.legends {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
width: 160px;
|
|
|
|
|
|
height: 55px;
|
|
|
|
|
|
right: 2%;
|
|
|
|
|
|
top: 76%;
|
2025-08-12 18:05:32 +08:00
|
|
|
|
background-image: url("@/assets/images/abnormalGroup/abnormal-group-legends.png");
|
2025-08-12 12:17:27 +08:00
|
|
|
|
}
|
2025-08-05 14:23:32 +08:00
|
|
|
|
.container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 503px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.timeList {
|
|
|
|
|
|
width: 95%;
|
|
|
|
|
|
height: 42px;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 20px;
|
|
|
|
|
|
bottom: 20px;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
2025-07-22 16:40:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|