|
|
@@ -0,0 +1,287 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import CountTo from '@/components/count-to1.vue'
|
|
|
+
|
|
|
+// --- 类型定义 ---
|
|
|
+type TimeType = 'day' | 'month' | 'year'
|
|
|
+
|
|
|
+// 模拟的生产异常数据接口
|
|
|
+interface ProductionExceptions {
|
|
|
+ standby: number // 待命
|
|
|
+ maintenance: number // 维修
|
|
|
+ exception: number // 异常
|
|
|
+ material: number // 物资
|
|
|
+ personnel: number // 人员
|
|
|
+ weather: number // 天气
|
|
|
+ thirdParty: number // 三方
|
|
|
+ client: number // 甲方
|
|
|
+ wellhead: number // 井口
|
|
|
+}
|
|
|
+
|
|
|
+// 模拟的巡检异常数据接口
|
|
|
+interface InspectionExceptions {
|
|
|
+ equipment: number // 异常设备
|
|
|
+ point: number // 异常点
|
|
|
+}
|
|
|
+
|
|
|
+// --- 状态管理 ---
|
|
|
+const currentTimeType = ref<TimeType>('month')
|
|
|
+const loading = ref(false)
|
|
|
+
|
|
|
+// 时间选项:日、月、年
|
|
|
+const timeOptions = [
|
|
|
+ { label: '今日', value: 'day' },
|
|
|
+ { label: '本月', value: 'month' },
|
|
|
+ { label: '本年', value: 'year' }
|
|
|
+]
|
|
|
+
|
|
|
+// 数据响应式对象
|
|
|
+const productionData = ref<ProductionExceptions>({
|
|
|
+ standby: 0,
|
|
|
+ maintenance: 0,
|
|
|
+ exception: 0,
|
|
|
+ material: 0,
|
|
|
+ personnel: 0,
|
|
|
+ weather: 0,
|
|
|
+ thirdParty: 0,
|
|
|
+ client: 0,
|
|
|
+ wellhead: 0
|
|
|
+})
|
|
|
+
|
|
|
+const inspectionData = ref<InspectionExceptions>({
|
|
|
+ equipment: 0,
|
|
|
+ point: 0
|
|
|
+})
|
|
|
+
|
|
|
+// --- 配置项:用于 v-for 循环渲染 ---
|
|
|
+const productionConfig = [
|
|
|
+ { key: 'standby', name: '待命' },
|
|
|
+ { key: 'maintenance', name: '维修' },
|
|
|
+ { key: 'exception', name: '异常' },
|
|
|
+ { key: 'material', name: '物资' },
|
|
|
+ { key: 'personnel', name: '人员' },
|
|
|
+ { key: 'weather', name: '天气' },
|
|
|
+ { key: 'thirdParty', name: '三方' },
|
|
|
+ { key: 'client', name: '甲方' },
|
|
|
+ { key: 'wellhead', name: '井口' }
|
|
|
+]
|
|
|
+
|
|
|
+const inspectionConfig = [
|
|
|
+ { key: 'equipment', name: '异常设备' },
|
|
|
+ { key: 'point', name: '异常点' }
|
|
|
+]
|
|
|
+
|
|
|
+// --- 模拟 API 请求 ---
|
|
|
+const fetchData = async () => {
|
|
|
+ loading.value = true
|
|
|
+
|
|
|
+ // 模拟获取时间范围
|
|
|
+ const now = dayjs()
|
|
|
+ let start = '',
|
|
|
+ end = ''
|
|
|
+ if (currentTimeType.value === 'day') {
|
|
|
+ start = now.startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ end = now.endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ } else if (currentTimeType.value === 'month') {
|
|
|
+ start = now.startOf('month').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ end = now.endOf('month').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ } else {
|
|
|
+ start = now.startOf('year').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ end = now.endOf('year').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`查询时间范围: ${start} - ${end}`)
|
|
|
+
|
|
|
+ // 模拟网络延迟
|
|
|
+ setTimeout(() => {
|
|
|
+ // 生成随机数模拟数据
|
|
|
+ const random = (max: number) => Math.floor(Math.random() * max)
|
|
|
+
|
|
|
+ // 假设这是 API 1: 获取生产异常
|
|
|
+ productionData.value = {
|
|
|
+ standby: random(20),
|
|
|
+ maintenance: random(10),
|
|
|
+ exception: random(5),
|
|
|
+ material: random(15),
|
|
|
+ personnel: random(5),
|
|
|
+ weather: random(8),
|
|
|
+ thirdParty: random(3),
|
|
|
+ client: random(5),
|
|
|
+ wellhead: random(10)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 假设这是 API 2: 获取巡检异常
|
|
|
+ inspectionData.value = {
|
|
|
+ equipment: random(50),
|
|
|
+ point: random(100)
|
|
|
+ }
|
|
|
+
|
|
|
+ loading.value = false
|
|
|
+ }, 500)
|
|
|
+}
|
|
|
+
|
|
|
+const handleTimeChange = () => {
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ fetchData()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ class="card size-full rounded-lg p-4 flex flex-col"
|
|
|
+ v-loading="loading"
|
|
|
+ element-loading-background="rgba(0, 0, 0, 0.3)"
|
|
|
+ >
|
|
|
+ <!-- 头部:标题 + 时间切换 -->
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <!-- 红色装饰条,代表异常/警示 -->
|
|
|
+ <div class="w-1 h-4 bg-[#FF5252] rounded-full shadow-[0_0_8px_#FF5252]"></div>
|
|
|
+ <div class="text-[#e0e0e0] text-lg font-bold">异常统计看板</div>
|
|
|
+ </div>
|
|
|
+ <el-segmented
|
|
|
+ size="default"
|
|
|
+ v-model="currentTimeType"
|
|
|
+ :options="timeOptions"
|
|
|
+ @change="handleTimeChange"
|
|
|
+ class="dark-segmented w-50!"
|
|
|
+ block
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 内容区域:上下布局 -->
|
|
|
+ <div class="flex-1 flex flex-col gap-4 min-h-0 overflow-y-auto pr-1">
|
|
|
+ <!-- 1. 生产异常模块 -->
|
|
|
+ <div class="section-container">
|
|
|
+ <div class="text-[#FFD740] text-sm font-bold mb-3 flex items-center gap-2">
|
|
|
+ <span class="w-1.5 h-1.5 rounded-full bg-[#FFD740]"></span>
|
|
|
+ 生产异常
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 3x3 网格 -->
|
|
|
+ <div class="grid grid-cols-5 gap-3">
|
|
|
+ <div v-for="item in productionConfig" :key="item.key" class="stat-box group">
|
|
|
+ <div class="text-[#9ca3af] text-xs mb-1">{{ item.name }}</div>
|
|
|
+
|
|
|
+ <CountTo
|
|
|
+ class="text-xl font-bold font-mono text-white group-hover:text-[#FFD740] transition-colors"
|
|
|
+ :start-val="0"
|
|
|
+ :end-val="productionData[item.key as keyof ProductionExceptions]"
|
|
|
+ >
|
|
|
+ <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span>
|
|
|
+ </CountTo>
|
|
|
+ <!-- <div
|
|
|
+ class="text-xl font-bold font-mono text-white group-hover:text-[#FFD740] transition-colors"
|
|
|
+ >
|
|
|
+ {{ productionData[item.key as keyof ProductionExceptions] }}
|
|
|
+ </div> -->
|
|
|
+ <!-- 底部微光条 -->
|
|
|
+ <div
|
|
|
+ class="absolute bottom-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-[#FFD740] to-transparent opacity-20 group-hover:opacity-100 transition-opacity"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 2. 巡检异常模块 -->
|
|
|
+ <div class="section-container">
|
|
|
+ <div class="text-[#00E5FF] text-sm font-bold mb-3 flex items-center gap-2">
|
|
|
+ <span class="w-1.5 h-1.5 rounded-full bg-[#00E5FF]"></span>
|
|
|
+ 巡检异常
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 2列 网格 -->
|
|
|
+ <div class="grid grid-cols-2 gap-3">
|
|
|
+ <div v-for="item in inspectionConfig" :key="item.key" class="stat-box group">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="text-[#9ca3af] text-xs">{{ item.name }}</div>
|
|
|
+ <!-- 可以加个小图标占位 -->
|
|
|
+ <div class="w-1 h-1 bg-[#00E5FF] rounded-full opacity-50"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <CountTo
|
|
|
+ class="text-2xl font-bold font-mono text-white mt-1 group-hover:text-[#00E5FF] transition-colors"
|
|
|
+ :start-val="0"
|
|
|
+ :end-val="inspectionData[item.key as keyof InspectionExceptions]"
|
|
|
+ >
|
|
|
+ <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span>
|
|
|
+ </CountTo>
|
|
|
+ <!-- <div
|
|
|
+ class="text-2xl font-bold font-mono text-white mt-1 group-hover:text-[#00E5FF] transition-colors"
|
|
|
+ >
|
|
|
+ {{ inspectionData[item.key as keyof InspectionExceptions] }}
|
|
|
+ </div> -->
|
|
|
+ <!-- 底部微光条 -->
|
|
|
+ <div
|
|
|
+ class="absolute bottom-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-[#00E5FF] to-transparent opacity-20 group-hover:opacity-100 transition-opacity"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 主卡片背景 */
|
|
|
+.card {
|
|
|
+ background-color: rgb(0 0 0 / 30%);
|
|
|
+ box-shadow: 0 2px 12px rgb(0 0 0 / 50%);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 子模块容器 */
|
|
|
+.section-container {
|
|
|
+ padding: 12px;
|
|
|
+ background: rgb(255 255 255 / 2%);
|
|
|
+ border: 1px solid rgb(255 255 255 / 3%);
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 单个数字盒子 */
|
|
|
+.stat-box {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ padding: 8px 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: default;
|
|
|
+ background: rgb(0 0 0 / 20%);
|
|
|
+ border-radius: 6px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-box:hover {
|
|
|
+ background: rgb(255 255 255 / 5%);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 分段控制器样式覆盖 */
|
|
|
+.dark-segmented {
|
|
|
+ --el-segmented-item-selected-color: #e5eaf3;
|
|
|
+ --el-border-radius-base: 16px;
|
|
|
+ --el-segmented-color: #cfd3dc;
|
|
|
+ --el-segmented-bg-color: #262727;
|
|
|
+ --el-segmented-item-selected-bg-color: #ff5252;
|
|
|
+ --el-segmented-item-selected-disabled-bg-color: rgb(42 89 138);
|
|
|
+ --el-segmented-item-hover-color: #e5eaf3;
|
|
|
+ --el-segmented-item-hover-bg-color: #39393a;
|
|
|
+ --el-segmented-item-active-bg-color: #424243;
|
|
|
+ --el-segmented-item-disabled-color: #8d9095;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-segmented__item) {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+</style>
|