diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development index cff2557643921373fa5e308f9113b18aedc65de3..87d2f50b98dee88de25e6b736004573a30d8b19c 100644 --- a/apps/web-antd/.env.development +++ b/apps/web-antd/.env.development @@ -4,9 +4,10 @@ VITE_PORT=5666 VITE_BASE=/ # 请求路径 -VITE_BASE_URL=http://127.0.0.1:48080 +#VITE_BASE_URL=http://127.0.0.1:48080 +VITE_BASE_URL=http://192.168.1.240:30800 # 接口地址 -VITE_GLOB_API_URL=/admin-api +VITE_GLOB_API_URL=http://192.168.1.240:30800/admin-api # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 VITE_UPLOAD_TYPE=server # 是否打开 devtools,true 为打开,false 为关闭 diff --git a/apps/web-antd/src/api/iot/alert/config/index.ts b/apps/web-antd/src/api/iot/alert/config/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef4414f235185b47b753f598f714191fa198faf1 --- /dev/null +++ b/apps/web-antd/src/api/iot/alert/config/index.ts @@ -0,0 +1,95 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AlertConfigApi { + /** IoT 告警配置 VO */ + export interface AlertConfig { + id?: number; + name: string; + description?: string; + level?: number; + status?: number; + sceneRuleIds?: number[]; + receiveUserIds?: number[]; + receiveUserNames?: string; + receiveTypes?: number[]; + createTime?: Date; + updateTime?: Date; + } +} + +/** IoT 告警配置 */ +export interface AlertConfig { + id?: number; + name?: string; + description?: string; + level?: number; + status?: number; + sceneRuleIds?: number[]; + receiveUserIds?: number[]; + receiveUserNames?: string; + receiveTypes?: number[]; + createTime?: Date; + updateTime?: Date; +} + +/** 查询告警配置分页 */ +export function getAlertConfigPage(params: PageParam) { + return requestClient.get>( + '/iot/alert-config/page', + { params }, + ); +} + +/** 查询告警配置详情 */ +export function getAlertConfig(id: number) { + return requestClient.get( + `/iot/alert-config/get?id=${id}`, + ); +} + +/** 查询所有告警配置列表 */ +export function getAlertConfigList() { + return requestClient.get('/iot/alert-config/list'); +} + +/** 新增告警配置 */ +export function createAlertConfig(data: AlertConfig) { + return requestClient.post('/iot/alert-config/create', data); +} + +/** 修改告警配置 */ +export function updateAlertConfig(data: AlertConfig) { + return requestClient.put('/iot/alert-config/update', data); +} + +/** 删除告警配置 */ +export function deleteAlertConfig(id: number) { + return requestClient.delete(`/iot/alert-config/delete?id=${id}`); +} + +/** 批量删除告警配置 */ +export function deleteAlertConfigList(ids: number[]) { + return requestClient.delete('/iot/alert-config/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 启用/禁用告警配置 */ +export function toggleAlertConfig(id: number, enabled: boolean) { + return requestClient.put(`/iot/alert-config/toggle`, { + id, + enabled, + }); +} + +/** 获取告警配置简单列表 */ +export function getSimpleAlertConfigList() { + return requestClient.get( + '/iot/alert-config/simple-list', + ); +} + +export { AlertConfigApi }; + diff --git a/apps/web-antd/src/api/iot/alert/record/index.ts b/apps/web-antd/src/api/iot/alert/record/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..02e8768f18548a95eca574fdf82769c3c09a8e77 --- /dev/null +++ b/apps/web-antd/src/api/iot/alert/record/index.ts @@ -0,0 +1,85 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace AlertRecordApi { + /** IoT 告警记录 VO */ + export interface AlertRecord { + id?: number; + configId?: number; + configName?: string; + configLevel?: number; + deviceId?: number; + deviceName?: string; + productId?: number; + productName?: string; + deviceMessage?: string; + processStatus?: boolean; + processRemark?: string; + processTime?: Date; + createTime?: Date; + } +} + +/** IoT 告警记录 */ +export interface AlertRecord { + id?: number; + configId?: number; + configName?: string; + configLevel?: number; + deviceId?: number; + deviceName?: string; + productId?: number; + productName?: string; + deviceMessage?: string; + processStatus?: boolean; + processRemark?: string; + processTime?: Date; + createTime?: Date; +} + +/** 查询告警记录分页 */ +export function getAlertRecordPage(params: PageParam) { + return requestClient.get>( + '/iot/alert-record/page', + { params }, + ); +} + +/** 查询告警记录详情 */ +export function getAlertRecord(id: number) { + return requestClient.get( + `/iot/alert-record/get?id=${id}`, + ); +} + +/** 处理告警记录 */ +export function processAlertRecord(id: number, remark?: string) { + return requestClient.put('/iot/alert-record/process', { + id, + remark, + }); +} + +/** 批量处理告警记录 */ +export function batchProcessAlertRecord(ids: number[], remark?: string) { + return requestClient.put('/iot/alert-record/batch-process', { + ids, + remark, + }); +} + +/** 删除告警记录 */ +export function deleteAlertRecord(id: number) { + return requestClient.delete(`/iot/alert-record/delete?id=${id}`); +} + +/** 批量删除告警记录 */ +export function deleteAlertRecordList(ids: number[]) { + return requestClient.delete('/iot/alert-record/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +export { AlertRecordApi }; + diff --git a/apps/web-antd/src/api/iot/device/device/index.ts b/apps/web-antd/src/api/iot/device/device/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..26770f975d7a2298c3d88eb86d26923cd67502a6 --- /dev/null +++ b/apps/web-antd/src/api/iot/device/device/index.ts @@ -0,0 +1,224 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IotDeviceApi { + /** IoT 设备 VO */ + export interface Device { + id?: number; // 设备 ID,主键,自增 + deviceName: string; // 设备名称 + productId: number; // 产品编号 + productKey?: string; // 产品标识 + deviceType?: number; // 设备类型 + nickname?: string; // 设备备注名称 + gatewayId?: number; // 网关设备 ID + state?: number; // 设备状态 + status?: number; // 设备状态(兼容字段) + onlineTime?: Date; // 最后上线时间 + offlineTime?: Date; // 最后离线时间 + activeTime?: Date; // 设备激活时间 + createTime?: Date; // 创建时间 + ip?: string; // 设备的 IP 地址 + firmwareVersion?: string; // 设备的固件版本 + deviceSecret?: string; // 设备密钥,用于设备认证,需安全存储 + mqttClientId?: string; // MQTT 客户端 ID + mqttUsername?: string; // MQTT 用户名 + mqttPassword?: string; // MQTT 密码 + authType?: string; // 认证类型 + locationType?: number; // 定位类型 + latitude?: number; // 设备位置的纬度 + longitude?: number; // 设备位置的经度 + areaId?: number; // 地区编码 + address?: string; // 设备详细地址 + serialNumber?: string; // 设备序列号 + config?: string; // 设备配置 + groupIds?: number[]; // 添加分组 ID + picUrl?: string; // 设备图片 + location?: string; // 位置信息(格式:经度,纬度) + } + + /** IoT 设备属性详细 VO */ + export interface DevicePropertyDetail { + identifier: string; // 属性标识符 + value: string; // 最新值 + updateTime: Date; // 更新时间 + name: string; // 属性名称 + dataType: string; // 数据类型 + dataSpecs: any; // 数据定义 + dataSpecsList: any[]; // 数据定义列表 + } + + /** IoT 设备属性 VO */ + export interface DeviceProperty { + identifier: string; // 属性标识符 + value: string; // 最新值 + updateTime: Date; // 更新时间 + } + + /** 设备认证参数 VO */ + export interface DeviceAuthInfo { + clientId: string; // 客户端 ID + username: string; // 用户名 + password: string; // 密码 + } + + /** IoT 设备发送消息 Request VO */ + export interface DeviceMessageSendReq { + deviceId: number; // 设备编号 + method: string; // 请求方法 + params?: any; // 请求参数 + } + + /** 设备分组更新请求 */ + export interface DeviceGroupUpdateReq { + ids: number[]; // 设备 ID 列表 + groupIds: number[]; // 分组 ID 列表 + } +} + +/** IoT 设备状态枚举 */ +export enum DeviceStateEnum { + INACTIVE = 0, // 未激活 + ONLINE = 1, // 在线 + OFFLINE = 2, // 离线 +} + +/** 查询设备分页 */ +export function getDevicePage(params: PageParam) { + return requestClient.get>( + '/iot/device/page', + { params }, + ); +} + +/** 查询设备详情 */ +export function getDevice(id: number) { + return requestClient.get(`/iot/device/get?id=${id}`); +} + +/** 新增设备 */ +export function createDevice(data: IotDeviceApi.Device) { + return requestClient.post('/iot/device/create', data); +} + +/** 修改设备 */ +export function updateDevice(data: IotDeviceApi.Device) { + return requestClient.put('/iot/device/update', data); +} + +/** 修改设备分组 */ +export function updateDeviceGroup(data: IotDeviceApi.DeviceGroupUpdateReq) { + return requestClient.put('/iot/device/update-group', data); +} + +/** 删除单个设备 */ +export function deleteDevice(id: number) { + return requestClient.delete(`/iot/device/delete?id=${id}`); +} + +/** 删除多个设备 */ +export function deleteDeviceList(ids: number[]) { + return requestClient.delete('/iot/device/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 导出设备 */ +export function exportDeviceExcel(params: any) { + return requestClient.download('/iot/device/export-excel', { params }); +} + +/** 获取设备数量 */ +export function getDeviceCount(productId: number) { + return requestClient.get(`/iot/device/count?productId=${productId}`); +} + +/** 获取设备的精简信息列表 */ +export function getSimpleDeviceList(deviceType?: number, productId?: number) { + return requestClient.get('/iot/device/simple-list', { + params: { deviceType, productId }, + }); +} + +/** 根据产品编号,获取设备的精简信息列表 */ +export function getDeviceListByProductId(productId: number) { + return requestClient.get('/iot/device/simple-list', { + params: { productId }, + }); +} + +/** 获取导入模板 */ +export function importDeviceTemplate() { + return requestClient.download('/iot/device/get-import-template'); +} + +/** 获取设备属性最新数据 */ +export function getLatestDeviceProperties(params: any) { + return requestClient.get( + '/iot/device/property/get-latest', + { params }, + ); +} + +/** 获取设备属性历史数据 */ +export function getHistoryDevicePropertyList(params: any) { + return requestClient.get>( + '/iot/device/property/history-list', + { params }, + ); +} + +/** 获取设备认证信息 */ +export function getDeviceAuthInfo(id: number) { + return requestClient.get( + '/iot/device/get-auth-info', + { params: { id } }, + ); +} + +/** 查询设备消息分页 */ +export function getDeviceMessagePage(params: PageParam) { + return requestClient.get>('/iot/device/message/page', { + params, + }); +} + +/** 查询设备消息配对分页 */ +export function getDeviceMessagePairPage(params: PageParam) { + return requestClient.get>('/iot/device/message/pair-page', { + params, + }); +} + +/** 发送设备消息 */ +export function sendDeviceMessage(params: IotDeviceApi.DeviceMessageSendReq) { + return requestClient.post('/iot/device/message/send', params); +} + +// Export aliases for compatibility +export const DeviceApi = { + getDevicePage, + getDevice, + createDevice, + updateDevice, + updateDeviceGroup, + deleteDevice, + deleteDeviceList, + exportDeviceExcel, + getDeviceCount, + getSimpleDeviceList, + getDeviceListByProductId, + importDeviceTemplate, + getLatestDeviceProperties, + getHistoryDevicePropertyList, + getDeviceAuthInfo, + getDeviceMessagePage, + getDeviceMessagePairPage, + sendDeviceMessage, +}; + +export type DeviceVO = IotDeviceApi.Device; +export type IotDeviceAuthInfoVO = IotDeviceApi.DeviceAuthInfo; +export type IotDevicePropertyDetailRespVO = IotDeviceApi.DevicePropertyDetail; +export type IotDevicePropertyRespVO = IotDeviceApi.DeviceProperty; + diff --git a/apps/web-antd/src/api/iot/device/group/index.ts b/apps/web-antd/src/api/iot/device/group/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b7da965f399632fd175efdd445e0cec38646e62 --- /dev/null +++ b/apps/web-antd/src/api/iot/device/group/index.ts @@ -0,0 +1,52 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IotDeviceGroupApi { + /** IoT 设备分组 VO */ + export interface DeviceGroup { + id?: number; // 分组 ID + name: string; // 分组名字 + status?: number; // 分组状态 + description?: string; // 分组描述 + deviceCount?: number; // 设备数量 + } +} + +/** 查询设备分组分页 */ +export function getDeviceGroupPage(params: PageParam) { + return requestClient.get>( + '/iot/device-group/page', + { params }, + ); +} + +/** 查询设备分组详情 */ +export function getDeviceGroup(id: number) { + return requestClient.get( + `/iot/device-group/get?id=${id}`, + ); +} + +/** 新增设备分组 */ +export function createDeviceGroup(data: IotDeviceGroupApi.DeviceGroup) { + return requestClient.post('/iot/device-group/create', data); +} + +/** 修改设备分组 */ +export function updateDeviceGroup(data: IotDeviceGroupApi.DeviceGroup) { + return requestClient.put('/iot/device-group/update', data); +} + +/** 删除设备分组 */ +export function deleteDeviceGroup(id: number) { + return requestClient.delete(`/iot/device-group/delete?id=${id}`); +} + +/** 获取设备分组的精简信息列表 */ +export function getSimpleDeviceGroupList() { + return requestClient.get( + '/iot/device-group/simple-list', + ); +} + diff --git a/apps/web-antd/src/api/iot/ota/firmware/index.ts b/apps/web-antd/src/api/iot/ota/firmware/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b359548677b5c7829711a5fa1653e1b05a2545ea --- /dev/null +++ b/apps/web-antd/src/api/iot/ota/firmware/index.ts @@ -0,0 +1,90 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IoTOtaFirmwareApi { + /** IoT OTA 固件 VO */ + export interface Firmware { + id?: number; + name: string; + version: string; + productId: number; + productName?: string; + description?: string; + fileUrl?: string; + fileMd5?: string; + fileSize?: number; + status?: number; + createTime?: Date; + updateTime?: Date; + } +} + +/** IoT OTA 固件 */ +export interface IoTOtaFirmware { + id?: number; + name?: string; + version?: string; + productId?: number; + productName?: string; + description?: string; + fileUrl?: string; + fileMd5?: string; + fileSize?: number; + status?: number; + createTime?: Date; + updateTime?: Date; +} + +/** 查询 OTA 固件分页 */ +export function getOtaFirmwarePage(params: PageParam) { + return requestClient.get>( + '/iot/ota-firmware/page', + { params }, + ); +} + +/** 查询 OTA 固件详情 */ +export function getOtaFirmware(id: number) { + return requestClient.get( + `/iot/ota-firmware/get?id=${id}`, + ); +} + +/** 新增 OTA 固件 */ +export function createOtaFirmware(data: IoTOtaFirmware) { + return requestClient.post('/iot/ota-firmware/create', data); +} + +/** 修改 OTA 固件 */ +export function updateOtaFirmware(data: IoTOtaFirmware) { + return requestClient.put('/iot/ota-firmware/update', data); +} + +/** 删除 OTA 固件 */ +export function deleteOtaFirmware(id: number) { + return requestClient.delete(`/iot/ota-firmware/delete?id=${id}`); +} + +/** 批量删除 OTA 固件 */ +export function deleteOtaFirmwareList(ids: number[]) { + return requestClient.delete('/iot/ota-firmware/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 更新 OTA 固件状态 */ +export function updateOtaFirmwareStatus(id: number, status: number) { + return requestClient.put(`/iot/ota-firmware/update-status`, { + id, + status, + }); +} + +/** 根据产品 ID 查询固件列表 */ +export function getOtaFirmwareListByProductId(productId: number) { + return requestClient.get( + '/iot/ota-firmware/list-by-product-id', + { params: { productId } }, + ); +} diff --git a/apps/web-antd/src/api/iot/ota/task/index.ts b/apps/web-antd/src/api/iot/ota/task/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..7747b9f9252c8327ca9c3f87333319d2e6472b87 --- /dev/null +++ b/apps/web-antd/src/api/iot/ota/task/index.ts @@ -0,0 +1,101 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IoTOtaTaskApi { + /** IoT OTA 升级任务 VO */ + export interface Task { + id?: number; + name: string; + description?: string; + firmwareId: number; + firmwareName?: string; + productId?: number; + productName?: string; + deviceScope?: number; + deviceIds?: number[]; + status?: number; + successCount?: number; + failureCount?: number; + pendingCount?: number; + createTime?: Date; + updateTime?: Date; + } +} + +/** IoT OTA 升级任务 */ +export interface OtaTask { + id?: number; + name?: string; + description?: string; + firmwareId?: number; + firmwareName?: string; + productId?: number; + productName?: string; + deviceScope?: number; + deviceIds?: number[]; + status?: number; + successCount?: number; + failureCount?: number; + pendingCount?: number; + createTime?: Date; + updateTime?: Date; +} + +/** 查询 OTA 升级任务分页 */ +export function getOtaTaskPage(params: PageParam) { + return requestClient.get>( + '/iot/ota-task/page', + { params }, + ); +} + +/** 查询 OTA 升级任务详情 */ +export function getOtaTask(id: number) { + return requestClient.get(`/iot/ota-task/get?id=${id}`); +} + +/** 新增 OTA 升级任务 */ +export function createOtaTask(data: OtaTask) { + return requestClient.post('/iot/ota-task/create', data); +} + +/** 修改 OTA 升级任务 */ +export function updateOtaTask(data: OtaTask) { + return requestClient.put('/iot/ota-task/update', data); +} + +/** 删除 OTA 升级任务 */ +export function deleteOtaTask(id: number) { + return requestClient.delete(`/iot/ota-task/delete?id=${id}`); +} + +/** 批量删除 OTA 升级任务 */ +export function deleteOtaTaskList(ids: number[]) { + return requestClient.delete('/iot/ota-task/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 取消 OTA 升级任务 */ +export function cancelOtaTask(id: number) { + return requestClient.put(`/iot/ota-task/cancel?id=${id}`); +} + +/** 启动 OTA 升级任务 */ +export function startOtaTask(id: number) { + return requestClient.put(`/iot/ota-task/start?id=${id}`); +} + +/** 暂停 OTA 升级任务 */ +export function pauseOtaTask(id: number) { + return requestClient.put(`/iot/ota-task/pause?id=${id}`); +} + +/** 恢复 OTA 升级任务 */ +export function resumeOtaTask(id: number) { + return requestClient.put(`/iot/ota-task/resume?id=${id}`); +} + +export { IoTOtaTaskApi }; + diff --git a/apps/web-antd/src/api/iot/ota/task/record/index.ts b/apps/web-antd/src/api/iot/ota/task/record/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d857a6aa92bf44f786016788b073372502aa6f4a --- /dev/null +++ b/apps/web-antd/src/api/iot/ota/task/record/index.ts @@ -0,0 +1,104 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IoTOtaTaskRecordApi { + /** IoT OTA 升级任务记录 VO */ + export interface TaskRecord { + id?: number; + taskId: number; + taskName?: string; + deviceId: number; + deviceName?: string; + firmwareId?: number; + firmwareName?: string; + firmwareVersion?: string; + status?: number; + progress?: number; + errorMessage?: string; + startTime?: Date; + endTime?: Date; + createTime?: Date; + } +} + +/** IoT OTA 升级任务记录 */ +export interface OtaTaskRecord { + id?: number; + taskId?: number; + taskName?: string; + deviceId?: number; + deviceName?: string; + firmwareId?: number; + firmwareName?: string; + firmwareVersion?: string; + status?: number; + progress?: number; + errorMessage?: string; + startTime?: Date; + endTime?: Date; + createTime?: Date; +} + +/** 查询 OTA 升级任务记录分页 */ +export function getOtaTaskRecordPage(params: PageParam) { + return requestClient.get>( + '/iot/ota-task-record/page', + { params }, + ); +} + +/** 查询 OTA 升级任务记录详情 */ +export function getOtaTaskRecord(id: number) { + return requestClient.get( + `/iot/ota-task-record/get?id=${id}`, + ); +} + +/** 根据任务 ID 查询记录列表 */ +export function getOtaTaskRecordListByTaskId(taskId: number) { + return requestClient.get( + '/iot/ota-task-record/list-by-task-id', + { params: { taskId } }, + ); +} + +/** 根据设备 ID 查询记录列表 */ +export function getOtaTaskRecordListByDeviceId(deviceId: number) { + return requestClient.get( + '/iot/ota-task-record/list-by-device-id', + { params: { deviceId } }, + ); +} + +/** 根据固件 ID 查询记录列表 */ +export function getOtaTaskRecordListByFirmwareId(firmwareId: number) { + return requestClient.get( + '/iot/ota-task-record/list-by-firmware-id', + { params: { firmwareId } }, + ); +} + +/** 重试升级任务记录 */ +export function retryOtaTaskRecord(id: number) { + return requestClient.put(`/iot/ota-task-record/retry?id=${id}`); +} + +/** 取消升级任务记录 */ +export function cancelOtaTaskRecord(id: number) { + return requestClient.put(`/iot/ota-task-record/cancel?id=${id}`); +} + +/** 获取升级任务记录状态统计 */ +export function getOtaTaskRecordStatusStatistics( + firmwareId?: number, + taskId?: number, +) { + return requestClient.get>( + '/iot/ota-task-record/status-statistics', + { params: { firmwareId, taskId } }, + ); +} + +export { IoTOtaTaskRecordApi }; + diff --git a/apps/web-antd/src/api/iot/product/category/index.ts b/apps/web-antd/src/api/iot/product/category/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..66faa608312d35d517625419193f4d04ddd32354 --- /dev/null +++ b/apps/web-antd/src/api/iot/product/category/index.ts @@ -0,0 +1,58 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IotProductCategoryApi { + /** IoT 產品分類 VO */ + export interface ProductCategory { + id?: number; // 分類 ID + name: string; // 分類名稱 + parentId?: number; // 父级分類 ID + sort?: number; // 分類排序 + status?: number; // 分類狀態 + description?: string; // 分類描述 + createTime?: string; // 創建時間 + } +} + +/** 查詢產品分類分頁 */ +export function getProductCategoryPage(params: PageParam) { + return requestClient.get>( + '/iot/product-category/page', + { params }, + ); +} + +/** 查詢產品分類詳情 */ +export function getProductCategory(id: number) { + return requestClient.get( + `/iot/product-category/get?id=${id}`, + ); +} + +/** 新增產品分類 */ +export function createProductCategory( + data: IotProductCategoryApi.ProductCategory, +) { + return requestClient.post('/iot/product-category/create', data); +} + +/** 修改產品分類 */ +export function updateProductCategory( + data: IotProductCategoryApi.ProductCategory, +) { + return requestClient.put('/iot/product-category/update', data); +} + +/** 刪除產品分類 */ +export function deleteProductCategory(id: number) { + return requestClient.delete(`/iot/product-category/delete?id=${id}`); +} + +/** 獲取產品分類精簡列表 */ +export function getSimpleProductCategoryList() { + return requestClient.get( + '/iot/product-category/simple-list', + ); +} + diff --git a/apps/web-antd/src/api/iot/product/product/index.ts b/apps/web-antd/src/api/iot/product/product/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..586487ed917b8d30296fbbcb3064d5c407c504ba --- /dev/null +++ b/apps/web-antd/src/api/iot/product/product/index.ts @@ -0,0 +1,114 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IotProductApi { + /** IoT 产品 VO */ + export interface Product { + id?: number; // 产品编号 + name: string; // 产品名称 + productKey?: string; // 产品标识 + protocolId?: number; // 协议编号 + protocolType?: number; // 接入协议类型 + categoryId?: number; // 产品所属品类标识符 + categoryName?: string; // 产品所属品类名称 + icon?: string; // 产品图标 + picUrl?: string; // 产品图片 + description?: string; // 产品描述 + status?: number; // 产品状态 + deviceType?: number; // 设备类型 + locationType?: number; // 定位类型 + netType?: number; // 联网方式 + codecType?: string; // 数据格式(编解码器类型) + dataFormat?: number; // 数据格式 + validateType?: number; // 认证方式 + deviceCount?: number; // 设备数量 + createTime?: Date; // 创建时间 + } +} + +/** IOT 产品设备类型枚举类 */ +export enum DeviceTypeEnum { + DEVICE = 0, // 直连设备 + GATEWAY_SUB = 1, // 网关子设备 + GATEWAY = 2, // 网关设备 +} + +/** IOT 产品定位类型枚举类 */ +export enum LocationTypeEnum { + IP = 1, // IP 定位 + MODULE = 2, // 设备定位 + MANUAL = 3, // 手动定位 +} + +/** IOT 数据格式(编解码器类型)枚举类 */ +export enum CodecTypeEnum { + ALINK = 'Alink', // 阿里云 Alink 协议 +} + +/** 查询产品分页 */ +export function getProductPage(params: PageParam) { + return requestClient.get>( + '/iot/product/page', + { params }, + ); +} + +/** 查询产品详情 */ +export function getProduct(id: number) { + return requestClient.get(`/iot/product/get?id=${id}`); +} + +/** 新增产品 */ +export function createProduct(data: IotProductApi.Product) { + return requestClient.post('/iot/product/create', data); +} + +/** 修改产品 */ +export function updateProduct(data: IotProductApi.Product) { + return requestClient.put('/iot/product/update', data); +} + +/** 删除产品 */ +export function deleteProduct(id: number) { + return requestClient.delete(`/iot/product/delete?id=${id}`); +} + +/** 导出产品 Excel */ +export function exportProduct(params: any) { + return requestClient.download('/iot/product/export-excel', { params }); +} + +/** 更新产品状态 */ +export function updateProductStatus(id: number, status: number) { + return requestClient.put( + `/iot/product/update-status?id=${id}&status=${status}`, + ); +} + +/** 查询产品(精简)列表 */ +export function getSimpleProductList() { + return requestClient.get('/iot/product/simple-list'); +} + +/** 根据 ProductKey 获取产品信息 */ +export function getProductByKey(productKey: string) { + return requestClient.get('/iot/product/get-by-key', { + params: { productKey }, + }); +} + +// Export aliases for compatibility +export const ProductApi = { + getProductPage, + getProduct, + createProduct, + updateProduct, + deleteProduct, + exportProduct, + updateProductStatus, + getSimpleProductList, + getProductByKey, +}; + +export type ProductVO = IotProductApi.Product; diff --git a/apps/web-antd/src/api/iot/rule/data/rule/index.ts b/apps/web-antd/src/api/iot/rule/data/rule/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8120e6c33d0d942cfc27c2bc8e06ace9a385405 --- /dev/null +++ b/apps/web-antd/src/api/iot/rule/data/rule/index.ts @@ -0,0 +1,84 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace DataRuleApi { + /** IoT 数据流转规则 VO */ + export interface Rule { + id?: number; + name: string; + description?: string; + status?: number; + productId?: number; + productKey?: string; + sourceConfigs?: SourceConfig[]; + sinkIds?: number[]; + createTime?: Date; + } + + /** IoT 数据源配置 */ + export interface SourceConfig { + productId?: number; + productKey?: string; + deviceId?: number; + type?: string; + topic?: string; + } +} + +/** IoT 数据流转规则 */ +export interface DataRule { + id?: number; + name?: string; + description?: string; + status?: number; + productId?: number; + productKey?: string; + sourceConfigs?: any[]; + sinkIds?: number[]; + createTime?: Date; +} + +/** 查询数据流转规则分页 */ +export function getDataRulePage(params: PageParam) { + return requestClient.get>( + '/iot/data-rule/page', + { params }, + ); +} + +/** 查询数据流转规则详情 */ +export function getDataRule(id: number) { + return requestClient.get(`/iot/data-rule/get?id=${id}`); +} + +/** 新增数据流转规则 */ +export function createDataRule(data: DataRule) { + return requestClient.post('/iot/data-rule/create', data); +} + +/** 修改数据流转规则 */ +export function updateDataRule(data: DataRule) { + return requestClient.put('/iot/data-rule/update', data); +} + +/** 删除数据流转规则 */ +export function deleteDataRule(id: number) { + return requestClient.delete(`/iot/data-rule/delete?id=${id}`); +} + +/** 批量删除数据流转规则 */ +export function deleteDataRuleList(ids: number[]) { + return requestClient.delete('/iot/data-rule/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 更新数据流转规则状态 */ +export function updateDataRuleStatus(id: number, status: number) { + return requestClient.put(`/iot/data-rule/update-status`, { + id, + status, + }); +} + diff --git a/apps/web-antd/src/api/iot/rule/data/sink/index.ts b/apps/web-antd/src/api/iot/rule/data/sink/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ec0307ee8f2e0eb0f71f04f2e1a53bff4ef07d2 --- /dev/null +++ b/apps/web-antd/src/api/iot/rule/data/sink/index.ts @@ -0,0 +1,151 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace DataSinkApi { + /** IoT 数据流转目的 VO */ + export interface Sink { + id?: number; + name: string; + description?: string; + status?: number; + type: string; + config?: any; + createTime?: Date; + } +} + +/** IoT 数据流转目的 */ +export interface DataSinkVO { + id?: number; + name?: string; + description?: string; + status?: number; + type?: string; + config?: any; + createTime?: Date; +} + +/** IoT 数据目的类型枚举 */ +export enum IotDataSinkTypeEnum { + HTTP = 'HTTP', + MQTT = 'MQTT', + KAFKA = 'KAFKA', + RABBITMQ = 'RABBITMQ', + ROCKETMQ = 'ROCKETMQ', + REDIS_STREAM = 'REDIS_STREAM', +} + +/** HTTP 配置 */ +export interface HttpConfig { + url?: string; + method?: string; + headers?: Record; + timeout?: number; +} + +/** MQTT 配置 */ +export interface MqttConfig { + broker?: string; + port?: number; + topic?: string; + username?: string; + password?: string; + clientId?: string; + qos?: number; +} + +/** Kafka 配置 */ +export interface KafkaMQConfig { + bootstrapServers?: string; + topic?: string; + acks?: string; + retries?: number; + batchSize?: number; +} + +/** RabbitMQ 配置 */ +export interface RabbitMQConfig { + host?: string; + port?: number; + virtualHost?: string; + username?: string; + password?: string; + exchange?: string; + routingKey?: string; + queue?: string; +} + +/** RocketMQ 配置 */ +export interface RocketMQConfig { + nameServer?: string; + topic?: string; + tag?: string; + producerGroup?: string; +} + +/** Redis Stream 配置 */ +export interface RedisStreamMQConfig { + host?: string; + port?: number; + password?: string; + database?: number; + streamKey?: string; + maxLen?: number; +} + +/** 查询数据流转目的分页 */ +export function getDataSinkPage(params: PageParam) { + return requestClient.get>( + '/iot/data-sink/page', + { params }, + ); +} + +/** 查询数据流转目的详情 */ +export function getDataSink(id: number) { + return requestClient.get(`/iot/data-sink/get?id=${id}`); +} + +/** 查询所有数据流转目的列表 */ +export function getDataSinkList() { + return requestClient.get('/iot/data-sink/list'); +} + +/** 查询数据流转目的简单列表 */ +export function getDataSinkSimpleList() { + return requestClient.get('/iot/data-sink/simple-list'); +} + +/** 新增数据流转目的 */ +export function createDataSink(data: DataSinkVO) { + return requestClient.post('/iot/data-sink/create', data); +} + +/** 修改数据流转目的 */ +export function updateDataSink(data: DataSinkVO) { + return requestClient.put('/iot/data-sink/update', data); +} + +/** 删除数据流转目的 */ +export function deleteDataSink(id: number) { + return requestClient.delete(`/iot/data-sink/delete?id=${id}`); +} + +/** 批量删除数据流转目的 */ +export function deleteDataSinkList(ids: number[]) { + return requestClient.delete('/iot/data-sink/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 更新数据流转目的状态 */ +export function updateDataSinkStatus(id: number, status: number) { + return requestClient.put(`/iot/data-sink/update-status`, { + id, + status, + }); +} + +export { DataSinkApi }; + diff --git a/apps/web-antd/src/api/iot/rule/scene/index.ts b/apps/web-antd/src/api/iot/rule/scene/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..36f968066e0afd626157585afa68281f32448669 --- /dev/null +++ b/apps/web-antd/src/api/iot/rule/scene/index.ts @@ -0,0 +1,163 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace RuleSceneApi { + /** IoT 场景联动规则 VO */ + export interface SceneRule { + id?: number; + name: string; + description?: string; + status?: number; + triggers?: Trigger[]; + actions?: Action[]; + createTime?: Date; + } + + /** IoT 场景联动规则触发器 */ + export interface Trigger { + type?: string; + productId?: number; + deviceId?: number; + identifier?: string; + operator?: string; + value?: any; + cronExpression?: string; + conditionGroups?: TriggerConditionGroup[]; + } + + /** IoT 场景联动规则触发条件组 */ + export interface TriggerConditionGroup { + conditions?: TriggerCondition[]; + operator?: string; + } + + /** IoT 场景联动规则触发条件 */ + export interface TriggerCondition { + productId?: number; + deviceId?: number; + identifier?: string; + operator?: string; + value?: any; + type?: string; + } + + /** IoT 场景联动规则动作 */ + export interface Action { + type?: string; + productId?: number; + deviceId?: number; + identifier?: string; + value?: any; + alertConfigId?: number; + } +} + +/** IoT 场景联动规则 */ +export interface IotSceneRule { + id?: number; + name?: string; + description?: string; + status?: number; + triggers?: Trigger[]; + actions?: Action[]; + createTime?: Date; +} + +/** IoT 场景联动规则触发器 */ +export interface Trigger { + type?: string; + productId?: number; + deviceId?: number; + identifier?: string; + operator?: string; + value?: any; + cronExpression?: string; + conditionGroups?: TriggerConditionGroup[]; +} + +/** IoT 场景联动规则触发条件组 */ +export interface TriggerConditionGroup { + conditions?: TriggerCondition[]; + operator?: string; +} + +/** IoT 场景联动规则触发条件 */ +export interface TriggerCondition { + productId?: number; + deviceId?: number; + identifier?: string; + operator?: string; + value?: any; + type?: string; +} + +/** IoT 场景联动规则动作 */ +export interface Action { + type?: string; + productId?: number; + deviceId?: number; + identifier?: string; + value?: any; + alertConfigId?: number; +} + +/** 查询场景联动规则分页 */ +export function getSceneRulePage(params: PageParam) { + return requestClient.get>( + '/iot/scene-rule/page', + { params }, + ); +} + +/** 查询场景联动规则详情 */ +export function getSceneRule(id: number) { + return requestClient.get( + `/iot/scene-rule/get?id=${id}`, + ); +} + +/** 新增场景联动规则 */ +export function createSceneRule(data: IotSceneRule) { + return requestClient.post('/iot/scene-rule/create', data); +} + +/** 修改场景联动规则 */ +export function updateSceneRule(data: IotSceneRule) { + return requestClient.put('/iot/scene-rule/update', data); +} + +/** 删除场景联动规则 */ +export function deleteSceneRule(id: number) { + return requestClient.delete(`/iot/scene-rule/delete?id=${id}`); +} + +/** 批量删除场景联动规则 */ +export function deleteSceneRuleList(ids: number[]) { + return requestClient.delete('/iot/scene-rule/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 更新场景联动规则状态 */ +export function updateSceneRuleStatus(id: number, status: number) { + return requestClient.put(`/iot/scene-rule/update-status`, { + id, + status, + }); +} + +/** 获取场景联动规则简单列表 */ +export function getSimpleRuleSceneList() { + return requestClient.get( + '/iot/scene-rule/simple-list', + ); +} + +// 别名导出(兼容旧代码) +export { + getSceneRulePage as getRuleScenePage, + deleteSceneRule as deleteRuleScene, + updateSceneRuleStatus as updateRuleSceneStatus, +}; + diff --git a/apps/web-antd/src/api/iot/statistics/index.ts b/apps/web-antd/src/api/iot/statistics/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd8bf037b42b363fa92c5f70ea0852688ad93c71 --- /dev/null +++ b/apps/web-antd/src/api/iot/statistics/index.ts @@ -0,0 +1,84 @@ +import { requestClient } from '#/api/request'; + +export namespace IotStatisticsApi { + /** IoT 统计摘要数据 */ + export interface StatisticsSummary { + productCategoryCount: number; + productCount: number; + deviceCount: number; + deviceMessageCount: number; + productCategoryTodayCount: number; + productTodayCount: number; + deviceTodayCount: number; + deviceMessageTodayCount: number; + deviceOnlineCount: number; + deviceOfflineCount: number; + deviceInactiveCount: number; + productCategoryDeviceCounts: Record; + } + + /** 时间戳-数值的键值对类型 */ + export interface TimeValueItem { + [key: string]: number; + } + + /** IoT 消息统计数据类型 */ + export interface DeviceMessageSummary { + statType: number; + upstreamCounts: TimeValueItem[]; + downstreamCounts: TimeValueItem[]; + } + + /** 消息统计数据项(按日期) */ + export interface DeviceMessageSummaryByDate { + time: string; + upstreamCount: number; + downstreamCount: number; + } + + /** 消息统计接口参数 */ + export interface DeviceMessageReq { + interval: number; + times?: string[]; + } +} + +/** 获取 IoT 统计摘要数据 */ +export function getStatisticsSummary() { + return requestClient.get( + '/iot/statistics/get-summary', + ); +} + +/** 获取设备消息的数据统计(按日期) */ +export function getDeviceMessageSummaryByDate( + params: IotStatisticsApi.DeviceMessageReq, +) { + return requestClient.get( + '/iot/statistics/get-device-message-summary-by-date', + { params }, + ); +} + +/** 获取设备消息统计摘要 */ +export function getDeviceMessageSummary(statType: number) { + return requestClient.get( + '/iot/statistics/get-device-message-summary', + { params: { statType } }, + ); +} + +// 导出 API 对象(兼容旧代码) +export const StatisticsApi = { + getStatisticsSummary, + getDeviceMessageSummaryByDate, + getDeviceMessageSummary, +}; + +// 导出类型别名(兼容旧代码) +export type IotStatisticsSummaryRespVO = IotStatisticsApi.StatisticsSummary; +export type IotStatisticsDeviceMessageSummaryRespVO = + IotStatisticsApi.DeviceMessageSummary; +export type IotStatisticsDeviceMessageSummaryByDateRespVO = + IotStatisticsApi.DeviceMessageSummaryByDate; +export type IotStatisticsDeviceMessageReqVO = IotStatisticsApi.DeviceMessageReq; diff --git a/apps/web-antd/src/api/iot/thingmodel/index.ts b/apps/web-antd/src/api/iot/thingmodel/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..eac8bea39aa8e81e03994b8e5c23e2b53f8e0203 --- /dev/null +++ b/apps/web-antd/src/api/iot/thingmodel/index.ts @@ -0,0 +1,206 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace ThingModelApi { + /** IoT 物模型数据 VO */ + export interface ThingModel { + id?: number; + productId?: number; + productKey?: string; + identifier: string; + name: string; + desc?: string; + type: string; + property?: ThingModelProperty; + event?: ThingModelEvent; + service?: ThingModelService; + } + + /** IoT 物模型属性 */ + export interface Property { + identifier: string; + name: string; + accessMode: string; + dataType: string; + dataSpecs?: any; + dataSpecsList?: any[]; + desc?: string; + } + + /** IoT 物模型服务 */ + export interface Service { + identifier: string; + name: string; + callType: string; + inputData?: any[]; + outputData?: any[]; + desc?: string; + } + + /** IoT 物模型事件 */ + export interface Event { + identifier: string; + name: string; + type: string; + outputData?: any[]; + desc?: string; + } +} + +/** IoT 物模型数据 */ +export interface ThingModelData { + id?: number; + productId?: number; + productKey?: string; + identifier?: string; + name?: string; + desc?: string; + type?: string; + dataType?: string; + property?: ThingModelProperty; + event?: ThingModelEvent; + service?: ThingModelService; +} + +/** IoT 物模型属性 */ +export interface ThingModelProperty { + identifier?: string; + name?: string; + accessMode?: string; + dataType?: string; + dataSpecs?: any; + dataSpecsList?: any[]; + desc?: string; +} + +/** IoT 物模型服务 */ +export interface ThingModelService { + identifier?: string; + name?: string; + callType?: string; + inputData?: any[]; + outputData?: any[]; + desc?: string; +} + +/** IoT 物模型事件 */ +export interface ThingModelEvent { + identifier?: string; + name?: string; + type?: string; + outputData?: any[]; + desc?: string; +} + +/** IoT 数据定义(数值型) */ +export interface DataSpecsNumberData { + min?: number | string; + max?: number | string; + step?: number | string; + unit?: string; + unitName?: string; +} + +/** IoT 数据定义(枚举/布尔型) */ +export interface DataSpecsEnumOrBoolData { + value: number | string; + name: string; +} + +/** IoT 物模型表单校验规则 */ +export interface ThingModelFormRules { + [key: string]: any; +} + +/** 验证布尔型名称 */ +export const validateBoolName = (_rule: any, value: any, callback: any) => { + if (!value) { + callback(new Error('枚举描述不能为空')); + } else { + callback(); + } +}; + +/** 查询产品物模型分页 */ +export function getThingModelPage(params: PageParam) { + return requestClient.get>( + '/iot/thing-model/page', + { params }, + ); +} + +/** 查询产品物模型详情 */ +export function getThingModel(id: number) { + return requestClient.get( + `/iot/thing-model/get?id=${id}`, + ); +} + +/** 根据产品 ID 查询物模型列表 */ +export function getThingModelListByProductId(productId: number) { + return requestClient.get( + '/iot/thing-model/list-by-product-id', + { params: { productId } }, + ); +} + +/** 根据产品标识查询物模型列表 */ +export function getThingModelListByProductKey(productKey: string) { + return requestClient.get( + '/iot/thing-model/list-by-product-key', + { params: { productKey } }, + ); +} + +/** 新增物模型 */ +export function createThingModel(data: ThingModelData) { + return requestClient.post('/iot/thing-model/create', data); +} + +/** 修改物模型 */ +export function updateThingModel(data: ThingModelData) { + return requestClient.put('/iot/thing-model/update', data); +} + +/** 删除物模型 */ +export function deleteThingModel(id: number) { + return requestClient.delete(`/iot/thing-model/delete?id=${id}`); +} + +/** 批量删除物模型 */ +export function deleteThingModelList(ids: number[]) { + return requestClient.delete('/iot/thing-model/delete-list', { + params: { ids: ids.join(',') }, + }); +} + +/** 导入物模型 TSL */ +export function importThingModelTSL(productId: number, tslData: any) { + return requestClient.post('/iot/thing-model/import-tsl', { + productId, + tslData, + }); +} + +/** 导出物模型 TSL */ +export function exportThingModelTSL(productId: number) { + return requestClient.get('/iot/thing-model/export-tsl', { + params: { productId }, + }); +} + +// Add a consolidated API object and getThingModelList alias +export const ThingModelApi = { + getThingModelPage, + getThingModel, + getThingModelList: getThingModelListByProductId, // alias for compatibility + getThingModelListByProductId, + getThingModelListByProductKey, + createThingModel, + updateThingModel, + deleteThingModel, + deleteThingModelList, + importThingModelTSL, + exportThingModelTSL, +}; diff --git a/apps/web-antd/src/router/routes/modules/iot.ts b/apps/web-antd/src/router/routes/modules/iot.ts new file mode 100644 index 0000000000000000000000000000000000000000..f43a547b06e647acc26ae2faf393374d06ee6a3b --- /dev/null +++ b/apps/web-antd/src/router/routes/modules/iot.ts @@ -0,0 +1,37 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + path: '/iot', + name: 'IoTCenter', + meta: { + title: 'IoT 物联网', + icon: 'lucide:cpu', + keepAlive: true, + hideInMenu: true, + }, + children: [ + { + path: 'product/detail/:id', + name: 'IoTProductDetail', + meta: { + title: '产品详情', + activePath: '/iot/device/product', + }, + component: () => import('#/views/iot/product/product/modules/detail/index.vue'), + }, + { + path: 'device/detail/:id', + name: 'IoTDeviceDetail', + meta: { + title: '设备详情', + activePath: '/iot/device/device', + }, + component: () => import('#/views/iot/device/device/modules/detail/index.vue'), + }, + ], + }, +]; + +export default routes; + diff --git a/apps/web-antd/src/views/iot/alert/config/data.ts b/apps/web-antd/src/views/iot/alert/config/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..09ca905c7734db069aaf3a790926c407627b3fac --- /dev/null +++ b/apps/web-antd/src/views/iot/alert/config/data.ts @@ -0,0 +1,196 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleRuleSceneList } from '#/api/iot/rule/scene'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改告警配置的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '配置名称', + component: 'Input', + componentProps: { + placeholder: '请输入配置名称', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '配置描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入配置描述', + rows: 3, + }, + }, + { + fieldName: 'level', + label: '告警级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_ALERT_LEVEL, 'number'), + placeholder: '请选择告警级别', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '配置状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + { + fieldName: 'sceneRuleIds', + label: '关联场景联动规则', + component: 'ApiSelect', + componentProps: { + api: getSimpleRuleSceneList, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择关联的场景联动规则', + }, + rules: 'required', + }, + { + fieldName: 'receiveUserIds', + label: '接收的用户', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择接收的用户', + }, + rules: 'required', + }, + { + fieldName: 'receiveTypes', + label: '接收类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_ALERT_RECEIVE_TYPE, 'number'), + mode: 'multiple', + placeholder: '请选择接收类型', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '配置名称', + component: 'Input', + componentProps: { + placeholder: '请输入配置名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '配置状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择配置状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '配置编号', + minWidth: 80, + }, + { + field: 'name', + title: '配置名称', + minWidth: 150, + }, + { + field: 'description', + title: '配置描述', + minWidth: 200, + }, + { + field: 'level', + title: '告警级别', + minWidth: 100, + slots: { default: 'level' }, + }, + { + field: 'status', + title: '配置状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'sceneRuleIds', + title: '关联场景联动规则', + minWidth: 150, + slots: { default: 'sceneRules' }, + }, + { + field: 'receiveUserNames', + title: '接收人', + minWidth: 150, + }, + { + field: 'receiveTypes', + title: '接收类型', + minWidth: 150, + slots: { default: 'receiveTypes' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/iot/alert/config/index.vue b/apps/web-antd/src/views/iot/alert/config/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..bae250661cec61a10b8838fde22c6e0c56f9f31d --- /dev/null +++ b/apps/web-antd/src/views/iot/alert/config/index.vue @@ -0,0 +1,187 @@ + + + diff --git a/apps/web-antd/src/views/iot/alert/modules/AlertConfigForm.vue b/apps/web-antd/src/views/iot/alert/modules/AlertConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..af90da10a7e5ac9c2d52285df0b546c0633917d4 --- /dev/null +++ b/apps/web-antd/src/views/iot/alert/modules/AlertConfigForm.vue @@ -0,0 +1,94 @@ + + + diff --git a/apps/web-antd/src/views/iot/alert/record/data.ts b/apps/web-antd/src/views/iot/alert/record/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..10ebb12f3907ba4895afeba982b5a65b414e2fc0 --- /dev/null +++ b/apps/web-antd/src/views/iot/alert/record/data.ts @@ -0,0 +1,148 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleAlertConfigList } from '#/api/iot/alert/config'; +import { getSimpleDeviceList } from '#/api/iot/device/device'; +import { getSimpleProductList } from '#/api/iot/product/product'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'configId', + label: '告警配置', + component: 'ApiSelect', + componentProps: { + api: getSimpleAlertConfigList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择告警配置', + allowClear: true, + showSearch: true, + }, + }, + { + fieldName: 'configLevel', + label: '告警级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_ALERT_LEVEL, 'number'), + placeholder: '请选择告警级别', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + }, + }, + { + fieldName: 'deviceId', + label: '设备', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceList, + labelField: 'deviceName', + valueField: 'id', + placeholder: '请选择设备', + allowClear: true, + showSearch: true, + }, + }, + { + fieldName: 'processStatus', + label: '是否处理', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING), + placeholder: '请选择是否处理', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '记录编号', + minWidth: 80, + }, + { + field: 'configName', + title: '告警名称', + minWidth: 150, + }, + { + field: 'configLevel', + title: '告警级别', + minWidth: 100, + slots: { default: 'configLevel' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 120, + slots: { default: 'product' }, + }, + { + field: 'deviceId', + title: '设备名称', + minWidth: 120, + slots: { default: 'device' }, + }, + { + field: 'deviceMessage', + title: '触发的设备消息', + minWidth: 150, + slots: { default: 'deviceMessage' }, + }, + { + field: 'processStatus', + title: '是否处理', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'processRemark', + title: '处理结果', + minWidth: 150, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 100, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/iot/alert/record/index.vue b/apps/web-antd/src/views/iot/alert/record/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..0cdb9b513612f81f54d5fb234e227a624dca940f --- /dev/null +++ b/apps/web-antd/src/views/iot/alert/record/index.vue @@ -0,0 +1,239 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/device/data.ts b/apps/web-antd/src/views/iot/device/device/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..903c1d9f0c26fe70b1df34d412ece97f38d81443 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/data.ts @@ -0,0 +1,321 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleDeviceGroupList } from '#/api/iot/device/group'; +import { DeviceTypeEnum, getSimpleProductList } from '#/api/iot/product/product'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + }, + rules: 'required', + }, + { + fieldName: 'deviceName', + label: 'DeviceName', + component: 'Input', + componentProps: { + placeholder: '请输入 DeviceName', + }, + rules: z + .string() + .min(4, 'DeviceName 长度不能少于 4 个字符') + .max(32, 'DeviceName 长度不能超过 32 个字符') + .regex( + /^[a-zA-Z0-9_.\-:@]{4,32}$/, + '支持英文字母、数字、下划线(_)、中划线(-)、点号(.)、半角冒号(:)和特殊字符@', + ), + }, + { + fieldName: 'gatewayId', + label: '网关设备', + component: 'ApiSelect', + componentProps: { + api: async () => { + const { getSimpleDeviceList } = await import( + '#/api/iot/device/device' + ); + return getSimpleDeviceList(DeviceTypeEnum.GATEWAY); + }, + labelField: 'nickname', + valueField: 'id', + placeholder: '子设备可选择父设备', + }, + dependencies: { + triggerFields: ['deviceType'], + show: (values) => values.deviceType === 1, // GATEWAY_SUB + }, + }, + { + fieldName: 'nickname', + label: '备注名称', + component: 'Input', + componentProps: { + placeholder: '请输入备注名称', + }, + rules: z + .string() + .min(4, '备注名称长度限制为 4~64 个字符') + .max(64, '备注名称长度限制为 4~64 个字符') + .regex( + /^[\u4e00-\u9fa5\u3040-\u30ff_a-zA-Z0-9]+$/, + '备注名称只能包含中文、英文字母、日文、数字和下划线(_)', + ) + .optional() + .or(z.literal('')), + }, + { + fieldName: 'groupIds', + label: '设备分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceGroupList, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择设备分组', + }, + }, + { + fieldName: 'serialNumber', + label: '设备序列号', + component: 'Input', + componentProps: { + placeholder: '请输入设备序列号', + }, + rules: z + .string() + .regex(/^[a-zA-Z0-9-_]+$/, '序列号只能包含字母、数字、中划线和下划线') + .optional() + .or(z.literal('')), + }, + // { + // fieldName: 'locationType', + // label: '定位类型', + // component: 'RadioGroup', + // componentProps: { + // options: getDictOptions(DICT_TYPE.IOT_LOCATION_TYPE, 'number'), + // buttonStyle: 'solid', + // optionType: 'button', + // }, + // }, + { + fieldName: 'longitude', + label: '设备经度', + component: 'InputNumber', + componentProps: { + placeholder: '请输入设备经度', + class: 'w-full', + }, + dependencies: { + triggerFields: ['locationType'], + show: (values) => values.locationType === 3, // MANUAL + }, + }, + { + fieldName: 'latitude', + label: '设备维度', + component: 'InputNumber', + componentProps: { + placeholder: '请输入设备维度', + class: 'w-full', + }, + dependencies: { + triggerFields: ['locationType'], + show: (values) => values.locationType === 3, // MANUAL + }, + }, + ]; +} + +/** 设备分组表单 */ +export function useGroupFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'groupIds', + label: '设备分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceGroupList, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择设备分组', + }, + rules: 'required', + }, + ]; +} + +/** 设备导入表单 */ +export function useImportFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'file', + label: '设备数据', + component: 'Upload', + rules: 'required', + help: '仅允许导入 xls、xlsx 格式文件', + }, + { + fieldName: 'updateSupport', + label: '是否覆盖', + component: 'Switch', + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + rules: z.boolean().default(false), + help: '是否更新已经存在的设备数据', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + allowClear: true, + }, + }, + { + fieldName: 'deviceName', + label: 'DeviceName', + component: 'Input', + componentProps: { + placeholder: '请输入 DeviceName', + allowClear: true, + }, + }, + { + fieldName: 'nickname', + label: '备注名称', + component: 'Input', + componentProps: { + placeholder: '请输入备注名称', + allowClear: true, + }, + }, + { + fieldName: 'deviceType', + label: '设备类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number'), + placeholder: '请选择设备类型', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '设备状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_DEVICE_STATUS, 'number'), + placeholder: '请选择设备状态', + allowClear: true, + }, + }, + { + fieldName: 'groupId', + label: '设备分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeviceGroupList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择设备分组', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'deviceName', + title: 'DeviceName', + minWidth: 150, + }, + { + field: 'nickname', + title: '备注名称', + minWidth: 120, + }, + { + field: 'productId', + title: '所属产品', + minWidth: 120, + slots: { default: 'product' }, + }, + { + field: 'deviceType', + title: '设备类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE }, + }, + }, + { + field: 'groupIds', + title: '所属分组', + minWidth: 150, + slots: { default: 'groups' }, + }, + { + field: 'status', + title: '设备状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_DEVICE_STATUS }, + }, + }, + { + field: 'onlineTime', + title: '最后上线时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + diff --git a/apps/web-antd/src/views/iot/device/device/index.vue b/apps/web-antd/src/views/iot/device/device/index.vue index 04fad976beb18282ac2096ac16b278fdc9d45cee..5d694f3cbb4072c37ba287fea1ea4bd4665d4f03 100644 --- a/apps/web-antd/src/views/iot/device/device/index.vue +++ b/apps/web-antd/src/views/iot/device/device/index.vue @@ -1,28 +1,469 @@ - + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/DeviceCardView.vue b/apps/web-antd/src/views/iot/device/device/modules/DeviceCardView.vue new file mode 100644 index 0000000000000000000000000000000000000000..924d6d7db0b570ad404589fdfdbfe6c0df40d47d --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/DeviceCardView.vue @@ -0,0 +1,473 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/DeviceForm.vue b/apps/web-antd/src/views/iot/device/device/modules/DeviceForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..1e811afa0774afab9ffb9d5bd0e456f915f6438c --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/DeviceForm.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/DeviceGroupForm.vue b/apps/web-antd/src/views/iot/device/device/modules/DeviceGroupForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..cf091a79ecce1b0caea7b3cd238d0a12fd9e47d2 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/DeviceGroupForm.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/DeviceImportForm.vue b/apps/web-antd/src/views/iot/device/device/modules/DeviceImportForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..9d12d3228ae68322970e8979b1ef25e8516ad17e --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/DeviceImportForm.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/components/DeviceTableSelect.vue b/apps/web-antd/src/views/iot/device/device/modules/components/DeviceTableSelect.vue new file mode 100644 index 0000000000000000000000000000000000000000..9e73072c24b3993597294c83c9edd8ecb4ecdbe9 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/components/DeviceTableSelect.vue @@ -0,0 +1,370 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailConfig.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..167380ffbb5242e4ee5e49cf9b6f9542d2196f7f --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailConfig.vue @@ -0,0 +1,140 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsHeader.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsHeader.vue new file mode 100644 index 0000000000000000000000000000000000000000..2465707eab3a95c20aca608f0cd2c4a64886b2fa --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsHeader.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsInfo.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsInfo.vue new file mode 100644 index 0000000000000000000000000000000000000000..3d758332bf450efc94bad28d9a94a8702660a4d6 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsInfo.vue @@ -0,0 +1,179 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsMessage.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsMessage.vue new file mode 100644 index 0000000000000000000000000000000000000000..86e55975da634c62f4fb7f23528c092657fff9aa --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsMessage.vue @@ -0,0 +1,232 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsSimulator.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsSimulator.vue new file mode 100644 index 0000000000000000000000000000000000000000..f6c9a1292a7d9b09b9c9ff11fa09e3ecde3f1075 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsSimulator.vue @@ -0,0 +1,485 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModel.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModel.vue new file mode 100644 index 0000000000000000000000000000000000000000..fdd8d85a2f0866dc475bc8b3ea3d80eb0b95446a --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModel.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelEvent.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelEvent.vue new file mode 100644 index 0000000000000000000000000000000000000000..dd2997ac408b69f62c7955fb80e80b689eaad845 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelEvent.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelProperty.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelProperty.vue new file mode 100644 index 0000000000000000000000000000000000000000..67b804cbeb3c3c0c1e28e68811b30f1cd6f9ad97 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelProperty.vue @@ -0,0 +1,243 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelPropertyHistory.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelPropertyHistory.vue new file mode 100644 index 0000000000000000000000000000000000000000..fa3dadfa3fc60e6ee8e7a11336323d3c8a9f7499 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelPropertyHistory.vue @@ -0,0 +1,488 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelService.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelService.vue new file mode 100644 index 0000000000000000000000000000000000000000..e91ca8b3b28e39cf3e2d00d05161fb9463026c29 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/DeviceDetailsThingModelService.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/index.vue b/apps/web-antd/src/views/iot/device/device/modules/detail/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..89812ea98941b5b0426632f2dbbec7de0c199414 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/modules/detail/index.vue @@ -0,0 +1,114 @@ + + + diff --git a/apps/web-antd/src/views/iot/device/group/data.ts b/apps/web-antd/src/views/iot/device/group/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..277256c6d40b0ff64cf36e8156067efa01e23850 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/group/data.ts @@ -0,0 +1,114 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +import { z } from '#/adapter/form'; +import { getSimpleDeviceGroupList } from '#/api/iot/device/group'; + +/** 新增/修改设备分组的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '分组名称', + component: 'Input', + componentProps: { + placeholder: '请输入分组名称', + }, + rules: z + .string() + .min(1, '分组名称不能为空') + .max(64, '分组名称长度不能超过 64 个字符'), + }, + { + fieldName: 'parentId', + label: '父级分组', + component: 'ApiTreeSelect', + componentProps: { + api: getSimpleDeviceGroupList, + fieldNames: { + label: 'name', + value: 'id', + }, + placeholder: '请选择父级分组', + allowClear: true, + }, + }, + { + fieldName: 'description', + label: '分组描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入分组描述', + rows: 3, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分组名称', + component: 'Input', + componentProps: { + placeholder: '请输入分组名称', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '分组名称', + minWidth: 200, + treeNode: true, + }, + { + field: 'description', + title: '分组描述', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + width: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'deviceCount', + title: '设备数量', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/iot/device/group/index.vue b/apps/web-antd/src/views/iot/device/group/index.vue index d21c2c5a8745e555c3ce9fb82bf9b36cffaf70a9..7bbb32e4c1da34fdea8098966f82370644faa9ec 100644 --- a/apps/web-antd/src/views/iot/device/group/index.vue +++ b/apps/web-antd/src/views/iot/device/group/index.vue @@ -1,28 +1,141 @@ - diff --git a/apps/web-antd/src/views/iot/device/group/modules/device-group-form.vue b/apps/web-antd/src/views/iot/device/group/modules/device-group-form.vue new file mode 100644 index 0000000000000000000000000000000000000000..0f9ee29f70993a39bbe277d122842c47401ab472 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/group/modules/device-group-form.vue @@ -0,0 +1,101 @@ + + + diff --git a/apps/web-antd/src/views/iot/home/chartOptions.ts b/apps/web-antd/src/views/iot/home/chartOptions.ts new file mode 100644 index 0000000000000000000000000000000000000000..556766624e3852e0950f67cae41a3d89c78526b3 --- /dev/null +++ b/apps/web-antd/src/views/iot/home/chartOptions.ts @@ -0,0 +1,93 @@ +/** + * 设备数量饼图配置 + */ +export function getDeviceCountChartOptions(productCategoryDeviceCounts: Record): any { + const data = Object.entries(productCategoryDeviceCounts).map( + ([name, value]) => ({ name, value }) + ); + + return { + tooltip: { + trigger: 'item', + formatter: '{b}: {c} 个 ({d}%)', + }, + legend: { + top: '5%', + right: '10%', + orient: 'vertical', + }, + series: [ + { + name: '设备数量', + type: 'pie', + radius: ['50%', '80%'], + center: ['30%', '50%'], + data: data, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, + }, + label: { + show: true, + formatter: '{b}: {c}', + }, + }, + ], + }; +} + +/** + * 仪表盘图表配置 + */ +export function getGaugeChartOptions(value: number, max: number, color: string, title: string): any { + return { + series: [ + { + type: 'gauge', + startAngle: 180, + endAngle: 0, + min: 0, + max: max, + center: ['50%', '70%'], + radius: '120%', + progress: { + show: true, + width: 12, + itemStyle: { + color: color, + }, + }, + axisLine: { + lineStyle: { + width: 12, + color: [[1, '#E5E7EB']], + }, + }, + axisTick: { show: false }, + splitLine: { show: false }, + axisLabel: { show: false }, + pointer: { show: false }, + detail: { + valueAnimation: true, + fontSize: 24, + fontWeight: 'bold', + color: color, + offsetCenter: [0, '-20%'], + formatter: '{value}', + }, + title: { + show: true, + offsetCenter: [0, '20%'], + fontSize: 14, + color: '#666', + }, + data: [{ value: value, name: title }], + }, + ], + }; +} + + diff --git a/apps/web-antd/src/views/iot/home/data.ts b/apps/web-antd/src/views/iot/home/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..320c3fb3feded88dda56b8b8ab89775de4ffc264 --- /dev/null +++ b/apps/web-antd/src/views/iot/home/data.ts @@ -0,0 +1,114 @@ +/** + * IoT 首页数据配置文件 + * + * 该文件封装了 IoT 首页所需的: + * - 统计数据接口定义 + * - 业务逻辑函数 + * - 工具函数 + */ + +import { ref, onMounted } from 'vue'; + +import type { IotStatisticsApi } from '#/api/iot/statistics'; + +import { getStatisticsSummary } from '#/api/iot/statistics'; + +/** 统计数据接口 - 使用 API 定义的类型 */ +export type StatsData = IotStatisticsApi.StatisticsSummary; + +/** 默认统计数据 */ +export const defaultStatsData: StatsData = { + productCategoryCount: 0, + productCount: 0, + deviceCount: 0, + deviceMessageCount: 0, + productCategoryTodayCount: 0, + productTodayCount: 0, + deviceTodayCount: 0, + deviceMessageTodayCount: 0, + deviceOnlineCount: 0, + deviceOfflineCount: 0, + deviceInactiveCount: 0, + productCategoryDeviceCounts: {}, +}; + +/** + * 加载统计数据 + * @returns Promise + */ +export async function loadStatisticsData(): Promise { + try { + const data = await getStatisticsSummary(); + return data; + } catch (error) { + console.error('获取统计数据出错:', error); + console.warn('使用 Mock 数据,请检查后端接口是否已实现'); + + // 返回 Mock 数据用于开发调试 + return { + productCategoryCount: 12, + productCount: 45, + deviceCount: 328, + deviceMessageCount: 15678, + productCategoryTodayCount: 2, + productTodayCount: 5, + deviceTodayCount: 23, + deviceMessageTodayCount: 1234, + deviceOnlineCount: 256, + deviceOfflineCount: 48, + deviceInactiveCount: 24, + productCategoryDeviceCounts: { + '智能家居': 120, + '工业设备': 98, + '环境监测': 65, + '智能穿戴': 45, + }, + }; + } +} + +/** + * IoT 首页业务逻辑 Hook + * 封装了首页的所有业务逻辑和状态管理 + */ +export function useIotHome() { + const loading = ref(true); + const statsData = ref(defaultStatsData); + + /** + * 加载数据 + */ + async function loadData() { + loading.value = true; + try { + statsData.value = await loadStatisticsData(); + } catch (error) { + console.error('获取统计数据出错:', error); + } finally { + loading.value = false; + } + } + + // 组件挂载时加载数据 + onMounted(() => { + loadData(); + }); + + return { + loading, + statsData, + loadData, + }; +} + +/** 格式化数字 - 大数字显示为 K/M */ +export const formatNumber = (num: number): string => { + if (num >= 1000000) { + return (num / 1000000).toFixed(1) + 'M'; + } + if (num >= 1000) { + return (num / 1000).toFixed(1) + 'K'; + } + return num.toString(); +}; + diff --git a/apps/web-antd/src/views/iot/home/index.vue b/apps/web-antd/src/views/iot/home/index.vue index 10705d5afd9ae9eeb2013fa77c427e77afb12828..d52e02a08fa440aeda9be17dbfafd65e8f21900c 100644 --- a/apps/web-antd/src/views/iot/home/index.vue +++ b/apps/web-antd/src/views/iot/home/index.vue @@ -1,28 +1,89 @@ - + + diff --git a/apps/web-antd/src/views/iot/home/modules/ComparisonCard.vue b/apps/web-antd/src/views/iot/home/modules/ComparisonCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..eadc6c714e425f818f84cd7cf603e19d36b0b66a --- /dev/null +++ b/apps/web-antd/src/views/iot/home/modules/ComparisonCard.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/home/modules/DeviceCountCard.vue b/apps/web-antd/src/views/iot/home/modules/DeviceCountCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..5005a32b13c032f2a827979e5bf436b53474fd95 --- /dev/null +++ b/apps/web-antd/src/views/iot/home/modules/DeviceCountCard.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/home/modules/DeviceStateCountCard.vue b/apps/web-antd/src/views/iot/home/modules/DeviceStateCountCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..334b7f6ea23772812f140e17aaaee8640c06872c --- /dev/null +++ b/apps/web-antd/src/views/iot/home/modules/DeviceStateCountCard.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/home/modules/MessageTrendCard.vue b/apps/web-antd/src/views/iot/home/modules/MessageTrendCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..27b9ca5e0a125024dc82784602388d769a948d3c --- /dev/null +++ b/apps/web-antd/src/views/iot/home/modules/MessageTrendCard.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/ota/data.ts b/apps/web-antd/src/views/iot/ota/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbae1bde0d9ae97cb6c936ac52d452b23757a891 --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/data.ts @@ -0,0 +1,170 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getSimpleProductList } from '#/api/iot/product/product'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改固件的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '固件名称', + component: 'Input', + componentProps: { + placeholder: '请输入固件名称', + }, + rules: 'required', + }, + { + fieldName: 'productId', + label: '所属产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + }, + rules: 'required', + }, + { + fieldName: 'version', + label: '版本号', + component: 'Input', + componentProps: { + placeholder: '请输入版本号', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '固件描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入固件描述', + rows: 3, + }, + }, + { + fieldName: 'fileUrl', + label: '固件文件', + component: 'Upload', + componentProps: { + maxCount: 1, + accept: '.bin,.hex,.zip', + }, + rules: 'required', + help: '支持上传 .bin、.hex、.zip 格式的固件文件', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '固件名称', + component: 'Input', + componentProps: { + placeholder: '请输入固件名称', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'id', + title: '固件编号', + width: 100, + }, + { + field: 'name', + title: '固件名称', + minWidth: 150, + }, + { + field: 'version', + title: '版本号', + width: 120, + }, + { + field: 'productName', + title: '所属产品', + minWidth: 150, + }, + { + field: 'description', + title: '固件描述', + minWidth: 200, + showOverflow: 'tooltip', + }, + { + field: 'fileSize', + title: '文件大小', + width: 120, + formatter: ({ cellValue }) => { + if (!cellValue) return '-'; + const kb = cellValue / 1024; + if (kb < 1024) return `${kb.toFixed(2)} KB`; + return `${(kb / 1024).toFixed(2)} MB`; + }, + }, + { + field: 'status', + title: '状态', + width: 100, + formatter: ({ cellValue }) => { + return cellValue === 1 ? '启用' : '禁用'; + }, + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/iot/ota/firmware/data.ts b/apps/web-antd/src/views/iot/ota/firmware/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d93d2166271e87bca7f4926d58666d610bbe5c1 --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/firmware/data.ts @@ -0,0 +1,207 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware'; + +import { message } from 'ant-design-vue'; + +import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware'; +import { getSimpleProductList } from '#/api/iot/product/product'; +import { $t } from '#/locales'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改固件的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '固件名称', + component: 'Input', + componentProps: { + placeholder: '请输入固件名称', + }, + rules: 'required', + }, + { + fieldName: 'productId', + label: '所属产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + }, + rules: 'required', + }, + { + fieldName: 'version', + label: '版本号', + component: 'Input', + componentProps: { + placeholder: '请输入版本号', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '固件描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入固件描述', + rows: 3, + }, + }, + { + fieldName: 'fileUrl', + label: '固件文件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + accept: ['bin', 'hex', 'zip'], + maxSize: 50, + helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '固件名称', + component: 'Input', + componentProps: { + placeholder: '请输入固件名称', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '固件编号', + minWidth: 80, + }, + { + field: 'name', + title: '固件名称', + minWidth: 150, + }, + { + field: 'version', + title: '版本号', + minWidth: 120, + }, + { + field: 'description', + title: '固件描述', + minWidth: 200, + }, + { + field: 'productId', + title: '所属产品', + minWidth: 150, + slots: { default: 'product' }, + }, + { + field: 'fileUrl', + title: '固件文件', + minWidth: 120, + slots: { default: 'fileUrl' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** Grid 配置项 */ +export function useGridOptions(): VxeTableGridOptions { + return { + columns: useGridColumns(), + height: 'auto', + keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page }, formValues) => { + return await getOtaFirmwarePage({ + pageNo: page.currentPage, + pageSize: page.pageSize, + ...formValues, + }); + }, + }, + }, + rowConfig: { + keyField: 'id', + isHover: true, + }, + toolbarConfig: { + refresh: true, + search: true, + }, + }; +} + +/** 删除固件 */ +export async function handleDeleteFirmware( + row: IoTOtaFirmwareApi.Firmware, + onSuccess: () => void, +) { + const hideLoading = message.loading({ + content: $t('ui.actionMessage.deleting', [row.name]), + duration: 0, + }); + try { + await deleteOtaFirmware(row.id as number); + message.success({ + content: $t('ui.actionMessage.deleteSuccess', [row.name]), + }); + onSuccess(); + } finally { + hideLoading(); + } +} diff --git a/apps/web-antd/src/views/iot/ota/firmware/index.vue b/apps/web-antd/src/views/iot/ota/firmware/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..9d1298bec8f8369aab608373c780fc5f287601bf --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/firmware/index.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-antd/src/views/iot/ota/index.vue b/apps/web-antd/src/views/iot/ota/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..21c3c10a5b23dfc4cabd2120015cca5f3bb9c698 --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/apps/web-antd/src/views/iot/ota/modules/OtaFirmwareForm.vue b/apps/web-antd/src/views/iot/ota/modules/OtaFirmwareForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..691c49070e514f7f3bf93717f256b0b467319ff7 --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/modules/OtaFirmwareForm.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-antd/src/views/iot/ota/modules/detail/index.vue b/apps/web-antd/src/views/iot/ota/modules/detail/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..579f53df6fbab95fdb393032de9101dcc87ac550 --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/modules/detail/index.vue @@ -0,0 +1,149 @@ + + + diff --git a/apps/web-antd/src/views/iot/ota/modules/firmware-detail/index.vue b/apps/web-antd/src/views/iot/ota/modules/firmware-detail/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..579f53df6fbab95fdb393032de9101dcc87ac550 --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/modules/firmware-detail/index.vue @@ -0,0 +1,149 @@ + + + diff --git a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskDetail.vue b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..74d8a6732697b7927fc1c356a276bc1b2e3cc5bc --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskDetail.vue @@ -0,0 +1,349 @@ + + + diff --git a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskForm.vue b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..468f28b962c72c572d6b3199438f1faaf67e9c2a --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskForm.vue @@ -0,0 +1,148 @@ + + + diff --git a/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskList.vue b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskList.vue new file mode 100644 index 0000000000000000000000000000000000000000..645446c510d23528eb4781d434ff6eb62b283134 --- /dev/null +++ b/apps/web-antd/src/views/iot/ota/modules/task/OtaTaskList.vue @@ -0,0 +1,242 @@ + + + diff --git a/apps/web-antd/src/views/iot/plugin/index.vue b/apps/web-antd/src/views/iot/plugin/index.vue index b23ae27191ed31f2da2643c242dabc56c2362c7d..cfaf8e752d809204cfed3f74f6d350d509180a64 100644 --- a/apps/web-antd/src/views/iot/plugin/index.vue +++ b/apps/web-antd/src/views/iot/plugin/index.vue @@ -1,28 +1,33 @@ diff --git a/apps/web-antd/src/views/iot/product/category/data.ts b/apps/web-antd/src/views/iot/product/category/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..63fefc2183952cc4c907da0f14e5f3da9ca7e9d3 --- /dev/null +++ b/apps/web-antd/src/views/iot/product/category/data.ts @@ -0,0 +1,191 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { IotProductCategoryApi } from '#/api/iot/product/category'; + +import { DICT_TYPE } from '@vben/constants'; +import { handleTree } from '@vben/utils'; + +import { message } from 'ant-design-vue'; + +import { z } from '#/adapter/form'; +import { + deleteProductCategory, + getProductCategoryPage, + getSimpleProductCategoryList +} from '#/api/iot/product/category'; +import { $t } from '#/locales'; + +/** 新增/修改产品分类的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '分类名称', + component: 'Input', + componentProps: { + placeholder: '请输入分类名称', + }, + rules: z + .string() + .min(1, '分类名称不能为空') + .max(64, '分类名称长度不能超过 64 个字符'), + }, + { + fieldName: 'parentId', + label: '父级分类', + component: 'ApiTreeSelect', + componentProps: { + api: getSimpleProductCategoryList, + fieldNames: { + label: 'name', + value: 'id', + }, + placeholder: '请选择父级分类', + allowClear: true, + }, + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + class: 'w-full', + min: 0, + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + defaultValue: 1, + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入分类描述', + rows: 3, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分类名称', + component: 'Input', + componentProps: { + placeholder: '请输入分类名称', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + placeholder: ['开始日期', '结束日期'], + allowClear: true, + class: 'w-full', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'seq', + title: 'ID', + width: 80, + }, + { + field: 'name', + title: '名字', + minWidth: 200, + treeNode: true, + }, + { + field: 'sort', + title: '排序', + width: 100, + }, + { + field: 'status', + title: '状态', + width: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'description', + title: '描述', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 删除分类 */ +export async function handleDeleteCategory(row: IotProductCategoryApi.ProductCategory, onSuccess?: () => void) { + const hideLoading = message.loading({ + content: $t('ui.actionMessage.deleting', [row.name]), + duration: 0, + }); + try { + await deleteProductCategory(row.id!); + message.success($t('ui.actionMessage.deleteSuccess', [row.name])); + onSuccess?.(); + } finally { + hideLoading(); + } +} + +/** 查询分类列表 */ +export async function queryProductCategoryList({ page }: any, formValues: any) { + const data = await getProductCategoryPage({ + pageNo: page.currentPage, + pageSize: page.pageSize, + ...formValues, + }); + // 转换为树形结构 + return { + ...data, + list: handleTree(data.list, 'id', 'parentId'), + }; +} diff --git a/apps/web-antd/src/views/iot/product/category/index.vue b/apps/web-antd/src/views/iot/product/category/index.vue index aa372022b89d6e19c95ac7c1bff0a59d77a2ef21..740eabc441aacdf0582ce72306ce885b1008e58a 100644 --- a/apps/web-antd/src/views/iot/product/category/index.vue +++ b/apps/web-antd/src/views/iot/product/category/index.vue @@ -1,28 +1,128 @@ diff --git a/apps/web-antd/src/views/iot/product/category/modules/ProductCategoryForm.vue b/apps/web-antd/src/views/iot/product/category/modules/ProductCategoryForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..b508b481e97a8c4b25152033e22f93e5f132480e --- /dev/null +++ b/apps/web-antd/src/views/iot/product/category/modules/ProductCategoryForm.vue @@ -0,0 +1,108 @@ + + + diff --git a/apps/web-antd/src/views/iot/product/product/data.ts b/apps/web-antd/src/views/iot/product/product/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e53f5d0fc36c459a0ed368d8246d46a60f05616 --- /dev/null +++ b/apps/web-antd/src/views/iot/product/product/data.ts @@ -0,0 +1,267 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { ref } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { downloadFileFromBlobPart } from '@vben/utils'; + +import { message } from 'ant-design-vue'; + +import { z } from '#/adapter/form'; +import { getSimpleProductCategoryList } from '#/api/iot/product/category'; +import { + deleteProduct, + exportProduct, + getProductPage +} from '#/api/iot/product/product'; + +/** 新增/修改产品的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '产品名称', + component: 'Input', + componentProps: { + placeholder: '请输入产品名称', + }, + rules: z + .string() + .min(1, '产品名称不能为空') + .max(64, '产品名称长度不能超过 64 个字符'), + }, + { + fieldName: 'categoryId', + label: '产品分类', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductCategoryList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品分类', + }, + rules: 'required', + }, + { + fieldName: 'deviceType', + label: '设备类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + { + fieldName: 'netType', + label: '联网方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_NET_TYPE, 'number'), + placeholder: '请选择联网方式', + }, + rules: 'required', + }, + { + fieldName: 'protocolType', + label: '接入协议', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'number'), + placeholder: '请选择接入协议', + }, + rules: 'required', + }, + { + fieldName: 'dataFormat', + label: '数据格式', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_DATA_FORMAT, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '产品描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入产品描述', + rows: 3, + }, + }, + { + fieldName: 'validateType', + label: '认证方式', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_VALIDATE_TYPE, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '产品状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '产品名称', + component: 'Input', + componentProps: { + placeholder: '请输入产品名称', + allowClear: true, + }, + }, + { + fieldName: 'productKey', + label: 'ProductKey', + component: 'Input', + componentProps: { + placeholder: '请输入产品标识', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: 'ID', + width: 80, + }, + { + field: 'productKey', + title: 'ProductKey', + minWidth: 150, + }, + { + field: 'categoryId', + title: '品类', + minWidth: 120, + slots: { default: 'category' }, + }, + { + field: 'deviceType', + title: '设备类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_PRODUCT_DEVICE_TYPE }, + }, + }, + { + field: 'icon', + title: '产品图标', + width: 100, + slots: { default: 'icon' }, + }, + { + field: 'picUrl', + title: '产品图片', + width: 100, + slots: { default: 'picUrl' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 加载产品分类列表 */ +export async function loadCategoryList() { + return await getSimpleProductCategoryList(); +} + +/** 获取分类名称 */ +export function getCategoryName(categoryList: any[], categoryId: number) { + const category = categoryList.find((c: any) => c.id === categoryId); + return category?.name || '未分类'; +} + +/** 删除产品 */ +export async function handleDeleteProduct(row: any, onSuccess?: () => void) { + const hideLoading = message.loading({ + content: `正在删除 ${row.name}...`, + duration: 0, + }); + try { + await deleteProduct(row.id); + message.success(`删除 ${row.name} 成功`); + onSuccess?.(); + } finally { + hideLoading(); + } +} + +/** 导出产品 */ +export async function handleExportProduct(searchParams: any) { + const data = await exportProduct(searchParams); + downloadFileFromBlobPart({ fileName: '产品列表.xls', source: data }); +} + +/** 查询产品列表 */ +export async function queryProductList({ page }: any, searchParams: any) { + return await getProductPage({ + pageNo: page.currentPage, + pageSize: page.pageSize, + ...searchParams, + }); +} + +/** 创建图片预览状态 */ +export function useImagePreview() { + const previewVisible = ref(false); + const previewImage = ref(''); + + function handlePreviewImage(url: string) { + previewImage.value = url; + previewVisible.value = true; + } + + return { + previewVisible, + previewImage, + handlePreviewImage, + }; +} diff --git a/apps/web-antd/src/views/iot/product/product/index.vue b/apps/web-antd/src/views/iot/product/product/index.vue index 1908600c115963a29401d36a6fb85dc43c1a9ed1..15f5ff540962447501548f6bb925a795061a1043 100644 --- a/apps/web-antd/src/views/iot/product/product/index.vue +++ b/apps/web-antd/src/views/iot/product/product/index.vue @@ -1,28 +1,340 @@ - + + + diff --git a/apps/web-antd/src/views/iot/product/product/modules/ProductCardView.vue b/apps/web-antd/src/views/iot/product/product/modules/ProductCardView.vue new file mode 100644 index 0000000000000000000000000000000000000000..eec04b2d84dfa3a2f62f1c7bbaa1322d2df8d6bf --- /dev/null +++ b/apps/web-antd/src/views/iot/product/product/modules/ProductCardView.vue @@ -0,0 +1,377 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/product/product/modules/ProductForm.vue b/apps/web-antd/src/views/iot/product/product/modules/ProductForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..b792f2246106117c7b879a7b085a2dbc9e7d8714 --- /dev/null +++ b/apps/web-antd/src/views/iot/product/product/modules/ProductForm.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-antd/src/views/iot/product/product/modules/components/ProductTableSelect.vue b/apps/web-antd/src/views/iot/product/product/modules/components/ProductTableSelect.vue new file mode 100644 index 0000000000000000000000000000000000000000..fa577bdf17add5e64a806056aba6f40fdfd1c17c --- /dev/null +++ b/apps/web-antd/src/views/iot/product/product/modules/components/ProductTableSelect.vue @@ -0,0 +1,190 @@ + + + + diff --git a/apps/web-antd/src/views/iot/product/product/modules/detail/ProductDetailsHeader.vue b/apps/web-antd/src/views/iot/product/product/modules/detail/ProductDetailsHeader.vue new file mode 100644 index 0000000000000000000000000000000000000000..52a56229ac539de5b214d0e3ad46d21c679e88ca --- /dev/null +++ b/apps/web-antd/src/views/iot/product/product/modules/detail/ProductDetailsHeader.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/web-antd/src/views/iot/product/product/modules/detail/ProductDetailsInfo.vue b/apps/web-antd/src/views/iot/product/product/modules/detail/ProductDetailsInfo.vue new file mode 100644 index 0000000000000000000000000000000000000000..82145bdc7b0c3219a7a3b1d5a702d33677b3ecaa --- /dev/null +++ b/apps/web-antd/src/views/iot/product/product/modules/detail/ProductDetailsInfo.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web-antd/src/views/iot/product/product/modules/detail/index.vue b/apps/web-antd/src/views/iot/product/product/modules/detail/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..4bcf877f2e02927a23a3ac7ae0c7241c554f479f --- /dev/null +++ b/apps/web-antd/src/views/iot/product/product/modules/detail/index.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/data.ts b/apps/web-antd/src/views/iot/rule/data/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..bbe4d67a70c946123fba8f329222bfa1b460db69 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/data.ts @@ -0,0 +1,105 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleProductList } from '#/api/iot/product/product'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '规则名称', + component: 'Input', + componentProps: { + placeholder: '请输入规则名称', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + api: getSimpleProductList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择产品', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '规则状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '规则编号', + minWidth: 80, + }, + { + field: 'name', + title: '规则名称', + minWidth: 150, + }, + { + field: 'productId', + title: '所属产品', + minWidth: 150, + slots: { default: 'product' }, + }, + { + field: 'description', + title: '规则描述', + minWidth: 200, + }, + { + field: 'status', + title: '规则状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'sinkCount', + title: '数据流转数', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/iot/rule/data/index.vue b/apps/web-antd/src/views/iot/rule/data/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..17ff79c885c827938dda9dbad76a0b93b5b847ba --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/rule/DataRuleForm.vue b/apps/web-antd/src/views/iot/rule/data/rule/DataRuleForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..4c5c73a556db9bc443ac59eecba96e09825777b8 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/rule/DataRuleForm.vue @@ -0,0 +1,117 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/rule/components/SourceConfigForm.vue b/apps/web-antd/src/views/iot/rule/data/rule/components/SourceConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..da818fd0434726e8914c5865cbd69a05d33050a7 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/rule/components/SourceConfigForm.vue @@ -0,0 +1,304 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/rule/data.ts b/apps/web-antd/src/views/iot/rule/data/rule/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..a07777490e9a39bc2014e93e141db79ecec44457 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/rule/data.ts @@ -0,0 +1,148 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '规则名称', + component: 'Input', + componentProps: { + placeholder: '请输入规则名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '规则状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 规则表单 Schema */ +export function useRuleFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + show: false, + triggerFields: ['id'], + }, + }, + { + fieldName: 'name', + label: '规则名称', + component: 'Input', + componentProps: { + placeholder: '请输入规则名称', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '规则描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入规则描述', + rows: 3, + }, + }, + { + fieldName: 'status', + label: '规则状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + defaultValue: 0, + rules: 'required', + }, + { + fieldName: 'sinkIds', + label: '数据目的', + component: 'Select', + componentProps: { + placeholder: '请选择数据目的', + mode: 'multiple', + allowClear: true, + options: [], + }, + rules: 'required', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '规则编号', + minWidth: 80, + }, + { + field: 'name', + title: '规则名称', + minWidth: 150, + }, + { + field: 'description', + title: '规则描述', + minWidth: 200, + }, + { + field: 'status', + title: '规则状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'sourceConfigs', + title: '数据源', + minWidth: 100, + formatter: ({ cellValue }: any) => `${cellValue?.length || 0} 个`, + }, + { + field: 'sinkIds', + title: '数据目的', + minWidth: 100, + formatter: ({ cellValue }: any) => `${cellValue?.length || 0} 个`, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + diff --git a/apps/web-antd/src/views/iot/rule/data/rule/index.vue b/apps/web-antd/src/views/iot/rule/data/rule/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..5965647c695d56598527c5656bd4c68c759f2551 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/rule/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/DataSinkForm.vue b/apps/web-antd/src/views/iot/rule/data/sink/DataSinkForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..676fa11d690f112c4affeb94006e297228c6e4e7 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/DataSinkForm.vue @@ -0,0 +1,148 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/HttpConfigForm.vue b/apps/web-antd/src/views/iot/rule/data/sink/config/HttpConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..b29cae14626fe7e041c31f5c4ac3617fb9e3dccc --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/HttpConfigForm.vue @@ -0,0 +1,102 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/KafkaMQConfigForm.vue b/apps/web-antd/src/views/iot/rule/data/sink/config/KafkaMQConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..21c215f1d30c0fdba1a7bcd3f6e8f63cea2182d2 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/KafkaMQConfigForm.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/MqttConfigForm.vue b/apps/web-antd/src/views/iot/rule/data/sink/config/MqttConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..c49602e95236275d6d3cf1ffa9a2e1b3382c051c --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/MqttConfigForm.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/RabbitMQConfigForm.vue b/apps/web-antd/src/views/iot/rule/data/sink/config/RabbitMQConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..50e3da64ab658cce5a64caa52dcd665a7bde0b75 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/RabbitMQConfigForm.vue @@ -0,0 +1,74 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/RedisStreamConfigForm.vue b/apps/web-antd/src/views/iot/rule/data/sink/config/RedisStreamConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..9b146ce298dac3f1253c93092dd50a0423787881 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/RedisStreamConfigForm.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/RocketMQConfigForm.vue b/apps/web-antd/src/views/iot/rule/data/sink/config/RocketMQConfigForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..63263cecc11261ebfa86231d272d807181fec776 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/RocketMQConfigForm.vue @@ -0,0 +1,57 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue b/apps/web-antd/src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue new file mode 100644 index 0000000000000000000000000000000000000000..919a9289805108d1ddff498c2ee005d4ff3c5ad7 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/config/index.ts b/apps/web-antd/src/views/iot/rule/data/sink/config/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3927dc9446caf1ebbace50f14817357cebd8c52 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/config/index.ts @@ -0,0 +1,15 @@ +import HttpConfigForm from './HttpConfigForm.vue' +import MqttConfigForm from './MqttConfigForm.vue' +import RocketMQConfigForm from './RocketMQConfigForm.vue' +import KafkaMQConfigForm from './KafkaMQConfigForm.vue' +import RabbitMQConfigForm from './RabbitMQConfigForm.vue' +import RedisStreamConfigForm from './RedisStreamConfigForm.vue' + +export { + HttpConfigForm, + MqttConfigForm, + RocketMQConfigForm, + KafkaMQConfigForm, + RabbitMQConfigForm, + RedisStreamConfigForm +} diff --git a/apps/web-antd/src/views/iot/rule/data/sink/data.ts b/apps/web-antd/src/views/iot/rule/data/sink/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..65f83828d587fb54fcb144224b95c6d282e9383f --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/data.ts @@ -0,0 +1,153 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '目的名称', + component: 'Input', + componentProps: { + placeholder: '请输入目的名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '目的状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'type', + label: '目的类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_DATA_SINK_TYPE_ENUM, 'number'), + placeholder: '请选择目的类型', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 目的表单 Schema */ +export function useSinkFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + show: false, + triggerFields: ['id'], + }, + }, + { + fieldName: 'name', + label: '目的名称', + component: 'Input', + componentProps: { + placeholder: '请输入目的名称', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '目的描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入目的描述', + rows: 3, + }, + }, + { + fieldName: 'type', + label: '目的类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_DATA_SINK_TYPE_ENUM, 'number'), + placeholder: '请选择目的类型', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '目的状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + defaultValue: 0, + rules: 'required', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '目的编号', + minWidth: 80, + }, + { + field: 'name', + title: '目的名称', + minWidth: 150, + }, + { + field: 'description', + title: '目的描述', + minWidth: 200, + }, + { + field: 'status', + title: '目的状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'type', + title: '目的类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_DATA_SINK_TYPE_ENUM }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + diff --git a/apps/web-antd/src/views/iot/rule/data/sink/index.vue b/apps/web-antd/src/views/iot/rule/data/sink/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..2cab535366061d3e44050844b25590d368d18797 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/data/sink/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/databridge/index.vue b/apps/web-antd/src/views/iot/rule/databridge/index.vue index b92d8cd7fa934b238bde0bddf45452ddf62d7fc0..5bd7ebf7982ff18cc078dc7cf55ed2254982144e 100644 --- a/apps/web-antd/src/views/iot/rule/databridge/index.vue +++ b/apps/web-antd/src/views/iot/rule/databridge/index.vue @@ -1,28 +1,46 @@ diff --git a/apps/web-antd/src/views/iot/rule/scene/data.ts b/apps/web-antd/src/views/iot/rule/scene/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..743c533945d8f25373020a78c210704b409ca1e7 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/data.ts @@ -0,0 +1,136 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '规则名称', + rules: 'required', + componentProps: { + placeholder: '请输入规则名称', + }, + }, + { + fieldName: 'status', + label: '规则状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'description', + label: '规则描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入规则描述', + rows: 3, + }, + formItemClass: 'col-span-2', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '规则名称', + component: 'Input', + componentProps: { + placeholder: '请输入规则名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '规则状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: getRangePickerDefaultProps(), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '规则编号', + minWidth: 80, + }, + { + field: 'name', + title: '规则名称', + minWidth: 150, + }, + { + field: 'description', + title: '规则描述', + minWidth: 200, + }, + { + field: 'status', + title: '规则状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'actionCount', + title: '执行动作数', + minWidth: 100, + }, + { + field: 'executeCount', + title: '执行次数', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/iot/rule/scene/form/RuleSceneForm.vue b/apps/web-antd/src/views/iot/rule/scene/form/RuleSceneForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..62ed8e6cb9bdad8d6ba0e2dd446aa475830629bc --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/RuleSceneForm.vue @@ -0,0 +1,330 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/configs/AlertConfig.vue b/apps/web-antd/src/views/iot/rule/scene/form/configs/AlertConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..bb6d393b9eded73efdc0b04f7d992b91a3fcce18 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/configs/AlertConfig.vue @@ -0,0 +1,81 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/configs/ConditionConfig.vue b/apps/web-antd/src/views/iot/rule/scene/form/configs/ConditionConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..7f904e61890ea60526e00ffd6abcd6c189636b78 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/configs/ConditionConfig.vue @@ -0,0 +1,301 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/configs/CurrentTimeConditionConfig.vue b/apps/web-antd/src/views/iot/rule/scene/form/configs/CurrentTimeConditionConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..92b64223a087c943b5b8e2246f6a46669485a7fb --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/configs/CurrentTimeConditionConfig.vue @@ -0,0 +1,234 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue b/apps/web-antd/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..2b18ccee1fb8d97b5594a2deb6806bdff9ab0550 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue @@ -0,0 +1,376 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue b/apps/web-antd/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..b458ec80ee988939bf020cf173437f5dbe64b036 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/configs/DeviceTriggerConfig.vue @@ -0,0 +1,251 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue b/apps/web-antd/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..35c811792409d11c11ab6c569410398480a06372 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/configs/MainConditionInnerConfig.vue @@ -0,0 +1,340 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue b/apps/web-antd/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..ace2519323edfb6c46b1c1986233efee9fba5f53 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/configs/SubConditionGroupConfig.vue @@ -0,0 +1,156 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue b/apps/web-antd/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue new file mode 100644 index 0000000000000000000000000000000000000000..344d283d6958cfe4e5994feac604430b70a57f2e --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/inputs/JsonParamsInput.vue @@ -0,0 +1,519 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/inputs/ValueInput.vue b/apps/web-antd/src/views/iot/rule/scene/form/inputs/ValueInput.vue new file mode 100644 index 0000000000000000000000000000000000000000..0ef0fddfcc357b6549208198d8c976e413965ca2 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/inputs/ValueInput.vue @@ -0,0 +1,266 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/sections/ActionSection.vue b/apps/web-antd/src/views/iot/rule/scene/form/sections/ActionSection.vue new file mode 100644 index 0000000000000000000000000000000000000000..95b3b201e0965b02c7d5b8005f878f04d16fd7fc --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/sections/ActionSection.vue @@ -0,0 +1,272 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/sections/BasicInfoSection.vue b/apps/web-antd/src/views/iot/rule/scene/form/sections/BasicInfoSection.vue new file mode 100644 index 0000000000000000000000000000000000000000..b115f0a4fa55189c0b561d74baca199e6fe8779b --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/sections/BasicInfoSection.vue @@ -0,0 +1,86 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/sections/TriggerSection.vue b/apps/web-antd/src/views/iot/rule/scene/form/sections/TriggerSection.vue new file mode 100644 index 0000000000000000000000000000000000000000..fdec510d0fa0c5084e822333fac680b17676d23b --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/sections/TriggerSection.vue @@ -0,0 +1,222 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/selectors/DeviceSelector.vue b/apps/web-antd/src/views/iot/rule/scene/form/selectors/DeviceSelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..bdaa1aff4728943a09367ca4861d27270df3a726 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/selectors/DeviceSelector.vue @@ -0,0 +1,103 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/selectors/OperatorSelector.vue b/apps/web-antd/src/views/iot/rule/scene/form/selectors/OperatorSelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..41957fa1027adf854db4aea85794804a0ede1c09 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/selectors/OperatorSelector.vue @@ -0,0 +1,264 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/selectors/ProductSelector.vue b/apps/web-antd/src/views/iot/rule/scene/form/selectors/ProductSelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..c54872701d23edbd8be3430889dfdb3cde11f768 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/selectors/ProductSelector.vue @@ -0,0 +1,79 @@ + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/form/selectors/PropertySelector.vue b/apps/web-antd/src/views/iot/rule/scene/form/selectors/PropertySelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..43abb6ba1137608ec129edf6b79bc54eb1e93d2e --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/form/selectors/PropertySelector.vue @@ -0,0 +1,437 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/index.vue b/apps/web-antd/src/views/iot/rule/scene/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..6e424e38926abdec83c72fabc87ccab14e570b92 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/index.vue @@ -0,0 +1,156 @@ + + + diff --git a/apps/web-antd/src/views/iot/rule/scene/modules/form.vue b/apps/web-antd/src/views/iot/rule/scene/modules/form.vue new file mode 100644 index 0000000000000000000000000000000000000000..a40cfcf8d53e65dd3b9acde015c5253982f22bd7 --- /dev/null +++ b/apps/web-antd/src/views/iot/rule/scene/modules/form.vue @@ -0,0 +1,86 @@ + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/data.ts b/apps/web-antd/src/views/iot/thingmodel/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd3a079e560e06842d5237e48ddc90874909486f --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/data.ts @@ -0,0 +1,65 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'type', + label: '功能类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE, 'number'), + placeholder: '请选择功能类型', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'type', + title: '功能类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.IOT_THING_MODEL_TYPE }, + }, + }, + { + field: 'name', + title: '功能名称', + minWidth: 150, + }, + { + field: 'identifier', + title: '标识符', + minWidth: 150, + }, + { + field: 'dataType', + title: '数据类型', + minWidth: 120, + slots: { default: 'dataType' }, + }, + { + field: 'dataDefinition', + title: '数据定义', + minWidth: 200, + slots: { default: 'dataDefinition' }, + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/iot/thingmodel/index.vue b/apps/web-antd/src/views/iot/thingmodel/index.vue index b283703aab847b7b1e761966e4fa87279b450e38..b941996d0e540d96812bba3e6bc54fe84aa4a053 100644 --- a/apps/web-antd/src/views/iot/thingmodel/index.vue +++ b/apps/web-antd/src/views/iot/thingmodel/index.vue @@ -1,28 +1,117 @@ - diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelEvent.vue b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelEvent.vue new file mode 100644 index 0000000000000000000000000000000000000000..d7ed21543d22ba3e8b7b08a3b252aa2339709f61 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelEvent.vue @@ -0,0 +1,58 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelForm.vue b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..28779025e8bf21cda9dadd6f5613f16c63c6e81b --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelForm.vue @@ -0,0 +1,223 @@ + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelInputOutputParam.vue b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelInputOutputParam.vue new file mode 100644 index 0000000000000000000000000000000000000000..922625f598d1898330d62876e12562e065c6e9f3 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelInputOutputParam.vue @@ -0,0 +1,151 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelProperty.vue b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelProperty.vue new file mode 100644 index 0000000000000000000000000000000000000000..8c25f8425c8626b8b635f00843fb84f9f2e3b802 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelProperty.vue @@ -0,0 +1,177 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelService.vue b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelService.vue new file mode 100644 index 0000000000000000000000000000000000000000..303e9e620a7acc4f672809398633eba75cd9ce4b --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelService.vue @@ -0,0 +1,64 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelTSL.vue b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelTSL.vue new file mode 100644 index 0000000000000000000000000000000000000000..cba5c035ecd35f2bd6b29591a826adcbfcea7b91 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/ThingModelTSL.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/components/DataDefinition.vue b/apps/web-antd/src/views/iot/thingmodel/modules/components/DataDefinition.vue new file mode 100644 index 0000000000000000000000000000000000000000..516cdb9fbb99ea8eb78e2f148359393a115f46b6 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/components/DataDefinition.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/components/index.ts b/apps/web-antd/src/views/iot/thingmodel/modules/components/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..66c692bacdf09d1ae5f3bc203416c64ca5141851 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/components/index.ts @@ -0,0 +1,3 @@ +import DataDefinition from './DataDefinition.vue' + +export { DataDefinition } diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelArrayDataSpecs.vue b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelArrayDataSpecs.vue new file mode 100644 index 0000000000000000000000000000000000000000..76dfdd4e4d3bc764d55cef85c5532d678df458b0 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelArrayDataSpecs.vue @@ -0,0 +1,56 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelEnumDataSpecs.vue b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelEnumDataSpecs.vue new file mode 100644 index 0000000000000000000000000000000000000000..e4b6d69f0e7c8ecf7717d48a1bbc94a0bd8d4e6c --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelEnumDataSpecs.vue @@ -0,0 +1,160 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelNumberDataSpecs.vue b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelNumberDataSpecs.vue new file mode 100644 index 0000000000000000000000000000000000000000..8f658b50393259705d2072d0ef0b64b0452fa716 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelNumberDataSpecs.vue @@ -0,0 +1,139 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelStructDataSpecs.vue b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelStructDataSpecs.vue new file mode 100644 index 0000000000000000000000000000000000000000..ee19a9b6394ab0e0c71e3208260a5d30f937e53d --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/ThingModelStructDataSpecs.vue @@ -0,0 +1,167 @@ + + + + + + diff --git a/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/index.ts b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..30151aea62ba1d0fc2d98f370418fada1d504fd7 --- /dev/null +++ b/apps/web-antd/src/views/iot/thingmodel/modules/dataSpecs/index.ts @@ -0,0 +1,11 @@ +import ThingModelEnumDataSpecs from './ThingModelEnumDataSpecs.vue' +import ThingModelNumberDataSpecs from './ThingModelNumberDataSpecs.vue' +import ThingModelArrayDataSpecs from './ThingModelArrayDataSpecs.vue' +import ThingModelStructDataSpecs from './ThingModelStructDataSpecs.vue' + +export { + ThingModelEnumDataSpecs, + ThingModelNumberDataSpecs, + ThingModelArrayDataSpecs, + ThingModelStructDataSpecs +} diff --git a/apps/web-antd/src/views/iot/utils/constants.ts b/apps/web-antd/src/views/iot/utils/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5cdad803377b19900e3177e652f50b44868954d --- /dev/null +++ b/apps/web-antd/src/views/iot/utils/constants.ts @@ -0,0 +1,546 @@ +/** 检查值是否为空 */ +const isEmpty = (value: any): boolean => { + return value === null || value === undefined || value === ''; +}; + +/** IoT 依赖注入 KEY */ +export const IOT_PROVIDE_KEY = { + PRODUCT: 'IOT_PRODUCT' +} + +/** IoT 产品物模型类型枚举类 */ +export const IoTThingModelTypeEnum = { + PROPERTY: 1, // 属性 + SERVICE: 2, // 服务 + EVENT: 3 // 事件 +} as const + +/** IoT 设备消息的方法枚举 */ +export const IotDeviceMessageMethodEnum = { + // ========== 设备状态 ========== + STATE_UPDATE: { + method: 'thing.state.update', + name: '设备状态变更', + upstream: true + }, + + // ========== 设备属性 ========== + PROPERTY_POST: { + method: 'thing.property.post', + name: '属性上报', + upstream: true + }, + PROPERTY_SET: { + method: 'thing.property.set', + name: '属性设置', + upstream: false + }, + + // ========== 设备事件 ========== + EVENT_POST: { + method: 'thing.event.post', + name: '事件上报', + upstream: true + }, + + // ========== 服务调用 ========== + SERVICE_INVOKE: { + method: 'thing.service.invoke', + name: '服务调用', + upstream: false + }, + + // ========== 设备配置 ========== + CONFIG_PUSH: { + method: 'thing.config.push', + name: '配置推送', + upstream: false + } +} + +// IoT 产品物模型服务调用方式枚举 +export const IoTThingModelServiceCallTypeEnum = { + ASYNC: { + label: '异步', + value: 'async' + }, + SYNC: { + label: '同步', + value: 'sync' + } +} as const +export const getThingModelServiceCallTypeLabel = (value: string): string | undefined => + Object.values(IoTThingModelServiceCallTypeEnum).find((type) => type.value === value)?.label + +// IoT 产品物模型事件类型枚举 +export const IoTThingModelEventTypeEnum = { + INFO: { + label: '信息', + value: 'info' + }, + ALERT: { + label: '告警', + value: 'alert' + }, + ERROR: { + label: '故障', + value: 'error' + } +} as const +export const getEventTypeLabel = (value: string): string | undefined => + Object.values(IoTThingModelEventTypeEnum).find((type) => type.value === value)?.label + +// IoT 产品物模型参数是输入参数还是输出参数 +export const IoTThingModelParamDirectionEnum = { + INPUT: 'input', // 输入参数 + OUTPUT: 'output' // 输出参数 +} as const + +// IoT 产品物模型访问模式枚举类 +export const IoTThingModelAccessModeEnum = { + READ_WRITE: { + label: '读写', + value: 'rw' + }, + READ_ONLY: { + label: '只读', + value: 'r' + }, + WRITE_ONLY: { + label: '只写', + value: 'w' + } +} as const + +/** 获取访问模式标签 */ +export const getAccessModeLabel = (value: string): string => { + const mode = Object.values(IoTThingModelAccessModeEnum).find((mode) => mode.value === value) + return mode?.label || value +} + +/** 属性值的数据类型 */ +export const IoTDataSpecsDataTypeEnum = { + INT: 'int', + FLOAT: 'float', + DOUBLE: 'double', + ENUM: 'enum', + BOOL: 'bool', + TEXT: 'text', + DATE: 'date', + STRUCT: 'struct', + ARRAY: 'array' +} as const + +export const getDataTypeOptions = () => { + return [ + { value: IoTDataSpecsDataTypeEnum.INT, label: '整数型' }, + { value: IoTDataSpecsDataTypeEnum.FLOAT, label: '单精度浮点型' }, + { value: IoTDataSpecsDataTypeEnum.DOUBLE, label: '双精度浮点型' }, + { value: IoTDataSpecsDataTypeEnum.ENUM, label: '枚举型' }, + { value: IoTDataSpecsDataTypeEnum.BOOL, label: '布尔型' }, + { value: IoTDataSpecsDataTypeEnum.TEXT, label: '文本型' }, + { value: IoTDataSpecsDataTypeEnum.DATE, label: '时间型' }, + { value: IoTDataSpecsDataTypeEnum.STRUCT, label: '结构体' }, + { value: IoTDataSpecsDataTypeEnum.ARRAY, label: '数组' } + ] +} + +/** 获得物体模型数据类型配置项名称 */ +export const getDataTypeOptionsLabel = (value: string) => { + if (isEmpty(value)) { + return value + } + const dataType = getDataTypeOptions().find((option) => option.value === value) + return dataType && `${dataType.value}(${dataType.label})` +} + +/** 获取数据类型显示名称(用于属性选择器) */ +export const getDataTypeName = (dataType: string): string => { + const typeMap = { + [IoTDataSpecsDataTypeEnum.INT]: '整数', + [IoTDataSpecsDataTypeEnum.FLOAT]: '浮点数', + [IoTDataSpecsDataTypeEnum.DOUBLE]: '双精度', + [IoTDataSpecsDataTypeEnum.TEXT]: '字符串', + [IoTDataSpecsDataTypeEnum.BOOL]: '布尔值', + [IoTDataSpecsDataTypeEnum.ENUM]: '枚举', + [IoTDataSpecsDataTypeEnum.DATE]: '日期', + [IoTDataSpecsDataTypeEnum.STRUCT]: '结构体', + [IoTDataSpecsDataTypeEnum.ARRAY]: '数组' + } + return typeMap[dataType] || dataType +} + +/** 获取数据类型标签类型(用于 el-tag 的 type 属性) */ +export const getDataTypeTagType = ( + dataType: string +): 'primary' | 'success' | 'info' | 'warning' | 'danger' => { + const tagMap = { + [IoTDataSpecsDataTypeEnum.INT]: 'primary', + [IoTDataSpecsDataTypeEnum.FLOAT]: 'success', + [IoTDataSpecsDataTypeEnum.DOUBLE]: 'success', + [IoTDataSpecsDataTypeEnum.TEXT]: 'info', + [IoTDataSpecsDataTypeEnum.BOOL]: 'warning', + [IoTDataSpecsDataTypeEnum.ENUM]: 'danger', + [IoTDataSpecsDataTypeEnum.DATE]: 'primary', + [IoTDataSpecsDataTypeEnum.STRUCT]: 'info', + [IoTDataSpecsDataTypeEnum.ARRAY]: 'warning' + } as const + return tagMap[dataType] || 'info' +} + +/** 物模型组标签常量 */ +export const THING_MODEL_GROUP_LABELS = { + PROPERTY: '设备属性', + EVENT: '设备事件', + SERVICE: '设备服务' +} as const + +// IoT OTA 任务设备范围枚举 +export const IoTOtaTaskDeviceScopeEnum = { + ALL: { + label: '全部设备', + value: 1 + }, + SELECT: { + label: '指定设备', + value: 2 + } +} as const + +// IoT OTA 任务状态枚举 +export const IoTOtaTaskStatusEnum = { + IN_PROGRESS: { + label: '进行中', + value: 10 + }, + END: { + label: '已结束', + value: 20 + }, + CANCELED: { + label: '已取消', + value: 30 + } +} as const + +// IoT OTA 升级记录状态枚举 +export const IoTOtaTaskRecordStatusEnum = { + PENDING: { + label: '待推送', + value: 0 + }, + PUSHED: { + label: '已推送', + value: 10 + }, + UPGRADING: { + label: '升级中', + value: 20 + }, + SUCCESS: { + label: '升级成功', + value: 30 + }, + FAILURE: { + label: '升级失败', + value: 40 + }, + CANCELED: { + label: '升级取消', + value: 50 + } +} as const + +// ========== 场景联动规则相关常量 ========== + +/** IoT 场景联动触发器类型枚举 */ +export const IotRuleSceneTriggerTypeEnum = { + DEVICE_STATE_UPDATE: 1, // 设备上下线变更 + DEVICE_PROPERTY_POST: 2, // 物模型属性上报 + DEVICE_EVENT_POST: 3, // 设备事件上报 + DEVICE_SERVICE_INVOKE: 4, // 设备服务调用 + TIMER: 100 // 定时触发 +} as const + +/** 触发器类型选项配置 */ +export const triggerTypeOptions = [ + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE, + label: '设备状态变更' + }, + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, + label: '设备属性上报' + }, + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST, + label: '设备事件上报' + }, + { + value: IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE, + label: '设备服务调用' + }, + { + value: IotRuleSceneTriggerTypeEnum.TIMER, + label: '定时触发' + } +] + +/** 判断是否为设备触发器类型 */ +export const isDeviceTrigger = (type: number): boolean => { + const deviceTriggerTypes = [ + IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE, + IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, + IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST, + IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE + ] as number[] + return deviceTriggerTypes.includes(type) +} + +// ========== 场景联动规则执行器相关常量 ========== + +/** IoT 场景联动执行器类型枚举 */ +export const IotRuleSceneActionTypeEnum = { + DEVICE_PROPERTY_SET: 1, // 设备属性设置 + DEVICE_SERVICE_INVOKE: 2, // 设备服务调用 + ALERT_TRIGGER: 100, // 告警触发 + ALERT_RECOVER: 101 // 告警恢复 +} as const + +/** 执行器类型选项配置 */ +export const getActionTypeOptions = () => [ + { + value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET, + label: '设备属性设置' + }, + { + value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE, + label: '设备服务调用' + }, + { + value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER, + label: '触发告警' + }, + { + value: IotRuleSceneActionTypeEnum.ALERT_RECOVER, + label: '恢复告警' + } +] + +/** 获取执行器类型标签 */ +export const getActionTypeLabel = (type: number): string => { + const option = getActionTypeOptions().find((opt) => opt.value === type) + return option?.label || '未知类型' +} + +/** IoT 场景联动触发条件参数操作符枚举 */ +export const IotRuleSceneTriggerConditionParameterOperatorEnum = { + EQUALS: { name: '等于', value: '=' }, // 等于 + NOT_EQUALS: { name: '不等于', value: '!=' }, // 不等于 + GREATER_THAN: { name: '大于', value: '>' }, // 大于 + GREATER_THAN_OR_EQUALS: { name: '大于等于', value: '>=' }, // 大于等于 + LESS_THAN: { name: '小于', value: '<' }, // 小于 + LESS_THAN_OR_EQUALS: { name: '小于等于', value: '<=' }, // 小于等于 + IN: { name: '在...之中', value: 'in' }, // 在...之中 + NOT_IN: { name: '不在...之中', value: 'not in' }, // 不在...之中 + BETWEEN: { name: '在...之间', value: 'between' }, // 在...之间 + NOT_BETWEEN: { name: '不在...之间', value: 'not between' }, // 不在...之间 + LIKE: { name: '字符串匹配', value: 'like' }, // 字符串匹配 + NOT_NULL: { name: '非空', value: 'not null' } // 非空 +} as const + +/** IoT 场景联动触发条件类型枚举 */ +export const IotRuleSceneTriggerConditionTypeEnum = { + DEVICE_STATUS: 1, // 设备状态 + DEVICE_PROPERTY: 2, // 设备属性 + CURRENT_TIME: 3 // 当前时间 +} as const + +/** 获取条件类型选项 */ +export const getConditionTypeOptions = () => [ + { + value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS, + label: '设备状态' + }, + { + value: IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY, + label: '设备属性' + }, + { + value: IotRuleSceneTriggerConditionTypeEnum.CURRENT_TIME, + label: '当前时间' + } +] + +/** 设备状态枚举 - 统一的设备状态管理 */ +export const IoTDeviceStatusEnum = { + // 在线状态 + ONLINE: { + label: '在线', + value: 'online', + tagType: 'success' + }, + OFFLINE: { + label: '离线', + value: 'offline', + tagType: 'danger' + }, + // 启用状态 + ENABLED: { + label: '正常', + value: 0, + value2: 'enabled', + tagType: 'success' + }, + DISABLED: { + label: '禁用', + value: 1, + value2: 'disabled', + tagType: 'danger' + }, + // 激活状态 + ACTIVATED: { + label: '已激活', + value2: 'activated', + tagType: 'success' + }, + NOT_ACTIVATED: { + label: '未激活', + value2: 'not_activated', + tagType: 'info' + } +} as const + +/** 设备选择器特殊选项 */ +export const DEVICE_SELECTOR_OPTIONS = { + ALL_DEVICES: { + id: 0, + deviceName: '全部设备' + } +} as const + +/** IoT 场景联动触发时间操作符枚举 */ +export const IotRuleSceneTriggerTimeOperatorEnum = { + BEFORE_TIME: { name: '在时间之前', value: 'before_time' }, // 在时间之前 + AFTER_TIME: { name: '在时间之后', value: 'after_time' }, // 在时间之后 + BETWEEN_TIME: { name: '在时间之间', value: 'between_time' }, // 在时间之间 + AT_TIME: { name: '在指定时间', value: 'at_time' }, // 在指定时间 + BEFORE_TODAY: { name: '在今日之前', value: 'before_today' }, // 在今日之前 + AFTER_TODAY: { name: '在今日之后', value: 'after_today' }, // 在今日之后 + TODAY: { name: '在今日之间', value: 'today' } // 在今日之间 +} as const + +/** 获取触发器类型标签 */ +export const getTriggerTypeLabel = (type: number): string => { + const option = triggerTypeOptions.find((item) => item.value === type) + return option?.label || '未知类型' +} + +// ========== JSON 参数输入组件相关常量 ========== + +/** JSON 参数输入组件类型枚举 */ +export const JsonParamsInputTypeEnum = { + SERVICE: 'service', + EVENT: 'event', + PROPERTY: 'property', + CUSTOM: 'custom' +} as const + +/** JSON 参数输入组件类型 */ +export type JsonParamsInputType = + (typeof JsonParamsInputTypeEnum)[keyof typeof JsonParamsInputTypeEnum] + +/** JSON 参数输入组件文本常量 */ +export const JSON_PARAMS_INPUT_CONSTANTS = { + // 基础文本 + PLACEHOLDER: '请输入JSON格式的参数', + JSON_FORMAT_CORRECT: 'JSON 格式正确', + QUICK_FILL_LABEL: '快速填充:', + EXAMPLE_DATA_BUTTON: '示例数据', + CLEAR_BUTTON: '清空', + VIEW_EXAMPLE_TITLE: '查看参数示例', + COMPLETE_JSON_FORMAT: '完整 JSON 格式:', + REQUIRED_TAG: '必填', + + // 错误信息 + PARAMS_MUST_BE_OBJECT: '参数必须是一个有效的 JSON 对象', + PARAM_REQUIRED_ERROR: (paramName: string) => `参数 ${paramName} 为必填项`, + JSON_FORMAT_ERROR: (error: string) => `JSON格式错误: ${error}`, + UNKNOWN_ERROR: '未知错误', + + // 类型相关标题 + TITLES: { + SERVICE: (name?: string) => `${name || '服务'} - 输入参数示例`, + EVENT: (name?: string) => `${name || '事件'} - 输出参数示例`, + PROPERTY: '属性设置 - 参数示例', + CUSTOM: (name?: string) => `${name || '自定义'} - 参数示例`, + DEFAULT: '参数示例' + }, + + // 参数标签 + PARAMS_LABELS: { + SERVICE: '输入参数', + EVENT: '输出参数', + PROPERTY: '属性参数', + CUSTOM: '参数列表', + DEFAULT: '参数' + }, + + // 空状态消息 + EMPTY_MESSAGES: { + SERVICE: '此服务无需输入参数', + EVENT: '此事件无输出参数', + PROPERTY: '无可设置的属性', + CUSTOM: '无参数配置', + DEFAULT: '无参数' + }, + + // 无配置消息 + NO_CONFIG_MESSAGES: { + SERVICE: '请先选择服务', + EVENT: '请先选择事件', + PROPERTY: '请先选择产品', + CUSTOM: '请先进行配置', + DEFAULT: '请先进行配置' + } +} as const + +/** JSON 参数输入组件图标常量 */ +export const JSON_PARAMS_INPUT_ICONS = { + // 标题图标 + TITLE_ICONS: { + SERVICE: 'ep:service', + EVENT: 'ep:bell', + PROPERTY: 'ep:edit', + CUSTOM: 'ep:document', + DEFAULT: 'ep:document' + }, + + // 参数图标 + PARAMS_ICONS: { + SERVICE: 'ep:edit', + EVENT: 'ep:upload', + PROPERTY: 'ep:setting', + CUSTOM: 'ep:list', + DEFAULT: 'ep:edit' + }, + + // 状态图标 + STATUS_ICONS: { + ERROR: 'ep:warning', + SUCCESS: 'ep:circle-check' + } +} as const + +/** JSON 参数输入组件示例值常量 */ +export const JSON_PARAMS_EXAMPLE_VALUES = { + [IoTDataSpecsDataTypeEnum.INT]: { display: '25', value: 25 }, + [IoTDataSpecsDataTypeEnum.FLOAT]: { display: '25.5', value: 25.5 }, + [IoTDataSpecsDataTypeEnum.DOUBLE]: { display: '25.5', value: 25.5 }, + [IoTDataSpecsDataTypeEnum.BOOL]: { display: 'false', value: false }, + [IoTDataSpecsDataTypeEnum.TEXT]: { display: '"auto"', value: 'auto' }, + [IoTDataSpecsDataTypeEnum.ENUM]: { display: '"option1"', value: 'option1' }, + [IoTDataSpecsDataTypeEnum.STRUCT]: { display: '{}', value: {} }, + [IoTDataSpecsDataTypeEnum.ARRAY]: { display: '[]', value: [] }, + DEFAULT: { display: '""', value: '' } +} as const