潘志宝
2024-09-18 24d32ba3488d6b097525ecc086113778be6f41dc
data tag
已添加9个文件
已修改7个文件
1286 ■■■■■ 文件已修改
src/api/data/channel/kio/tag.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/channel/opcda/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/channel/opcda/tag.ts 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/channel/opcua/index.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/channel/opcua/tag.ts 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/constants.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/kio/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/kio/tag/TagForm.vue 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/kio/tag/index.vue 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/modbus/tag/TagForm.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/opcda/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/opcda/tag/TagForm.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/opcda/tag/index.vue 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/opcua/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/opcua/tag/TagForm.vue 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/channel/opcua/tag/index.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/channel/kio/tag.ts
对比新文件
@@ -0,0 +1,42 @@
import request from '@/config/axios'
export interface KioTagVO {
  id: string
  tagName: string
  dataType: string
  tagId: number
  tagDesc: string
  enabled: boolean
  device: string
  samplingRate: number
}
export interface KioTagPageReqVO extends PageParam {
  tagName?: string
  tagDesc?: string
}
// 查询KioTag列表
export const getKioTagPage = (params: KioTagPageReqVO) => {
  return request.get({ url: '/data/channel/kio/tag/page', params })
}
// 查询KioTag详情
export const getKioTag = (id: number) => {
  return request.get({ url: `/data/channel/kio/tag/info/${id}`})
}
// 新增KioTag
export const createKioTag = (data: KioTagVO) => {
  return request.post({ url: '/data/channel/kio/tag/create', data })
}
// 修改KioTag
export const updateKioTag = (data: KioTagVO) => {
  return request.put({ url: '/data/channel/kio/tag/update', data })
}
// 删除KioTag
export const deleteKioTag = (id: number) => {
  return request.delete({ url: '/data/channel/kio/tag/delete?id=' + id })
}
src/api/data/channel/opcda/index.ts
@@ -26,7 +26,7 @@
// 新增OpcDaDevice
export const createOpcDaDevice = (data: OpcDaDeviceVO) => {
  return request.post({ url: '/data/channel/opcda/device/add', data })
  return request.post({ url: '/data/channel/opcda/device/create', data })
}
// 修改OpcDaDevice
src/api/data/channel/opcda/tag.ts
对比新文件
@@ -0,0 +1,40 @@
import request from '@/config/axios'
export interface OpcdaTagVO {
  id: string
  serverId: string
  tagName: string
  dataType: string
  enabled: boolean
  itemId: string
}
export interface OpcdaTagPageReqVO extends PageParam {
  serverId?: string
  tagName?: string
}
// 查询OpcdaTag列表
export const getOpcdaTagPage = (params: OpcdaTagPageReqVO) => {
  return request.get({ url: '/data/channel/opcda/tag/page', params })
}
// 查询OpcdaTag详情
export const getOpcdaTag = (id: number) => {
  return request.get({ url: `/data/channel/opcda/tag/info/${id}`})
}
// 新增OpcdaTag
export const createOpcdaTag = (data: OpcdaTagVO) => {
  return request.post({ url: '/data/channel/opcda/tag/create', data })
}
// 修改OpcdaTag
export const updateOpcdaTag = (data: OpcdaTagVO) => {
  return request.put({ url: '/data/channel/opcda/tag/update', data })
}
// 删除OpcdaTag
export const deleteOpcdaTag = (id: number) => {
  return request.delete({ url: '/data/channel/opcda/tag/delete?id=' + id })
}
src/api/data/channel/opcua/index.ts
@@ -30,7 +30,7 @@
// 新增OpcUaDevice
export const createOpcUaDevice = (data: OpcUaDeviceVO) => {
  return request.post({ url: '/data/channel/opcua/device/add', data })
  return request.post({ url: '/data/channel/opcua/device/create', data })
}
// 修改OpcUaDevice
@@ -39,6 +39,6 @@
}
// 删除OpcUaDevice
export const deleteOpcUaDevice = (id: number) => {
export const deleteOpcUaDevice = (id: string) => {
  return request.delete({ url: '/data/channel/opcua/device/delete?id=' + id })
}
src/api/data/channel/opcua/tag.ts
对比新文件
@@ -0,0 +1,41 @@
import request from '@/config/axios'
export interface OpcuaTagVO {
  id: string
  device: string
  tagName: string
  dataType: string
  enabled: boolean
  address: string
  samplingRate: number
}
export interface OpcuaTagPageReqVO extends PageParam {
  device?: string
  tagName?: string
}
// 查询OpcuaTag列表
export const getOpcuaTagPage = (params: OpcuaTagPageReqVO) => {
  return request.get({ url: '/data/channel/opcua/tag/page', params })
}
// 查询OpcuaTag详情
export const getOpcuaTag = (id: number) => {
  return request.get({ url: `/data/channel/opcua/tag/info/${id}`})
}
// 新增OpcuaTag
export const createOpcuaTag = (data: OpcuaTagVO) => {
  return request.post({ url: '/data/channel/opcua/tag/create', data })
}
// 修改OpcuaTag
export const updateOpcuaTag = (data: OpcuaTagVO) => {
  return request.put({ url: '/data/channel/opcua/tag/update', data })
}
// 删除OpcuaTag
export const deleteOpcuaTag = (id: number) => {
  return request.delete({ url: '/data/channel/opcua/tag/delete?id=' + id })
}
src/utils/constants.ts
@@ -451,3 +451,8 @@
  ENABLE: 1, // 启用
  DISABLE: 0 // 禁用
}
export const CommonEnabledBool = {
  ENABLE: true, // 启用
  DISABLE: false // 禁用
}
src/views/data/channel/kio/index.vue
@@ -30,7 +30,7 @@
          type="primary"
          plain
          @click="openForm('create')"
          v-hasPermi="['system:tenant:create']"
          v-hasPermi="['data:channel-kio:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" />
          新增
@@ -52,15 +52,23 @@
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
            v-hasPermi="['system:tenant:update']"
            v-hasPermi="['data:channel-kio:update']"
          >
            编辑
          </el-button>
          <el-button
            link
            type="primary"
            @click="openTagList(scope.row.name)"
            v-hasPermi="['data:channel-kio:update']"
          >
            TAG
          </el-button>
          <el-button
            link
            type="danger"
            @click="handleDelete(scope.row.id)"
            v-hasPermi="['system:tenant:delete']"
            v-hasPermi="['data:channel-kio:delete']"
          >
            删除
          </el-button>
@@ -79,10 +87,14 @@
  <!-- 表单弹窗:添加/修改 -->
  <KioDeviceForm ref="formRef" @success="getList" />
  <!-- TAG弹窗:添加/修改 -->
  <TagList ref="tagRef" @success="getList" />
</template>
<script lang="ts" setup>
import * as KioApi from '@/api/data/channel/kio'
import KioDeviceForm from './KioDeviceForm.vue'
  import TagList from './tag/index.vue'
defineOptions({name: 'DataKio'})
@@ -131,6 +143,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/kio/tag/TagForm.vue
对比新文件
@@ -0,0 +1,162 @@
<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="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="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 KioTagApi from '@/api/data/channel/kio/tag'
  import { CommonEnabled } from '@/utils/constants'
  import {isPositiveInteger} from '@/utils/validate'
  import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
  defineOptions({name: 'KioTagForm'})
  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,
    tagId: undefined,
    tagDesc: '',
    enabled: CommonEnabled.ENABLE,
    device: undefined,
    samplingRate: undefined
  })
  const validateNum = (rule, value, callback) => {
    if (!isPositiveInteger(value)) {
      callback(new Error('格式不正确'))
    } else {
      callback()
    }
  }
  const formRules = reactive({
    tagName: [{required: true, message: 'Tag名称不能为空', 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 KioTagApi.getKioTag(id)
      } 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 KioTagApi.KioTagVO
      if (formType.value === 'create') {
        await KioTagApi.createKioTag(data)
        message.success(t('common.createSuccess'))
      } else {
        await KioTagApi.updateKioTag(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,
      tagId: undefined,
      tagDesc: '',
      enabled: CommonEnabled.ENABLE,
      device: undefined,
      samplingRate: undefined
    }
    formRef.value?.resetFields()
  }
</script>
ss
src/views/data/channel/kio/tag/index.vue
对比新文件
@@ -0,0 +1,214 @@
<template>
  <el-drawer
    v-model="drawer"
    size="50%"
    title="Kio 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-kio: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="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-kio:update']"
            >
              编辑
            </el-button>
            <el-button
              link
              type="danger"
              @click="handleDelete(scope.row.id)"
              v-hasPermi="['data:channel-kio: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 KioTagApi from "@/api/data/channel/kio/tag";
  import TagForm from './TagForm.vue'
  defineOptions({name: 'KioTag'})
  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
  })
  const queryFormRef = ref() // 搜索的表单
  const exportLoading = ref(false) // 导出的加载中
  /** 查询列表 */
  const getList = async () => {
    loading.value = true
    try {
      const page = await KioTagApi.getKioTagPage(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 KioTagApi.deleteKioTag(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 = ''
  }
  const handleClose = (done: () => void) => {
    drawer.value = false
  }
</script>
src/views/data/channel/modbus/tag/TagForm.vue
@@ -133,7 +133,6 @@
      formLoading.value = true
      try {
        formData.value = await ModBusTagApi.getModBusTag(id)
        formData.device = device
      } finally {
        formLoading.value = false
      }
src/views/data/channel/opcda/index.vue
@@ -30,7 +30,7 @@
          type="primary"
          plain
          @click="openForm('create')"
          v-hasPermi="['system:tenant:create']"
          v-hasPermi="['data:channel-opcda:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" />
          新增
@@ -53,15 +53,23 @@
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
            v-hasPermi="['system:tenant:update']"
            v-hasPermi="['data:channel-opcda:update']"
          >
            编辑
          </el-button>
          <el-button
            link
            type="primary"
            @click="openTagList(scope.row.id)"
            v-hasPermi="['data:channel-opcda:update']"
          >
            TAG
          </el-button>
          <el-button
            link
            type="danger"
            @click="handleDelete(scope.row.id)"
            v-hasPermi="['system:tenant:delete']"
            v-hasPermi="['data:channel-opcda:delete']"
          >
            删除
          </el-button>
@@ -80,10 +88,14 @@
  <!-- 表单弹窗:添加/修改 -->
  <OpcDaDeviceForm ref="formRef" @success="getList" />
  <!-- TAG弹窗:添加/修改 -->
  <TagList ref="tagRef" @success="getList" />
</template>
<script lang="ts" setup>
import * as OpcDaApi from '@/api/data/channel/opcda'
import OpcDaDeviceForm from './OpcDaDeviceForm.vue'
import TagList from './tag/index.vue'
defineOptions({name: 'DataOpcDa'})
@@ -131,6 +143,12 @@
    formRef.value.open(type, id)
  }
  /** TAG操作 */
  const tagRef = ref()
  const openTagList = (id?: string) => {
    tagRef.value.open(id)
  }
  /** 删除按钮操作 */
  const handleDelete = async (id: number) => {
    try {
src/views/data/channel/opcda/tag/TagForm.vue
对比新文件
@@ -0,0 +1,141 @@
<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="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="itemId" prop="itemId">
            <el-input v-model="formData.itemId" placeholder="请输入ItemId"/>
          </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-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 OpcdaTagApi from '@/api/data/channel/opcda/tag'
  import { CommonEnabledBool } from '@/utils/constants'
  import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
  defineOptions({name: 'OpcdaTagForm'})
  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,
    serverId: undefined,
    tagName: undefined,
    dataType: undefined,
    enabled: CommonEnabledBool.ENABLE,
    itemId: undefined
  })
  const formRules = reactive({
    tagName: [{required: true, message: '标签名不能为空', trigger: 'blur'}],
    dataType: [{required: true, message: '数据类型不能为空', trigger: 'blur'}]
  })
  const formRef = ref() // 表单 Ref
  /** 打开弹窗 */
  const open = async (type: string, id?: number, serverId?: string) => {
    dialogVisible.value = true
    dialogTitle.value = t('action.' + type)
    formType.value = type
    resetForm()
    if (serverId) {
      formData.value.serverId = serverId
    }
    // 修改时,设置数据
    if (id) {
      formLoading.value = true
      try {
        formData.value = await OpcdaTagApi.getOpcdaTag(id)
      } 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 OpcdaTagApi.OpcdaTagVO
      if (formType.value === 'create') {
        await OpcdaTagApi.createOpcdaTag(data)
        message.success(t('common.createSuccess'))
      } else {
        await OpcdaTagApi.updateOpcdaTag(data)
        message.success(t('common.updateSuccess'))
      }
      dialogVisible.value = false
      // 发送操作成功的事件
      emit('success')
    } finally {
      formLoading.value = false
    }
  }
  /** 重置表单 */
  const resetForm = () => {
    formData.value = {
      id: undefined,
      serverId: undefined,
      tagName: undefined,
      dataType: undefined,
      enabled: CommonEnabledBool.ENABLE,
      itemId: undefined
    }
    formRef.value?.resetFields()
  }
</script>
src/views/data/channel/opcda/tag/index.vue
对比新文件
@@ -0,0 +1,198 @@
<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>
          <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="dataType"
          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 OpcdaTagApi from "@/api/data/channel/opcda/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,
    serverId: undefined,
    tagName: undefined
  })
  const queryFormRef = ref() // 搜索的表单
  const exportLoading = ref(false) // 导出的加载中
  /** 查询列表 */
  const getList = async () => {
    loading.value = true
    try {
      const page = await OpcdaTagApi.getOpcdaTagPage(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.serverId)
  }
  /** 删除按钮操作 */
  const handleDelete = async (id: number) => {
    try {
      // 删除的二次确认
      await message.delConfirm()
      // 发起删除
      await OpcdaTagApi.deleteOpcdaTag(id)
      message.success(t('common.delSuccess'))
      // 刷新列表
      await getList()
    } catch {
    }
  }
  /** 打开弹窗 */
  const open = async (serverId?: string) => {
    resetForm()
    drawer.value = true
    queryParams.serverId = serverId
    if (serverId) {
      getList()
    }
  }
  defineExpose({open}) // 提供 open 方法,用于打开弹窗
  /** 重置表单 */
  const resetForm = () => {
    queryParams.pageNo = 1
    queryParams.pageSize = 10
    queryParams.serverId = ''
    queryParams.tagName = ''
  }
  const handleClose = (done: () => void) => {
    drawer.value = false
  }
</script>
src/views/data/channel/opcua/index.vue
@@ -30,7 +30,7 @@
          type="primary"
          plain
          @click="openForm('create')"
          v-hasPermi="['system:tenant:create']"
          v-hasPermi="['data:channel-opcua:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" />
          新增
@@ -58,15 +58,23 @@
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
            v-hasPermi="['system:tenant:update']"
            v-hasPermi="['data:channel-opcua:update']"
          >
            编辑
          </el-button>
          <el-button
            link
            type="primary"
            @click="openTagList(scope.row.serverName)"
            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-opcua:delete']"
          >
            删除
          </el-button>
@@ -85,10 +93,14 @@
  <!-- 表单弹窗:添加/修改 -->
  <OpcUaDeviceForm ref="formRef" @success="getList" />
  <!-- TAG弹窗:添加/修改 -->
  <TagList ref="tagRef" @success="getList"/>
</template>
<script lang="ts" setup>
import * as OpcUaApi from '@/api/data/channel/opcua'
import OpcUaDeviceForm from './OpcUaDeviceForm.vue'
  import TagList from './tag/index.vue'
defineOptions({name: 'DataOpcUa'})
@@ -136,6 +148,12 @@
    formRef.value.open(type, id)
  }
  /** TAG操作 */
  const tagRef = ref()
  const openTagList = (serverName?: string) => {
    tagRef.value.open(serverName)
  }
  /** 删除按钮操作 */
  const handleDelete = async (id: number) => {
    try {
src/views/data/channel/opcua/tag/TagForm.vue
对比新文件
@@ -0,0 +1,143 @@
<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="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="address" prop="address">
            <el-input v-model="formData.address" 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-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 OpcuaTagApi from '@/api/data/channel/opcua/tag'
  import { CommonEnabledBool } from '@/utils/constants'
  import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
  defineOptions({name: 'OpcuaTagForm'})
  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,
    device: undefined,
    tagName: undefined,
    dataType: undefined,
    enabled: CommonEnabledBool.ENABLE,
    address: undefined,
    samplingRate: undefined
  })
  const formRules = reactive({
    tagName: [{required: true, message: '标签名不能为空', 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 OpcuaTagApi.getOpcuaTag(id)
      } 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 OpcuaTagApi.OpcuaTagVO
      if (formType.value === 'create') {
        await OpcuaTagApi.createOpcuaTag(data)
        message.success(t('common.createSuccess'))
      } else {
        await OpcuaTagApi.updateOpcuaTag(data)
        message.success(t('common.updateSuccess'))
      }
      dialogVisible.value = false
      // 发送操作成功的事件
      emit('success')
    } finally {
      formLoading.value = false
    }
  }
  /** 重置表单 */
  const resetForm = () => {
    formData.value = {
      id: undefined,
      device: undefined,
      tagName: undefined,
      dataType: undefined,
      enabled: CommonEnabledBool.ENABLE,
      address: undefined,
      samplingRate: undefined
    }
    formRef.value?.resetFields()
  }
</script>
src/views/data/channel/opcua/tag/index.vue
对比新文件
@@ -0,0 +1,221 @@
<template>
  <el-drawer
    v-model="drawer"
    size="50%"
    title="Opcua 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="请输入地址"
            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-opcua: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="dataType"
          label="数据类型"
          header-align="center"
          align="center"
        />
        <el-table-column
          prop="address"
          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-opcua:update']"
            >
              编辑
            </el-button>
            <el-button
              link
              type="danger"
              @click="handleDelete(scope.row.id)"
              v-hasPermi="['data:channel-opcua: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 OpcuaTagApi from "@/api/data/channel/opcua/tag";
  import TagForm from './TagForm.vue'
  defineOptions({name: 'OpcuaTag'})
  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 OpcuaTagApi.getOpcuaTagPage(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 OpcuaTagApi.deleteOpcuaTag(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>