Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
b91d073b8d | |||
ee6aa163d9 | |||
55e57eb219 | |||
f951aeb520 |
@@ -20,9 +20,6 @@ jobs:
|
|||||||
- name: 安装pnpm
|
- name: 安装pnpm
|
||||||
run: npm i pnpm -g
|
run: npm i pnpm -g
|
||||||
|
|
||||||
- name: 配置镜像
|
|
||||||
run: git config --global url."https://".insteadOf git://
|
|
||||||
|
|
||||||
- name: 安装依赖
|
- name: 安装依赖
|
||||||
run: |
|
run: |
|
||||||
git config --global url."https://".insteadOf git://
|
git config --global url."https://".insteadOf git://
|
||||||
|
@@ -20,6 +20,8 @@ VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj6
|
|||||||
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
||||||
# 客户端id
|
# 客户端id
|
||||||
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
||||||
|
# 开启WEBSOCKET
|
||||||
|
VITE_APP_WEBSOCKET=true
|
||||||
|
|
||||||
# 开启SSE
|
# 开启SSE
|
||||||
VITE_GLOB_SSE_ENABLE=true
|
VITE_GLOB_SSE_ENABLE=true
|
||||||
|
@@ -26,6 +26,8 @@ VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj6
|
|||||||
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
||||||
# 客户端id
|
# 客户端id
|
||||||
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
||||||
|
# 开启WEBSOCKET
|
||||||
|
VITE_APP_WEBSOCKET=true
|
||||||
|
|
||||||
# 开启SSE
|
# 开启SSE
|
||||||
VITE_GLOB_SSE_ENABLE=true
|
VITE_GLOB_SSE_ENABLE=true
|
||||||
|
@@ -54,7 +54,7 @@
|
|||||||
"echarts-gl": "^2.0.9",
|
"echarts-gl": "^2.0.9",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mpegts.js": "1.7.3",
|
"mpegts.js": "^1.8.0",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"tinymce": "^7.3.0",
|
"tinymce": "^7.3.0",
|
||||||
"unplugin-vue-components": "^0.27.3",
|
"unplugin-vue-components": "^0.27.3",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { MeterInfoVO, MeterInfoForm, MeterInfoQuery } from './model'
|
import type { MeterInfoVO, MeterInfoForm, MeterInfoQuery } from './model'
|
||||||
|
|
||||||
import type { ID, IDS, PageResult, TreeNode } from '#/api/common';
|
import type { ID, IDS, PageResult, TreeNode } from '#/api/common'
|
||||||
|
|
||||||
import { commonExport } from '#/api/helper'
|
import { commonExport } from '#/api/helper'
|
||||||
import { requestClient } from '#/api/request'
|
import { requestClient } from '#/api/request'
|
||||||
@@ -64,6 +64,13 @@ export function meterInfoRemove(id: ID | IDS) {
|
|||||||
* @param level
|
* @param level
|
||||||
* @returns 水电气树
|
* @returns 水电气树
|
||||||
*/
|
*/
|
||||||
export function queryTree(meterType: number | string) {
|
export function queryTree(params?: any) {
|
||||||
return requestClient.get<TreeNode<Number>[]>(`/property/meterInfo/tree/${meterType}`)
|
return requestClient.get<TreeNode<Number>[]>(`/property/meterInfo/tree`, { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取水/电/气表当前读数/状态
|
||||||
|
*/
|
||||||
|
export function currentReading(params?: any) {
|
||||||
|
return requestClient.get<void>(`/property/meterInfo/currentReading`, { params })
|
||||||
}
|
}
|
162
apps/web-antd/src/api/websocket.ts
Normal file
162
apps/web-antd/src/api/websocket.ts
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
|
interface WebSocketCallbacks {
|
||||||
|
onOpen?: (event: Event) => void;
|
||||||
|
onMessage?: (event: MessageEvent) => void;
|
||||||
|
onError?: (error: Event) => void;
|
||||||
|
onClose?: (event: CloseEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class WebSocketService {
|
||||||
|
private webSocket: WebSocket | null = null;
|
||||||
|
private webSocketURL: string = '';
|
||||||
|
|
||||||
|
// 默认回调函数
|
||||||
|
private onOpenCallback: (event: Event) => void;
|
||||||
|
private onMessageCallback: (event: MessageEvent) => void;
|
||||||
|
private onErrorCallback: (error: Event) => void;
|
||||||
|
private onCloseCallback: (event: CloseEvent) => void;
|
||||||
|
|
||||||
|
constructor(callbacks?: WebSocketCallbacks) {
|
||||||
|
// 设置回调函数,使用自定义回调或默认回调
|
||||||
|
this.onOpenCallback = callbacks?.onOpen || this.defaultOnOpen;
|
||||||
|
this.onMessageCallback = callbacks?.onMessage || this.defaultOnMessage;
|
||||||
|
this.onErrorCallback = callbacks?.onError || this.defaultOnError;
|
||||||
|
this.onCloseCallback = callbacks?.onClose || this.defaultOnClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化WebSocket连接
|
||||||
|
initWebSocket(webSocketURL: string): void {
|
||||||
|
this.webSocketURL = webSocketURL;
|
||||||
|
try {
|
||||||
|
this.webSocket = new WebSocket(webSocketURL);
|
||||||
|
|
||||||
|
this.webSocket.onopen = this.onOpenCallback;
|
||||||
|
this.webSocket.onmessage = this.onMessageCallback;
|
||||||
|
this.webSocket.onerror = this.onErrorCallback;
|
||||||
|
this.webSocket.onclose = this.onCloseCallback;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize WebSocket:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置onOpen回调并更新WebSocket事件处理器
|
||||||
|
setOnOpenCallback(callback: (event: Event) => void): void {
|
||||||
|
this.onOpenCallback = callback;
|
||||||
|
if (this.webSocket) {
|
||||||
|
this.webSocket.onopen = this.onOpenCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置onMessage回调并更新WebSocket事件处理器
|
||||||
|
setOnMessageCallback(callback: (event: MessageEvent) => void): void {
|
||||||
|
this.onMessageCallback = callback;
|
||||||
|
if (this.webSocket) {
|
||||||
|
this.webSocket.onmessage = this.onMessageCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置onError回调并更新WebSocket事件处理器
|
||||||
|
setOnErrorCallback(callback: (error: Event) => void): void {
|
||||||
|
this.onErrorCallback = callback;
|
||||||
|
if (this.webSocket) {
|
||||||
|
this.webSocket.onerror = this.onErrorCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置onClose回调并更新WebSocket事件处理器
|
||||||
|
setOnCloseCallback(callback: (event: CloseEvent) => void): void {
|
||||||
|
this.onCloseCallback = callback;
|
||||||
|
if (this.webSocket) {
|
||||||
|
this.webSocket.onclose = this.onCloseCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认连接建立成功的回调
|
||||||
|
private defaultOnOpen(event: Event): void {
|
||||||
|
console.log('WebSocket连接建立成功', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认收到服务器消息的回调
|
||||||
|
private defaultOnMessage(event: MessageEvent): void {
|
||||||
|
console.log('收到服务器消息', event.data);
|
||||||
|
// 通常这里会解析数据并更新页面
|
||||||
|
// const data = JSON.parse(event.data);
|
||||||
|
// 根据消息类型处理不同业务...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认发生错误的回调
|
||||||
|
private defaultOnError(error: Event): void {
|
||||||
|
console.error('WebSocket连接错误', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认连接关闭的回调
|
||||||
|
private defaultOnClose(event: CloseEvent): void {
|
||||||
|
console.log('WebSocket连接关闭', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭连接
|
||||||
|
close(): void {
|
||||||
|
if (this.webSocket) {
|
||||||
|
this.webSocket.close();
|
||||||
|
this.webSocket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
sendMessage(message: string | object): void {
|
||||||
|
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
||||||
|
const dataToSend = typeof message === 'string' ? message : JSON.stringify(message);
|
||||||
|
this.webSocket.send(dataToSend);
|
||||||
|
} else {
|
||||||
|
console.error('WebSocket连接未就绪');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前WebSocket状态
|
||||||
|
getReadyState(): number | null {
|
||||||
|
return this.webSocket ? this.webSocket.readyState : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取WebSocket URL
|
||||||
|
getWebSocketURL(): string {
|
||||||
|
return this.webSocketURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新连接
|
||||||
|
reconnect(): void {
|
||||||
|
if (this.webSocketURL) {
|
||||||
|
this.close();
|
||||||
|
this.initWebSocket(this.webSocketURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个可导出的WebSocket实例
|
||||||
|
let globalWebSocketService: WebSocketService | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化WebSocket连接的可导出方法
|
||||||
|
* @param url WebSocket服务器地址
|
||||||
|
* @param callbacks 回调函数对象(可选)
|
||||||
|
* @returns WebSocketService实例
|
||||||
|
*/
|
||||||
|
export function initWebSocket(callbacks?: WebSocketCallbacks): void {
|
||||||
|
if (!globalWebSocketService) {
|
||||||
|
globalWebSocketService = new WebSocketService(callbacks);
|
||||||
|
}
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const clinetId = import.meta.env.VITE_GLOB_APP_CLIENT_ID;
|
||||||
|
const api = import.meta.env.VITE_GLOB_API_URL;
|
||||||
|
const host = window.location.host;
|
||||||
|
const url = `ws://${host}${api}/resource/websocket?clientid=${clinetId}&Authorization=Bearer ${accessStore.accessToken}`;
|
||||||
|
globalWebSocketService.initWebSocket(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全局WebSocket服务实例
|
||||||
|
* @returns WebSocketService实例或null
|
||||||
|
*/
|
||||||
|
export function getWebSocketService(): WebSocketService | null {
|
||||||
|
return globalWebSocketService;
|
||||||
|
}
|
@@ -1,30 +1,31 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue'
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access'
|
||||||
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
import { registerLoadingDirective } from '@vben/common-ui/es/loading'
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences'
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores'
|
||||||
import '@vben/styles';
|
import '@vben/styles'
|
||||||
import '@vben/styles/antd';
|
import '@vben/styles/antd'
|
||||||
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core'
|
||||||
|
|
||||||
import { setupGlobalComponent } from '#/components/global';
|
import { setupGlobalComponent } from '#/components/global'
|
||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales'
|
||||||
|
|
||||||
import { initComponentAdapter } from './adapter/component';
|
import { initComponentAdapter } from './adapter/component'
|
||||||
import { initSetupVbenForm } from './adapter/form';
|
import { initSetupVbenForm } from './adapter/form'
|
||||||
import App from './app.vue';
|
import App from './app.vue'
|
||||||
import { router } from './router';
|
import { router } from './router'
|
||||||
|
import { initWebSocket } from '#/api/websocket'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter()
|
||||||
|
|
||||||
// 初始化表单组件
|
// 初始化表单组件
|
||||||
await initSetupVbenForm();
|
await initSetupVbenForm()
|
||||||
|
|
||||||
// // 设置弹窗的默认配置
|
// // 设置弹窗的默认配置
|
||||||
// setDefaultModalProps({
|
// setDefaultModalProps({
|
||||||
@@ -35,49 +36,53 @@ async function bootstrap(namespace: string) {
|
|||||||
// zIndex: 1020,
|
// zIndex: 1020,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const app = createApp(App);
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
// 全局组件
|
// 全局组件
|
||||||
setupGlobalComponent(app);
|
setupGlobalComponent(app)
|
||||||
// 注册v-loading指令
|
// 注册v-loading指令
|
||||||
registerLoadingDirective(app, {
|
registerLoadingDirective(app, {
|
||||||
loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
|
loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
|
||||||
spinning: 'spinning',
|
spinning: 'spinning',
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
await setupI18n(app);
|
await setupI18n(app)
|
||||||
|
|
||||||
// 配置 pinia-tore
|
// 配置 pinia-tore
|
||||||
await initStores(app, { namespace });
|
await initStores(app, { namespace })
|
||||||
|
|
||||||
|
// 初始化WebSocket
|
||||||
|
initWebSocket()
|
||||||
|
|
||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
registerAccessDirective(app);
|
registerAccessDirective(app)
|
||||||
|
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
const { initTippy } = await import('@vben/common-ui/es/tippy')
|
||||||
initTippy(app);
|
initTippy(app)
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router)
|
||||||
|
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
const { MotionPlugin } = await import('@vben/plugins/motion')
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin)
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (preferences.app.dynamicTitle) {
|
if (preferences.app.dynamicTitle) {
|
||||||
const routeTitle = router.currentRoute.value.meta?.title;
|
const routeTitle = router.currentRoute.value.meta?.title
|
||||||
const pageTitle =
|
const pageTitle =
|
||||||
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
|
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name
|
||||||
useTitle(pageTitle);
|
useTitle(pageTitle)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app')
|
||||||
}
|
}
|
||||||
|
|
||||||
export { bootstrap };
|
export { bootstrap }
|
||||||
|
@@ -1,14 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { PropType } from "vue";
|
import type { PropType } from 'vue';
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from 'vue';
|
||||||
import { handleNode } from "@vben/utils";
|
import { handleNode } from '@vben/utils';
|
||||||
import { Empty, Skeleton, Tree } from "ant-design-vue";
|
import { Empty, Skeleton, Tree } from 'ant-design-vue';
|
||||||
import { queryTree } from "#/api/property/energyManagement/meterInfo";
|
import { queryTree } from '#/api/property/energyManagement/meterInfo';
|
||||||
import type { TreeNode } from "#/api/common";
|
import type { TreeNode } from '#/api/common';
|
||||||
|
|
||||||
defineOptions({ inheritAttrs: false });
|
defineOptions({ inheritAttrs: false });
|
||||||
|
|
||||||
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
|
const props = defineProps({
|
||||||
|
isMeter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
/**
|
/**
|
||||||
@@ -23,13 +28,13 @@ const emit = defineEmits<{
|
|||||||
select: [selectedKeys: string[], info: any];
|
select: [selectedKeys: string[], info: any];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const selectTreeId = defineModel("selectTreeId", {
|
const selectTreeId = defineModel('selectTreeId', {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchValue = defineModel("searchValue", {
|
const searchValue = defineModel('searchValue', {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const treeArray = ref<TreeNode[]>([]);
|
const treeArray = ref<TreeNode[]>([]);
|
||||||
@@ -38,8 +43,8 @@ const showTreeSkeleton = ref<boolean>(true);
|
|||||||
|
|
||||||
async function loadTree() {
|
async function loadTree() {
|
||||||
showTreeSkeleton.value = true;
|
showTreeSkeleton.value = true;
|
||||||
searchValue.value = "";
|
searchValue.value = '';
|
||||||
const ret = await queryTree(1);
|
const ret = await queryTree({ meterType: 1, isMeter: props.isMeter });
|
||||||
handleNode(ret, 3);
|
handleNode(ret, 3);
|
||||||
treeArray.value = ret;
|
treeArray.value = ret;
|
||||||
showTreeSkeleton.value = false;
|
showTreeSkeleton.value = false;
|
||||||
@@ -62,33 +67,46 @@ onMounted(loadTree);
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$attrs.class">
|
<div :class="$attrs.class">
|
||||||
<Skeleton :loading="showTreeSkeleton"
|
<Skeleton
|
||||||
:paragraph="{ rows: 8 }"
|
:loading="showTreeSkeleton"
|
||||||
active
|
:paragraph="{ rows: 8 }"
|
||||||
class="p-[8px] flex-1 min-h-0">
|
active
|
||||||
<div class="bg-background flex h-full flex-col overflow-y-auto rounded-lg">
|
class="min-h-0 flex-1 p-[8px]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-background flex h-full flex-col overflow-y-auto rounded-lg"
|
||||||
|
>
|
||||||
<div class="h-full overflow-x-hidden px-[8px]">
|
<div class="h-full overflow-x-hidden px-[8px]">
|
||||||
<Tree v-bind="$attrs"
|
<Tree
|
||||||
v-if="treeArray.length > 0"
|
v-bind="$attrs"
|
||||||
v-model:selected-keys="selectTreeId"
|
v-if="treeArray.length > 0"
|
||||||
:show-line="{ showLeafIcon: false }"
|
v-model:selected-keys="selectTreeId"
|
||||||
:tree-data="treeArray"
|
:show-line="{ showLeafIcon: false }"
|
||||||
:virtual="false"
|
:tree-data="treeArray"
|
||||||
default-expand-all
|
:virtual="false"
|
||||||
@select="(selectedKeys, info) => $emit('select', selectedKeys, info)">
|
default-expand-all
|
||||||
|
@select="
|
||||||
|
(selectedKeys, info) => $emit('select', selectedKeys, info)
|
||||||
|
"
|
||||||
|
>
|
||||||
<template #title="{ label }">
|
<template #title="{ label }">
|
||||||
<span v-if="label.indexOf(searchValue) > -1">
|
<span v-if="label.indexOf(searchValue) > -1">
|
||||||
{{ label.substring(0, label.indexOf(searchValue)) }}
|
{{ label.substring(0, label.indexOf(searchValue)) }}
|
||||||
<span style="color: #f50">{{ searchValue }}</span>
|
<span style="color: #f50">{{ searchValue }}</span>
|
||||||
{{ label.substring(label.indexOf(searchValue) + searchValue.length) }}
|
{{
|
||||||
|
label.substring(
|
||||||
|
label.indexOf(searchValue) + searchValue.length,
|
||||||
|
)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ label }}</span>
|
<span v-else>{{ label }}</span>
|
||||||
</template>
|
</template>
|
||||||
</Tree>
|
</Tree>
|
||||||
<div v-else
|
<div v-else class="mt-5">
|
||||||
class="mt-5">
|
<Empty
|
||||||
<Empty :image="Empty.PRESENTED_IMAGE_SIMPLE"
|
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||||
description="暂无数据" />
|
description="暂无数据"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,516 +1,286 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RadioGroup, RadioButton, message } from 'ant-design-vue';
|
import { useAccessStore } from '@vben/stores';
|
||||||
import { Page } from '@vben/common-ui';
|
import { BackTop, message, Spin } from 'ant-design-vue';
|
||||||
|
import { Page, VbenCountToAnimator } from '@vben/common-ui';
|
||||||
import { ref, onMounted, onBeforeUnmount, reactive } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, reactive } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
|
||||||
import type { ECharts, EChartsOption } from 'echarts';
|
|
||||||
import FloorTree from '../components/floor-tree.vue';
|
import FloorTree from '../components/floor-tree.vue';
|
||||||
import dayjs from 'dayjs';
|
import { getWebSocketService } from '#/api/websocket';
|
||||||
import { meterRecordTrend } from '#/api/property/energyManagement/meterRecord';
|
import { currentReading } from '#/api/property/energyManagement/meterInfo';
|
||||||
|
|
||||||
// 左边楼层用
|
const ws = getWebSocketService();
|
||||||
const selectFloorId = ref<string[]>([]);
|
|
||||||
|
|
||||||
const chainData = reactive({
|
if (ws) {
|
||||||
todayEnergy: '231.78',
|
// 使用setOnMessageCallback方法设置消息回调
|
||||||
yesterdaySamePeriodEnergy: '269.56',
|
ws.setOnMessageCallback((event: MessageEvent) => {
|
||||||
dayTrendPercentage: '-14.02%',
|
// 解析数据并更新UI
|
||||||
dayTrendValue: '-37.78',
|
try {
|
||||||
currentMonthEnergy: '18758.39',
|
const data = JSON.parse(event.data);
|
||||||
lastMonthSamePeriodEnergy: '--',
|
if (data.type === 'meter') {
|
||||||
monthTrendPercentage: '--',
|
if (typeof data.data === 'undefined') {
|
||||||
monthTrendValue: '--',
|
message.warn('当前楼层暂无电表!');
|
||||||
currentYearEnergy: '18758.39',
|
}
|
||||||
lastYearSamePeriodEnergy: '--',
|
readingData.value = data.data;
|
||||||
yearTrendPercentage: '--',
|
readingTime.value = data.readingTime;
|
||||||
yearTrendValue: '--',
|
}
|
||||||
});
|
} catch (e) {
|
||||||
|
console.error('Error parsing data:');
|
||||||
const peakData = reactive({
|
|
||||||
todayPeakPower: '2961.08',
|
|
||||||
todayPeakTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
yesterdayPeakPower: '2993.89',
|
|
||||||
yesterdayPeakTime: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const energyTrendTime = ref('1');
|
|
||||||
|
|
||||||
// 能耗趋势图表容器
|
|
||||||
const energyTrendChart = ref<HTMLElement | null>(null);
|
|
||||||
const energyTrendInstance = ref<ECharts | null>(null);
|
|
||||||
|
|
||||||
const energyTrendOption: EChartsOption = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: [],
|
|
||||||
name: '时间',
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
name: 'kW.h',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
data: [],
|
|
||||||
type: 'bar',
|
|
||||||
markPoint: {
|
|
||||||
data: [
|
|
||||||
{ type: 'max', name: 'Max' },
|
|
||||||
{ type: 'min', name: 'Min' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const initEnergyTrendChart = () => {
|
|
||||||
if (energyTrendChart.value) {
|
|
||||||
// 销毁旧实例
|
|
||||||
energyTrendInstance.value?.dispose();
|
|
||||||
// 创建新实例
|
|
||||||
energyTrendInstance.value = echarts.init(energyTrendChart.value);
|
|
||||||
// 设置配置项
|
|
||||||
energyTrendInstance.value.setOption(energyTrendOption);
|
|
||||||
// buildingEnergyTrendData('1');
|
|
||||||
// 可选:添加窗口大小变化监听
|
|
||||||
const resizeHandler = () => {
|
|
||||||
energyTrendInstance.value?.resize();
|
|
||||||
};
|
|
||||||
window.addEventListener('resize', resizeHandler);
|
|
||||||
|
|
||||||
// 在组件卸载前移除监听
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('resize', resizeHandler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildingEnergyTrendData(val: string) {
|
|
||||||
if (trendData.value.hour == null) {
|
|
||||||
message.warning('请先选择楼层或电表!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
let timeArr = [];
|
|
||||||
let valArr = [];
|
|
||||||
let name = '时间';
|
|
||||||
if (val == '1') {
|
|
||||||
const hour = now.getHours();
|
|
||||||
for (let i = 0; i < hour; i++) {
|
|
||||||
timeArr.push(i + ':00');
|
|
||||||
}
|
}
|
||||||
valArr = trendData.value.hour.today.data;
|
readingLoading.value = false;
|
||||||
} else if (val == '2') {
|
});
|
||||||
const day = now.getDate();
|
|
||||||
for (let i = 1; i < day; i++) {
|
|
||||||
timeArr.push(i);
|
|
||||||
}
|
|
||||||
name = '日期';
|
|
||||||
valArr = trendData.value.day.nowMonth.data;
|
|
||||||
} else {
|
|
||||||
const month = now.getMonth() + 1;
|
|
||||||
for (let i = 1; i < month + 1; i++) {
|
|
||||||
timeArr.push(i);
|
|
||||||
}
|
|
||||||
name = '月份';
|
|
||||||
valArr = trendData.value.month.nowYear.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (energyTrendInstance.value) {
|
// 如果需要,也可以设置错误回调
|
||||||
energyTrendInstance.value.setOption({
|
ws.setOnErrorCallback((error: any) => {
|
||||||
xAxis: { data: timeArr, name },
|
console.log('Error in WebSocket:');
|
||||||
series: [{ data: valArr }],
|
currentReading({ meterType: 0, floorId: 0 });
|
||||||
});
|
readingLoading.value = false;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//日用电率
|
|
||||||
const powerCurveChart = ref<HTMLElement | null>(null);
|
|
||||||
const powerCurveInstance = ref<ECharts | null>(null);
|
|
||||||
|
|
||||||
const powerCurveOption: EChartsOption = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'cross',
|
|
||||||
crossStyle: {
|
|
||||||
color: '#999',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
feature: {
|
|
||||||
magicType: { show: true, type: ['line', 'bar'] },
|
|
||||||
restore: { show: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
data: ['今日', '昨日'],
|
|
||||||
},
|
|
||||||
xAxis: [
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
data: [
|
|
||||||
'0:00',
|
|
||||||
'1:00',
|
|
||||||
'2:00',
|
|
||||||
'3:00',
|
|
||||||
'4:00',
|
|
||||||
'5:00',
|
|
||||||
'6:00',
|
|
||||||
'7:00',
|
|
||||||
'8:00',
|
|
||||||
'9:00',
|
|
||||||
'10:00',
|
|
||||||
'11:00',
|
|
||||||
'12:00',
|
|
||||||
'13:00',
|
|
||||||
'14:00',
|
|
||||||
'15:00',
|
|
||||||
'16:00',
|
|
||||||
'17:00',
|
|
||||||
'18:00',
|
|
||||||
'19:00',
|
|
||||||
'20:00',
|
|
||||||
'21:00',
|
|
||||||
'22:00',
|
|
||||||
'23:00',
|
|
||||||
],
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow',
|
|
||||||
},
|
|
||||||
name: '时',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
yAxis: [
|
|
||||||
{
|
|
||||||
type: 'value',
|
|
||||||
name: 'kW',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '昨日',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
data: [],
|
|
||||||
markPoint: {
|
|
||||||
data: [{ type: 'max' }, { type: 'min' }],
|
|
||||||
},
|
|
||||||
itemStyle: { color: '#cbb0e3' }, // 数据点颜色
|
|
||||||
lineStyle: { color: '#cbb0e3' }, // 线条颜色
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const initPowerCurveChart = () => {
|
|
||||||
if (powerCurveChart.value) {
|
|
||||||
// 销毁旧实例
|
|
||||||
powerCurveInstance.value?.dispose();
|
|
||||||
// 创建新实例
|
|
||||||
powerCurveInstance.value = echarts.init(powerCurveChart.value);
|
|
||||||
// 设置配置项
|
|
||||||
powerCurveInstance.value.setOption(powerCurveOption);
|
|
||||||
// 可选:添加窗口大小变化监听
|
|
||||||
const resizeHandler = () => {
|
|
||||||
powerCurveInstance.value?.resize();
|
|
||||||
};
|
|
||||||
window.addEventListener('resize', resizeHandler);
|
|
||||||
|
|
||||||
// 在组件卸载前移除监听
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('resize', resizeHandler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件挂载后初始化图表
|
|
||||||
onMounted(() => {
|
|
||||||
initEnergyTrendChart();
|
|
||||||
initPowerCurveChart();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 组件卸载前销毁图表实例
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
energyTrendInstance.value?.dispose();
|
currentReading({ meterType: 0, floorId: 0 });
|
||||||
powerCurveInstance.value?.dispose();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const trendData = ref<any>({});
|
const readingData = ref<any>({});
|
||||||
|
const readingTime = ref('');
|
||||||
|
let readingLoading = ref(false);
|
||||||
async function handleSelectFloor(selectedKeys, info) {
|
async function handleSelectFloor(selectedKeys, info) {
|
||||||
const now = new Date();
|
if (typeof selectedKeys[0] === 'undefined') {
|
||||||
// 获取年、月、日
|
return;
|
||||||
const year = now.getFullYear();
|
}
|
||||||
// 月份从0开始,所以要+1,并格式化为两位数
|
if (ws.webSocket.readyState !== 1) {
|
||||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
message.warn('websocket未连接!请刷新页面重试!');
|
||||||
// 日期格式化为两位数
|
return;
|
||||||
const day = String(now.getDate()).padStart(2, '0');
|
}
|
||||||
|
readingLoading.value = true;
|
||||||
let data = {
|
await currentReading({
|
||||||
day: year + '-' + month + '-' + day,
|
|
||||||
month: year + '-' + month,
|
|
||||||
year: year,
|
|
||||||
meterType: 1,
|
meterType: 1,
|
||||||
meterId: null,
|
floorId: selectedKeys[0],
|
||||||
floorId: null,
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
if (info.node.level == 3) {
|
function targetFn() {
|
||||||
data.floorId = selectedKeys[0];
|
return document.getElementById('right-panel');
|
||||||
} else {
|
|
||||||
data.meterId = selectedKeys[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const trend = await meterRecordTrend(data);
|
|
||||||
trendData.value = trend;
|
|
||||||
|
|
||||||
// 趋势曲线
|
|
||||||
let timeArr = [];
|
|
||||||
let valArr = [];
|
|
||||||
let name = '时间';
|
|
||||||
if (energyTrendTime.value == '1') {
|
|
||||||
const hour = now.getHours();
|
|
||||||
for (let i = 0; i < hour; i++) {
|
|
||||||
timeArr.push(i + ':00');
|
|
||||||
}
|
|
||||||
valArr = trend.hour.today.data;
|
|
||||||
} else if (energyTrendTime.value == '2') {
|
|
||||||
const day = now.getDate();
|
|
||||||
for (let i = 1; i < day; i++) {
|
|
||||||
timeArr.push(i);
|
|
||||||
}
|
|
||||||
name = '日期';
|
|
||||||
valArr = trend.day.nowMonth.data;
|
|
||||||
} else {
|
|
||||||
const month = now.getMonth() + 1;
|
|
||||||
for (let i = 1; i < month + 1; i++) {
|
|
||||||
timeArr.push(i);
|
|
||||||
}
|
|
||||||
name = '月份';
|
|
||||||
valArr = trend.month.nowYear.data;
|
|
||||||
}
|
|
||||||
if (energyTrendInstance.value) {
|
|
||||||
energyTrendInstance.value.setOption({
|
|
||||||
xAxis: { data: timeArr, name },
|
|
||||||
series: [{ data: valArr }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powerCurveInstance.value) {
|
|
||||||
powerCurveInstance.value.setOption({
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '今日',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
data: trend.hour.today.data,
|
|
||||||
markPoint: {
|
|
||||||
data: [{ type: 'max' }, { type: 'min' }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '昨日',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
data: trend.hour.yesterday.data,
|
|
||||||
markPoint: {
|
|
||||||
data: [{ type: 'max' }, { type: 'min' }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="true">
|
<Page :auto-content-height="true">
|
||||||
<div class="flex h-full gap-[8px]">
|
<div class="flex h-full gap-[8px]">
|
||||||
<FloorTree class="w-[260px]" @select="handleSelectFloor"></FloorTree>
|
<FloorTree
|
||||||
<div class="flex-1 overflow-hidden">
|
:isMeter="false"
|
||||||
<div class="row">
|
class="w-[260px]"
|
||||||
<div class="energy-trend-container">
|
@select="handleSelectFloor"
|
||||||
<div class="energy-trend-top">
|
></FloorTree>
|
||||||
<div class="section-header">
|
<div class="flex-1" id="right-panel">
|
||||||
<div class="header-title">能耗趋势</div>
|
<Spin :spinning="readingLoading" size="large" style="height: 100px">
|
||||||
|
<div class="cards-container">
|
||||||
|
<div v-for="item in readingData" class="meterInfo-card">
|
||||||
|
<h2>
|
||||||
|
{{ item.meterName }}
|
||||||
|
</h2>
|
||||||
|
<div class="meterInfo-reading">
|
||||||
|
<p>
|
||||||
|
<VbenCountToAnimator
|
||||||
|
:end-val="item.initReading"
|
||||||
|
:decimals="2"
|
||||||
|
prefix=""
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="meterInfo-list">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span>采集时间:</span>
|
||||||
|
<span>{{ readingTime }}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>设备位置:</span>
|
||||||
|
<span>{{ item.installLocation }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="button-get-plan">
|
||||||
|
<a
|
||||||
|
v-if="item.communicationState !== 0"
|
||||||
|
style="background: #6bb1e3"
|
||||||
|
>
|
||||||
|
<span>在线</span>
|
||||||
|
</a>
|
||||||
|
<a v-else style="background: orange">
|
||||||
|
<span>离线</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<RadioGroup
|
|
||||||
v-model:value="energyTrendTime"
|
|
||||||
button-style="solid"
|
|
||||||
size="small"
|
|
||||||
@change="buildingEnergyTrendData(energyTrendTime)"
|
|
||||||
>
|
|
||||||
<RadioButton value="1">当日</RadioButton>
|
|
||||||
<RadioButton value="2">当月</RadioButton>
|
|
||||||
<RadioButton value="3">当年</RadioButton>
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-placeholder" ref="energyTrendChart"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Spin>
|
||||||
<div class="row">
|
|
||||||
<div class="power-curve-container">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="header-title">平均电功率曲线</div>
|
|
||||||
</div>
|
|
||||||
<div class="power-chart" ref="powerCurveChart"></div>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="power-peak-container">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="header-title">电功率峰值</div>
|
|
||||||
</div>
|
|
||||||
<div class="peak-item">
|
|
||||||
<p class="value">{{ peakData.todayPeakPower }}</p>
|
|
||||||
<p class="time">{{ peakData.todayPeakTime }}</p>
|
|
||||||
<div class="bottom-text">当日(kW)</div>
|
|
||||||
</div>
|
|
||||||
<div class="peak-item">
|
|
||||||
<p class="value">{{ peakData.yesterdayPeakPower }}</p>
|
|
||||||
<p class="time">{{ peakData.yesterdayPeakTime }}</p>
|
|
||||||
<div class="bottom-text">昨日(kW)</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<BackTop class="back-to-top" :target="targetFn"></BackTop>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.row {
|
/* 右侧内容区域样式 */
|
||||||
display: flex;
|
.flex-1 {
|
||||||
gap: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-section-container {
|
|
||||||
flex: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.energy-trend-container {
|
|
||||||
flex: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-curve-container {
|
|
||||||
margin-top: 10px;
|
|
||||||
flex: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-peak-container {
|
|
||||||
margin-top: 10px;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-y: auto; /* 添加垂直滚动条 */
|
||||||
|
padding: 10px;
|
||||||
|
height: 100%; /* 确保高度占满父容器 */
|
||||||
|
box-sizing: border-box; /* 包括padding在高度计算中 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.energy-trend-top {
|
/* 卡片容器样式 */
|
||||||
|
.cards-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 20px; /* 使用gap替代margin控制间距 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.meterInfo-card {
|
||||||
border-left: 4px solid #3671e8;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comparison-section-container,
|
|
||||||
.energy-trend-container,
|
|
||||||
.power-curve-container,
|
|
||||||
.power-peak-container {
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
width: 15rem;
|
||||||
|
padding-left: 2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-bottom: 4px solid #87ceeb;
|
||||||
|
box-shadow: 0 6px 30px rgba(173, 216, 230, 0.3);
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
height: 270px;
|
||||||
|
margin: 0 5px 20px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-card h2 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 27px;
|
||||||
|
color: #5faee3;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-card h2 span {
|
||||||
|
display: block;
|
||||||
|
margin-top: -4px;
|
||||||
|
color: #7eb8da;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-reading {
|
||||||
|
position: relative;
|
||||||
|
background: #f0f8ff;
|
||||||
|
width: 14.46rem;
|
||||||
|
margin-left: -0.65rem;
|
||||||
|
padding: 0.2rem 1.2rem;
|
||||||
|
border-radius: 5px 0 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-reading p {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0.4rem;
|
||||||
|
display: flex;
|
||||||
|
font-size: 1.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4a8cbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-reading p:before {
|
||||||
|
content: '';
|
||||||
|
margin-right: 5px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-reading p:after {
|
||||||
|
content: '/ KW.H';
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-reading div {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -23px;
|
||||||
|
right: 0px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 13px solid #87ceeb;
|
||||||
|
border-bottom: 10px solid transparent;
|
||||||
|
border-right: 13px solid transparent;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-list {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-list ul {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-list ul li {
|
||||||
|
color: #5a7d9a;
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-list ul li svg {
|
||||||
|
width: 0.9rem;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meterInfo-list ul li span {
|
||||||
|
font-weight: 300;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-get-plan {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-get-plan a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 15px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
text-decoration: none;
|
||||||
padding: 1rem;
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comparison-grid {
|
.button-get-plan a:hover {
|
||||||
display: grid;
|
transform: translateY(-3%);
|
||||||
grid-template-columns: repeat(3, 1fr);
|
box-shadow: 0 3px 10px rgba(123, 180, 220, 0.6); /* 浅蓝色阴影 */
|
||||||
gap: 5px;
|
background: #5fa0d0; /* 悬停时略深的蓝色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.comparison-item {
|
.button-get-plan .svg-rocket {
|
||||||
padding: 5px 10px;
|
margin-right: 10px;
|
||||||
border: 1px solid #e0e0e0;
|
width: 0.9rem;
|
||||||
text-align: center;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-value {
|
.back-to-top {
|
||||||
font-size: 22px;
|
width: 50px;
|
||||||
color: #333;
|
height: 50px;
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-title {
|
/* 返回顶部按钮 */
|
||||||
font-size: 12px;
|
.back-to-top:hover {
|
||||||
color: #666;
|
background-color: #6bb1e3;
|
||||||
}
|
transform: translateY(-5px);
|
||||||
|
|
||||||
.item-top {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-percent {
|
|
||||||
color: #7fb926;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border-bottom: 1px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-unit {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin-left: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-placeholder {
|
|
||||||
height: 36vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-chart {
|
|
||||||
height: 38vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.peak-item {
|
|
||||||
padding: 15px 0 0 0;
|
|
||||||
border: 1px solid #3671e8;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
.bottom-text {
|
|
||||||
background-color: #3671e8;
|
|
||||||
color: white;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.peak-item .value {
|
|
||||||
font-size: 22px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.peak-item .time {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -81,7 +81,7 @@ import { Page } from '@vben/common-ui'
|
|||||||
import { ref, onMounted, onBeforeUnmount, reactive } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, reactive } from 'vue'
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import type { ECharts, EChartsOption } from 'echarts'
|
import type { ECharts, EChartsOption } from 'echarts'
|
||||||
import FloorTree from "../../electricEnergy/components/floor-tree.vue"
|
import FloorTree from "../components/floor-tree.vue"
|
||||||
|
|
||||||
const chainData = reactive({
|
const chainData = reactive({
|
||||||
currentMonthEnergy: '9',
|
currentMonthEnergy: '9',
|
||||||
|
Reference in New Issue
Block a user