index.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <script setup lang="tsx">
  2. import { ref, onMounted, type VNodeChild } from 'vue'
  3. // import { NCard, NAlert, NButton, NRow, NCol, NStatistic, NList, NListItem, NThing, useMessage, useModal } from 'naive-ui'
  4. import { useRoute } from 'vue-router'
  5. import { useSitesMonitorStateStore } from '@/stores/modules/views/sitesMonitorState'
  6. import { useSitesMonitorConfigStore } from '@/stores/modules/views/sitesMonitorConfig';
  7. import { useSiteInfoDataStore } from '@/stores/modules/data/siteInfoData';
  8. import { useSiteModeDataStore } from '@/stores/modules/data/siteModeData';
  9. import { getManageConfig } from '@/service/sitesMonitor'
  10. import SiteItem from './component/siteItem.vue'
  11. import { NBackTop, NDatePicker, NFormItem, NVirtualList, NTooltip, NSwitch } from 'naive-ui'
  12. import type { SelectOption, DropdownOption, DropdownGroupOption, DropdownDividerOption, DropdownRenderOption } from 'naive-ui'
  13. import type { DropdownMixedOption, RenderOption } from 'naive-ui/es/dropdown/src/interface'
  14. import type { ApiMonitorInfo } from '@/service/sitesMonitor/interface'
  15. import { useSiteAlarmListDataStore } from '@/stores/modules/data/siteAlarmList';
  16. defineOptions({
  17. name: 'SitesMonitor',
  18. })
  19. const route = useRoute()
  20. const sitesMonitorStateStore = useSitesMonitorStateStore()
  21. const sitesMonitorConfigStore = useSitesMonitorConfigStore()
  22. const siteInfoDataStore = useSiteInfoDataStore()
  23. const siteModeDataStore = useSiteModeDataStore()
  24. const siteAlarmListStore = useSiteAlarmListDataStore()
  25. // 获取 name
  26. const monitorName = route.params.name as string
  27. const message = useMessage()
  28. const modal = useModal()
  29. // 实现顶部组件隐藏功能
  30. const headerCollapse = ref(true)
  31. let mainViewInTop: boolean = true
  32. let virtualListInTop: boolean = true
  33. const onShowBackTop = (value: boolean) => {
  34. mainViewInTop = !value
  35. if (value) {
  36. headerCollapse.value = false
  37. } else if (virtualListInTop) {
  38. headerCollapse.value = true
  39. }
  40. }
  41. const handleVirtualListScroll = (e: Event) => {
  42. const target = e.target as HTMLElement
  43. // scrollTop === 0 表示在顶部
  44. if (target.scrollTop <= 10) {
  45. virtualListInTop = true
  46. if (mainViewInTop) {
  47. headerCollapse.value = true
  48. }
  49. } else {
  50. virtualListInTop = false
  51. headerCollapse.value = false
  52. }
  53. }
  54. const showConfigDropdown = ref(false)
  55. const handleConfigDropdownClick = () => {
  56. showConfigDropdown.value = !showConfigDropdown.value
  57. }
  58. const handleDropdownSelect = (key: string | number, option: DropdownOption) => {
  59. console.log({ key, option })
  60. }
  61. const configValue = ref<{ [key in string]: boolean }>({
  62. })
  63. const createOptionsRender = (label: string, config: Ref, info?: string): () => VNodeChild => {
  64. const switchRender = createSwitchRender(config)
  65. const itemRender = () => h('div', {
  66. class: 'min-w-[150px] flex w-full justify-between items-center'
  67. }, {
  68. default: () => {
  69. const switchNode = switchRender()
  70. return [switchNode, label]
  71. }
  72. })
  73. if (info === undefined) {
  74. return itemRender
  75. }
  76. return () => h(
  77. NTooltip,
  78. { keepAliveOnHover: false, style: { width: 'max-content' } },
  79. {
  80. trigger: itemRender,
  81. default: () => info
  82. }
  83. )
  84. }
  85. const createSwitchRender = (switchValue: Ref<{ enable: boolean }>): () => VNodeChild => {
  86. return () => h(
  87. NSwitch,
  88. {
  89. value: switchValue.value.enable,
  90. 'onUpdate:value': (value: boolean) => {
  91. console.log({ value, switchValue })
  92. switchValue.value.enable = value
  93. },
  94. onClick: (e: MouseEvent) => {
  95. console.log({ switchValue: switchValue.value.enable })
  96. e.stopPropagation() // 阻止事件冒泡
  97. sitesMonitorConfigStore.updateConfig(monitorName)
  98. }
  99. }
  100. )
  101. }
  102. type ConfigOptionType = Omit<DropdownOption, "children">
  103. const configOptions: ConfigOptionType[] = [
  104. {
  105. label: createOptionsRender("状态", sitesMonitorConfigStore.getStateConfigState(monitorName)),
  106. key: "State",
  107. },
  108. {
  109. label: createOptionsRender("SOC", sitesMonitorConfigStore.getSocConfigState(monitorName)),
  110. key: 'SOC',
  111. },
  112. {
  113. label: createOptionsRender("aPower", sitesMonitorConfigStore.getAPowerConfigState(monitorName)),
  114. key: 'aPower'
  115. },
  116. {
  117. label: createOptionsRender("告警", sitesMonitorConfigStore.getAlarmConfigState(monitorName)),
  118. key: "Alarm",
  119. },
  120. {
  121. label: createOptionsRender("版本", sitesMonitorConfigStore.getVersionConfigState(monitorName)),
  122. key: "Version",
  123. },
  124. {
  125. label: createOptionsRender("资费", sitesMonitorConfigStore.getTariffsConfigState(monitorName)),
  126. key: "Tariffs",
  127. },
  128. {
  129. label: createOptionsRender("其他", sitesMonitorConfigStore.getOthersConfigState(monitorName)),
  130. key: 'Others',
  131. }
  132. ] as const
  133. type KeyType = typeof configOptions[number]['key']
  134. type Stringify<T> = T extends string | number ? `${T}` : never
  135. type KeyList = Stringify<NonNullable<typeof configOptions[number]['key']>>
  136. // // 工具:把 string | number 统一转成 string
  137. // type Stringify<T> = T extends string | number ? `${T}` : never
  138. // // 提取所有合法 key
  139. // type ExtractKeyUnion<T extends readonly { key?: any }[]> =
  140. // Stringify<NonNullable<T[number]['key']>>
  141. // type AConfigType = ExtractKeyUnion<typeof configOptions>
  142. // // 生成 { key: boolean }
  143. // type KeyBoolObject<T extends readonly any[]> = {
  144. // [K in ExtractKeyUnion<T>]: boolean
  145. // }
  146. // 配置值的类型
  147. // type ConfigType = KeyBoolObject<typeof configOptions>
  148. // const configValue = ref<KeyBoolObject<typeof configOptions>>(configOptions.reduce<Record<string, true>>((acc, item) => {
  149. // if (item.key != null) {
  150. // acc[String(item.key)] = false
  151. // }
  152. // return acc
  153. // }, {}))
  154. const siteFilterOptions = computed<SelectOption[]>(() => {
  155. return monitorSiteList.value.map((item) => {
  156. return {
  157. label: item,
  158. value: item
  159. }
  160. })
  161. })
  162. const selectedSites = ref<string[]>([])
  163. const monitorSiteList = ref<string[]>([])
  164. const dateSelectValue = sitesMonitorStateStore.monitorStateDateGet(monitorName)
  165. const dateSelect = computed(() => {
  166. return new Date(dateSelectValue.value).toLocaleDateString('en-CA'); // en-CA 默认是 YYYY-MM-DD
  167. })
  168. interface VirtualItemType {
  169. key: string
  170. value: string
  171. }
  172. const virtualItem = computed<VirtualItemType[]>(() => {
  173. if (selectedSites.value.length > 0) {
  174. return selectedSites.value.map((item) => {
  175. return {
  176. key: item,
  177. value: item
  178. }
  179. })
  180. }
  181. return monitorSiteList.value.map((item) => {
  182. return {
  183. key: item,
  184. value: item
  185. }
  186. })
  187. })
  188. const loadMonitorConfig = async (name: string): Promise<ApiMonitorInfo> => {
  189. const manageConfig = await getManageConfig(name)
  190. sitesMonitorConfigStore.setConfig(name, manageConfig.pageConfig)
  191. return manageConfig
  192. }
  193. onMounted(async () => {
  194. if (monitorName === undefined || monitorName === "") return
  195. // 加载监控数据
  196. const config = await loadMonitorConfig(monitorName)
  197. monitorSiteList.value = config.siteList
  198. })
  199. onUnmounted(() => {
  200. // 渲染前情况stores缓存
  201. siteInfoDataStore.clearSitesData(monitorSiteList.value)
  202. siteModeDataStore.clearSitesData(monitorSiteList.value)
  203. siteAlarmListStore.clearSitesData(monitorSiteList.value)
  204. })
  205. </script>
  206. <template>
  207. <div class="flex flex-col gap-y-2 p-4">
  208. <NBackTop :show="false" @update:show="onShowBackTop" :visibility-height="10" />
  209. <NCollapseTransition class="flex flex-col gap-y-2" :show="headerCollapse" :visibility-height="10">
  210. <NAlert type="info" closable>
  211. {{ monitorName }}
  212. </NAlert>
  213. <NCard content-class="flex gap-4">
  214. <NFormItem class="inline-block" :show-feedback="false" label="配置">
  215. <NDropdown :show="showConfigDropdown" :options="configOptions" placement="bottom-start"
  216. @select="handleDropdownSelect">
  217. <NButton circle @click="handleConfigDropdownClick">
  218. <template #icon>
  219. <i class="i-ph:puzzle-piece-bold" />
  220. </template>
  221. </NButton>
  222. </NDropdown>
  223. </NFormItem>
  224. <NFormItem class="inline-block" :show-feedback="false" label="站点筛选">
  225. <NSelect class="min-w-[250px]" :consistent-menu-width="false" v-model:value="selectedSites" multiple
  226. :max-tag-count="1" filterable clearable placeholder="站点选择" :options="siteFilterOptions"
  227. :ellipsis-tag-popover-props="{ trigger: 'hover', placement: 'top' }" />
  228. </NFormItem>
  229. <NFormItem class="inline-block" :show-feedback="false" label="选择日期">
  230. <NDatePicker v-model:value="dateSelectValue" type="date" />
  231. </NFormItem>
  232. </NCard>
  233. <NCard>
  234. <NRow>
  235. <NCol :span="12">
  236. <NStatistic label="统计数据" :value="monitorSiteList.length">
  237. <template #prefix>
  238. <span class="inline-block size-5 i-ph:ghost-duotone"></span>
  239. </template>
  240. <template #suffix>
  241. / {{ monitorSiteList.length }}
  242. </template>
  243. </NStatistic>
  244. </NCol>
  245. <NCol :span="12">
  246. <NStatistic label="活跃站点">
  247. {{ monitorSiteList.length }}
  248. </NStatistic>
  249. </NCol>
  250. </NRow>
  251. </NCard>
  252. </NCollapseTransition>
  253. <NCard>
  254. <NVirtualList class="max-h-[820px]" :items="virtualItem" item-resizable :item-size="380" key-field="key"
  255. @scroll="handleVirtualListScroll" visible-items-tag="div"
  256. :visible-items-props="{ class: 'flex flex-col gap-4' }">
  257. <template #default="{ item, index }: { item: VirtualItemType, index: number }">
  258. <NCard :key="item.key" class="list-item min-h-[380px]" hoverable>
  259. <SiteItem :monitorName="monitorName" :sn="item.value" :date="dateSelect" />
  260. </NCard>
  261. </template>
  262. </NVirtualList>
  263. </NCard>
  264. </div>
  265. </template>