|
|
@@ -0,0 +1,243 @@
|
|
|
+<template>
|
|
|
+ <NModal v-model:show="show" preset="card" class="max-w-3xl" :title="currentSn">
|
|
|
+ <!-- 日期选择:← 日期 → -->
|
|
|
+ <div class="mb-3 flex items-center gap-2">
|
|
|
+ <NButton text :focusable="false" @click="prevDay">
|
|
|
+ <span class="i-ph:caret-left-bold text-lg" />
|
|
|
+ </NButton>
|
|
|
+ <NPopover trigger="click" placement="bottom" :show-arrow="false">
|
|
|
+ <template #trigger>
|
|
|
+ <span class="cursor-pointer select-none rounded px-2 py-1 text-sm font-medium hover:bg-neutral-100 dark:hover:bg-neutral-700">
|
|
|
+ {{ currentDateStr }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ <NDatePicker
|
|
|
+ v-model:value="currentDateValue"
|
|
|
+ type="date"
|
|
|
+ panel
|
|
|
+ :is-date-disabled="isDateDisabled"
|
|
|
+ />
|
|
|
+ </NPopover>
|
|
|
+ <NButton text :focusable="false" :disabled="isToday" @click="nextDay">
|
|
|
+ <span class="i-ph:caret-right-bold text-lg" />
|
|
|
+ </NButton>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 站点信息头部 -->
|
|
|
+ <div class="mb-2">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <span class="font-bold">{{ currentSn }}</span>
|
|
|
+ <!-- 告警图标 -->
|
|
|
+ <NPopover v-if="alarmList.length > 0" placement="right-start" trigger="click">
|
|
|
+ <template #trigger>
|
|
|
+ <NButton
|
|
|
+ class="i-ph:warning-circle-fill text-red-500 hover:text-red-600 transition-colors duration-200"
|
|
|
+ size="small"
|
|
|
+ text
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <NTimeline :icon-size="20">
|
|
|
+ <NTimelineItem
|
|
|
+ v-for="alarmItem in alarmList"
|
|
|
+ :key="alarmItem.alarmCode"
|
|
|
+ color="grey"
|
|
|
+ :content="alarmItem.logName"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <i v-if="alarmItem.level === 2" class="i-ph:warning-circle-fill text-red-500" />
|
|
|
+ <i v-else class="i-ph:warning-fill text-yellow-500" />
|
|
|
+ </template>
|
|
|
+ </NTimelineItem>
|
|
|
+ </NTimeline>
|
|
|
+ </NPopover>
|
|
|
+ <!-- aPower SN -->
|
|
|
+ <template v-if="aPowerList.length > 0">
|
|
|
+ <NDivider vertical />
|
|
|
+ <span class="text-gray-500 text-sm">[{{ aPowerList.join(', ') }}]</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Tags: 工作模式 + 版本 -->
|
|
|
+ <NSpace v-if="siteTags.length > 0" size="small" class="mt-2">
|
|
|
+ <NTag v-for="tag in siteTags" :key="tag" :bordered="false" type="info" size="small">
|
|
|
+ {{ tag }}
|
|
|
+ </NTag>
|
|
|
+ </NSpace>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 功率流图表 -->
|
|
|
+ <NSpin :show="chartLoading" :delay="500">
|
|
|
+ <PowerFlowChart ref="powerFlowChartRef" class="h-[300px]" :height="300" />
|
|
|
+ </NSpin>
|
|
|
+ </NModal>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import {
|
|
|
+ NModal,
|
|
|
+ NDatePicker,
|
|
|
+ NSpin,
|
|
|
+ NPopover,
|
|
|
+ NButton,
|
|
|
+ NDivider,
|
|
|
+ NSpace,
|
|
|
+ NTag,
|
|
|
+ NTimeline,
|
|
|
+ NTimelineItem,
|
|
|
+} from 'naive-ui'
|
|
|
+import { ref, computed, watch, nextTick } from 'vue'
|
|
|
+import PowerFlowChart from './powerFlowChart.vue'
|
|
|
+import { useSitePowerFlowDataStore } from '@/stores/modules/data/sitePowerFlowData'
|
|
|
+import { useSiteInfoDataStore } from '@/stores/modules/data/siteInfoData'
|
|
|
+import { useSiteModeDataStore } from '@/stores/modules/data/siteModeData'
|
|
|
+import { useSiteAlarmListDataStore } from '@/stores/modules/data/siteAlarmList'
|
|
|
+import type { SiteAlarmList } from '@/views/sites-monitor/interface'
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'SiteDataModal',
|
|
|
+})
|
|
|
+
|
|
|
+const show = ref(false)
|
|
|
+const currentSn = ref('')
|
|
|
+const currentDateValue = ref<number>(Date.now())
|
|
|
+const chartLoading = ref(false)
|
|
|
+
|
|
|
+// 日期相关计算
|
|
|
+const ONE_DAY = 24 * 60 * 60 * 1000
|
|
|
+
|
|
|
+const currentDateStr = computed(() => formatDate(currentDateValue.value))
|
|
|
+
|
|
|
+const isToday = computed(() => {
|
|
|
+ const today = formatDate(Date.now())
|
|
|
+ return currentDateStr.value === today
|
|
|
+})
|
|
|
+
|
|
|
+function isDateDisabled(timestamp: number): boolean {
|
|
|
+ // 禁用今天之后的日期
|
|
|
+ const todayEnd = new Date()
|
|
|
+ todayEnd.setHours(23, 59, 59, 999)
|
|
|
+ return timestamp > todayEnd.getTime()
|
|
|
+}
|
|
|
+
|
|
|
+function prevDay() {
|
|
|
+ currentDateValue.value = currentDateValue.value - ONE_DAY
|
|
|
+}
|
|
|
+
|
|
|
+function nextDay() {
|
|
|
+ if (!isToday.value) {
|
|
|
+ currentDateValue.value = currentDateValue.value + ONE_DAY
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 站点附加信息
|
|
|
+const alarmList = ref<SiteAlarmList[]>([])
|
|
|
+const aPowerList = ref<string[]>([])
|
|
|
+const siteTags = ref<string[]>([])
|
|
|
+
|
|
|
+const powerFlowChartRef = ref<InstanceType<typeof PowerFlowChart>>()
|
|
|
+const sitePowerFlowDataStore = useSitePowerFlowDataStore()
|
|
|
+const siteInfoDataStore = useSiteInfoDataStore()
|
|
|
+const siteModeDataStore = useSiteModeDataStore()
|
|
|
+const siteAlarmListStore = useSiteAlarmListDataStore()
|
|
|
+
|
|
|
+function formatDate(timestamp: number): string {
|
|
|
+ const d = new Date(timestamp)
|
|
|
+ const y = d.getFullYear()
|
|
|
+ const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(d.getDate()).padStart(2, '0')
|
|
|
+ return `${y}-${m}-${day}`
|
|
|
+}
|
|
|
+
|
|
|
+// 加载功率流图表
|
|
|
+async function loadPowerFlow() {
|
|
|
+ const sn = currentSn.value
|
|
|
+ const date = formatDate(currentDateValue.value)
|
|
|
+ if (!sn || !date) return
|
|
|
+
|
|
|
+ chartLoading.value = true
|
|
|
+ try {
|
|
|
+ let powerData = sitePowerFlowDataStore.powerFlowChartGet(sn, date)
|
|
|
+ if (powerData === undefined) {
|
|
|
+ await sitePowerFlowDataStore.queryPowerFlow(sn, date)
|
|
|
+ powerData = sitePowerFlowDataStore.powerFlowChartGet(sn, date)
|
|
|
+ }
|
|
|
+ powerFlowChartRef.value?.redraw(powerData)
|
|
|
+ } finally {
|
|
|
+ chartLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载站点信息(aPower SN、版本)
|
|
|
+async function loadSiteInfo(sn: string) {
|
|
|
+ let siteInfo = siteInfoDataStore.siteInfoGet(sn)
|
|
|
+ if (siteInfo === undefined) {
|
|
|
+ siteInfo = await siteInfoDataStore.querySiteInfo(sn)
|
|
|
+ }
|
|
|
+ if (siteInfo) {
|
|
|
+ // aPower SN
|
|
|
+ if (siteInfo.fhpSn) {
|
|
|
+ aPowerList.value = siteInfo.fhpSn.split(',').map((s) => s.trim()).filter(Boolean)
|
|
|
+ }
|
|
|
+ // 版本 tag
|
|
|
+ if (siteInfo.version) {
|
|
|
+ siteTags.value.push(siteInfo.version)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载工作模式
|
|
|
+async function loadWorkMode(sn: string) {
|
|
|
+ let modeInfo = siteModeDataStore.siteModeInfoGet(sn)
|
|
|
+ if (modeInfo === undefined) {
|
|
|
+ modeInfo = await siteModeDataStore.querySiteModeInfo(sn)
|
|
|
+ }
|
|
|
+ if (modeInfo) {
|
|
|
+ const currentId = modeInfo.currendId
|
|
|
+ modeInfo.list.forEach((item) => {
|
|
|
+ if (item.id === currentId) {
|
|
|
+ siteTags.value.push(`${item.name} - (${item.soc}%)`)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载告警列表
|
|
|
+async function loadAlarmList(sn: string) {
|
|
|
+ let list = siteAlarmListStore.siteAlarmListGet(sn)
|
|
|
+ if (list === undefined) {
|
|
|
+ list = await siteAlarmListStore.querySiteAlarmList(sn)
|
|
|
+ }
|
|
|
+ if (list) {
|
|
|
+ alarmList.value = [...list]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 日期变化时重新加载图表
|
|
|
+watch(currentDateValue, () => {
|
|
|
+ if (show.value) loadPowerFlow()
|
|
|
+})
|
|
|
+
|
|
|
+function open(sn: string) {
|
|
|
+ // 重置状态
|
|
|
+ currentSn.value = sn
|
|
|
+ currentDateValue.value = Date.now()
|
|
|
+ alarmList.value = []
|
|
|
+ aPowerList.value = []
|
|
|
+ siteTags.value = []
|
|
|
+ show.value = true
|
|
|
+
|
|
|
+ // 加载数据
|
|
|
+ nextTick(() => {
|
|
|
+ loadPowerFlow()
|
|
|
+ loadSiteInfo(sn)
|
|
|
+ loadWorkMode(sn)
|
|
|
+ loadAlarmList(sn)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function close() {
|
|
|
+ show.value = false
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({ open, close })
|
|
|
+</script>
|