|
|
@@ -0,0 +1,540 @@
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ class="bg-gradient-to-br from-slate-950 via-blue-950 to-slate-900 text-cyan-100 p-6 relative"
|
|
|
+ :class="{ 'fullscreen-layout': isFullscreen }"
|
|
|
+ >
|
|
|
+ <!-- Animated background grid -->
|
|
|
+ <div class="absolute inset-0 opacity-20">
|
|
|
+ <div class="absolute inset-0 grid-pattern"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Scanning line effect -->
|
|
|
+ <div class="absolute inset-0 pointer-events-none">
|
|
|
+ <div class="scan-line"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Header -->
|
|
|
+ <div class="relative z-10 mb-6">
|
|
|
+ <div class="flex items-center justify-center border-b-2 border-cyan-500/30 pb-4 gap-8">
|
|
|
+ <h1 class="text-2xl font-bold text-cyan-300 flex-6 tracking-wider">
|
|
|
+ {{ dataInfo.groupName }}
|
|
|
+ </h1>
|
|
|
+
|
|
|
+ <div class="flex flex-4 items-center gap-8 text-sm">
|
|
|
+ <div
|
|
|
+ style="border: 0.5px solid #085b77"
|
|
|
+ class="px-4 py-2 bg-cyan-500/10 border border-cyan-500/30 skew-x-[-12deg] hover:bg-cyan-500/20 transition-all cursor-pointer"
|
|
|
+ >
|
|
|
+ <span class="pr-4">部门名称</span>
|
|
|
+ <span class="inline-block skew-x-[12deg]">{{ dataInfo.deptName }}</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ style="border: 0.5px solid #085b77"
|
|
|
+ class="px-4 py-2 bg-cyan-500/10 border border-cyan-500/30 skew-x-[-12deg] hover:bg-cyan-500/20 transition-all cursor-pointer"
|
|
|
+ >
|
|
|
+ <span class="pr-4">井号</span>
|
|
|
+ <span class="inline-block skew-x-[12deg]">{{ dataInfo.wellName }}</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ style="border: 0.5px solid #085b77"
|
|
|
+ class="px-4 py-2 bg-cyan-500/10 border border-cyan-500/30 skew-x-[-12deg] hover:bg-cyan-500/20 transition-all cursor-pointer"
|
|
|
+ >
|
|
|
+ <span class="pr-4">增加机编号</span>
|
|
|
+ <span class="inline-block skew-x-[12deg]">{{ dataInfo.deviceCode }}</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ @click="toggleFullscreen"
|
|
|
+ style="border: 0.5px solid #085b77"
|
|
|
+ class="px-4 py-2 bg-cyan-500/10 border border-cyan-500/30 skew-x-[-12deg] hover:bg-cyan-500/20 transition-all cursor-pointer"
|
|
|
+ >
|
|
|
+ <span class="inline-block skew-x-[12deg]">{{
|
|
|
+ isFullscreen ? '退出全屏' : '全屏'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="relative z-10 grid grid-cols-12 gap-4 mt-5 p-6">
|
|
|
+ <!-- Left panels - 固定高度 -->
|
|
|
+ <div class="col-span-3">
|
|
|
+ <div class="panel h-full flex flex-col">
|
|
|
+ <!-- 增压机参数 -->
|
|
|
+ <div class="flex-1 border-b border-cyan-500/30 pb-2 mb-2">
|
|
|
+ <div class="flex justify-between items-center panel-title mb-2">
|
|
|
+ <h4>增压机参数</h4>
|
|
|
+ <dv-decoration3 style="width: 120px; height: 30px" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="space-y-3 overflow-y-auto overflow-x-hidden flex-grow">
|
|
|
+ <DataRow
|
|
|
+ v-for="item in psaData"
|
|
|
+ :key="item.modelName"
|
|
|
+ :label="item.modelName"
|
|
|
+ :value="formatValue(item.value, getUnitFromModelName(item.modelName))"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 累计运行时间 -->
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="flex justify-between items-center panel-title mb-2">
|
|
|
+ <h4>累计运行时间</h4>
|
|
|
+ <dv-decoration3 style="width: 120px; height: 30px" />
|
|
|
+ </div>
|
|
|
+ <div class="space-y-3 overflow-y-auto overflow-x-hidden flex-grow">
|
|
|
+ <DataRow
|
|
|
+ v-for="item in airTreatmentData"
|
|
|
+ :key="item.modelName"
|
|
|
+ :label="item.modelName"
|
|
|
+ :value="formatValue(item.value, getUnitFromModelName(item.modelName))"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="col-span-6 space-y-4">
|
|
|
+ <!-- 3D模型面板 -->
|
|
|
+ <div class="panel h-[60%] relative overflow-hidden">
|
|
|
+ <div class="relative h-full flex items-center justify-center">
|
|
|
+ <ModelViewer />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 状态参数面板 -->
|
|
|
+ <div class="panel h-[40%]">
|
|
|
+ <div class="mb-2">
|
|
|
+ <dv-decoration7>
|
|
|
+ <h3 class="text-cyan-300 text-lg font-bold px-1"> 状态参数 </h3>
|
|
|
+ </dv-decoration7>
|
|
|
+ </div>
|
|
|
+ <div class="grid grid-cols-5 gap-4 h-[calc(100%-2rem)]">
|
|
|
+ <div
|
|
|
+ v-for="(group, i) in compressorData"
|
|
|
+ :key="i"
|
|
|
+ class="space-y-2 text-center h-full overflow-y-auto"
|
|
|
+ >
|
|
|
+ <div class="text-sm" v-for="item in group" :key="item.modelName">
|
|
|
+ <div class="text-cyan-200">{{ item.modelName }}</div>
|
|
|
+ <div class="text-white font-mono flex items-center justify-center">
|
|
|
+ <span
|
|
|
+ :class="['w-3 h-3 rounded-full inline-block mr-2', getStatusClass(item.value)]"
|
|
|
+ ></span>
|
|
|
+ {{ getStatusDisplayText(item.modelName, item.value) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Right panel - 氮气参数和井口参数平分高度 -->
|
|
|
+ <div class="col-span-3">
|
|
|
+ <div class="panel h-[50%] mb-2">
|
|
|
+ <div class="flex items-center justify-between mb-4 border-b border-cyan-500/30 pb-2">
|
|
|
+ <h3 class="text-cyan-300 text-lg font-bold">氮气参数</h3>
|
|
|
+ <dv-decoration8 :reverse="true" style="width: 110px; height: 20px" />
|
|
|
+ </div>
|
|
|
+ <div class="space-y-2 text-sm overflow-y-auto overflow-x-hidden h-[calc(100%-3rem)]">
|
|
|
+ <DataRow
|
|
|
+ v-for="item in liquidDrivenData"
|
|
|
+ :key="item.modelName"
|
|
|
+ :label="item.modelName"
|
|
|
+ :value="formatValue(item.value, getUnitFromModelName(item.modelName))"
|
|
|
+ :compact="true"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="panel h-[50%]">
|
|
|
+ <div class="flex items-center justify-between mb-4 border-b border-cyan-500/30 pb-2">
|
|
|
+ <h3 class="text-cyan-300 text-lg font-bold">井口参数 </h3>
|
|
|
+ <dv-decoration8 :reverse="true" style="width: 110px; height: 20px" />
|
|
|
+ </div>
|
|
|
+ <div class="space-y-2 text-sm overflow-y-auto overflow-x-hidden h-[calc(100%-3rem)]">
|
|
|
+ <DataRow
|
|
|
+ v-for="item in wellheadData"
|
|
|
+ :key="item.modelName"
|
|
|
+ :label="item.modelName"
|
|
|
+ :value="formatValue(item.value, getUnitFromModelName(item.modelName))"
|
|
|
+ :compact="true"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted, nextTick } from 'vue'
|
|
|
+import DataRow from './data-row.vue'
|
|
|
+import ModelViewer from './ModelViewer2.vue'
|
|
|
+import { IotDeviceApi } from '@/api/pms/device'
|
|
|
+import { useRoute } from 'vue-router'
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'Kanban'
|
|
|
+})
|
|
|
+
|
|
|
+let dataInfo = ref<any>({})
|
|
|
+let psaData = ref<any[]>([])
|
|
|
+let airTreatmentData = ref<any[]>([])
|
|
|
+let mediumPressureData = ref<any[]>([])
|
|
|
+let liquidDrivenData = ref<any[]>([])
|
|
|
+let wellheadData = ref<any[]>([]) // 井口参数
|
|
|
+let compressorData = ref<any[][]>([])
|
|
|
+const isFullscreen = ref(false)
|
|
|
+
|
|
|
+// 从modelName中提取单位
|
|
|
+const getUnitFromModelName = (modelName: string) => {
|
|
|
+ if (!modelName) return ''
|
|
|
+
|
|
|
+ // 根据不同的参数类型返回单位
|
|
|
+ if (modelName.includes('压力') || modelName.includes('压力P')) return 'MPa'
|
|
|
+ if (modelName.includes('温度') || modelName.includes('T')) return '℃'
|
|
|
+ if (modelName.includes('流量')) return 'Nm³'
|
|
|
+ if (modelName.includes('时间')) return '小时'
|
|
|
+ if (modelName.includes('氮气纯度')) return '%'
|
|
|
+ if (modelName.includes('环境温度')) return '℃'
|
|
|
+ if (modelName.includes('喷油')) return modelName.includes('温度') ? '℃' : 'MPa'
|
|
|
+ if (modelName.includes('轴瓦温度')) return '℃'
|
|
|
+ if (modelName.includes('润滑油温度')) return '℃'
|
|
|
+ if (modelName.includes('排气温度')) return '℃'
|
|
|
+ if (modelName.includes('进气压力')) return 'MPa'
|
|
|
+ if (modelName.includes('排气压力')) return 'MPa'
|
|
|
+ if (modelName.includes('累计流量')) return 'm³'
|
|
|
+ if (modelName.includes('瞬时流量')) return 'm³/h'
|
|
|
+
|
|
|
+ return '' // 默认无单位
|
|
|
+}
|
|
|
+
|
|
|
+// 判断是否为状态类参数
|
|
|
+const isStatusParameter = (paramName: string) => {
|
|
|
+ const statusKeywords = ['无油1#', '无油2#', '曲轴油位', '油泵状态', '风机状态', '急停状态']
|
|
|
+ return statusKeywords.some((keyword) => paramName.includes(keyword))
|
|
|
+}
|
|
|
+
|
|
|
+// 获取状态类参数的显示文本
|
|
|
+const getStatusDisplayText = (paramName: string, value: string | number) => {
|
|
|
+ if (value === '' || value === null || value === undefined) {
|
|
|
+ return '--'
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据不同参数类型返回不同的状态文本
|
|
|
+ if (paramName.includes('急停状态')) {
|
|
|
+ return value === '1' || value === 1 || value === 'true' ? '正常' : '急停'
|
|
|
+ } else if (paramName.includes('油泵状态') || paramName.includes('风机状态')) {
|
|
|
+ return value === '1' || value === 1 || value === 'true' ? '运行' : '停止'
|
|
|
+ } else if (paramName.includes('无油')) {
|
|
|
+ return value === '1' || value === 1 || value === 'true' ? '正常' : '缺油'
|
|
|
+ } else if (paramName.includes('曲轴油位')) {
|
|
|
+ return value === '1' || value === 1 || value === 'true' ? '正常' : '低油位'
|
|
|
+ } else {
|
|
|
+ return value === '1' || value === 1 || value === 'true' ? '开启' : '关闭'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取状态类名
|
|
|
+const getStatusClass = (statusValue: string | number) => {
|
|
|
+ return statusValue === '1' || statusValue === 1 || statusValue === 'true'
|
|
|
+ ? 'status-active'
|
|
|
+ : 'status-inactive'
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化数值显示
|
|
|
+const formatValue = (value: string, unit: string) => {
|
|
|
+ if (
|
|
|
+ value === undefined ||
|
|
|
+ value === null ||
|
|
|
+ value === '' ||
|
|
|
+ value === 'null' ||
|
|
|
+ (value === '0' && unit === '')
|
|
|
+ ) {
|
|
|
+ return '--'
|
|
|
+ }
|
|
|
+ return unit ? `${value} ${unit}` : value
|
|
|
+}
|
|
|
+
|
|
|
+const toggleFullscreen = async () => {
|
|
|
+ if (!document.fullscreenElement) {
|
|
|
+ // 进入全屏
|
|
|
+ document.body.classList.add('app-fullscreen')
|
|
|
+
|
|
|
+ // 等待 DOM 更新后请求全屏
|
|
|
+ await nextTick()
|
|
|
+ document.documentElement.requestFullscreen()
|
|
|
+ } else {
|
|
|
+ // 退出全屏
|
|
|
+ document
|
|
|
+ .exitFullscreen()
|
|
|
+ .then(() => {
|
|
|
+ document.body.classList.remove('app-fullscreen')
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.error('退出全屏失败:', err)
|
|
|
+ document.body.classList.remove('app-fullscreen')
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleFullscreenChange = () => {
|
|
|
+ isFullscreen.value = !!document.fullscreenElement
|
|
|
+ if (!document.fullscreenElement) {
|
|
|
+ document.body.classList.remove('app-fullscreen')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
|
+})
|
|
|
+
|
|
|
+// 添加 onUnmounted 来清理事件监听器
|
|
|
+onUnmounted(() => {
|
|
|
+ document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
|
+})
|
|
|
+
|
|
|
+const getStatusText = (statusValue: string | number) => {
|
|
|
+ return statusValue === '1' || statusValue === 1 || statusValue === 'true' ? '运行中' : '停止'
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ try {
|
|
|
+ const res = await IotDeviceApi.getDeviceSetParams(route.query.deviceSetId)
|
|
|
+
|
|
|
+ // 设置基础信息
|
|
|
+ dataInfo.value = {
|
|
|
+ groupName: res.groupName,
|
|
|
+ deptName: res.deptName,
|
|
|
+ wellName: res.wellName,
|
|
|
+ deviceCode: res.deviceCode
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分离增压机参数、状态参数、氮气参数和井口参数
|
|
|
+ const allCompressorParams = res['增压机'] || []
|
|
|
+
|
|
|
+ // 定义状态参数关键词
|
|
|
+ const statusKeywords = [
|
|
|
+ '无油1#',
|
|
|
+ '无油2#',
|
|
|
+ '曲轴油位',
|
|
|
+ '油泵状态',
|
|
|
+ '风机状态',
|
|
|
+ '急停状态',
|
|
|
+ '振动'
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 定义氮气参数关键词
|
|
|
+ const gasKeywords = ['氮气纯度', '瞬时流量', '累计流量']
|
|
|
+
|
|
|
+ // 定义井口参数关键词 - 使用精确匹配
|
|
|
+ const wellheadKeywords = ['油压', '套压', '表套', '二套']
|
|
|
+
|
|
|
+ // 过滤出状态参数
|
|
|
+ const statusParams = allCompressorParams.filter((item) =>
|
|
|
+ statusKeywords.includes(item.modelName)
|
|
|
+ )
|
|
|
+
|
|
|
+ // 过滤出氮气参数
|
|
|
+ const gasParams = allCompressorParams.filter((item) => gasKeywords.includes(item.modelName))
|
|
|
+
|
|
|
+ // 过滤出井口参数 - 使用精确匹配
|
|
|
+ const wellheadParams = allCompressorParams.filter((item) =>
|
|
|
+ wellheadKeywords.includes(item.modelName)
|
|
|
+ )
|
|
|
+
|
|
|
+ // 过滤出非状态参数、非氮气参数和非井口参数(真正的增压机参数)
|
|
|
+ const nonStatusGasWellheadParams = allCompressorParams.filter(
|
|
|
+ (item) =>
|
|
|
+ !statusKeywords.includes(item.modelName) &&
|
|
|
+ !gasKeywords.includes(item.modelName) &&
|
|
|
+ !wellheadKeywords.includes(item.modelName)
|
|
|
+ )
|
|
|
+
|
|
|
+ // 设置各部分数据
|
|
|
+ psaData.value = nonStatusGasWellheadParams // 增压机参数(排除状态参数、氮气参数和井口参数)
|
|
|
+ mediumPressureData.value = statusParams // 状态参数
|
|
|
+
|
|
|
+ // 显示累计运行时间
|
|
|
+ airTreatmentData.value = [
|
|
|
+ {
|
|
|
+ modelName: '累计运行时间',
|
|
|
+ value: res.totalRuntime || 0
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 氮气参数
|
|
|
+ liquidDrivenData.value = gasParams
|
|
|
+
|
|
|
+ // 井口参数
|
|
|
+ wellheadData.value = wellheadParams
|
|
|
+
|
|
|
+ // 将状态参数分组显示
|
|
|
+ const chunkArray = (arr: any[], size: number) => {
|
|
|
+ const result = []
|
|
|
+ for (let i = 0; i < arr.length; i += size) {
|
|
|
+ result.push(arr.slice(i, i + size))
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将状态参数分成多组
|
|
|
+ compressorData.value = chunkArray(statusParams, 5)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取设备参数失败:', error)
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* @keyframes scan {
|
|
|
+ 0% {
|
|
|
+ top: 0;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ top: 100%;
|
|
|
+ }
|
|
|
+} */
|
|
|
+
|
|
|
+@keyframes gridMove {
|
|
|
+ 0% {
|
|
|
+ transform: translate(0, 0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: translate(50px, 50px);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.grid-pattern {
|
|
|
+ background-image: linear-gradient(rgba(6, 182, 212, 0.3) 1px, transparent 1px),
|
|
|
+ linear-gradient(90deg, rgba(6, 182, 212, 0.3) 1px, transparent 1px);
|
|
|
+ background-size: 50px 50px;
|
|
|
+ animation: gridMove 20s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.scan-line {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 2px;
|
|
|
+ background: linear-gradient(to right, transparent, rgb(6, 182, 212), transparent);
|
|
|
+ opacity: 0.3;
|
|
|
+ animation: scan 4s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.scan-line-fast {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 4px;
|
|
|
+ background: linear-gradient(to right, transparent, rgba(6, 182, 212, 0.6), transparent);
|
|
|
+ animation: scan 3s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.panel {
|
|
|
+ background: linear-gradient(to bottom right, rgba(15, 23, 42, 0.8), rgba(30, 58, 138, 0.5));
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ border: 2px solid rgba(6, 182, 212, 0.4);
|
|
|
+ padding: 1rem;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ box-shadow: 0 0 20px rgba(6, 182, 212, 0.3);
|
|
|
+ transition: all 0.3s;
|
|
|
+ min-height: 0; /* 允许flex项目收缩到其内容以下 */
|
|
|
+}
|
|
|
+
|
|
|
+.panel:hover {
|
|
|
+ box-shadow: 0 0 30px rgba(6, 182, 212, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.panel-title {
|
|
|
+ color: rgb(103, 232, 249);
|
|
|
+ font-size: 1.125rem;
|
|
|
+ font-weight: bold;
|
|
|
+ border-bottom: 1px solid rgba(6, 182, 212, 0.3);
|
|
|
+ padding-bottom: 0.5rem;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.status-indicator {
|
|
|
+ width: 2rem;
|
|
|
+ height: 2rem;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: pulse 1.5s ease-in-out infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.status-active {
|
|
|
+ background: rgb(34, 197, 94);
|
|
|
+ box-shadow: 0 0 15px rgba(34, 197, 94, 0.8);
|
|
|
+}
|
|
|
+
|
|
|
+.status-inactive {
|
|
|
+ background: rgb(239, 68, 68);
|
|
|
+ box-shadow: 0 0 15px rgba(239, 68, 68, 0.8);
|
|
|
+}
|
|
|
+
|
|
|
+.btn {
|
|
|
+ padding: 0.25rem 0.75rem;
|
|
|
+ border-radius: 0.25rem;
|
|
|
+ color: white;
|
|
|
+ transition: all 0.3s;
|
|
|
+ border: none;
|
|
|
+ cursor: pointer;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-green {
|
|
|
+ background: rgb(22, 163, 74);
|
|
|
+ box-shadow: 0 0 10px rgba(22, 163, 74, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-green:hover {
|
|
|
+ background: rgb(34, 197, 94);
|
|
|
+ box-shadow: 0 0 20px rgba(22, 163, 74, 0.8);
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-red {
|
|
|
+ background: rgb(220, 38, 38);
|
|
|
+ box-shadow: 0 0 10px rgba(220, 38, 38, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-red:hover {
|
|
|
+ background: rgb(239, 68, 68);
|
|
|
+ box-shadow: 0 0 20px rgba(220, 38, 38, 0.8);
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 全屏模式下组件特定样式 */
|
|
|
+.fullscreen-layout {
|
|
|
+ height: 100vh !important;
|
|
|
+ width: 100vw !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ padding-top: 20px !important;
|
|
|
+ padding-left: 10px !important;
|
|
|
+ padding-right: 10px !important;
|
|
|
+ padding-bottom: 150px !important;
|
|
|
+ position: fixed !important;
|
|
|
+ top: 0 !important;
|
|
|
+ left: 0 !important;
|
|
|
+ overflow: auto !important;
|
|
|
+ overflow-x: hidden !important;
|
|
|
+ max-width: 100vw !important;
|
|
|
+ max-height: 100vh !important;
|
|
|
+
|
|
|
+ scrollbar-width: none; /* Firefox */
|
|
|
+ -ms-overflow-style: none; /* Internet Explorer 10+ */
|
|
|
+}
|
|
|
+
|
|
|
+/* 在现有样式中添加 */
|
|
|
+.panel-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-content {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+</style>
|