refactor:采集设备-设备属性列表重构

main
黄伟杰 1 month ago
parent f612f1c1a2
commit 0cfe869931

@ -4,7 +4,12 @@ NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://localhost:48081'
# VITE_BASE_URL='http://localhost:48081'
# 线上环境
# VITE_BASE_URL='https://besure.ngsk.tech:7001'
# 本地联调
VITE_BASE_URL='http://192.168.5.113:48081'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=server

File diff suppressed because it is too large Load Diff

@ -66,21 +66,21 @@ export const DeviceApi = {
},
// 新增设备属性
createDeviceAttribute: async (data) => {
return await request.post({ url: `/iot/device/device-attribute/create`, data })
return await request.post({ url: `/iot/device-contact-model/create`, data })
},
// 修改设备属性
updateDeviceAttribute: async (data) => {
return await request.put({ url: `/iot/device/device-attribute/update`, data })
return await request.put({ url: `/iot/device-contact-model/update`, data })
},
// 删除设备属性
deleteDeviceAttribute: async (id: number) => {
return await request.delete({ url: `/iot/device/device-attribute/delete?id=` + id })
return await request.delete({ url: `/iot/device-contact-model/delete?id=` + id })
},
// 获得设备属性
getDeviceAttribute: async (id: number) => {
return await request.get({ url: `/iot/device/device-attribute/get?id=` + id })
return await request.get({ url: `/iot/device-contact-model/get?id=` + id })
}
}

@ -56,7 +56,7 @@ export default defineComponent({
//
onMounted(() => {
const tableRef = unref(elTableRef)
emit('register', tableRef?.$parent, elTableRef)
emit('register', tableRef?.$parent, elTableRef.value)
})
const pageSizeRef = ref(props.pageSize)

@ -7,36 +7,24 @@
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="属性编码" prop="attributeCode">
<el-input v-model="formData.attributeCode" placeholder="请输入属性编码" />
<el-form-item label="点位编码" prop="attributeCode">
<el-input
v-model="formData.attributeCode"
placeholder="请输入点位编码"
@input="handleAttributeCodeInput"
/>
</el-form-item>
<el-form-item label="属性名称" prop="attributeName">
<el-input v-model="formData.attributeName" placeholder="请输入属性名称" />
<el-form-item label="点位名称" prop="attributeName">
<el-input v-model="formData.attributeName" placeholder="请输入点位名称" />
</el-form-item>
<el-form-item label="PLC点位地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入PLC点位地址" />
</el-form-item>
<el-form-item label="地址描述" prop="description">
<Editor v-model="formData.description" height="150px" />
</el-form-item>
<el-form-item label="地址类型" prop="attributeType">
<el-select v-model="formData.attributeType" placeholder="请选择地址类型">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.MES_DATA_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="读写方式" prop="ioType">
<el-select v-model="formData.ioType" placeholder="请选择读写方式">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ATTRIBUTE_IO_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
<el-form-item label="点位类型" prop="attributeType">
<el-select
v-model="formData.attributeType"
clearable
filterable
placeholder="请选择点位类型"
>
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.name" />
</el-select>
</el-form-item>
<el-form-item label="数据类型" prop="dataType">
@ -49,45 +37,37 @@
/>
</el-select>
</el-form-item>
<el-form-item label="类型描述" prop="dataTypeRemark">
<el-input v-model="formData.dataTypeRemark" placeholder="请输入类型描述" />
<el-form-item label="寄存器地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入寄存器地址" />
</el-form-item>
<el-form-item label="单位" prop="dataUnit">
<el-input v-model="formData.dataUnit" placeholder="请输入单位" />
</el-form-item>
<el-form-item label="数据计算公式" prop="dataFormula">
<el-input v-model="formData.dataFormula" placeholder="请输入数据计算公式" />
</el-form-item>
<el-form-item label="网关id" prop="gatewayId">
<el-input v-model="formData.gatewayId" placeholder="请输入网关id" />
<el-form-item label="倍率" prop="ratio">
<el-input v-model="formData.ratio" placeholder="请输入倍率" />
</el-form-item>
<el-form-item label="告警id" prop="alertId">
<el-input v-model="formData.alertId" placeholder="请输入告警id" />
<el-form-item label="顺序" prop="sort">
<el-input v-model="formData.sort" placeholder="请输入顺序" @input="handleSortInput" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="是否启用" prop="isEnable">
<el-radio-group v-model="formData.isEnable">
<el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
<el-input
v-model="formData.remark"
placeholder="请输入备注"
maxlength="100"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { DeviceApi } from '@/api/iot/device'
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
const { t } = useI18n() //
const message = useMessage() //
@ -96,43 +76,153 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const typeList = ref<DeviceAttributeTypeVO[]>([])
const loadTypeList = async () => {
if (typeList.value.length > 0) {
return
}
try {
const data = await DeviceAttributeTypeApi.getDeviceAttributeTypePage({ pageNo: 1, pageSize: 10 })
typeList.value = data?.list ?? []
} catch {
typeList.value = []
}
}
const formData = ref({
id: undefined,
attributeCode: undefined,
attributeName: undefined,
address: undefined,
description: undefined,
attributeType: undefined,
ioType: undefined,
dataType: undefined,
dataTypeRemark: undefined,
dataUnit: undefined,
dataFormula: undefined,
gatewayId: undefined,
deviceId: undefined,
alertId: undefined,
remark: undefined,
isEnable: undefined
id: undefined as number | undefined,
attributeCode: undefined as string | undefined,
attributeName: undefined as string | undefined,
attributeType: undefined as string | undefined,
dataType: undefined as string | undefined,
address: undefined as string | undefined,
dataUnit: undefined as string | undefined,
ratio: undefined as string | undefined,
sort: undefined as string | undefined,
remark: undefined as string | undefined,
deviceModelId: undefined as number | undefined
})
const handleAttributeCodeInput = (val: string) => {
formData.value.attributeCode = val?.replace(/[\u4e00-\u9fa5]/g, '')
}
const handleSortInput = (val: string) => {
formData.value.sort = val?.replace(/\D/g, '')
}
const formRules = reactive({
attributeCode: [{ required: true, message: '属性编码不能为空', trigger: 'blur' }],
attributeName: [{ required: true, message: '属性名称不能为空', trigger: 'blur' }],
isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }]
attributeCode: [
{ required: true, message: '点位编码不能为空', trigger: 'blur' },
{
validator: (_rule: any, value: string, callback: any) => {
if (!value) {
callback()
return
}
if (/[\u4e00-\u9fa5]/.test(value)) {
callback(new Error('点位编码不允许输入中文'))
return
}
callback()
},
trigger: ['blur', 'change']
}
],
attributeName: [{ required: true, message: '点位名称不能为空', trigger: 'blur' }],
sort: [
{
validator: (_rule: any, value: string, callback: any) => {
if (!value) {
callback()
return
}
if (!/^\d+$/.test(value)) {
callback(new Error('顺序只能输入数字'))
return
}
callback()
},
trigger: ['blur', 'change']
}
],
remark: [
{
validator: (_rule: any, value: string, callback: any) => {
if (value && value.length > 100) {
callback(new Error('备注不能超过100字'))
return
}
callback()
},
trigger: ['blur', 'change']
}
]
})
const formRef = ref() // Ref
const buildSubmitData = () => {
const {
id,
attributeCode,
attributeName,
attributeType,
dataType,
address,
dataUnit,
ratio,
sort,
remark,
deviceModelId
} = formData.value
const data: any = {
attributeCode,
attributeName,
attributeType,
dataType,
address,
dataUnit,
ratio,
sort,
remark,
deviceModelId
}
if (formType.value === 'update') {
data.id = id
}
return data
}
/** 打开弹窗 */
const open = async (type: string, id?: number, deviceId: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
formData.value.deviceId = deviceId
formData.value.deviceModelId = deviceId
await loadTypeList()
//
if (id) {
formLoading.value = true
try {
formData.value = await DeviceApi.getDeviceAttribute(id)
if (!(formData.value as any)?.deviceModelId) {
;(formData.value as any).deviceModelId = deviceId
}
const currentType = (formData.value as any)?.attributeType
if (currentType !== undefined && currentType !== null && currentType !== '') {
const matched = typeList.value.find(
(item) =>
item.name === currentType ||
item.id === currentType ||
item.code === currentType ||
String(item.id) === String(currentType)
)
if (matched?.name) {
;(formData.value as any).attributeType = matched.name
}
}
} finally {
formLoading.value = false
}
@ -148,7 +238,7 @@ const submitForm = async () => {
//
formLoading.value = true
try {
const data = formData.value
const data = buildSubmitData()
if (formType.value === 'create') {
await DeviceApi.createDeviceAttribute(data)
message.success(t('common.createSuccess'))
@ -170,19 +260,14 @@ const resetForm = () => {
id: undefined,
attributeCode: undefined,
attributeName: undefined,
address: undefined,
description: undefined,
attributeType: undefined,
ioType: undefined,
dataType: undefined,
dataTypeRemark: undefined,
address: undefined,
dataUnit: undefined,
dataFormula: undefined,
gatewayId: undefined,
deviceId: undefined,
alertId: undefined,
ratio: undefined,
sort: undefined,
remark: undefined,
isEnable: undefined
deviceModelId: undefined
}
formRef.value?.resetFields()
}

@ -1,51 +1,99 @@
<template>
<!-- 列表 -->
<ContentWrap>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['iot:device:create']"
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="属性编码" align="left" prop="attributeCode" width="150px"/>
<el-table-column label="属性名称" align="left" prop="attributeName" width="150px"/>
<el-table-column label="PLC点位地址" align="center" prop="address" />
<el-table-column label="地址描述" align="center" prop="description" />
<el-table-column label="地址类型" align="center" prop="attributeType">
<el-form-item label="点位编码" prop="attributeCode">
<el-input
v-model="queryParams.attributeCode"
placeholder="请输入点位编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="点位名称" prop="attributeName">
<el-input
v-model="queryParams.attributeName"
placeholder="请输入点位名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="点位类型" prop="attributeType">
<el-select
v-model="queryParams.attributeType"
clearable
filterable
placeholder="请选择"
class="!w-240px"
>
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.name" />
</el-select>
</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="['iot:device:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column label="点位编码" align="left" prop="attributeCode" width="150px" />
<el-table-column label="点位名称" align="left" prop="attributeName" width="150px" />
<el-table-column label="点位类型" align="center" prop="attributeType" width="120px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_DATA_TYPE" :value="scope.row.attributeType" />
{{ getAttributeTypeLabel(scope.row.attributeType) }}
</template>
</el-table-column>
<el-table-column label="读写方式" align="center" prop="ioType">
<el-table-column label="数据类型" align="center" prop="dataType" width="120px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_ATTRIBUTE_IO_TYPE" :value="scope.row.ioType" />
<dict-tag :type="DICT_TYPE.IOT_DEVICE_DATA_TYPE" :value="scope.row.dataType" />
</template>
</el-table-column>
<el-table-column label="数据类型" align="center" prop="dataType">
<el-table-column label="寄存器地址" align="center" prop="address" min-width="140px" />
<el-table-column label="最新值" align="center" prop="latestValue" min-width="120px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_DEVICE_DATA_TYPE" :value="scope.row.dataType" />
{{ scope.row.latestValue ?? '-' }}
</template>
</el-table-column>
<el-table-column label="类型描述" align="center" prop="dataTypeRemark" />
<el-table-column label="单位" align="center" prop="dataUnit" />
<el-table-column label="计算公式" align="center" prop="dataFormula" />
<el-table-column label="告警" align="center" prop="alertId" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="单位" align="center" prop="dataUnit" width="80px" />
<el-table-column label="倍率" align="center" prop="ratio" width="80px" />
<el-table-column
label="创建时间"
label="最新采集时间"
align="center"
prop="createTime"
prop="latestCollectTime"
:formatter="dateFormatter"
width="170px"
/>
<el-table-column label="是否启用" align="center" prop="isEnable" fixed="right">
<el-table-column label="顺序" align="center" prop="sort" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.isEnable" />
{{ scope.row.sort ?? '-' }}
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="160px" />
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
@ -67,7 +115,6 @@
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
@ -75,14 +122,15 @@
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<DeviceAttributeForm ref="formRef" @success="getList" />
<DeviceAttributeForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { DeviceApi } from '@/api/iot/device'
import DeviceAttributeForm from './DeviceAttributeForm.vue'
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
const { t } = useI18n() //
const message = useMessage() //
@ -90,15 +138,38 @@ const message = useMessage() // 消息弹窗
const props = defineProps<{
deviceId?: number // id
}>()
const typeList = ref<DeviceAttributeTypeVO[]>([])
const loading = ref(false) //
const list = ref([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceId: undefined as unknown
deviceId: undefined as unknown,
attributeCode: undefined,
attributeName: undefined,
attributeType: undefined
})
const queryFormRef = ref()
const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []
}
const getAttributeTypeLabel = (attributeType: any) => {
if (attributeType === undefined || attributeType === null || attributeType === '') {
return ''
}
const matched = typeList.value.find(
(item) => item.name === attributeType || item.id === attributeType || item.code === attributeType
)
return matched?.name ?? String(attributeType)
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
() => props.deviceId,
@ -113,7 +184,7 @@ watch(
)
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true
try {
const data = await DeviceApi.getDeviceAttributePage(queryParams)
@ -125,11 +196,26 @@ const getList = async () => {
}
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
if (!props.deviceId) {
return
}
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
if (!props.deviceId) {
return
}
queryFormRef.value?.resetFields()
queryParams.attributeCode = undefined
queryParams.attributeName = undefined
queryParams.attributeType = undefined
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
@ -152,4 +238,13 @@ const handleDelete = async (id: number) => {
await getList()
} catch {}
}
onMounted(async () => {
try {
const data = await DeviceAttributeTypeApi.getDeviceAttributeTypePage({ pageNo: 1, pageSize: 10 })
typeList.value = data?.list ?? []
} catch {
typeList.value = []
}
})
</script>

@ -85,8 +85,6 @@
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
highlight-current-row
@current-change="handleCurrentChange"
>
<el-table-column label="设备编号" align="left" prop="deviceCode" width="150px"/>
<el-table-column label="设备名称" align="left" prop="deviceName" width="200px"/>
@ -131,8 +129,9 @@
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.isEnable" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="150px">
<el-table-column label="操作" align="center" fixed="right" width="220px">
<template #default="scope">
<el-button link type="primary" @click.stop="handleShowAttribute(scope.row)">点位</el-button>
<el-button
link
type="primary"
@ -166,8 +165,9 @@
<!-- 子表的列表 -->
<ContentWrap>
<el-tabs model-value="deviceAttribute">
<el-tab-pane label="设备属性" name="deviceAttribute">
<DeviceAttributeList :device-id="currentRow.id" />
<el-tab-pane :label="deviceAttributeTabLabel" name="deviceAttribute">
<DeviceAttributeList v-if="attributeDeviceId" :device-id="attributeDeviceId" />
<el-empty v-else description="请点击设备列表的“点位”查看设备属性" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
@ -269,10 +269,19 @@ const handleExport = async () => {
}
}
/** 选中行操作 */
const currentRow = ref({}) //
const handleCurrentChange = (row) => {
currentRow.value = row
const attributeDeviceId = ref<number | undefined>(undefined)
const attributeDeviceName = ref('')
const deviceAttributeTabLabel = computed(() => {
if (!attributeDeviceId.value) {
return '设备属性'
}
return attributeDeviceName.value ? `设备属性:${attributeDeviceName.value}` : '设备属性'
})
const handleShowAttribute = (row: any) => {
attributeDeviceId.value = row?.id
attributeDeviceName.value = row?.deviceName ?? ''
}
let timer: any = null;

@ -29,14 +29,14 @@ export default ({command, mode}: ConfigEnv): UserConfig => {
host: "0.0.0.0",
open: env.VITE_OPEN === 'true',
// 本地跨域代理. 目前注释的原因暂时没有用途server 端已经支持跨域
// proxy: {
// ['/admin-api']: {
// target: env.VITE_BASE_URL,
// ws: false,
// changeOrigin: true,
// rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''),
// },
// },
proxy: {
['/admin-api']: {
target: env.VITE_BASE_URL,
ws: false,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''),
},
},
},
// 项目使用的vite插件。 单独提取到build/vite/plugin中管理
plugins: createVitePlugins(),

Loading…
Cancel
Save