| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- <script setup lang="tsx">
- import { ref, onMounted, type VNodeChild } from 'vue'
- // import { NCard, NAlert, NButton, NRow, NCol, NStatistic, NList, NListItem, NThing, useMessage, useModal } from 'naive-ui'
- import { useRoute } from 'vue-router'
- import { useSitesMonitorStateStore } from '@/stores/modules/views/sitesMonitorState'
- import { useSitesMonitorConfigStore } from '@/stores/modules/views/sitesMonitorConfig';
- import { useSiteInfoDataStore } from '@/stores/modules/data/siteInfoData';
- import { useSiteModeDataStore } from '@/stores/modules/data/siteModeData';
- import { getManageConfig } from '@/service/sitesMonitor'
- import SiteItem from './component/siteItem.vue'
- import { NBackTop, NDatePicker, NFormItem, NVirtualList, NTooltip, NSwitch } from 'naive-ui'
- import type { SelectOption, DropdownOption, DropdownGroupOption, DropdownDividerOption, DropdownRenderOption } from 'naive-ui'
- import type { DropdownMixedOption, RenderOption } from 'naive-ui/es/dropdown/src/interface'
- import type { ApiMonitorInfo } from '@/service/sitesMonitor/interface'
- import { useSiteAlarmListDataStore } from '@/stores/modules/data/siteAlarmList';
- defineOptions({
- name: 'SitesMonitor',
- })
- const route = useRoute()
- const sitesMonitorStateStore = useSitesMonitorStateStore()
- const sitesMonitorConfigStore = useSitesMonitorConfigStore()
- const siteInfoDataStore = useSiteInfoDataStore()
- const siteModeDataStore = useSiteModeDataStore()
- const siteAlarmListStore = useSiteAlarmListDataStore()
- // 获取 name
- const monitorName = route.params.name as string
- const message = useMessage()
- const modal = useModal()
- // 实现顶部组件隐藏功能
- const headerCollapse = ref(true)
- let mainViewInTop: boolean = true
- let virtualListInTop: boolean = true
- const onShowBackTop = (value: boolean) => {
- mainViewInTop = !value
- if (value) {
- headerCollapse.value = false
- } else if (virtualListInTop) {
- headerCollapse.value = true
- }
- }
- const handleVirtualListScroll = (e: Event) => {
- const target = e.target as HTMLElement
- // scrollTop === 0 表示在顶部
- if (target.scrollTop <= 10) {
- virtualListInTop = true
- if (mainViewInTop) {
- headerCollapse.value = true
- }
- } else {
- virtualListInTop = false
- headerCollapse.value = false
- }
- }
- const showConfigDropdown = ref(false)
- const handleConfigDropdownClick = () => {
- showConfigDropdown.value = !showConfigDropdown.value
- }
- const handleDropdownSelect = (key: string | number, option: DropdownOption) => {
- console.log({ key, option })
- }
- const configValue = ref<{ [key in string]: boolean }>({
- })
- const createOptionsRender = (label: string, config: Ref, info?: string): () => VNodeChild => {
- const switchRender = createSwitchRender(config)
- const itemRender = () => h('div', {
- class: 'min-w-[150px] flex w-full justify-between items-center'
- }, {
- default: () => {
- const switchNode = switchRender()
- return [switchNode, label]
- }
- })
- if (info === undefined) {
- return itemRender
- }
- return () => h(
- NTooltip,
- { keepAliveOnHover: false, style: { width: 'max-content' } },
- {
- trigger: itemRender,
- default: () => info
- }
- )
- }
- const createSwitchRender = (switchValue: Ref<{ enable: boolean }>): () => VNodeChild => {
- return () => h(
- NSwitch,
- {
- value: switchValue.value.enable,
- 'onUpdate:value': (value: boolean) => {
- console.log({ value, switchValue })
- switchValue.value.enable = value
- },
- onClick: (e: MouseEvent) => {
- console.log({ switchValue: switchValue.value.enable })
- e.stopPropagation() // 阻止事件冒泡
- sitesMonitorConfigStore.updateConfig(monitorName)
- }
- }
- )
- }
- type ConfigOptionType = Omit<DropdownOption, "children">
- const configOptions: ConfigOptionType[] = [
- {
- label: createOptionsRender("状态", sitesMonitorConfigStore.getStateConfigState(monitorName)),
- key: "State",
- },
- {
- label: createOptionsRender("SOC", sitesMonitorConfigStore.getSocConfigState(monitorName)),
- key: 'SOC',
- },
- {
- label: createOptionsRender("aPower", sitesMonitorConfigStore.getAPowerConfigState(monitorName)),
- key: 'aPower'
- },
- {
- label: createOptionsRender("告警", sitesMonitorConfigStore.getAlarmConfigState(monitorName)),
- key: "Alarm",
- },
- {
- label: createOptionsRender("版本", sitesMonitorConfigStore.getVersionConfigState(monitorName)),
- key: "Version",
- },
- {
- label: createOptionsRender("资费", sitesMonitorConfigStore.getTariffsConfigState(monitorName)),
- key: "Tariffs",
- },
- {
- label: createOptionsRender("其他", sitesMonitorConfigStore.getOthersConfigState(monitorName)),
- key: 'Others',
- }
- ] as const
- type KeyType = typeof configOptions[number]['key']
- type Stringify<T> = T extends string | number ? `${T}` : never
- type KeyList = Stringify<NonNullable<typeof configOptions[number]['key']>>
- // // 工具:把 string | number 统一转成 string
- // type Stringify<T> = T extends string | number ? `${T}` : never
- // // 提取所有合法 key
- // type ExtractKeyUnion<T extends readonly { key?: any }[]> =
- // Stringify<NonNullable<T[number]['key']>>
- // type AConfigType = ExtractKeyUnion<typeof configOptions>
- // // 生成 { key: boolean }
- // type KeyBoolObject<T extends readonly any[]> = {
- // [K in ExtractKeyUnion<T>]: boolean
- // }
- // 配置值的类型
- // type ConfigType = KeyBoolObject<typeof configOptions>
- // const configValue = ref<KeyBoolObject<typeof configOptions>>(configOptions.reduce<Record<string, true>>((acc, item) => {
- // if (item.key != null) {
- // acc[String(item.key)] = false
- // }
- // return acc
- // }, {}))
- const siteFilterOptions = computed<SelectOption[]>(() => {
- return monitorSiteList.value.map((item) => {
- return {
- label: item,
- value: item
- }
- })
- })
- const selectedSites = ref<string[]>([])
- const monitorSiteList = ref<string[]>([])
- const dateSelectValue = sitesMonitorStateStore.monitorStateDateGet(monitorName)
- const dateSelect = computed(() => {
- return new Date(dateSelectValue.value).toLocaleDateString('en-CA'); // en-CA 默认是 YYYY-MM-DD
- })
- interface VirtualItemType {
- key: string
- value: string
- }
- const virtualItem = computed<VirtualItemType[]>(() => {
- if (selectedSites.value.length > 0) {
- return selectedSites.value.map((item) => {
- return {
- key: item,
- value: item
- }
- })
- }
- return monitorSiteList.value.map((item) => {
- return {
- key: item,
- value: item
- }
- })
- })
- const loadMonitorConfig = async (name: string): Promise<ApiMonitorInfo> => {
- const manageConfig = await getManageConfig(name)
- sitesMonitorConfigStore.setConfig(name, manageConfig.pageConfig)
- return manageConfig
- }
- onMounted(async () => {
- if (monitorName === undefined || monitorName === "") return
- // 加载监控数据
- const config = await loadMonitorConfig(monitorName)
- monitorSiteList.value = config.siteList
- })
- onUnmounted(() => {
- // 渲染前情况stores缓存
- siteInfoDataStore.clearSitesData(monitorSiteList.value)
- siteModeDataStore.clearSitesData(monitorSiteList.value)
- siteAlarmListStore.clearSitesData(monitorSiteList.value)
- })
- </script>
- <template>
- <div class="flex flex-col gap-y-2 p-4">
- <NBackTop :show="false" @update:show="onShowBackTop" :visibility-height="10" />
- <NCollapseTransition class="flex flex-col gap-y-2" :show="headerCollapse" :visibility-height="10">
- <NAlert type="info" closable>
- {{ monitorName }}
- </NAlert>
- <NCard content-class="flex gap-4">
- <NFormItem class="inline-block" :show-feedback="false" label="配置">
- <NDropdown :show="showConfigDropdown" :options="configOptions" placement="bottom-start"
- @select="handleDropdownSelect">
- <NButton circle @click="handleConfigDropdownClick">
- <template #icon>
- <i class="i-ph:puzzle-piece-bold" />
- </template>
- </NButton>
- </NDropdown>
- </NFormItem>
- <NFormItem class="inline-block" :show-feedback="false" label="站点筛选">
- <NSelect class="min-w-[250px]" :consistent-menu-width="false" v-model:value="selectedSites" multiple
- :max-tag-count="1" filterable clearable placeholder="站点选择" :options="siteFilterOptions"
- :ellipsis-tag-popover-props="{ trigger: 'hover', placement: 'top' }" />
- </NFormItem>
- <NFormItem class="inline-block" :show-feedback="false" label="选择日期">
- <NDatePicker v-model:value="dateSelectValue" type="date" />
- </NFormItem>
- </NCard>
- <NCard>
- <NRow>
- <NCol :span="12">
- <NStatistic label="统计数据" :value="monitorSiteList.length">
- <template #prefix>
- <span class="inline-block size-5 i-ph:ghost-duotone"></span>
- </template>
- <template #suffix>
- / {{ monitorSiteList.length }}
- </template>
- </NStatistic>
- </NCol>
- <NCol :span="12">
- <NStatistic label="活跃站点">
- {{ monitorSiteList.length }}
- </NStatistic>
- </NCol>
- </NRow>
- </NCard>
- </NCollapseTransition>
- <NCard>
- <NVirtualList class="max-h-[820px]" :items="virtualItem" item-resizable :item-size="380" key-field="key"
- @scroll="handleVirtualListScroll" visible-items-tag="div"
- :visible-items-props="{ class: 'flex flex-col gap-4' }">
- <template #default="{ item, index }: { item: VirtualItemType, index: number }">
- <NCard :key="item.key" class="list-item min-h-[380px]" hoverable>
- <SiteItem :monitorName="monitorName" :sn="item.value" :date="dateSelect" />
- </NCard>
- </template>
- </NVirtualList>
- </NCard>
- </div>
- </template>
|