From 240ede976955f8b0688af0f39387ebf90de211f3 Mon Sep 17 00:00:00 2001 From: dengboling Date: Fri, 4 Sep 2026 17:43:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BD=91=E5=85=B3=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=20+=20=E8=AE=BE=E5=A4=87=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E7=BD=91=E5=85=B3=E4=B8=8B=E6=8B=89=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 src/api/gateway.js(网关 CRUD + 测试连接 + options API + 枚举映射) - 新增 src/views/config/gateway/Index.vue(网关管理页面:列表/新增/编辑/测试连接/详情抽屉+下挂设备) - 修改 src/views/config/device/Index.vue(GatewayCode 文本输入改为 GatewayId 下拉选择,列表列显示 GatewayName) - 新增 src/api/product.js、src/api/thingPoint.js(产品/物模型点 API) - 新增 src/components/thing/(PointSendForm、ThingPointTable、ThingAlarmPanel 共享组件) --- src/api/gateway.js | 54 ++++ src/api/product.js | 44 ++++ src/api/thingPoint.js | 57 +++++ src/components/thing/PointSendForm.vue | 154 ++++++++++++ src/components/thing/ThingAlarmPanel.vue | 206 +++++++++++++++ src/components/thing/ThingPointTable.vue | 229 +++++++++++++++++ src/views/config/device/Index.vue | 32 ++- src/views/config/gateway/Index.vue | 307 ++++++++++++++++++++++- 8 files changed, 1072 insertions(+), 11 deletions(-) create mode 100644 src/api/gateway.js create mode 100644 src/api/product.js create mode 100644 src/api/thingPoint.js create mode 100644 src/components/thing/PointSendForm.vue create mode 100644 src/components/thing/ThingAlarmPanel.vue create mode 100644 src/components/thing/ThingPointTable.vue diff --git a/src/api/gateway.js b/src/api/gateway.js new file mode 100644 index 0000000..8f55412 --- /dev/null +++ b/src/api/gateway.js @@ -0,0 +1,54 @@ +import request from '@/utils/request' + +// 后端路由前缀(GatewayController: [Route("api/config/gateway")]) +const base = '/config/gateway' + +// ============ 枚举常量(与后端 IotDeviceProtocolEnum 一一对应,复用 device.js) ============ +export const PROTOCOL_TYPES = { + 1: 'Modbus TCP', + 2: 'Modbus RTU', + 3: 'S7', + 4: 'TCP', + 5: '串口' +} +export const protocolOptions = Object.entries(PROTOCOL_TYPES).map(([value, label]) => ({ value: Number(value), label })) +export const protocolLabel = (v) => PROTOCOL_TYPES[v] || '-' + +// 在线状态 +export const ONLINE_STATUS = { + 0: { label: '离线', tag: 'info' }, + 1: { label: '在线', tag: 'success' }, + 3: { label: '异常', tag: 'danger' } +} +export const onlineStatusLabel = (s) => ONLINE_STATUS[s]?.label || '离线' +export const onlineStatusTag = (s) => ONLINE_STATUS[s]?.tag || 'info' + +// 校验位 +export const PARITY_OPTIONS = [ + { value: 0, label: 'None' }, + { value: 1, label: 'Odd' }, + { value: 2, label: 'Even' } +] + +// ============ 网关接口 ============ + +// 网关列表(分页;X-Total-Count) +export const getGatewayList = (params) => request.get(`${base}/list`, { params }) + +// 网关详情 +export const getGatewayDetail = (id) => request.get(`${base}/${id}`) + +// 新增网关 +export const addGateway = (data) => request.post(base, data) + +// 修改网关 +export const updateGateway = (data) => request.put(base, data) + +// 删除网关 +export const deleteGateway = (id) => request.delete(`${base}/${id}`) + +// 测试连接 +export const testGatewayConnection = (id) => request.post(`${base}/${id}/test-connection`) + +// 下拉选项(设备表单用) +export const getGatewayOptions = () => request.get(`${base}/options`) diff --git a/src/api/product.js b/src/api/product.js new file mode 100644 index 0000000..d15db57 --- /dev/null +++ b/src/api/product.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 后端路由前缀(ProductController: [Route("api/config/product")]) +const base = '/config/product' + +// ============ 枚举常量(与后端 Model.Entity.Config 枚举一一对应) ============ + +// 通讯协议(IotDeviceProtocolEnum) +export const PROTOCOL_TYPES = { + 1: 'Modbus TCP', + 2: 'Modbus RTU', + 3: 'S7', + 4: 'TCP', + 5: '串口' +} +export const protocolOptions = Object.entries(PROTOCOL_TYPES).map(([value, label]) => ({ value: Number(value), label })) +export const protocolLabel = (v) => PROTOCOL_TYPES[v] || '-' + +// ============ 产品分类接口 ============ + +export const getProductCategories = () => request.get(`${base}/category/list`) +export const addProductCategory = (data) => request.post(`${base}/category/add`, data) +export const updateProductCategory = (data) => request.put(`${base}/category/update`, data) +export const deleteProductCategory = (id) => request.delete(`${base}/category/${id}`) + +// ============ 产品接口 ============ + +// 产品下拉选项(设备选择所属产品用) +export const getProductOptions = () => request.get(`${base}/options`) + +// 产品列表(服务端分页;结果数组上附带 total,来自响应头 X-Total-Count) +export const getProductList = (params) => request.get(`${base}/list`, { params }) + +// 产品详情 +export const getProductDetail = (id) => request.get(`${base}/${id}`) + +// 新增产品(后端校验型号唯一) +export const addProduct = (data) => request.post(base, data) + +// 修改产品 +export const updateProduct = (data) => request.put(base, data) + +// 删除产品(软删除) +export const deleteProduct = (id) => request.delete(`${base}/${id}`) diff --git a/src/api/thingPoint.js b/src/api/thingPoint.js new file mode 100644 index 0000000..c5faff5 --- /dev/null +++ b/src/api/thingPoint.js @@ -0,0 +1,57 @@ +import request from '@/utils/request' + +// 后端路由前缀(ThingPointController: [Route("api/config/thing-point")]) +const base = '/config/thing-point' + +// ============ 枚举常量(与后端 Model.Entity.Config.ThingEnums 对应) ============ + +// 归属类型(ThingOwnerTypeEnum) +export const OWNER_TYPES = { 1: '产品', 2: '设备' } + +// 读写权限(ThingRwEnum) +export const RW_TYPES = { + 1: { label: '只读' }, + 2: { label: '只写' }, + 3: { label: '读写' } +} +export const rwOptions = Object.entries(RW_TYPES).map(([value, v]) => ({ value: Number(value), label: v.label })) +export const rwLabel = (v) => RW_TYPES[v]?.label || '-' + +// 寄存器类型(ThingRegisterTypeEnum) +export const REGISTER_TYPES = { + 1: { label: '线圈', canWrite: true }, + 2: { label: '保持寄存器', canWrite: true }, + 3: { label: '输入寄存器', canWrite: false } +} +export const registerOptions = Object.entries(REGISTER_TYPES).map(([value, v]) => ({ value: Number(value), label: v.label })) +export const registerLabel = (v) => REGISTER_TYPES[v]?.label || '-' + +// 数据类型(ThingDataTypeEnum) +export const DATA_TYPES = { + 1: { label: 'Bool' }, + 2: { label: 'Int16' }, + 3: { label: 'UInt16' }, + 4: { label: 'Int32' }, + 5: { label: 'Float' } +} +export const dataOptions = Object.entries(DATA_TYPES).map(([value, v]) => ({ value: Number(value), label: v.label })) +export const dataLabel = (v) => DATA_TYPES[v]?.label || '-' + +// ============ 物模型点接口 ============ + +// 点列表(ownerType: 1=产品 2=设备) +export const getThingPoints = (ownerType, ownerId, keyword) => request.get(`${base}/list`, { + params: { ownerType, ownerId, keyword: keyword || undefined } +}) + +// 点详情 +export const getThingPoint = (id) => request.get(`${base}/${id}`) + +// 新增点 +export const addThingPoint = (data) => request.post(base, data) + +// 修改点 +export const updateThingPoint = (data) => request.put(base, data) + +// 删除点 +export const deleteThingPoint = (id) => request.delete(`${base}/${id}`) diff --git a/src/components/thing/PointSendForm.vue b/src/components/thing/PointSendForm.vue new file mode 100644 index 0000000..6de4018 --- /dev/null +++ b/src/components/thing/PointSendForm.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/src/components/thing/ThingAlarmPanel.vue b/src/components/thing/ThingAlarmPanel.vue new file mode 100644 index 0000000..b6d2b93 --- /dev/null +++ b/src/components/thing/ThingAlarmPanel.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/src/components/thing/ThingPointTable.vue b/src/components/thing/ThingPointTable.vue new file mode 100644 index 0000000..b8d84f7 --- /dev/null +++ b/src/components/thing/ThingPointTable.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/views/config/device/Index.vue b/src/views/config/device/Index.vue index 31e7ccf..8580d6d 100644 --- a/src/views/config/device/Index.vue +++ b/src/views/config/device/Index.vue @@ -48,7 +48,11 @@ - - + + + @@ -136,8 +140,10 @@ - - + + + + @@ -172,7 +178,7 @@ {{ detailDevice.Name }} {{ detailDevice.DeviceType }} {{ detailDevice.ProductName || '未分类' }} - {{ detailDevice.GatewayCode }} + {{ detailDevice.GatewayName || '未关联' }} {{ detailDevice.SlaveId }} {{ protocolLabel(detailDevice.ProtocolType) }} @@ -245,6 +251,7 @@ import { protocolOptions, protocolLabel, onlineStatusLabel, onlineStatusTag } from '@/api/device' import { getProductOptions } from '@/api/product' +import { getGatewayOptions } from '@/api/gateway' import { getCurve } from '@/api/dashboard' import ThingPointTable from '@/components/thing/ThingPointTable.vue' import ThingAlarmPanel from '@/components/thing/ThingAlarmPanel.vue' @@ -259,6 +266,7 @@ const pageSize = ref(10) const keyword = ref('') const searchProductId = ref('') const productOptions = ref([]) +const gatewayOptions = ref([]) async function fetchProductOptions() { try { @@ -268,6 +276,14 @@ async function fetchProductOptions() { } } +async function fetchGatewayOptions() { + try { + gatewayOptions.value = (await getGatewayOptions()) || [] + } catch (e) { + gatewayOptions.value = [] + } +} + async function fetchList() { loading.value = true try { @@ -301,7 +317,7 @@ const deviceTypes = [ ] const defaultForm = () => ({ - Id: '', Code: '', Name: '', DeviceType: '', ProductId: '', GatewayCode: '', + Id: '', Code: '', Name: '', DeviceType: '', ProductId: '', GatewayId: '', SlaveId: 1, ProtocolType: 1, Manufacturer: '', IsEnabled: true, Remark: '' }) const form = reactive(defaultForm()) @@ -309,8 +325,7 @@ const form = reactive(defaultForm()) const formRules = { Code: [{ required: true, message: '请输入设备编号', trigger: 'blur' }], Name: [{ required: true, message: '请输入设备名称', trigger: 'blur' }], - DeviceType: [{ required: true, message: '请选择设备类型', trigger: 'change' }], - GatewayCode: [{ required: true, message: '请输入所属网关', trigger: 'blur' }] + DeviceType: [{ required: true, message: '请选择设备类型', trigger: 'change' }] } function openAdd() { @@ -325,7 +340,7 @@ function openEdit(row) { Name: row.Name, DeviceType: row.DeviceType, ProductId: row.ProductId && row.ProductId !== '0' ? row.ProductId : '', - GatewayCode: row.GatewayCode, + GatewayId: row.GatewayId && row.GatewayId !== '0' ? row.GatewayId : '', SlaveId: row.SlaveId, ProtocolType: row.ProtocolType, Manufacturer: row.Manufacturer || '', @@ -394,6 +409,7 @@ function onMoreCommand(cmd, row) { onMounted(() => { fetchList() fetchProductOptions() + fetchGatewayOptions() window.addEventListener('click', closeCtxMenu) }) onBeforeUnmount(() => { diff --git a/src/views/config/gateway/Index.vue b/src/views/config/gateway/Index.vue index 174a524..fe6ff4b 100644 --- a/src/views/config/gateway/Index.vue +++ b/src/views/config/gateway/Index.vue @@ -2,13 +2,314 @@
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ detailRow.Code }} + {{ detailRow.Name }} + {{ protocolLabel(detailRow.ProtocolType) }} + {{ connAddress(detailRow) }} + + {{ onlineStatusLabel(detailRow.OnlineStatus) }} + + {{ fmtTime(detailRow.LastConnectedTime) }} + {{ detailRow.LastError || '-' }} + {{ detailRow.IsEnabled ? '是' : '否' }} + {{ detailRow.Remark || '-' }} + + + + + + + + + + + + + + +
+ +