From b05c43105564b174de6476835e7c55bca21fcb31 Mon Sep 17 00:00:00 2001 From: 潘志宝 <979469083@qq.com> Date: 星期六, 14 九月 2024 16:56:34 +0800 Subject: [PATCH] modbus tag --- src/views/data/channel/modbus/ModBusDeviceForm.vue | 89 +++++--- src/api/data/channel/modbus/tag.ts | 43 +++ src/views/data/channel/modbus/tag/index.vue | 234 +++++++++++++++++++++ src/utils/constants.ts | 5 src/views/data/channel/modbus/tag/TagForm.vue | 185 ++++++++++++++++ src/utils/dict.ts | 2 src/views/data/channel/modbus/index.vue | 24 + src/utils/validate.ts | 63 +++++ src/api/data/channel/modbus/index.ts | 2 9 files changed, 610 insertions(+), 37 deletions(-) diff --git a/src/api/data/channel/modbus/index.ts b/src/api/data/channel/modbus/index.ts index d86f914..2fa3860 100644 --- a/src/api/data/channel/modbus/index.ts +++ b/src/api/data/channel/modbus/index.ts @@ -32,7 +32,7 @@ // 新增ModBusDevice export const createModBusDevice = (data: ModBusDeviceVO) => { - return request.post({ url: '/data/channel/modbus/device/add', data }) + return request.post({ url: '/data/channel/modbus/device/create', data }) } // 修改ModBusDevice diff --git a/src/api/data/channel/modbus/tag.ts b/src/api/data/channel/modbus/tag.ts new file mode 100644 index 0000000..a6299df --- /dev/null +++ b/src/api/data/channel/modbus/tag.ts @@ -0,0 +1,43 @@ +import request from '@/config/axios' + +export interface ModBusTagVO { + id: string + tagName: string + dataType: string + enabled: boolean + format: string + tag: string + address: string + samplingRate: number + tagDesc: string +} + +export interface ModBusTagPageReqVO extends PageParam { + tagName?: string + address?: string +} + +// 查询ModBusTag列表 +export const getModBusTagPage = (params: ModBusTagPageReqVO) => { + return request.get({ url: '/data/channel/modbus/tag/page', params }) +} + +// 查询ModBusTag详情 +export const getModBusTag = (id: number) => { + return request.get({ url: `/data/channel/modbus/tag/info/${id}`}) +} + +// 新增ModBusTag +export const createModBusTag = (data: ModBusTagVO) => { + return request.post({ url: '/data/channel/modbus/tag/create', data }) +} + +// 修改ModBusTag +export const updateModBusTag = (data: ModBusTagVO) => { + return request.put({ url: '/data/channel/modbus/tag/update', data }) +} + +// 删除ModBusTag +export const deleteModBusTag = (id: number) => { + return request.delete({ url: '/data/channel/modbus/tag/delete?id=' + id }) +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b21f067..360cf05 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -446,3 +446,8 @@ SALE_OUT: 21, SALE_RETURN: 22 } + +export const CommonEnabled = { + ENABLE: 1, // 启用 + DISABLE: 0 // 禁用 +} diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 12724e0..c0f80f6 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -239,4 +239,6 @@ // ========== DATA - 数据平台模块 ========== DATA_FIELD_TYPE = 'data_field_type', + TAG_DATA_TYPE = 'tag_data_type', + IS_ENABLED = 'is_enabled' } diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..8d754d5 --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,63 @@ +/** + * 邮箱 + * @param {*} s + */ +export function isEmail (s) { + return /^([a-zA-Z0-9._-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s) +} + +/** + * 手机号码 + * @param {*} s + */ +export function isMobile (s) { + return /^1[0-9]{10}$/.test(s) +} + +/** + * 电话号码 + * @param {*} s + */ +export function isPhone (s) { + return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s) +} + +/** + * URL地址 + * @param {*} s + */ +export function isURL (s) { + return /^http[s]?:\/\/.*/.test(s) +} + +/** + * 正整数 + * @param {*} s + */ +export function isPositiveInteger (s) { + return /^[1-9]\d*$/.test(s) +} + +/** + * 整数 + * @param {*} s + */ +export function isInteger (s) { + return /^\d*$/.test(s) +} + +/** + * 正数 + * @param {*} s + */ +export function isPositiveNum (s) { + return /^([0-9]*|\d*.\d{1}?\d*)$/.test(s) +} + +/** + * IP + * @param {*} s + */ +export function isIP (s) { + return /^(\d+\.\d+\.\d+\.\d+)$/.test(s) +} diff --git a/src/views/data/channel/modbus/ModBusDeviceForm.vue b/src/views/data/channel/modbus/ModBusDeviceForm.vue index 5ab02f1..ee57f6a 100644 --- a/src/views/data/channel/modbus/ModBusDeviceForm.vue +++ b/src/views/data/channel/modbus/ModBusDeviceForm.vue @@ -10,62 +10,62 @@ <el-row> <el-col :span="12"> <el-form-item label="设备名称" prop="name"> - <el-input v-model="formData.name" placeholder="请输入设备名称" /> + <el-input v-model="formData.name" placeholder="请输入设备名称"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="IP地址" prop="address"> - <el-input v-model="formData.address" placeholder="请输入IP地址" /> + <el-input v-model="formData.address" placeholder="请输入IP地址"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="端口" prop="port"> - <el-input v-model="formData.port" placeholder="请输入端口" /> + <el-input v-model="formData.port" placeholder="请输入端口"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="不活动超时(ms)" prop="connectInactivityTimeout"> - <el-input v-model="formData.connectInactivityTimeout" placeholder="请输入不活动超时" /> + <el-input v-model="formData.connectInactivityTimeout" placeholder="请输入不活动超时"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="重连超时(ms)" prop="reconnectInterval"> - <el-input v-model="formData.reconnectInterval" placeholder="请输入重连超时" /> + <el-input v-model="formData.reconnectInterval" placeholder="请输入重连超时"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="重试次数" prop="attemptsBeforeTimeout"> - <el-input v-model="formData.attemptsBeforeTimeout" placeholder="请输入重试次数" /> + <el-input v-model="formData.attemptsBeforeTimeout" placeholder="请输入重试次数"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="重试间隔(ms)" prop="waitToRetryMilliseconds"> - <el-input v-model="formData.waitToRetryMilliseconds" placeholder="请输入重试间隔" /> + <el-input v-model="formData.waitToRetryMilliseconds" placeholder="请输入重试间隔"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="读超时(ms)" prop="readTimeout"> - <el-input v-model="formData.readTimeout" placeholder="请输入读超时" /> + <el-input v-model="formData.readTimeout" placeholder="请输入读超时"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="写超时(ms)" prop="writeTimeout"> - <el-input v-model="formData.writeTimeout" placeholder="请输入写超时" /> + <el-input v-model="formData.writeTimeout" placeholder="请输入写超时"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="是否使用优化" prop="useOptimizedBlockRead"> - <el-input v-model="formData.useOptimizedBlockRead" placeholder="请输入是否使用优化" /> + <el-input v-model="formData.useOptimizedBlockRead" placeholder="请输入是否使用优化"/> </el-form-item> </el-col> </el-row> @@ -77,11 +77,12 @@ </Dialog> </template> <script lang="ts" setup> -import * as ModBusApi from '@/api/data/channel/modbus' + import * as ModBusApi from '@/api/data/channel/modbus' + import {isIP, isPositiveInteger} from '@/utils/validate' -defineOptions({ name: 'DataModBusDeviceForm' }) + defineOptions({name: 'DataModBusDeviceForm'}) - const { t } = useI18n() // 国际化 + const {t} = useI18n() // 国际化 const message = useMessage() // 消息弹窗 const dialogVisible = ref(false) // 弹窗的是否展示 const dialogTitle = ref('') // 弹窗的标题 @@ -92,21 +93,43 @@ name: undefined, address: undefined, port: undefined, - connectInactivityTimeout: undefined, - reconnectInterval: undefined, - attemptsBeforeTimeout: undefined, - waitToRetryMilliseconds: undefined, - readTimeout: undefined, - writeTimeout: undefined, - useOptimizedBlockRead: undefined, - projectReference: undefined + connectInactivityTimeout: "", + reconnectInterval: "5000", + attemptsBeforeTimeout: "3", + waitToRetryMilliseconds: "250", + readTimeout: "3000", + writeTimeout: "3000", + useOptimizedBlockRead: "true", + projectReference: '' }) + const validateIP = (rule, value, callback) => { + if (!isIP(value)) { + callback(new Error('IP地址不正确')) + } else { + callback() + } + } + const validateNum = (rule, value, callback) => { + if (!isPositiveInteger(value)) { + callback(new Error('格式不正确')) + } else { + callback() + } + } + const formRules = reactive({ - name: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }], - address: [{ required: true, message: 'IP地址不能为空', trigger: 'blur' }], - port: [{ required: true, message: '端口不能为空', trigger: 'blur' }] + name: [{required: true, message: '设备名称不能为空', trigger: 'blur'}], + address: [ + {required: true, message: 'IP地址不能为空', trigger: 'blur'}, + {validator: validateIP, trigger: 'blur'} + ], + port: [ + {required: true, message: '端口不能为空', trigger: 'blur'}, + {validator: validateNum, trigger: 'blur'} + ] }) const formRef = ref() // 表单 Ref + /** 打开弹窗 */ const open = async (type: string, id?: number) => { @@ -124,7 +147,7 @@ } } } - defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + defineExpose({open}) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -159,14 +182,14 @@ name: undefined, address: undefined, port: undefined, - connectInactivityTimeout: undefined, - reconnectInterval: undefined, - attemptsBeforeTimeout: undefined, - waitToRetryMilliseconds: undefined, - readTimeout: undefined, - writeTimeout: undefined, - useOptimizedBlockRead: undefined, - projectReference: undefined + connectInactivityTimeout: "", + reconnectInterval: "5000", + attemptsBeforeTimeout: "3", + waitToRetryMilliseconds: "250", + readTimeout: "3000", + writeTimeout: "3000", + useOptimizedBlockRead: "true", + projectReference: '' } formRef.value?.resetFields() } diff --git a/src/views/data/channel/modbus/index.vue b/src/views/data/channel/modbus/index.vue index 27b491f..58b0942 100644 --- a/src/views/data/channel/modbus/index.vue +++ b/src/views/data/channel/modbus/index.vue @@ -39,7 +39,7 @@ type="primary" plain @click="openForm('create')" - v-hasPermi="['system:tenant:create']" + v-hasPermi="['data:channel-modbus:create']" > <Icon icon="ep:plus" class="mr-5px" /> 新增 @@ -68,15 +68,23 @@ link type="primary" @click="openForm('update', scope.row.id)" - v-hasPermi="['system:tenant:update']" + v-hasPermi="['data:channel-modbus:update']" > 编辑 </el-button> <el-button link + type="primary" + @click="openTagList(scope.row.name)" + v-hasPermi="['data:channel-modbus:update']" + > + TAG + </el-button> + <el-button + link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['system:tenant:delete']" + v-hasPermi="['data:channel-modbus:delete']" > 删除 </el-button> @@ -95,10 +103,14 @@ <!-- 表单弹窗:添加/修改 --> <ModBusDeviceForm ref="formRef" @success="getList" /> + <!-- TAG弹窗:添加/修改 --> + <TagList ref="tagRef" @success="getList" /> + </template> <script lang="ts" setup> import * as ModbusApi from '@/api/data/channel/modbus' import ModBusDeviceForm from './ModBusDeviceForm.vue' +import TagList from './tag/index.vue' defineOptions({name: 'DataModBus'}) @@ -147,6 +159,12 @@ formRef.value.open(type, id) } + /** TAG操作 */ + const tagRef = ref() + const openTagList = (name?: string) => { + tagRef.value.open(name) + } + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { diff --git a/src/views/data/channel/modbus/tag/TagForm.vue b/src/views/data/channel/modbus/tag/TagForm.vue new file mode 100644 index 0000000..fc70722 --- /dev/null +++ b/src/views/data/channel/modbus/tag/TagForm.vue @@ -0,0 +1,185 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="120px" + > + <el-row> + <el-col :span="12"> + <el-form-item label="关联设备" prop="device"> + <el-input v-model="formData.device" readonly/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="Tag名称" prop="tagName"> + <el-input v-model="formData.tagName" placeholder="请输Tag名称"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="数据类型" prop="dataType"> + <el-select v-model="formData.dataType" placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.TAG_DATA_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="地址" prop="address"> + <el-input v-model="formData.address" placeholder="请输入地址"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="大小端" prop="format"> + <el-input v-model="formData.format" placeholder="请输入大小端" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="采集频率" prop="samplingRate"> + <el-input v-model="formData.samplingRate" placeholder="请输入采集频率"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="是否启用" prop="enabled"> + <el-select v-model="formData.enabled" placeholder="请选择"> + <el-option + v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="描述" prop="tagDesc"> + <el-input v-model="formData.tagDesc" placeholder="描述"/> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + import * as ModBusTagApi from '@/api/data/channel/modbus/tag' + import { CommonEnabled } from '@/utils/constants' + import {isPositiveInteger} from '@/utils/validate' + import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict' + + defineOptions({name: 'ModBusTagForm'}) + + const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('') // 弹窗的标题 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formType = ref('') // 表单的类型:create - 新增;update - 修改 + const formData = ref({ + id: undefined, + tagName: undefined, + dataType: undefined, + enabled: CommonEnabled.ENABLE, + format: undefined, + device: '1', + address: '', + samplingRate: undefined, + tagDesc: '', + }) + const validateNum = (rule, value, callback) => { + if (!isPositiveInteger(value)) { + callback(new Error('格式不正确')) + } else { + callback() + } + } + const formRules = reactive({ + device: [{required: true, message: '关联设备不能为空', trigger: 'blur'}], + address: [ + {required: true, message: '地址不能为空', trigger: 'blur'}, + {validator: validateNum, trigger: 'blur'}], + dataType: [{required: true, message: '数据类型不能为空', trigger: 'blur'}] + }) + const formRef = ref() // 表单 Ref + + /** 打开弹窗 */ + const open = async (type: string, id?: number, device?: string) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + if (device) { + formData.value.device = device + } + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ModBusTagApi.getModBusTag(id) + formData.device = device + } finally { + formLoading.value = false + } + } + } + defineExpose({open}) // 提供 open 方法,用于打开弹窗 + + /** 提交表单 */ + const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 + const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as ModBusTagApi.ModBusTagVO + if (formType.value === 'create') { + await ModBusTagApi.createModBusTag(data) + message.success(t('common.createSuccess')) + } else { + await ModBusTagApi.updateModBusTag(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } + } + + /** 重置表单 */ + const resetForm = () => { + formData.value = { + id: undefined, + tagName: undefined, + dataType: undefined, + enabled: CommonEnabled.ENABLE, + format: undefined, + device: '', + address: '', + samplingRate: undefined, + tagDesc: '', + } + formRef.value?.resetFields() + } +</script> diff --git a/src/views/data/channel/modbus/tag/index.vue b/src/views/data/channel/modbus/tag/index.vue new file mode 100644 index 0000000..335b5e1 --- /dev/null +++ b/src/views/data/channel/modbus/tag/index.vue @@ -0,0 +1,234 @@ +<template> + <el-drawer + v-model="drawer" + size="50%" + title="ModBus Tag" + :direction="direction" + :before-close="handleClose" + > + <!-- 搜索 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="Tag名称" prop="tagName"> + <el-input + v-model="queryParams.tagName" + placeholder="请输入Tag名称" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="地址" prop="address"> + <el-input + v-model="queryParams.address" + placeholder="请输入Modbus地址" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['data:channel-modbus:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column + prop="tagName" + label="Tag名称" + header-align="center" + align="left" + min-width="150" + /> + <el-table-column + prop="tagDesc" + label="Tag描述" + header-align="center" + align="left" + min-width="150" + /> + <el-table-column + prop="dataType" + label="数据类型" + header-align="center" + align="center" + /> + <el-table-column + prop="address" + label="地址" + header-align="center" + align="center" + /> + <el-table-column + prop="format" + label="大小端" + header-align="center" + align="center" + /> + <el-table-column + prop="samplingRate" + label="采集频率" + header-align="center" + align="center" + /> + <el-table-column + prop="enabled" + label="是否启用" + header-align="center" + align="center" + > + <template #default="scope"> + <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag> + <el-tag v-else size="small" type="danger">否</el-tag> + </template> + </el-table-column> + <el-table-column label="操作" align="center" min-width="110" fixed="right"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['data:channel-modbus:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['data:channel-modbus:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + <!-- 表单弹窗:添加/修改 --> + <TagForm ref="formRef" @success="getList" /> + </el-drawer> +</template> +<script lang="ts" setup> + import type { DrawerProps } from 'element-plus' + import * as ModBusTagApi from "@/api/data/channel/modbus/tag"; + import TagForm from './TagForm.vue' + + defineOptions({name: 'ModBusTag'}) + + const message = useMessage() // 消息弹窗 + const {t} = useI18n() // 国际化 + + const drawer = ref(false) + const direction = ref<DrawerProps['direction']>('rtl') + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 列表的数据 + const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + device: undefined, + tagName: undefined, + address: undefined + }) + const queryFormRef = ref() // 搜索的表单 + const exportLoading = ref(false) // 导出的加载中 + + /** 查询列表 */ + const getList = async () => { + loading.value = true + try { + const page = await ModBusTagApi.getModBusTagPage(queryParams) + list.value = page.list + total.value = page.total + } finally { + loading.value = false + } + } + + /** 搜索按钮操作 */ + const handleQuery = () => { + queryParams.pageNo = 1 + getList() + } + + /** 重置按钮操作 */ + const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() + } + + /** 添加/修改操作 */ + const formRef = ref() + const openForm = (type: string, id?: number) => { + formRef.value.open(type, id, queryParams.device) + } + + /** 删除按钮操作 */ + const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ModBusTagApi.deleteModBusTag(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } + } + + /** 打开弹窗 */ + const open = async (device?: string) => { + resetForm() + drawer.value = true + queryParams.device = device + if (device) { + getList() + } + } + defineExpose({open}) // 提供 open 方法,用于打开弹窗 + + /** 重置表单 */ + const resetForm = () => { + queryParams.pageNo = 1 + queryParams.pageSize = 10 + queryParams.device = '' + queryParams.tagName = '' + queryParams.address = '' + } + + const handleClose = (done: () => void) => { + drawer.value = false + } +</script> -- Gitblit v1.9.3