潘志宝
2024-09-14 b05c43105564b174de6476835e7c55bca21fcb31
modbus tag
已添加4个文件
已修改5个文件
617 ■■■■■ 文件已修改
src/api/data/channel/modbus/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/channel/modbus/tag.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/constants.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dict.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/validate.ts 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/modbus/ModBusDeviceForm.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/modbus/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/modbus/tag/TagForm.vue 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/modbus/tag/index.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
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 })
}
src/utils/constants.ts
@@ -446,3 +446,8 @@
  SALE_OUT: 21,
  SALE_RETURN: 22
}
export const CommonEnabled = {
  ENABLE: 1, // 启用
  DISABLE: 0 // 禁用
}
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'
}
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)
}
src/views/data/channel/modbus/ModBusDeviceForm.vue
@@ -78,6 +78,7 @@
</template>
<script lang="ts" setup>
import * as ModBusApi from '@/api/data/channel/modbus'
  import {isIP, isPositiveInteger} from '@/utils/validate'
defineOptions({ name: 'DataModBusDeviceForm' })
@@ -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' }]
    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) => {
@@ -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()
  }
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 {
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>
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>