diff --git a/src/api/iot/device/group/index.ts b/src/api/iot/device/group/index.ts new file mode 100644 index 00000000..02cca70b --- /dev/null +++ b/src/api/iot/device/group/index.ts @@ -0,0 +1,43 @@ +import request from '@/config/axios' + +// IoT 设备分组 VO +export interface DeviceGroupVO { + id: number // 分组 ID + name: string // 分组名字 + status: number // 分组状态 + description: string // 分组描述 + deviceCount?: number // 设备数量 +} + +// IoT 设备分组 API +export const DeviceGroupApi = { + // 查询IoT 设备分组分页 + getDeviceGroupPage: async (params: any) => { + return await request.get({ url: `/iot/device-group/page`, params }) + }, + + // 查询IoT 设备分组详情 + getDeviceGroup: async (id: number) => { + return await request.get({ url: `/iot/device-group/get?id=` + id }) + }, + + // 新增IoT 设备分组 + createDeviceGroup: async (data: DeviceGroupVO) => { + return await request.post({ url: `/iot/device-group/create`, data }) + }, + + // 修改IoT 设备分组 + updateDeviceGroup: async (data: DeviceGroupVO) => { + return await request.put({ url: `/iot/device-group/update`, data }) + }, + + // 删除IoT 设备分组 + deleteDeviceGroup: async (id: number) => { + return await request.delete({ url: `/iot/device-group/delete?id=` + id }) + }, + + // 获取设备分组的精简信息列表 + getSimpleDeviceGroupList: async () => { + return await request.get({ url: `/iot/device-group/simple-list` }) + } +} diff --git a/src/api/iot/device/index.ts b/src/api/iot/device/index.ts index 4f61d56d..a483484d 100644 --- a/src/api/iot/device/index.ts +++ b/src/api/iot/device/index.ts @@ -28,6 +28,7 @@ export interface DeviceVO { areaId: number // 地区编码 address: string // 设备详细地址 serialNumber: string // 设备序列号 + groupIds?: number[] // 添加分组 ID } export interface DeviceUpdateStatusVO { @@ -81,16 +82,39 @@ export const DeviceApi = { return await request.put({ url: `/iot/device/update-status`, data }) }, - // 删除设备 + // 修改设备分组 + updateDeviceGroup: async (data: { + ids: number[] + groupIds: number[] + }) => { + return await request.put({ url: `/iot/device/update-group`, data }) + }, + + // 删除单个设备 deleteDevice: async (id: number) => { return await request.delete({ url: `/iot/device/delete?id=` + id }) }, + // 删除多个设备 + deleteDeviceList: async (ids: number[]) => { + return await request.delete({ url: `/iot/device/delete-list`, params: { ids: ids.join(',') } }) + }, + + // 导出设备 + exportDeviceExcel: async (params: any) => { + return await request.download({ url: `/iot/device/export-excel`, params }) + }, + // 获取设备数量 getDeviceCount: async (productId: number) => { return await request.get({ url: `/iot/device/count?productId=` + productId }) }, + // 获取设备的精简信息列表 + getSimpleDeviceList: async (deviceType?: number) => { + return await request.get({ url: `/iot/device/simple-list?`, params: { deviceType } }) + }, + // 获取设备属性最新数据 getDevicePropertiesLatestData: async (params: any) => { return await request.get({ url: `/iot/device/data/latest`, params }) diff --git a/src/api/iot/product/product/index.ts b/src/api/iot/product/product/index.ts index e9dfdcaf..496fb049 100644 --- a/src/api/iot/product/product/index.ts +++ b/src/api/iot/product/product/index.ts @@ -8,6 +8,8 @@ export interface ProductVO { protocolId: number // 协议编号 categoryId: number // 产品所属品类标识符 categoryName?: string // 产品所属品类名称 + icon: string // 产品图标 + picUrl: string // 产品图片 description: string // 产品描述 validateType: number // 数据校验级别 status: number // 产品状态 @@ -17,7 +19,6 @@ export interface ProductVO { dataFormat: number // 数据格式 deviceCount: number // 设备数量 createTime: Date // 创建时间 - picUrl: string // 产品图片 URL } // IOT 数据校验级别枚举类 diff --git a/src/assets/imgs/iot/device.png b/src/assets/imgs/iot/device.png new file mode 100644 index 00000000..79339cdf Binary files /dev/null and b/src/assets/imgs/iot/device.png differ diff --git a/src/assets/svgs/iot/card-fill.svg b/src/assets/svgs/iot/card-fill.svg new file mode 100644 index 00000000..4c74ecdc --- /dev/null +++ b/src/assets/svgs/iot/card-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/iot/cube.svg b/src/assets/svgs/iot/cube.svg new file mode 100644 index 00000000..200ac1b1 --- /dev/null +++ b/src/assets/svgs/iot/cube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/views/iot/device/DeviceForm.vue b/src/views/iot/device/device/DeviceForm.vue similarity index 50% rename from src/views/iot/device/DeviceForm.vue rename to src/views/iot/device/device/DeviceForm.vue index 95eff822..bda22adb 100644 --- a/src/views/iot/device/DeviceForm.vue +++ b/src/views/iot/device/device/DeviceForm.vue @@ -13,6 +13,7 @@ placeholder="请选择产品" :disabled="formType === 'update'" clearable + @change="handleProductChange" > + + + + + 重新生成 + + + + - - + + + + + + + + + + + + + + + + + + + + + + + 确 定 @@ -41,13 +86,16 @@ diff --git a/src/views/iot/device/device/DeviceGroupForm.vue b/src/views/iot/device/device/DeviceGroupForm.vue new file mode 100644 index 00000000..387e1454 --- /dev/null +++ b/src/views/iot/device/device/DeviceGroupForm.vue @@ -0,0 +1,90 @@ + + + + + + + + + + + 确 定 + 取 消 + + + + + diff --git a/src/views/iot/device/detail/DeviceDataDetail.vue b/src/views/iot/device/device/detail/DeviceDataDetail.vue similarity index 100% rename from src/views/iot/device/detail/DeviceDataDetail.vue rename to src/views/iot/device/device/detail/DeviceDataDetail.vue diff --git a/src/views/iot/device/detail/DeviceDetailsHeader.vue b/src/views/iot/device/device/detail/DeviceDetailsHeader.vue similarity index 100% rename from src/views/iot/device/detail/DeviceDetailsHeader.vue rename to src/views/iot/device/device/detail/DeviceDetailsHeader.vue diff --git a/src/views/iot/device/detail/DeviceDetailsInfo.vue b/src/views/iot/device/device/detail/DeviceDetailsInfo.vue similarity index 100% rename from src/views/iot/device/detail/DeviceDetailsInfo.vue rename to src/views/iot/device/device/detail/DeviceDetailsInfo.vue diff --git a/src/views/iot/device/detail/DeviceDetailsModel.vue b/src/views/iot/device/device/detail/DeviceDetailsModel.vue similarity index 100% rename from src/views/iot/device/detail/DeviceDetailsModel.vue rename to src/views/iot/device/device/detail/DeviceDetailsModel.vue diff --git a/src/views/iot/device/detail/index.vue b/src/views/iot/device/device/detail/index.vue similarity index 100% rename from src/views/iot/device/detail/index.vue rename to src/views/iot/device/device/detail/index.vue diff --git a/src/views/iot/device/device/index.vue b/src/views/iot/device/device/index.vue new file mode 100644 index 00000000..bcc2df21 --- /dev/null +++ b/src/views/iot/device/device/index.vue @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 搜索 + + + + 重置 + + + + 新增 + + + 导出 + + + 添加到分组 + + + 批量删除 + + + + + + + + + + + + + + + + + + {{ item.deviceName }} + + + + + + + 所属产品 + {{ + products.find((p) => p.id === item.productId)?.name + }} + + + 设备类型 + + + + DeviceKey + {{ item.deviceKey }} + + + + + + + + + + + + + + + 编辑 + + + + 详情 + + + + 日志 + + + + + + + + + + + + + + + + + + {{ scope.row.deviceName }} + + + + + + {{ products.find((p) => p.id === scope.row.productId)?.name || '-' }} + + + + + + + + + + + + {{ deviceGroups.find((g) => g.id === id)?.name }} + + + + + + + + + + + + + + 查看 + + 日志 + + 编辑 + + + 删除 + + + + + + + + + + + + + + + + diff --git a/src/views/iot/device/group/DeviceGroupForm.vue b/src/views/iot/device/group/DeviceGroupForm.vue new file mode 100644 index 00000000..64872573 --- /dev/null +++ b/src/views/iot/device/group/DeviceGroupForm.vue @@ -0,0 +1,112 @@ + + + + + + + + + + {{ dict.label }} + + + + + + + + + 确 定 + 取 消 + + + + diff --git a/src/views/iot/device/group/index.vue b/src/views/iot/device/group/index.vue new file mode 100644 index 00000000..ea2e4bea --- /dev/null +++ b/src/views/iot/device/group/index.vue @@ -0,0 +1,169 @@ + + + + + + + + + + + + 搜索 + 重置 + + 新增 + + + + + + + + + + + + + + + + + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + diff --git a/src/views/iot/device/index.vue b/src/views/iot/device/index.vue deleted file mode 100644 index 20acff5e..00000000 --- a/src/views/iot/device/index.vue +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 搜索 - - - - 重置 - - - - 新增 - - - - - - - - - - - {{ scope.row.deviceName }} - - - - - - {{ productMap[scope.row.productId] }} - - - - - - - - - - - - - - - - - 查看 - - - 编辑 - - - 删除 - - - - - - - - - - - - - diff --git a/src/views/iot/product/product/ProductForm.vue b/src/views/iot/product/product/ProductForm.vue index b974717b..e4eec674 100644 --- a/src/views/iot/product/product/ProductForm.vue +++ b/src/views/iot/product/product/ProductForm.vue @@ -7,12 +7,18 @@ label-width="110px" v-loading="formLoading" > - + + > + + + 重新生成 + + + @@ -145,7 +151,7 @@ const formData = ref({ validateType: ValidateTypeEnum.WEAK }) const formRules = reactive({ - productKey: [{ required: true, message: '产品标识不能为空', trigger: 'blur' }], + productKey: [{ required: true, message: 'ProductKey 不能为空', trigger: 'blur' }], name: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }], categoryId: [{ required: true, message: '产品分类不能为空', trigger: 'change' }], deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'change' }], @@ -184,7 +190,7 @@ const open = async (type: string, id?: number) => { } } else { // 新增时,生成随机 productKey - formData.value.productKey = generateRandomStr(16) + generateProductKey() } // 加载分类列表 categoryList.value = await ProductCategoryApi.getSimpleProductCategoryList() @@ -231,4 +237,9 @@ const resetForm = () => { } formRef.value?.resetFields() } + +/** 生成 ProductKey */ +const generateProductKey = () => { + formData.value.productKey = generateRandomStr(16) +} diff --git a/src/views/iot/product/product/detail/ProductTopic.vue b/src/views/iot/product/product/detail/ProductTopic.vue index 1e95da5a..a691a614 100644 --- a/src/views/iot/product/product/detail/ProductTopic.vue +++ b/src/views/iot/product/product/detail/ProductTopic.vue @@ -3,9 +3,9 @@ () -// 定义列 -const columns1 = reactive([ +// TODO 芋艿:不确定未来会不会改,所以先写死 + +// 基础通信 Topic 列 +const basicColumn = reactive([ { label: '功能', field: 'function', width: 150 }, { label: 'Topic 类', field: 'topicClass', width: 800 }, { label: '操作权限', field: 'operationPermission', width: 100 }, { label: '描述', field: 'description' } ]) -const columns2 = reactive([ - { label: '功能', field: 'function', width: 150 }, - { label: 'Topic 类', field: 'topicClass', width: 800 }, - { label: '操作权限', field: 'operationPermission', width: 100 }, - { label: '描述', field: 'description' } -]) - -// TODO @haohao:这个,有没可能写到一个枚举里,方便后续维护? /Users/yunai/Java/yudao-ui-admin-vue3/src/views/ai/utils/constants.ts -const data1 = computed(() => { +// 基础通信 Topic 数据 +const basicData = computed(() => { if (!props.product || !props.product.productKey) return [] return [ { @@ -147,7 +142,16 @@ const data1 = computed(() => { ] }) -const data2 = computed(() => { +// 物模型通信 Topic 列 +const functionColumn = reactive([ + { label: '功能', field: 'function', width: 150 }, + { label: 'Topic 类', field: 'topicClass', width: 800 }, + { label: '操作权限', field: 'operationPermission', width: 100 }, + { label: '描述', field: 'description' } +]) + +// 物模型通信 Topic 数据 +const functionData = computed(() => { if (!props.product || !props.product.productKey) return [] return [ { diff --git a/src/views/iot/product/product/detail/index.vue b/src/views/iot/product/product/detail/index.vue index e520ced1..22d6af73 100644 --- a/src/views/iot/product/product/detail/index.vue +++ b/src/views/iot/product/product/detail/index.vue @@ -37,7 +37,7 @@ const message = useMessage() const id = Number(route.params.id) // 编号 const loading = ref(true) // 加载中 const product = ref({} as ProductVO) // 详情 -const activeTab = ref('info') // 默认激活的标签页 +const activeTab = ref('info') // 默认为 info 标签页 provide(IOT_PROVIDE_KEY.PRODUCT, product) // 提供产品信息给产品信息详情页的所有子组件 @@ -69,6 +69,11 @@ onMounted(async () => { return } await getProductData(id) + // 处理 tab 参数 + const { tab } = route.query + if (tab) { + activeTab.value = tab as string + } // 查询设备数量 if (product.value.id) { product.value.deviceCount = await getDeviceCount(product.value.id) diff --git a/src/views/iot/product/product/index.vue b/src/views/iot/product/product/index.vue index 21ae605d..1e48a594 100644 --- a/src/views/iot/product/product/index.vue +++ b/src/views/iot/product/product/index.vue @@ -63,52 +63,89 @@ - - - - - - {{ item.name }} - {{ item.productKey }} + + + + + + + + + + + {{ item.name }} + + + + + + + 产品分类 + {{ item.categoryName }} + + + 产品类型 + + + + 产品标识 + {{ item.productKey }} + + + + + + + + + + + + + + + 编辑 + + + + 详情 + + + + 物模型 + + + + + + - - - {{ item.categoryName }} - - - - - 编辑 - - - 删除 - - - - + + + @@ -199,14 +236,19 @@ import { ProductApi, ProductVO } from '@/api/iot/product/product' import ProductForm from './ProductForm.vue' import { DICT_TYPE } from '@/utils/dict' import download from '@/utils/download' +import defaultPicUrl from '@/assets/imgs/iot/device.png' +import defaultIconUrl from '@/assets/svgs/iot/cube.svg' /** iot 产品列表 */ defineOptions({ name: 'IoTProduct' }) const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 +const { push } = useRouter() +const route = useRoute() const loading = ref(true) // 列表的加载中 +const activeName = ref('info') // 当前激活的标签页 const list = ref([]) // 列表的数据 const total = ref(0) // 列表的总页数 const queryParams = reactive({ @@ -216,7 +258,7 @@ const queryParams = reactive({ productKey: undefined }) const queryFormRef = ref() // 搜索的表单 -const exportLoading = ref(false) // 导出的加载中 +const exportLoading = ref(false) // 导出加载中 const viewMode = ref<'card' | 'list'>('card') // 视图模式状态 /** 查询列表 */ @@ -250,11 +292,19 @@ const openForm = (type: string, id?: number) => { } /** 打开详情 */ -const { push } = useRouter() const openDetail = (id: number) => { push({ name: 'IoTProductDetail', params: { id } }) } +/** 打开物模型 */ +const openObjectModel = (item: ProductVO) => { + push({ + name: 'IoTProductDetail', + params: { id: item.id }, + query: { tab: 'function' } + }) +} + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { @@ -286,5 +336,10 @@ const handleExport = async () => { /** 初始化 **/ onMounted(() => { getList() + // 处理 tab 参数 + const { tab } = route.query + if (tab) { + activeName.value = tab as string + } })