diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development index d522a706..aa18ed97 100644 --- a/apps/web-antd/.env.development +++ b/apps/web-antd/.env.development @@ -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= # 客户端id VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e +# 开启WEBSOCKET +VITE_APP_WEBSOCKET=true # 开启SSE VITE_GLOB_SSE_ENABLE=true diff --git a/apps/web-antd/.env.production b/apps/web-antd/.env.production index d228ac48..a26175cd 100644 --- a/apps/web-antd/.env.production +++ b/apps/web-antd/.env.production @@ -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= # 客户端id VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e +# 开启WEBSOCKET +VITE_APP_WEBSOCKET=true # 开启SSE VITE_GLOB_SSE_ENABLE=true diff --git a/apps/web-antd/src/api/analytics/index.ts b/apps/web-antd/src/api/analytics/index.ts new file mode 100644 index 00000000..38754d53 --- /dev/null +++ b/apps/web-antd/src/api/analytics/index.ts @@ -0,0 +1,63 @@ +import { requestClient } from '#/api/request'; + +/** + * 查询工单数量 + * @param params + * @returns 工单数量 + */ +export function getIndexCount() { + return requestClient.get('/property/index/indexCount'); +} + +// 今日预警分类统计 +export function getStatisticsCurrDay() { + return requestClient.get('/sis/alarmEvents/query/statistics/currDay'); +} +// 所有预警信息分类统计 +export function getStatistics() { + return requestClient.get('/sis/alarmEvents/query/statistics'); +} +// /** +// * 导出资产管理列表 +// * @param params +// * @returns 资产管理列表 +// */ +// export function assetExport(params?: AssetQuery) { +// return commonExport('/property/asset/export', params ?? {}); +// } + +// /** +// * 查询资产管理详情 +// * @param id id +// * @returns 资产管理详情 +// */ +// export function assetInfo(id: ID) { +// return requestClient.get(`/property/asset/${id}`); +// } + +// /** +// * 新增资产管理 +// * @param data +// * @returns void +// */ +// export function assetAdd(data: AssetForm) { +// return requestClient.postWithMsg('/property/asset', data); +// } + +// /** +// * 更新资产管理 +// * @param data +// * @returns void +// */ +// export function assetUpdate(data: AssetForm) { +// return requestClient.putWithMsg('/property/asset', data); +// } + +// /** +// * 删除资产管理 +// * @param id id +// * @returns void +// */ +// export function assetRemove(id: ID | IDS) { +// return requestClient.deleteWithMsg(`/property/asset/${id}`); +// } diff --git a/apps/web-antd/src/api/property/energyManagement/meterInfo/index.ts b/apps/web-antd/src/api/property/energyManagement/meterInfo/index.ts index b3c505df..b6af3266 100644 --- a/apps/web-antd/src/api/property/energyManagement/meterInfo/index.ts +++ b/apps/web-antd/src/api/property/energyManagement/meterInfo/index.ts @@ -1,6 +1,6 @@ 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 { requestClient } from '#/api/request' @@ -64,6 +64,13 @@ export function meterInfoRemove(id: ID | IDS) { * @param level * @returns 水电气树 */ -export function queryTree(meterType: number | string) { - return requestClient.get[]>(`/property/meterInfo/tree/${meterType}`) +export function queryTree(params?: any) { + return requestClient.get[]>(`/property/meterInfo/tree`, { params }) +} + +/** + * 获取水/电/气表当前读数/状态 + */ +export function currentReading(params?: any) { + return requestClient.get(`/property/meterInfo/currentReading`, { params }) } \ No newline at end of file diff --git a/apps/web-antd/src/api/property/energyManagement/meterRecord/index.ts b/apps/web-antd/src/api/property/energyManagement/meterRecord/index.ts index a9465c99..e0db5b01 100644 --- a/apps/web-antd/src/api/property/energyManagement/meterRecord/index.ts +++ b/apps/web-antd/src/api/property/energyManagement/meterRecord/index.ts @@ -1,4 +1,9 @@ -import type { MeterRecordVO, MeterRecordForm, MeterRecordQuery, MeterRecordTrend } from './model'; +import type { + MeterRecordVO, + MeterRecordForm, + MeterRecordQuery, + MeterRecordTrend, +} from './model'; import type { ID, IDS } from '#/api/common'; import type { PageResult } from '#/api/common'; @@ -7,12 +12,15 @@ import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** -* 查询抄表记录列表 -* @param params -* @returns 抄表记录列表 -*/ + * 查询抄表记录列表 + * @param params + * @returns 抄表记录列表 + */ export function meterRecordList(params?: MeterRecordQuery) { - return requestClient.get>('/property/meterRecord/list', { params }); + return requestClient.get>( + '/property/meterRecord/list', + { params }, + ); } /** @@ -62,10 +70,10 @@ export function meterRecordRemove(id: ID | IDS) { /** * 获取用电/气/水趋势分析数据 - * + * * @param params * @returns 用电/气/水趋势分析数据 */ export function meterRecordTrend(params: MeterRecordTrend) { - return requestClient.get('/property/meterRecord/trend', { params }); + return requestClient.get('/property/meterRecord/trend', { params }); } diff --git a/apps/web-antd/src/api/property/energyManagement/meterRecord/model.d.ts b/apps/web-antd/src/api/property/energyManagement/meterRecord/model.d.ts index a5d60084..de68f989 100644 --- a/apps/web-antd/src/api/property/energyManagement/meterRecord/model.d.ts +++ b/apps/web-antd/src/api/property/energyManagement/meterRecord/model.d.ts @@ -1,182 +1,179 @@ -import type { PageQuery, BaseEntity } from '#/api/common' +import type { PageQuery, BaseEntity } from '#/api/common'; export interface MeterRecordVO { /** * 记录ID */ - id: string | number + id: string | number; /** * 仪表编号 */ - meterId: string | number + meterId: string | number; /** - * 仪表类型 - */ - meterType: string | number + * 仪表类型 + */ + meterType: string | number; /** * 抄表员ID */ - readerId: string | number + readerId: string | number; /** * 抄表时间 */ - readingTime: string + readingTime: string; /** * 当前读数 */ - currentReading: number + currentReading: number; /** * 上次读数 */ - previousReading: number + previousReading: number; /** * 用量 */ - consumption: number + consumption: number; /** * 抄表方式(1手动 2自动 3用户上报) */ - readingMethod: number + readingMethod: number; /** * 抄表照片 */ - imgOssid: string | number - + imgOssid: string | number; } export interface MeterRecordForm extends BaseEntity { /** * 记录ID */ - id?: string | number + id?: string | number; /** * 仪表编号 */ - meterId?: string | number + meterId?: string | number; /** * 抄表员ID */ - readerId?: string | number + readerId?: string | number; /** * 抄表时间 */ - readingTime?: string + readingTime?: string; /** * 当前读数 */ - currentReading?: number + currentReading?: number; /** * 上次读数 */ - previousReading?: number + previousReading?: number; /** * 用量 */ - consumption?: number + consumption?: number; /** * 抄表方式(1手动 2自动 3用户上报) */ - readingMethod?: number + readingMethod?: number; /** * 抄表照片 */ - imgOssid?: string | number - + imgOssid?: string | number; } export interface MeterRecordQuery extends PageQuery { /** * 仪表编号 */ - meterId?: string | number + meterId?: string | number; /** * 抄表员ID */ - readerId?: string | number + readerId?: string | number; /** * 抄表时间 */ - readingTime?: string + readingTime?: string; /** * 当前读数 */ - currentReading?: number + currentReading?: number; /** * 上次读数 */ - previousReading?: number + previousReading?: number; /** * 用量 */ - consumption?: number + consumption?: number; /** * 抄表方式(1手动 2自动 3用户上报) */ - readingMethod?: number + readingMethod?: number; /** * 抄表照片 */ - imgOssid?: string | number + imgOssid?: string | number; /** - * 日期范围参数 - */ - params?: any + * 日期范围参数 + */ + params?: any; } - export interface MeterRecordTrend { /** * 仪表类型 */ - meterType?: string | number + meterType?: string | number; /** * 仪表ID */ - meterId: string | number + meterId: any; /** * 楼层ID */ - floorId: string | number + floorId: any; /** * 日期 */ - day?: string + day?: string; /** * 月份 */ - month?: string + month?: string; /** * 年份 */ - year?: string + year?: string; } diff --git a/apps/web-antd/src/api/property/room/model.d.ts b/apps/web-antd/src/api/property/room/model.d.ts index cb62110d..13556d92 100644 --- a/apps/web-antd/src/api/property/room/model.d.ts +++ b/apps/web-antd/src/api/property/room/model.d.ts @@ -20,6 +20,7 @@ export interface RoomVO { * 房间类型('住宅','商铺','办公室','设备间','公共区域') */ roomType: string; + roomTypeName: string; /** * 建筑面积(平方米) @@ -50,6 +51,40 @@ export interface RoomVO { * 状态('空置','已售','已租','自用') */ status: string; + /** + * 房间图片 + */ + imgUrl: string; + imgPath: string; + + + /** + * 是否重要 + */ + isMatter: string; + /** + * 状态 + */ + statusName: string; + + /** + * 小区 + */ + communityText: string; + + /** + * 建筑 + */ + buildingText: string; + + /** + * 楼层 + */ + floorText: string; + /** + * 所属单位 + */ + residentUnitText: string; } diff --git a/apps/web-antd/src/api/property/roomBooking/conferenceSettings/model.d.ts b/apps/web-antd/src/api/property/roomBooking/conferenceSettings/model.d.ts index 11d1e48a..91d61285 100644 --- a/apps/web-antd/src/api/property/roomBooking/conferenceSettings/model.d.ts +++ b/apps/web-antd/src/api/property/roomBooking/conferenceSettings/model.d.ts @@ -1,4 +1,4 @@ -import type { PageQuery, BaseEntity } from '#/api/common'; +import type {PageQuery, BaseEntity} from '#/api/common'; export interface MeetVO { /** @@ -90,6 +90,10 @@ export interface MeetVO { * 负责人 */ principalsName: string; + /** + * 保密等级 + */ + secrecyGrade: string; } export interface MeetForm extends BaseEntity { @@ -236,8 +240,8 @@ export interface MeetQuery extends PageQuery { searchValue?: string; /** - * 日期范围参数 - */ + * 日期范围参数 + */ params?: any; /** @@ -274,7 +278,7 @@ export interface MeetQuery extends PageQuery { picture: string; } -export interface ConferenceSettingsDetail{ +export interface ConferenceSettingsDetail { /** * 主键 */ @@ -375,7 +379,7 @@ export interface ConferenceSettingsDetail{ } -export interface MeetBo{ +export interface MeetBo { /** * 会议室名称 */ diff --git a/apps/web-antd/src/api/property/roomBooking/participants/index.ts b/apps/web-antd/src/api/property/roomBooking/participants/index.ts new file mode 100644 index 00000000..53a994a9 --- /dev/null +++ b/apps/web-antd/src/api/property/roomBooking/participants/index.ts @@ -0,0 +1,59 @@ +import type { ParticipantsVO, ParticipantsForm, ParticipantsQuery } from './model'; +import type { ID, IDS } from '#/api/common'; +import type { PageResult } from '#/api/common'; +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +/** +* 查询会议室参会记录列表 +* @param params +* @returns 会议室参会记录列表 +*/ +export function participantsList(params?: ParticipantsQuery) { + return requestClient.get>('/property/participants/list', { params }); +} + +/** + * 导出会议室参会记录列表 + * @param params + * @returns 会议室参会记录列表 + */ +export function participantsExport(params?: ParticipantsQuery) { + return commonExport('/property/participants/export', params ?? {}); +} + +/** + * 查询会议室参会记录详情 + * @param id id + * @returns 会议室参会记录详情 + */ +export function participantsInfo(id: ID) { + return requestClient.get(`/property/participants/${id}`); +} + +/** + * 新增会议室参会记录 + * @param data + * @returns void + */ +export function participantsAdd(data: ParticipantsForm) { + return requestClient.postWithMsg('/property/participants', data); +} + +/** + * 更新会议室参会记录 + * @param data + * @returns void + */ +export function participantsUpdate(data: ParticipantsForm) { + return requestClient.putWithMsg('/property/participants', data); +} + +/** + * 删除会议室参会记录 + * @param id id + * @returns void + */ +export function participantsRemove(id: ID | IDS) { + return requestClient.deleteWithMsg(`/property/participants/${id}`); +} diff --git a/apps/web-antd/src/api/property/roomBooking/participants/model.d.ts b/apps/web-antd/src/api/property/roomBooking/participants/model.d.ts new file mode 100644 index 00000000..15bb4773 --- /dev/null +++ b/apps/web-antd/src/api/property/roomBooking/participants/model.d.ts @@ -0,0 +1,54 @@ +import type { PageQuery, BaseEntity } from '#/api/common'; + +export interface ParticipantsVO { + /** + * 主键id + */ + id: string | number; + + /** + * 会议室id + */ + meetId: string | number; + + /** + * 入驻人员id + */ + residentPersonId: string | number; + +} + +export interface ParticipantsForm extends BaseEntity { + /** + * 主键id + */ + id?: string | number; + + /** + * 会议室id + */ + meetId?: string | number; + + /** + * 入驻人员id + */ + residentPersonId?: string | number; + +} + +export interface ParticipantsQuery extends PageQuery { + /** + * 会议室id + */ + meetId?: string | number; + + /** + * 入驻人员id + */ + residentPersonId?: string | number; + + /** + * 日期范围参数 + */ + params?: any; +} diff --git a/apps/web-antd/src/api/websocket.ts b/apps/web-antd/src/api/websocket.ts new file mode 100644 index 00000000..8a136fbb --- /dev/null +++ b/apps/web-antd/src/api/websocket.ts @@ -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; +} \ No newline at end of file diff --git a/apps/web-antd/src/assets/tree/player-err.png b/apps/web-antd/src/assets/tree/player-err.png new file mode 100644 index 00000000..cb930672 Binary files /dev/null and b/apps/web-antd/src/assets/tree/player-err.png differ diff --git a/apps/web-antd/src/assets/tree/playering.png b/apps/web-antd/src/assets/tree/playering.png new file mode 100644 index 00000000..46e9952d Binary files /dev/null and b/apps/web-antd/src/assets/tree/playering.png differ diff --git a/apps/web-antd/src/assets/tree/unplayer.png b/apps/web-antd/src/assets/tree/unplayer.png new file mode 100644 index 00000000..a8d8d52b Binary files /dev/null and b/apps/web-antd/src/assets/tree/unplayer.png differ diff --git a/apps/web-antd/src/bootstrap.ts b/apps/web-antd/src/bootstrap.ts index 45d2df24..41501005 100644 --- a/apps/web-antd/src/bootstrap.ts +++ b/apps/web-antd/src/bootstrap.ts @@ -1,30 +1,30 @@ -import { createApp, watchEffect } from 'vue'; +import { createApp, watchEffect } from 'vue' -import { registerAccessDirective } from '@vben/access'; -import { registerLoadingDirective } from '@vben/common-ui/es/loading'; -import { preferences } from '@vben/preferences'; -import { initStores } from '@vben/stores'; -import '@vben/styles'; -import '@vben/styles/antd'; +import { registerAccessDirective } from '@vben/access' +import { registerLoadingDirective } from '@vben/common-ui/es/loading' +import { preferences } from '@vben/preferences' +import { initStores } from '@vben/stores' +import '@vben/styles' +import '@vben/styles/antd' -import { useTitle } from '@vueuse/core'; +import { useTitle } from '@vueuse/core' -import { setupGlobalComponent } from '#/components/global'; -import { $t, setupI18n } from '#/locales'; +import { setupGlobalComponent } from '#/components/global' +import { $t, setupI18n } from '#/locales' -import { initComponentAdapter } from './adapter/component'; -import { initSetupVbenForm } from './adapter/form'; -import App from './app.vue'; -import { router } from './router'; +import { initComponentAdapter } from './adapter/component' +import { initSetupVbenForm } from './adapter/form' +import App from './app.vue' +import { router } from './router' async function bootstrap(namespace: string) { // 初始化组件适配器 - await initComponentAdapter(); + await initComponentAdapter() // 初始化表单组件 - await initSetupVbenForm(); + await initSetupVbenForm() // // 设置弹窗的默认配置 // setDefaultModalProps({ @@ -35,49 +35,50 @@ async function bootstrap(namespace: string) { // zIndex: 1020, // }); - const app = createApp(App); + + const app = createApp(App) // 全局组件 - setupGlobalComponent(app); + setupGlobalComponent(app) // 注册v-loading指令 registerLoadingDirective(app, { loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 spinning: 'spinning', - }); + }) // 国际化 i18n 配置 - await setupI18n(app); + await setupI18n(app) // 配置 pinia-tore - await initStores(app, { namespace }); + await initStores(app, { namespace }) // 安装权限指令 - registerAccessDirective(app); + registerAccessDirective(app) // 初始化 tippy - const { initTippy } = await import('@vben/common-ui/es/tippy'); - initTippy(app); + const { initTippy } = await import('@vben/common-ui/es/tippy') + initTippy(app) // 配置路由及路由守卫 - app.use(router); + app.use(router) // 配置Motion插件 - const { MotionPlugin } = await import('@vben/plugins/motion'); - app.use(MotionPlugin); + const { MotionPlugin } = await import('@vben/plugins/motion') + app.use(MotionPlugin) // 动态更新标题 watchEffect(() => { if (preferences.app.dynamicTitle) { - const routeTitle = router.currentRoute.value.meta?.title; + const routeTitle = router.currentRoute.value.meta?.title const pageTitle = - (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; - useTitle(pageTitle); + (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name + useTitle(pageTitle) } - }); + }) - app.mount('#app'); + app.mount('#app') } -export { bootstrap }; +export { bootstrap } diff --git a/apps/web-antd/src/store/auth.ts b/apps/web-antd/src/store/auth.ts index cd8c4b9f..2160ba05 100644 --- a/apps/web-antd/src/store/auth.ts +++ b/apps/web-antd/src/store/auth.ts @@ -1,27 +1,27 @@ -import type { LoginAndRegisterParams } from '@vben/common-ui'; -import type { UserInfo } from '@vben/types'; +import type { LoginAndRegisterParams } from '@vben/common-ui' +import type { UserInfo } from '@vben/types' -import { ref } from 'vue'; -import { useRouter } from 'vue-router'; +import { ref } from 'vue' +import { useRouter } from 'vue-router' -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; -import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; +import { LOGIN_PATH } from '@vben/constants' +import { preferences } from '@vben/preferences' +import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores' -import { notification } from 'ant-design-vue'; -import { defineStore } from 'pinia'; +import { notification } from 'ant-design-vue' +import { defineStore } from 'pinia' -import { doLogout, getUserInfoApi, loginApi, seeConnectionClose } from '#/api'; -import { $t } from '#/locales'; +import { doLogout, getUserInfoApi, loginApi, seeConnectionClose } from '#/api' +import { $t } from '#/locales' -import { useDictStore } from './dict'; +import { useDictStore } from './dict' export const useAuthStore = defineStore('auth', () => { - const accessStore = useAccessStore(); - const userStore = useUserStore(); - const router = useRouter(); + const accessStore = useAccessStore() + const userStore = useUserStore() + const router = useRouter() - const loginLoading = ref(false); + const loginLoading = ref(false) /** * 异步处理登录操作 @@ -33,30 +33,30 @@ export const useAuthStore = defineStore('auth', () => { onSuccess?: () => Promise | void, ) { // 异步处理用户登录操作并获取 accessToken - let userInfo: null | UserInfo = null; + let userInfo: null | UserInfo = null try { - loginLoading.value = true; - const { access_token } = await loginApi(params); + loginLoading.value = true + const { access_token } = await loginApi(params) // 将 accessToken 存储到 accessStore 中 - accessStore.setAccessToken(access_token); - accessStore.setRefreshToken(access_token); + accessStore.setAccessToken(access_token) + accessStore.setRefreshToken(access_token) // 获取用户信息并存储到 accessStore 中 - userInfo = await fetchUserInfo(); + userInfo = await fetchUserInfo() /** * 设置用户信息 */ - userStore.setUserInfo(userInfo); + userStore.setUserInfo(userInfo) /** * 在这里设置权限 */ - accessStore.setAccessCodes(userInfo.permissions); + accessStore.setAccessCodes(userInfo.permissions) if (accessStore.loginExpired) { - accessStore.setLoginExpired(false); + accessStore.setLoginExpired(false) } else { - onSuccess ? await onSuccess?.() : await router.push('/analytics'); + onSuccess ? await onSuccess?.() : await router.push('/analytics') } if (userInfo?.realName) { @@ -64,48 +64,48 @@ export const useAuthStore = defineStore('auth', () => { description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, duration: 3, message: $t('authentication.loginSuccess'), - }); + }) } } finally { - loginLoading.value = false; + loginLoading.value = false } return { userInfo, - }; + } } async function logout(redirect: boolean = true) { try { - await seeConnectionClose(); - await doLogout(); + await seeConnectionClose() + await doLogout() } catch (error) { - console.error(error); + console.error(error) } finally { - resetAllStores(); - accessStore.setLoginExpired(false); + resetAllStores() + accessStore.setLoginExpired(false) // 回登陆页带上当前路由地址 await router.replace({ path: LOGIN_PATH, query: redirect ? { - redirect: encodeURIComponent(router.currentRoute.value.fullPath), - } + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + } : {}, - }); + }) } } async function fetchUserInfo() { - const backUserInfo = await getUserInfoApi(); + const backUserInfo = await getUserInfoApi() /** * 登录超时的情况 */ if (!backUserInfo) { - throw new Error('获取用户信息失败.'); + throw new Error('获取用户信息失败.') } - const { permissions = [], roles = [], user } = backUserInfo; + const { permissions = [], roles = [], user } = backUserInfo /** * 从后台user -> vben user转换 */ @@ -116,19 +116,20 @@ export const useAuthStore = defineStore('auth', () => { roles, userId: user.userId, username: user.userName, - }; - userStore.setUserInfo(userInfo); + } + userStore.setUserInfo(userInfo) + /** * 需要重新加载字典 * 比如退出登录切换到其他租户 */ - const dictStore = useDictStore(); - dictStore.resetCache(); - return userInfo; + const dictStore = useDictStore() + dictStore.resetCache() + return userInfo } function $reset() { - loginLoading.value = false; + loginLoading.value = false } return { @@ -137,5 +138,5 @@ export const useAuthStore = defineStore('auth', () => { fetchUserInfo, loginLoading, logout, - }; -}); + } +}) diff --git a/apps/web-antd/src/store/notify.ts b/apps/web-antd/src/store/notify.ts index a183ba15..00e81fa4 100644 --- a/apps/web-antd/src/store/notify.ts +++ b/apps/web-antd/src/store/notify.ts @@ -1,21 +1,21 @@ -import type { NotificationItem } from '@vben/layouts'; +import type { NotificationItem } from '@vben/layouts' -import { computed, ref, watch } from 'vue'; +import { computed, ref, watch } from 'vue' -import { useAppConfig } from '@vben/hooks'; -import { SvgMessageUrl } from '@vben/icons'; -import { $t } from '@vben/locales'; -import { useAccessStore, useUserStore } from '@vben/stores'; +import { useAppConfig } from '@vben/hooks' +import { SvgMessageUrl } from '@vben/icons' +import { $t } from '@vben/locales' +import { useAccessStore, useUserStore } from '@vben/stores' -import { useEventSource } from '@vueuse/core'; -import { notification } from 'ant-design-vue'; -import dayjs from 'dayjs'; -import { defineStore } from 'pinia'; +import { useEventSource } from '@vueuse/core' +import { notification } from 'ant-design-vue' +import dayjs from 'dayjs' +import { defineStore } from 'pinia' const { apiURL, clientId, sseEnable } = useAppConfig( import.meta.env, import.meta.env.PROD, -); +) export const useNotifyStore = defineStore( 'app-notify', @@ -23,18 +23,18 @@ export const useNotifyStore = defineStore( /** * return才会被持久化 存储全部消息 */ - const notificationList = ref([]); - const sseList = ref(["111"]); - const userStore = useUserStore(); + const notificationList = ref([]) + const sseList = ref(["111"]) + const userStore = useUserStore() const userId = computed(() => { - return userStore.userInfo?.userId || '0'; - }); + return userStore.userInfo?.userId || '0' + }) const notifications = computed(() => { return notificationList.value.filter( (item) => item.userId === userId.value, - ); - }); + ) + }) /** * 开始监听sse消息 @@ -44,40 +44,45 @@ export const useNotifyStore = defineStore( * 未开启 不监听 */ if (!sseEnable) { - return; + return } - const accessStore = useAccessStore(); - const token = accessStore.accessToken; + const accessStore = useAccessStore() + const token = accessStore.accessToken - const sseAddr = `${apiURL}/resource/sse?clientid=${clientId}&Authorization=Bearer ${token}`; + const sseAddr = `${apiURL}/resource/sse?clientid=${clientId}&Authorization=Bearer ${token}` const { data } = useEventSource(sseAddr, [], { autoReconnect: { delay: 1000, onFailed() { - console.error('sse重连失败.'); + console.error('sse重连失败.') }, retries: 3, }, - }); + }) watch(data, (message) => { - if (!message) return; - console.log(`接收到消息: ${message}`); + if (!message) return + console.log(`接收到消息: ${message}`) try { // 尝试解析JSON - const obj = JSON.parse(message); + const obj = JSON.parse(message) // 检查解析结果是否为对象且不为null - if (obj.getType() ==="yvjin"){ - sseList.value.join(message) + if (obj.type === "yvjin") { + sseList.value.push(message) + } + + // 仪表数据 + if (obj.type === "meter") { + sseList.value.push(message) } } catch (e) { notification.success({ description: message, duration: 3, message: $t('component.notice.received'), - }); + }) notificationList.value.unshift({ // avatar: `https://api.multiavatar.com/${random(0, 10_000)}.png`, 随机头像 @@ -87,12 +92,12 @@ export const useNotifyStore = defineStore( message, title: $t('component.notice.title'), userId: userId.value, - }); + }) // 需要手动置空 vue3在值相同时不会触发watch - data.value = null; + data.value = null } - }); + }) } /** @@ -102,10 +107,10 @@ export const useNotifyStore = defineStore( notificationList.value .filter((item) => item.userId === userId.value) .forEach((item) => { - item.isRead = true; - }); + item.isRead = true + }) } - function getsseList(){ + function getsseList() { console.log(sseList.value) return sseList.value } @@ -115,7 +120,7 @@ export const useNotifyStore = defineStore( * @param item 通知 */ function setRead(item: NotificationItem) { - !item.isRead && (item.isRead = true); + !item.isRead && (item.isRead = true) } /** @@ -124,7 +129,7 @@ export const useNotifyStore = defineStore( function clearAllMessage() { notificationList.value = notificationList.value.filter( (item) => item.userId !== userId.value, - ); + ) } /** @@ -141,7 +146,7 @@ export const useNotifyStore = defineStore( notificationList.value .filter((item) => item.userId === userId.value) .some((item) => !item.isRead), - ); + ) return { $reset, @@ -154,11 +159,11 @@ export const useNotifyStore = defineStore( setRead, showDot, startListeningMessage, - }; + } }, { persist: { pick: ['notificationList'], }, }, -); +) diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue index 9bd90fc8..353d4c96 100644 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue @@ -2,12 +2,46 @@ import type { EchartsUIType } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; -import { onMounted, ref } from 'vue'; - +import { onMounted, ref, defineExpose } from 'vue'; +import { meterRecordTrend } from '#/api/property/energyManagement/meterRecord'; +import dayjs from 'dayjs'; const chartRef = ref(); const { renderEcharts } = useEcharts(chartRef); +// 添加日期选择相关逻辑 +const selectedDate = ref(dayjs().format('YYYY-MM')); +// 获取当月天数 +const getDaysInMonth = (date: any) => { + const year = parseInt(date.split('-')[0]); + const month = parseInt(date.split('-')[1]); + const daysInMonth = new Date(year, month, 0).getDate(); + return Array.from({ length: daysInMonth }, (_, i) => i + 1); +}; +const getMeterRecordTrend = async (selectedDate: any) => { + const res = await meterRecordTrend({ + day: dayjs().format('YYYY-MM-DD'), + month: selectedDate.value, + year: dayjs().format('YYYY'), + meterType: 1, + meterId: null, + floorId: null, + }); -onMounted(() => { + // 处理返回的数据 + const chartData = res.day.nowMonth.data || []; + + // 创建一个映射,将日期和数值对应起来 + const dataMap: Record = {}; + chartData.forEach(([day, value]: [string, string]) => { + dataMap[day] = value; + }); + + // 获取当月所有日期 + const days = getDaysInMonth(selectedDate.value); + + // 为没有数据的日期填充0,构建完整的数据数组 + const seriesData = days.map((day) => { + return parseFloat(dataMap[day.toString()] || '0'); + }); renderEcharts({ grid: { bottom: 0, @@ -19,29 +53,13 @@ onMounted(() => { series: [ { areaStyle: {}, - data: [ - 111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000, - 36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222, - 111, - ], + data: seriesData, itemStyle: { color: '#5ab1ef', }, smooth: true, type: 'line', }, - { - areaStyle: {}, - data: [ - 33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000, - 11_000, 2221, 1201, 390, 198, 60, 30, 22, 11, - ], - itemStyle: { - color: '#019680', - }, - smooth: true, - type: 'line', - }, ], tooltip: { axisPointer: { @@ -65,7 +83,7 @@ onMounted(() => { show: false, }, boundaryGap: false, - data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`), + data: days, splitLine: { lineStyle: { type: 'solid', @@ -80,7 +98,7 @@ onMounted(() => { axisTick: { show: false, }, - max: 80_000, + // max: 800, splitArea: { show: true, }, @@ -89,9 +107,18 @@ onMounted(() => { }, ], }); +}; +onMounted(async () => { + getMeterRecordTrend(selectedDate); +}); +// 暴露方法给父组件调用 +defineExpose({ + getMeterRecordTrend, }); diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue index 651abbc1..e61485e9 100644 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue @@ -2,77 +2,79 @@ import type { EchartsUIType } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; -import { onMounted, ref } from 'vue'; +import { watch, onMounted, ref, defineProps, computed } from 'vue'; const chartRef = ref(); const { renderEcharts } = useEcharts(chartRef); +const props = defineProps<{ + statisticsData?: any[]; // 根据实际数据结构调整类型 +}>(); +// 在组件顶层定义 computed 属性 +const chartData = computed(() => { + if (!props.statisticsData) return []; + return props.statisticsData.map((item) => ({ + name: item.typeName, + value: item.total, + })); +}); -onMounted(() => { +// 监听数据变化,重新渲染图表 +watch( + () => props.statisticsData, + (newData) => { + if (newData) { + updateChart(); + } + }, + { immediate: true }, +); + +// 封装图表更新逻辑 +const updateChart = () => { renderEcharts({ legend: { - bottom: 0, - data: ['访问', '趋势'], - }, - radar: { - indicator: [ - { - name: '网页', - }, - { - name: '移动端', - }, - { - name: 'Ipad', - }, - { - name: '客户端', - }, - { - name: '第三方', - }, - { - name: '其它', - }, - ], - radius: '60%', - splitNumber: 8, + bottom: '2%', + left: 'center', }, series: [ { - areaStyle: { - opacity: 1, - shadowBlur: 0, - shadowColor: 'rgba(0,0,0,.2)', - shadowOffsetX: 0, - shadowOffsetY: 10, + animationDelay() { + return Math.random() * 100; }, - data: [ - { - itemStyle: { - color: '#b6a2de', - }, - name: '访问', - value: [90, 50, 86, 40, 50, 20], + animationEasing: 'exponentialInOut', + animationType: 'scale', + avoidLabelOverlap: false, + color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'], + data: chartData.value, + emphasis: { + label: { + fontSize: '12', + fontWeight: 'bold', + show: true, }, - { - itemStyle: { - color: '#5ab1ef', - }, - name: '趋势', - value: [70, 75, 70, 76, 20, 85], - }, - ], - itemStyle: { - // borderColor: '#fff', - borderRadius: 10, - borderWidth: 2, }, - symbolSize: 0, - type: 'radar', + label: { + position: 'center', + show: false, + }, + labelLine: { + show: false, + }, + name: '预警类型', + type: 'pie', }, ], - tooltip: {}, + tooltip: { + trigger: 'item', + }, }); +}; + +onMounted(() => { + // 组件挂载时尝试更新图表 + if (props.statisticsData) { + updateChart(); + } }); diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue index f38b5c03..b333bb2b 100644 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue @@ -2,12 +2,34 @@ import type { EchartsUIType } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; -import { onMounted, ref } from 'vue'; +import { watch, onMounted, ref, defineProps, computed } from 'vue'; +const props = defineProps<{ + workData?: any[]; // 根据实际数据结构调整类型 +}>(); const chartRef = ref(); const { renderEcharts } = useEcharts(chartRef); +const chartData = computed(() => { + if (!props.workData) return []; + return props.workData.map((item) => ({ + name: item.type, + value: item.quantity, + })); +}); -onMounted(() => { +// 监听数据变化,重新渲染图表 +watch( + () => props.workData, + (newData) => { + if (newData) { + updateChart(); + } + }, + { immediate: true }, +); + +// 封装图表更新逻辑 +const updateChart = () => { renderEcharts({ series: [ { @@ -18,15 +40,8 @@ onMounted(() => { animationType: 'scale', center: ['50%', '50%'], color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'], - data: [ - { name: '外包', value: 500 }, - { name: '定制', value: 310 }, - { name: '技术支持', value: 274 }, - { name: '远程', value: 400 }, - ].sort((a, b) => { - return a.value - b.value; - }), - name: '商业占比', + data: chartData.value, + name: '工单类型', radius: '80%', roseType: 'radius', type: 'pie', @@ -37,6 +52,13 @@ onMounted(() => { trigger: 'item', }, }); +}; + +onMounted(() => { + // 组件挂载时尝试更新图表 + if (props.workData) { + updateChart(); + } }); diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue index fd3bf104..e2025cfb 100644 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue +++ b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue @@ -2,12 +2,41 @@ import type { EchartsUIType } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; -import { onMounted, ref } from 'vue'; +import { watch, onMounted, ref, defineProps, computed } from 'vue'; +const props = defineProps<{ + statusData?: any[]; // 根据实际数据结构调整类型 +}>(); const chartRef = ref(); const { renderEcharts } = useEcharts(chartRef); +const chartData = computed(() => { + if (!props.statusData) return []; + return props.statusData.map((item) => ({ + name: + item.state == 0 + ? '使用中' + : item.state == 1 + ? '停用中' + : item.state == 2 + ? '已报废' + : '闲置中', + value: item.quantity, + })); +}); -onMounted(() => { +// 监听数据变化,重新渲染图表 +watch( + () => props.statusData, + (newData) => { + if (newData) { + updateChart(); + } + }, + { immediate: true }, +); + +// 封装图表更新逻辑 +const updateChart = () => { renderEcharts({ legend: { bottom: '2%', @@ -22,12 +51,7 @@ onMounted(() => { animationType: 'scale', avoidLabelOverlap: false, color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'], - data: [ - { name: '搜索引擎', value: 1048 }, - { name: '直接访问', value: 735 }, - { name: '邮件营销', value: 580 }, - // { name: '联盟广告', value: 484 }, - ], + data: chartData.value, emphasis: { label: { fontSize: '12', @@ -47,7 +71,7 @@ onMounted(() => { labelLine: { show: false, }, - name: '访问来源', + name: '设备状态', radius: ['40%', '65%'], type: 'pie', }, @@ -56,6 +80,14 @@ onMounted(() => { trigger: 'item', }, }); +}; + +onMounted(() => { + console.log('Child component mounted, props:', props.statusData); + // 组件挂载时尝试更新图表 + if (props.statusData) { + updateChart(); + } }); diff --git a/apps/web-antd/src/views/dashboard/analytics/index.vue b/apps/web-antd/src/views/dashboard/analytics/index.vue index 0236c92a..f330e43d 100644 --- a/apps/web-antd/src/views/dashboard/analytics/index.vue +++ b/apps/web-antd/src/views/dashboard/analytics/index.vue @@ -1,12 +1,5 @@