Files
DianZhanDemo/app/components/PowerStation/ModelPropertiesPanel.vue
ch197511161 1427be34fd 属性修改
2025-12-13 15:55:09 +08:00

877 lines
28 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="h-full flex flex-col overflow-hidden p-4">
<!-- 头部区域 (固定) -->
<div class="flex-none pb-2 border-b border-cyan-500/10 mb-2">
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<div class="w-1 h-5 bg-cyan-500 shadow-[0_0_8px_#06b6d4]"></div>
<h3
class="text-lg font-bold text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-blue-500 font-mono tracking-wide"
>
属性面板 <span class="text-xs text-cyan-500/50 font-normal">/// PROPERTIES</span>
</h3>
</div>
<div class="flex items-center gap-2">
<el-tag v-if="store.currentNodeId" class="tech-tag font-mono">{{
store.currentNodeId
}}</el-tag>
<span v-else class="text-gray-500 text-sm font-mono">NO SELECTION</span>
<el-tooltip :content="isMaximized ? '还原' : '最大化'" placement="left" effect="dark">
<button class="tech-icon-btn" @click="emit('toggleMaximize')">
<el-icon><FullScreen /></el-icon>
</button>
</el-tooltip>
</div>
</div>
</div>
<!-- 内容区域 (可滚动) -->
<div
class="flex-1 overflow-y-auto overflow-x-hidden min-h-0 custom-scrollbar"
v-if="store.currentNodeId"
>
<div class="flex flex-wrap">
<div class="w-[450px]">
<!-- 基本信息 -->
<div class="tech-divider mb-2">
<span class="text-cyan-400 font-mono text-sm">基本信息</span>
<div class="h-[1px] bg-gradient-to-r from-cyan-500/50 to-transparent flex-1 ml-4"></div>
</div>
<el-descriptions :column="1" border size="small" class="tech-descriptions mb-2">
<el-descriptions-item
v-for="(item, index) in properties.basic"
:key="index"
:label="item.label"
>
{{ item.value }}
</el-descriptions-item>
</el-descriptions>
</div>
<div class="w-[450px]">
<!-- 技术参数 -->
<div class="tech-divider mb-2">
<span class="text-cyan-400 font-mono text-sm">技术参数</span>
<div class="h-[1px] bg-gradient-to-r from-cyan-500/50 to-transparent flex-1 ml-4"></div>
</div>
<el-descriptions :column="1" border size="small" class="tech-descriptions mb-2">
<el-descriptions-item
v-for="(item, index) in properties.technical"
:key="index"
:label="item.label"
>
{{ item.value }}
</el-descriptions-item>
</el-descriptions>
</div>
</div>
<!-- 运行状态 -->
<div class="tech-divider mb-2">
<span class="text-cyan-400 font-mono text-sm">状态</span>
<div class="h-[1px] bg-gradient-to-r from-cyan-500/50 to-transparent flex-1 ml-4"></div>
</div>
<div class="flex flex-wrap gap-3">
<div
v-for="(item, index) in properties.status"
:key="index"
class="sm:w-[50%] lg:w-[210px] flex justify-between p-2 bg-[#0f172a] border border-cyan-500/10 rounded cursor-pointer hover:border-cyan-500/50 hover:bg-[#0f172a]/80 transition-all group"
@click="openTableDialog(item)"
>
<span
class="text-sm text-gray-400 font-mono group-hover:text-cyan-400 transition-colors"
>{{ item.label }}</span
>
<div class="flex items-center gap-2">
<el-tag v-if="item.status" :type="item.status" size="small" class="tech-status-tag">{{
item.value
}}</el-tag>
<span v-else class="font-medium text-cyan-300 font-mono">{{ item.value }}</span>
<el-icon
class="text-gray-600 group-hover:text-cyan-400 transition-colors text-xs opacity-0 group-hover:opacity-100"
><List
/></el-icon>
</div>
</div>
</div>
</div>
<!-- 图库功能模块 -->
<div v-if="showGallery" class="mb-6">
<el-divider content-position="left">CAD图库</el-divider>
<!-- 加载状态 -->
<div v-if="loading" class="flex justify-center items-center h-32">
<el-icon class="is-loading text-2xl text-blue-500">
<Loading />
</el-icon>
<span class="ml-2 text-gray-600">正在加载图片...</span>
</div>
<div v-else>
<!-- 轮播组件 -->
<div class="mb-4"></div>
<!-- 视图模式切换 -->
<div class="flex justify-between items-center mb-3">
<span class="text-sm text-gray-600"> {{ imageList.length }} 张图片</span>
<el-radio-group v-model="viewMode" size="small">
<el-radio-button value="grid">
<el-icon><Grid /></el-icon>
网格
</el-radio-button>
<el-radio-button value="carousel">
<el-icon><PictureRounded /></el-icon>
走马灯
</el-radio-button>
</el-radio-group>
</div>
<!-- 缩略图区域 -->
<div class="transition-all duration-300">
<!-- 网格视图 -->
<div
v-if="viewMode === 'grid'"
class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3"
>
<div
v-for="(image, index) in imageList"
:key="image.id"
class="relative group cursor-pointer rounded-lg overflow-hidden border-2 transition-all duration-200 hover:border-blue-400"
:class="{ 'border-blue-500 ring-2 ring-blue-200': currentIndex === index }"
@click="openPreview(index)"
>
<el-image
:src="image.thumb"
:alt="image.meta.title"
fit="contain"
class="w-full h-18"
lazy
>
<template #placeholder>
<div class="w-full h-18 flex items-center justify-center bg-gray-200">
<el-icon class="text-xl text-gray-400"><Picture /></el-icon>
</div>
</template>
</el-image>
<!-- 查看详情按钮 -->
<div
class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-all duration-200 flex items-center justify-center"
>
<el-button
type="primary"
size="small"
circle
class="opacity-0 group-hover:opacity-100 transition-opacity duration-200"
>
<el-icon><ZoomIn /></el-icon>
</el-button>
</div>
<div
class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-2"
>
<p class="text-white text-xs truncate">{{ image.meta.title }}</p>
</div>
</div>
</div>
<!-- 走马灯视图 -->
<div v-else>
<el-carousel
v-model="currentIndex"
:interval="3000"
arrow="always"
indicator-position="outside"
height="150px"
class="rounded-lg overflow-hidden"
>
<el-carousel-item v-for="(image, index) in imageList" :key="image.id">
<div
class="w-full h-full bg-gray-100/0 flex items-center justify-center cursor-pointer"
@click="openPreview(index)"
@dblclick="showImageInfo(index)"
>
<el-image
:src="image.thumb"
:alt="image.meta.title"
fit="contain"
class="w-full h-full"
lazy
>
<template #placeholder>
<div class="w-full h-full flex items-center justify-center bg-gray-200">
<el-icon class="text-3xl text-gray-400"><Picture /></el-icon>
</div>
</template>
<template #error>
<div class="w-full h-full flex items-center justify-center bg-gray-200">
<el-icon class="text-3xl text-red-400"><Warning /></el-icon>
</div>
</template>
</el-image>
</div>
</el-carousel-item>
</el-carousel>
</div>
</div>
</div>
</div>
<div
v-else
class="h-full flex flex-col items-center justify-center text-gray-400 min-h-[300px]"
>
<el-icon class="text-4xl mb-2"><InfoFilled /></el-icon>
<p>请在左侧模型树或3D视图中选择一个部件查看详情</p>
</div>
<!-- 大图预览组件 -->
<el-image-viewer
v-if="showPreview"
:url-list="previewList"
:initial-index="previewIndex"
@close="showPreview = false"
>
<template #toolbar="{ index }">
<div class="flex items-center gap-2">
<el-button
type="primary"
size="small"
circle
title="下载图片"
@click="downloadImage(index)"
>
<el-icon><Download /></el-icon>
</el-button>
<el-button
type="primary"
size="small"
circle
title="复制链接"
@click="copyImageUrl(index)"
>
<el-icon><CopyDocument /></el-icon>
</el-button>
<el-button
type="primary"
size="small"
circle
title="查看信息"
@click="showImageInfo(index)"
>
<el-icon><InfoFilled /></el-icon>
</el-button>
</div>
</template>
</el-image-viewer>
<!-- 图片信息对话框 -->
<el-dialog v-model="showInfoDialog" title="图片信息" width="400px">
<div v-if="currentImage" class="space-y-3">
<div class="flex justify-between">
<span class="text-gray-600">标题:</span>
<span>{{ currentImage.meta.title }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">标签:</span>
<el-tag size="small">{{ currentImage.meta.tag }}</el-tag>
</div>
<div class="flex justify-between">
<span class="text-gray-600">尺寸:</span>
<span>{{ currentImage.meta.dimensions }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">创建时间:</span>
<span>{{ currentImage.meta.createdAt }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">文件大小:</span>
<span>{{ currentImage.meta.size }}</span>
</div>
</div>
</el-dialog>
<!-- 数据表格弹窗 -->
<el-dialog
v-model="showTableDialog"
:title="currentParamItem ? `${currentParamItem.label} - 历史记录` : '运行状态记录'"
width="900px"
append-to-body
class="tech-dialog-modal"
>
<div class="mb-4 flex flex-wrap gap-4 items-center">
<div class="text-sm text-cyan-400 font-mono">
<span class="mr-2">当前节点:</span>
<el-tag size="small" class="tech-tag">{{ store.currentNodeId || 'Unknown' }}</el-tag>
</div>
<div class="flex-1"></div>
<el-button
size="small"
type="primary"
class="tech-button"
@click="() => generateMockData()"
>
<el-icon class="mr-1"><Refresh /></el-icon> 刷新数据
</el-button>
</div>
<el-table :data="tableData" height="500" stripe style="width: 100%">
<el-table-column prop="time" label="记录时间" width="200" />
<el-table-column prop="parameter" label="监测参数" width="180" />
<el-table-column prop="value" label="监测数值" />
<el-table-column prop="status" label="状态判定" width="120">
<template #default="scope">
<el-tag :type="scope.row.statusType" size="small">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="operator" label="记录人" width="120" />
</el-table>
</el-dialog>
</div>
</template>
<script setup lang="ts">
// 导入必要的Vue API
import { computed, onMounted, ref, watch } from 'vue';
// 导入必要的图标组件
import {
CopyDocument,
Download,
FullScreen,
Grid,
InfoFilled,
List,
Loading,
Picture,
PictureRounded,
Refresh,
Warning,
ZoomIn,
} from '@element-plus/icons-vue';
// 导入Element Plus组件
import { ElMessage } from 'element-plus';
// 导入电站数据状态管理
import { usePowerStationStore } from '~/stores/powerStation';
// 定义组件属性
const props = defineProps<{
isMaximized?: boolean // 是否最大化显示
}>()
// 定义组件事件
const emit = defineEmits(['toggleMaximize'])
// 获取电站状态管理实例
const store = usePowerStationStore()
// 图片数据类型定义
interface ImageMeta {
title: string
tag: string
dimensions: string
createdAt: string
size: string
}
interface ImageItem {
id: string
thumb: string
src: string
meta: ImageMeta
}
interface PropertyItem {
label: string
value: string
unit?: string
status?: string
}
interface PropertiesData {
basic: PropertyItem[]
technical: PropertyItem[]
status: PropertyItem[]
}
// 响应式数据
const loading = ref(false)
const imageList = ref<ImageItem[]>([])
const currentIndex = ref(0)
const viewMode = ref<'grid' | 'carousel'>('carousel')
const showPreview = ref(false)
const previewIndex = ref(0)
const showInfoDialog = ref(false)
const currentImage = ref<ImageItem | null>(null)
const properties = ref<PropertiesData>({ basic: [], technical: [], status: [] })
// 表格弹窗相关
const showTableDialog = ref(false)
const tableData = ref<any[]>([])
// 表格弹窗相关
const currentParamItem = ref<PropertyItem | null>(null)
const openTableDialog = (item: PropertyItem) => {
currentParamItem.value = item
showTableDialog.value = true
generateMockData(item)
}
const generateMockData = (item?: PropertyItem) => {
const targetItem = item || currentParamItem.value
if (!targetItem) return
const label = targetItem.label
const operators = ['系统自动', '操作员A', '操作员B']
// 根据不同的标签生成不同的模拟数据
tableData.value = Array.from({ length: 20 }).map((_, i) => {
let value = ''
let statusObj = { label: '正常', type: 'success' }
// 基于标签的简单的模拟逻辑
if (label.includes('温度')) {
const val = 50 + Math.random() * 20
value = val.toFixed(1) + ' °C'
if (val > 68) statusObj = { label: '高温警告', type: 'warning' }
} else if (label.includes('压力')) {
const val = 10 + Math.random() * 5
value = val.toFixed(2) + ' MPa'
if (val > 14.5) statusObj = { label: '高压', type: 'warning' }
} else if (label.includes('健康')) {
// 健康度
const val = 90 + Math.random() * 10
value = Math.min(100, val).toFixed(1) + '%'
if (val < 92) statusObj = { label: '需关注', type: 'warning' }
} else if (label.includes('状态') || label.includes('运行')) {
const states = ['正常运行', '以正常运行', '低负荷运行']
value = states[Math.floor(Math.random() * states.length)]
// 偶尔出个异常
if (Math.random() > 0.9) {
value = '停机维护'
statusObj = { label: '停机', type: 'info' }
}
} else if (label.includes('报警')) {
const subStatus = Math.random()
if (subStatus > 0.8) {
value = '轻微报警'
statusObj = { label: '报警', type: 'warning' }
} else {
value = '无报警'
}
} else {
// 默认回退
value = (Math.random() * 100).toFixed(2)
}
// 随机插入一些异常
if (Math.random() > 0.95 && statusObj.type === 'success') {
statusObj = { label: '异常', type: 'danger' }
}
return {
time: new Date(Date.now() - i * 1000 * 60 * 30).toLocaleString(),
parameter: label,
value: value,
status: statusObj.label,
// @ts-ignore - Element Plus tag type
statusType: statusObj.type || 'success',
operator: operators[i % operators.length],
}
})
}
// 计算属性
// 计算属性
const showGallery = computed(() => {
// 开发环境下总是显示图库用于测试
if (process.dev) return true
// 检查currentNodeId是否包含CAD相关标识
return store.currentNodeId?.includes('CAD') || false
})
const previewList = computed(() => {
return imageList.value.map(item => item.src)
})
// 异步加载属性数据
const fetchProperties = async (nodeId: string) => {
if (!nodeId) {
console.error('获取属性数据失败: nodeId不能为空')
// 清空属性数据
properties.value = {
basic: [],
technical: [],
status: [],
}
return
}
try {
const response = await $fetch('/api/power-station/properties', {
query: { nodeId },
})
properties.value = response as PropertiesData
} catch (error) {
console.error('获取属性数据失败:', error)
// 如果API调用失败使用模拟数据
properties.value = {
basic: [
{ label: 'ID', value: nodeId },
{ label: '名称', value: '一级再热器进口连接管' },
{ label: '节点类型', value: '设备' },
{ label: '所属系统', value: '主系统' },
{ label: '材质', value: 'SA213-T12' },
{ label: '规格', value: '∅610*38' },
],
technical: [
{ label: '设计压力', value: '16.5 MPa', unit: 'MPa' },
{ label: '设计温度', value: '545', unit: '°C' },
{ label: '材料', value: 'SA-516 Gr.70' },
{ label: '厚度', value: '25', unit: 'mm' },
],
status: [
{ label: '运行状态', value: '正常运行', status: 'success' },
{ label: '健康度', value: '95%', status: 'success' },
{ label: '维护状态', value: '正常', status: 'success' },
{ label: '报警状态', value: '无报警', status: 'success' },
],
}
}
}
// 异步加载图片数据
const loadImages = async () => {
loading.value = true
try {
// 模拟异步加载
await new Promise(resolve => setTimeout(resolve, 500))
// 构建图片数据
imageList.value = [
{
id: 'cad1',
thumb: '/Cad_Preview/一级过热器出口连接管.png',
src: '/Cad/一级过热器出口连接管.png',
meta: {
title: '一级过热器出口连接管',
tag: '主视图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '2.3MB',
},
},
{
id: 'cad2',
thumb: '/Cad_Preview/一级再热器出口连接管.png',
src: '/Cad/一级再热器出口连接管.png',
meta: {
title: '一级再热器出口连接管',
tag: '侧视图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '1.8MB',
},
},
{
id: 'cad3',
thumb: '/Cad_Preview/二级过热器出口连接管.png',
src: '/Cad/二级过热器出口连接管.png',
meta: {
title: '二级过热器出口连接管',
tag: '俯视图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '3.1MB',
},
},
{
id: 'cad4',
thumb: '/Cad_Preview/一级再热器管排.png',
src: '/Cad/一级再热器管排.png',
meta: {
title: '一级再热器管排',
tag: '管排图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '2.1MB',
},
},
{
id: 'cad5',
thumb: '/Cad_Preview/二级再热器管排.png',
src: '/Cad/二级再热器管排.png',
meta: {
title: '二级再热器管排',
tag: '管排图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '2.5MB',
},
},
{
id: 'cad6',
thumb: '/Cad_Preview/二级过热器管排.png',
src: '/Cad/二级过热器管排.png',
meta: {
title: '二级过热器管排',
tag: '管排图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '2.7MB',
},
},
{
id: 'cad7',
thumb: '/Cad_Preview/一级再热器进口连接管.png',
src: '/Cad/一级再热器进口连接管.png',
meta: {
title: '一级再热器进口连接管',
tag: '进口图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '1.9MB',
},
},
{
id: 'cad8',
thumb: '/Cad_Preview/二级再热器进口连接管.png',
src: '/Cad/二级再热器进口连接管.png',
meta: {
title: '二级再热器进口连接管',
tag: '进口图',
dimensions: '1920x1080',
createdAt: '2024-12-04',
size: '2.2MB',
},
},
]
} catch (error) {
console.error('加载图片失败:', error)
} finally {
loading.value = false
}
}
// 选择图片(当前未使用,保留备用)
// const selectImage = (index: number) => {
// currentIndex.value = index
// }
// 打开预览
const openPreview = (index: number) => {
currentIndex.value = index // 同步更新当前索引
previewIndex.value = index
showPreview.value = true
}
// 下载图片
const downloadImage = (index: number) => {
const image = imageList.value[index]
if (image) {
const link = document.createElement('a')
link.href = image.src
link.download = `${image.meta.title}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
}
// 复制图片链接
const copyImageUrl = async (index: number) => {
const image = imageList.value[index]
if (image) {
try {
await navigator.clipboard.writeText(window.location.origin + image.src)
ElMessage.success('图片链接已复制到剪贴板')
} catch (error) {
ElMessage.error('复制失败,请手动复制')
}
}
}
// 显示图片信息
const showImageInfo = (index: number) => {
currentImage.value = imageList.value[index]
showInfoDialog.value = true
}
// 监听currentNodeId变化重新加载数据
watch(
() => store.currentNodeId,
newId => {
if (newId) {
// 当选择的节点ID变化时重新获取属性数据
fetchProperties(newId)
// 如果需要显示图库,加载图片
if (showGallery.value) {
loadImages()
}
}
}
)
// 组件挂载时初始化数据
onMounted(() => {
if (store.currentNodeId) {
// 获取初始属性数据
fetchProperties(store.currentNodeId)
// 如果需要显示图库,加载图片
if (showGallery.value) {
loadImages()
}
}
})
</script>
<style scoped>
/* Tech Icon Button */
.tech-icon-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(15, 23, 42, 0.5);
border: 1px solid rgba(34, 211, 238, 0.2);
color: #94a3b8;
cursor: pointer;
transition: all 0.3s ease;
}
.tech-icon-btn:hover {
background: rgba(6, 182, 212, 0.1);
border-color: #22d3ee;
color: #22d3ee;
box-shadow: 0 0 10px rgba(6, 182, 212, 0.4);
}
/* Tech Tag */
:deep(.tech-tag) {
background-color: rgba(6, 182, 212, 0.1);
border-color: rgba(6, 182, 212, 0.3);
color: #22d3ee;
}
/* Tech Descriptions */
:deep(.tech-descriptions .el-descriptions__body) {
background-color: transparent;
}
:deep(.tech-descriptions .el-descriptions__label) {
background-color: rgba(15, 23, 42, 0.8) !important;
color: #94a3b8;
font-family: monospace;
border-color: rgba(34, 211, 238, 0.1) !important;
}
:deep(.tech-descriptions .el-descriptions__content) {
background-color: transparent !important;
color: #e2e8f0;
font-family: monospace;
border-color: rgba(34, 211, 238, 0.1) !important;
}
/* Tech Status Tag */
:deep(.tech-status-tag.el-tag--success) {
background-color: rgba(16, 185, 129, 0.1);
border-color: rgba(16, 185, 129, 0.2);
color: #34d399;
}
/* Tech Button */
:deep(.tech-button) {
background-color: rgba(6, 182, 212, 0.1);
border-color: #06b6d4;
color: #22d3ee;
font-family: monospace;
}
:deep(.tech-button:hover) {
background-color: rgba(6, 182, 212, 0.2);
box-shadow: 0 0 15px rgba(6, 182, 212, 0.3);
}
/* Custom Scrollbar */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.5);
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(34, 211, 238, 0.2);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(34, 211, 238, 0.4);
}
</style>
<style>
/* Styling for the tech dialog modal - Global style to affect element-plus dialog projected to body */
.tech-dialog-modal {
background-color: #0b1120 !important;
border: 1px solid #06b6d4 !important;
box-shadow: 0 0 20px rgba(6, 182, 212, 0.2) !important;
}
.tech-dialog-modal .el-dialog__header {
border-bottom: 1px solid rgba(34, 211, 238, 0.1);
margin-right: 0 !important;
padding: 15px 20px !important;
}
.tech-dialog-modal .el-dialog__title {
color: #22d3ee !important;
font-family: monospace;
font-weight: bold;
}
.tech-dialog-modal .el-dialog__body {
background-color: #0b1120 !important;
color: #94a3b8 !important;
padding: 20px !important;
}
.tech-dialog-modal .el-dialog__close {
color: #22d3ee !important;
}
/* Table overrides inside dialog */
.tech-dialog-modal .el-table {
background-color: transparent !important;
--el-table-tr-bg-color: transparent !important;
--el-table-header-bg-color: rgba(15, 23, 42, 0.8) !important;
--el-table-row-hover-bg-color: rgba(6, 182, 212, 0.1) !important;
--el-table-border-color: rgba(34, 211, 238, 0.1) !important;
color: #94a3b8 !important;
}
.tech-dialog-modal .el-table th.el-table__cell {
background-color: rgba(15, 23, 42, 0.8) !important;
color: #22d3ee !important;
border-bottom: 1px solid rgba(34, 211, 238, 0.2) !important;
}
.tech-dialog-modal .el-table td.el-table__cell {
border-bottom: 1px solid rgba(34, 211, 238, 0.1) !important;
}
.tech-dialog-modal .el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
background-color: rgba(30, 41, 59, 0.3) !important;
}
/* Tag overrides within dialog */
.tech-dialog-modal .el-tag {
background-color: rgba(15, 23, 42, 0.8) !important;
border-color: rgba(34, 211, 238, 0.3) !important;
}
</style>