From db0a1198773c95a2680887d23d0fbaba7f8475de Mon Sep 17 00:00:00 2001 From: dengzedong <dengzedong@email> Date: 星期三, 18 九月 2024 09:25:00 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- src/views/data/channel/opcua/tag/TagForm.vue | 143 ++++++ src/views/data/channel/opcua/tag/index.vue | 221 +++++++++ src/utils/constants.ts | 5 src/views/data/channel/opcda/tag/TagForm.vue | 141 ++++++ src/views/data/channel/kio/tag/TagForm.vue | 162 +++++++ src/views/data/channel/opcua/index.vue | 58 + src/api/data/channel/opcua/tag.ts | 41 + src/api/data/channel/opcda/index.ts | 2 src/api/data/channel/opcua/index.ts | 4 src/api/data/channel/opcda/tag.ts | 40 + src/views/data/channel/modbus/tag/TagForm.vue | 1 src/views/data/channel/opcda/index.vue | 24 src/views/data/channel/opcda/tag/index.vue | 198 ++++++++ src/views/data/channel/kio/index.vue | 46 + src/views/data/channel/kio/tag/index.vue | 214 +++++++++ src/api/data/channel/kio/tag.ts | 42 + 16 files changed, 1,301 insertions(+), 41 deletions(-) diff --git a/src/api/data/channel/kio/tag.ts b/src/api/data/channel/kio/tag.ts new file mode 100644 index 0000000..32a714e --- /dev/null +++ b/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 }) +} diff --git a/src/api/data/channel/opcda/index.ts b/src/api/data/channel/opcda/index.ts index 2347d4a..9dccd36 100644 --- a/src/api/data/channel/opcda/index.ts +++ b/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 diff --git a/src/api/data/channel/opcda/tag.ts b/src/api/data/channel/opcda/tag.ts new file mode 100644 index 0000000..4f90eb1 --- /dev/null +++ b/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 }) +} diff --git a/src/api/data/channel/opcua/index.ts b/src/api/data/channel/opcua/index.ts index f553a41..c9edeef 100644 --- a/src/api/data/channel/opcua/index.ts +++ b/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 }) } diff --git a/src/api/data/channel/opcua/tag.ts b/src/api/data/channel/opcua/tag.ts new file mode 100644 index 0000000..e1373fb --- /dev/null +++ b/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 }) +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 360cf05..d0b4888 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -451,3 +451,8 @@ ENABLE: 1, // 启用 DISABLE: 0 // 禁用 } + +export const CommonEnabledBool = { + ENABLE: true, // 启用 + DISABLE: false // 禁用 +} diff --git a/src/views/data/channel/kio/index.vue b/src/views/data/channel/kio/index.vue index 36cea40..eeeb0fc 100644 --- a/src/views/data/channel/kio/index.vue +++ b/src/views/data/channel/kio/index.vue @@ -19,20 +19,20 @@ </el-form-item> <el-form-item> <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> + <Icon icon="ep:search" class="mr-5px"/> 搜索 </el-button> <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> + <Icon icon="ep:refresh" class="mr-5px"/> 重置 </el-button> <el-button type="primary" plain @click="openForm('create')" - v-hasPermi="['system:tenant:create']" + v-hasPermi="['data:channel-kio:create']" > - <Icon icon="ep:plus" class="mr-5px" /> + <Icon icon="ep:plus" class="mr-5px"/> 新增 </el-button> </el-form-item> @@ -42,25 +42,33 @@ <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list"> - <el-table-column label="实例名称" align="center" prop="instanceName" /> - <el-table-column label="IP地址" align="center" prop="address" /> - <el-table-column label="端口" align="center" prop="port" /> - <el-table-column label="用户名" align="center" prop="username" /> + <el-table-column label="实例名称" align="center" prop="instanceName"/> + <el-table-column label="IP地址" align="center" prop="address"/> + <el-table-column label="端口" align="center" prop="port"/> + <el-table-column label="用户名" align="center" prop="username"/> <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="['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> @@ -77,14 +85,18 @@ </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <KioDeviceForm ref="formRef" @success="getList" /> + <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 * as KioApi from '@/api/data/channel/kio' + import KioDeviceForm from './KioDeviceForm.vue' + import TagList from './tag/index.vue' -defineOptions({name: 'DataKio'}) + defineOptions({name: 'DataKio'}) const message = useMessage() // 消息弹窗 const {t} = useI18n() // 国际化 @@ -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 { diff --git a/src/views/data/channel/kio/tag/TagForm.vue b/src/views/data/channel/kio/tag/TagForm.vue new file mode 100644 index 0000000..1f3604e --- /dev/null +++ b/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 diff --git a/src/views/data/channel/kio/tag/index.vue b/src/views/data/channel/kio/tag/index.vue new file mode 100644 index 0000000..32d489a --- /dev/null +++ b/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> diff --git a/src/views/data/channel/modbus/tag/TagForm.vue b/src/views/data/channel/modbus/tag/TagForm.vue index fc70722..a8ec15d 100644 --- a/src/views/data/channel/modbus/tag/TagForm.vue +++ b/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 } diff --git a/src/views/data/channel/opcda/index.vue b/src/views/data/channel/opcda/index.vue index d9b62fb..b538779 100644 --- a/src/views/data/channel/opcda/index.vue +++ b/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 { diff --git a/src/views/data/channel/opcda/tag/TagForm.vue b/src/views/data/channel/opcda/tag/TagForm.vue new file mode 100644 index 0000000..9f4b6ab --- /dev/null +++ b/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> diff --git a/src/views/data/channel/opcda/tag/index.vue b/src/views/data/channel/opcda/tag/index.vue new file mode 100644 index 0000000..98f57da --- /dev/null +++ b/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> diff --git a/src/views/data/channel/opcua/index.vue b/src/views/data/channel/opcua/index.vue index a30307a..25a160f 100644 --- a/src/views/data/channel/opcua/index.vue +++ b/src/views/data/channel/opcua/index.vue @@ -19,20 +19,20 @@ </el-form-item> <el-form-item> <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> + <Icon icon="ep:search" class="mr-5px"/> 搜索 </el-button> <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> + <Icon icon="ep:refresh" class="mr-5px"/> 重置 </el-button> <el-button type="primary" plain @click="openForm('create')" - v-hasPermi="['system:tenant:create']" + v-hasPermi="['data:channel-opcua:create']" > - <Icon icon="ep:plus" class="mr-5px" /> + <Icon icon="ep:plus" class="mr-5px"/> 新增 </el-button> </el-form-item> @@ -42,31 +42,39 @@ <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list"> - <el-table-column label="服务名" align="center" prop="serverName" /> - <el-table-column label="端点URL" align="center" prop="endpointUrl" /> - <el-table-column label="安全策略" align="center" prop="securityPolicy" /> - <el-table-column label="安全模式" align="center" prop="securityMode" /> - <el-table-column label="连接方式" align="center" prop="connectionType" /> - <el-table-column label="用户名" align="center" prop="userName" /> - <el-table-column label="密码" align="center" prop="password" /> - <el-table-column label="安全证书路径" align="center" prop="certificatePath" /> - <el-table-column label="设备不活动超时时间" align="center" prop="connectInactivityTimeout" /> - <el-table-column label="重连超时" align="center" prop="reconnectInterval" /> + <el-table-column label="服务名" align="center" prop="serverName"/> + <el-table-column label="端点URL" align="center" prop="endpointUrl"/> + <el-table-column label="安全策略" align="center" prop="securityPolicy"/> + <el-table-column label="安全模式" align="center" prop="securityMode"/> + <el-table-column label="连接方式" align="center" prop="connectionType"/> + <el-table-column label="用户名" align="center" prop="userName"/> + <el-table-column label="密码" align="center" prop="password"/> + <el-table-column label="安全证书路径" align="center" prop="certificatePath"/> + <el-table-column label="设备不活动超时时间" align="center" prop="connectInactivityTimeout"/> + <el-table-column label="重连超时" align="center" prop="reconnectInterval"/> <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="['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> @@ -83,14 +91,18 @@ </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <OpcUaDeviceForm ref="formRef" @success="getList" /> + <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 * as OpcUaApi from '@/api/data/channel/opcua' + import OpcUaDeviceForm from './OpcUaDeviceForm.vue' + import TagList from './tag/index.vue' -defineOptions({name: 'DataOpcUa'}) + defineOptions({name: 'DataOpcUa'}) const message = useMessage() // 消息弹窗 const {t} = useI18n() // 国际化 @@ -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 { diff --git a/src/views/data/channel/opcua/tag/TagForm.vue b/src/views/data/channel/opcua/tag/TagForm.vue new file mode 100644 index 0000000..810fbff --- /dev/null +++ b/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> diff --git a/src/views/data/channel/opcua/tag/index.vue b/src/views/data/channel/opcua/tag/index.vue new file mode 100644 index 0000000..a535147 --- /dev/null +++ b/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> -- Gitblit v1.9.3