kkk-ops 3 months ago
commit 35ad4f9a79

@ -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

@ -4,12 +4,12 @@ NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://192.168.5.5:48081'
VITE_BASE_URL=' '
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://192.168.5.5:48081/admin-api/infra/file/upload'
VITE_UPLOAD_URL='/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api

@ -86,7 +86,7 @@
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
"editor.defaultFormatter": "Vue.volar"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",

File diff suppressed because it is too large Load Diff

@ -7,6 +7,7 @@ export interface DeviceVO {
deviceName: string // 设备名称
deviceType: string // 设备类型
status: string // 状态
isConnect?: string | number
readTopic: string // 读主题
writeTopic: string // 写主题
gatewayId: number // 网关id
@ -21,6 +22,13 @@ export interface DeviceVO {
url: string // 端点url
username: string // 用户名
password: string // 密码
certificate?: string // 证书
secretKey?: string // 秘钥
}
export interface DeviceConnectParams {
id: string | number
isConnect: string | number
}
// 物联设备 API
@ -49,8 +57,17 @@ export const DeviceApi = {
},
// 删除物联设备
deleteDevice: async (id: number) => {
return await request.delete({ url: `/iot/device/delete?id=` + id })
deleteDevice: async (ids: string) => {
return await request.delete({ url: `/iot/device/delete?ids=` + ids })
},
// 复制物联设备
copyDevice: async (id: number) => {
return await request.post({ url: `/iot/device/copy`, params: { id } })
},
connectDevice: async (params: DeviceConnectParams) => {
return await request.post({ url: `/iot/device/connect`, data: params })
},
// 导出物联设备 Excel
@ -66,21 +83,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 })
deleteDeviceAttribute: async (ids: string) => {
return await request.delete({ url: `/iot/device-contact-model/delete?ids=` + ids })
},
// 获得设备属性
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 })
}
}

@ -5,6 +5,7 @@ export interface DeviceAttributeTypeVO {
id: number // ID
code: string // 分类编码
name: string // 分类名称
sort: number // 分类顺序
remark: string // 备注
}
@ -31,8 +32,8 @@ export const DeviceAttributeTypeApi = {
},
// 删除采集点分类
deleteDeviceAttributeType: async (id: number) => {
return await request.delete({ url: `/iot/device-attribute-type/delete?id=` + id })
deleteDeviceAttributeType: async (ids: string) => {
return await request.delete({ url: `/iot/device-attribute-type/delete?ids=` + ids })
},
// 导出采集点分类 Excel
@ -40,8 +41,8 @@ export const DeviceAttributeTypeApi = {
return await request.download({ url: `/iot/device-attribute-type/export-excel`, params })
},
// 获得采集点分类列表
getDeviceAttributeTypeList: async () => {
return await request.get({ url: `/iot/device-attribute-type/list`})
},
// 获得采集点分类列表
getDeviceAttributeTypeList: async () => {
return await request.get({ url: `/iot/device-attribute-type/list` })
},
}

@ -32,8 +32,13 @@ export const DeviceModelApi = {
},
// 删除采集设备模型
deleteDeviceModel: async (id: number) => {
return await request.delete({ url: `/iot/device-model/delete?id=` + id })
deleteDeviceModel: async (ids: string) => {
return await request.delete({ url: `/iot/device-model/delete?ids=` + ids })
},
// 复制采集设备模型
copyDeviceModel: async (id: number) => {
return await request.post({ url: `/iot/device-model/copy`, params: { id } })
},
// 导出采集设备模型 Excel
@ -41,8 +46,8 @@ export const DeviceModelApi = {
return await request.download({ url: `/iot/device-model/export-excel`, params })
},
// 获得采集设备模型列表
getDeviceModelList: async () => {
return await request.get({ url: `/iot/device-model/list`})
},
// 获得采集设备模型列表
getDeviceModelList: async () => {
return await request.get({ url: `/iot/device-model/list` })
},
}

@ -37,8 +37,8 @@ export const DeviceModelAttributeApi = {
},
// 删除采集设备模型-点位管理
deleteDeviceModelAttribute: async (id: number) => {
return await request.delete({ url: `/iot/device-model-attribute/delete?id=` + id })
deleteDeviceModelAttribute: async (ids: string) => {
return await request.delete({ url: `/iot/device-model-attribute/delete?ids=` + ids })
},
// 导出采集设备模型-点位管理 Excel

@ -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)

@ -15,7 +15,7 @@ const dictStore = useDictStoreWithOut()
export interface DictDataType {
dictType: string
label: string
value: string | number | boolean
value: string | number
colorType: ElementPlusInfoType | ''
cssClass: string
}

@ -1,18 +1,31 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<template #title>
<div class="flex flex-col">
<div>{{ dialogTitle }}</div>
<div
v-if="formType === 'setting' && formData.deviceName"
class="text-12px leading-16px text-[var(--el-text-color-secondary)]"
>
设备名称{{ formData.deviceName }}
</div>
</div>
</template>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
:rules="activeRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="formData.deviceCode" placeholder="请输入设备编号" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="formData.deviceName" placeholder="请输入设备名称" />
</el-form-item>
<template v-if="formType === 'create'">
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="formData.deviceCode" placeholder="请输入设备编号" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="formData.deviceName" placeholder="请输入设备名称" />
</el-form-item>
</template>
<!-- <el-form-item label="设备类型" prop="deviceType">
<el-select v-model="formData.deviceType" placeholder="请选择设备类型">
<el-option
@ -49,56 +62,73 @@
<!-- <el-form-item label="离线间隔" prop="offLineDuration">
<el-input v-model="formData.offLineDuration" placeholder="请输入离线间隔" />
</el-form-item> -->
<el-form-item label="模型选择" prop="deviceModelId">
<el-select
v-model="formData.deviceModelId"
clearable
filterable
placeholder="请选择设备模型"
>
<el-option
v-for="item in modelList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="采集周期/s" prop="sampleCycle">
<el-input v-model="formData.sampleCycle" placeholder="请输入采集周期" />
</el-form-item>
<el-form-item label="端点URL" prop="url">
<el-input v-model="formData.url" placeholder="请输入端点URL" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="formData.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" placeholder="请输入密码" />
</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"
<template v-if="formType === 'create'">
<el-form-item label="模型选择" prop="deviceModelId">
<el-select
v-model="formData.deviceModelId"
clearable
filterable
placeholder="请选择设备模型"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-option v-for="item in modelList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="采集周期/s" prop="sampleCycle">
<el-input v-model="formData.sampleCycle" placeholder="请输入采集周期" />
</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-form-item>
</template>
<template v-else-if="formType === 'update'">
<el-form-item label="采集周期/s" prop="sampleCycle">
<el-input v-model="formData.sampleCycle" 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-form-item>
</template>
<template v-else>
<el-form-item label="端点URL" prop="url">
<el-input v-model="formData.url" placeholder="请输入端点URL" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="formData.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" placeholder="请输入密码" show-password />
</el-form-item>
</template>
</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 { getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import { DeviceApi, DeviceVO } from '@/api/iot/device'
import {DeviceModelApi, DeviceModelVO} from "@/api/iot/devicemodel";
@ -135,16 +165,28 @@ const formData = ref({
password: undefined,
})
const formRules = reactive({
deviceCode: [{ required: true, message: '设备编码不能为空', trigger: 'blur' }],
deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }]
create: {
deviceCode: [{ required: true, message: '设备编码不能为空', trigger: 'blur' }],
deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }]
},
update: {
isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }]
},
setting: {
url: [{ required: true, message: '端点URL不能为空', trigger: 'blur' }]
}
})
const activeRules = computed(() => {
return (formRules as any)[formType.value] ?? {}
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
dialogTitle.value = type === 'setting' ? '设备设置' : t('action.' + type)
formType.value = type
resetForm()
//
@ -167,11 +209,18 @@ const submitForm = async () => {
//
formLoading.value = true
try {
const data = formData.value as unknown as DeviceVO
const { id, deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable, url, username, password } = formData.value as any
if (formType.value === 'create') {
await DeviceApi.createDevice(data)
const data: Partial<DeviceVO> = { deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable }
await DeviceApi.createDevice(data as DeviceVO)
message.success(t('common.createSuccess'))
} else if (formType.value === 'update') {
const data: any = { id, deviceCode, deviceName, deviceModelId, sampleCycle, isEnable }
await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess'))
} else {
const data: any = { id, deviceCode, deviceName, deviceModelId, isEnable, url, username, password }
await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess'))
}
@ -198,7 +247,13 @@ const resetForm = () => {
offLineDuration: undefined,
lastOnlineTime: undefined,
remark: undefined,
isEnable: true
isEnable: true,
deviceModelId: undefined,
protocol: undefined,
sampleCycle: undefined,
url: undefined,
username: undefined,
password: undefined,
}
formRef.value?.resetFields()
}

@ -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,31 +76,131 @@ 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,
deviceId: 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,
deviceId
} = formData.value
const parsedSort =
sort === undefined || sort === null || sort === ''
? undefined
: Number.isNaN(Number(sort))
? undefined
: Number(sort)
const data: any = {
attributeCode,
attributeName,
attributeType,
dataType,
address,
dataUnit,
ratio,
sort: parsedSort,
remark,
deviceId
}
if (formType.value === 'update') {
data.id = id
}
return data
}
/** 打开弹窗 */
const open = async (type: string, id?: number, deviceId: number) => {
dialogVisible.value = true
@ -128,11 +208,34 @@ const open = async (type: string, id?: number, deviceId: number) => {
formType.value = type
resetForm()
formData.value.deviceId = deviceId
await loadTypeList()
//
if (id) {
formLoading.value = true
try {
formData.value = await DeviceApi.getDeviceAttribute(id)
if (!(formData.value as any)?.deviceId) {
;(formData.value as any).deviceId = deviceId
}
const currentSort = (formData.value as any)?.sort
if (currentSort !== undefined && currentSort !== null) {
;(formData.value as any).sort = String(currentSort)
}
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 +251,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 +273,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
deviceId: undefined
}
formRef.value?.resetFields()
}

@ -1,104 +1,146 @@
<template>
<!-- 列表 -->
<ContentWrap>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['iot:device:create']"
>
<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 class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="100px">
<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-button
type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['iot:device:export']">
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
<el-button type="danger" plain @click="handleBatchDelete" v-hasPermi="['iot:device:delete']">
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
ref="tableRef" 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="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="170px"
/>
<el-table-column label="是否启用" align="center" prop="isEnable" fixed="right">
label="最新采集时间" align="center" prop="latestCollectTime" :formatter="dateFormatter"
width="170px" />
<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
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['iot:device:update']"
>
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['iot:device:update']">
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['iot:device:delete']"
>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['iot:device:delete']">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@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 download from '@/utils/download'
import { DeviceApi } from '@/api/iot/device'
import { DeviceModelAttributeApi } from '@/api/iot/devicemodelattribute'
import DeviceAttributeForm from './DeviceAttributeForm.vue'
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
const { t } = useI18n() //
const message = useMessage() //
const tableRef = ref()
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 exportLoading = ref(false)
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,
@ -109,11 +151,11 @@ watch(
queryParams.deviceId = val
handleQuery()
},
{ immediate: true, deep: true }
{ immediate: true, deep: true }
)
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true
try {
const data = await DeviceApi.getDeviceAttributePage(queryParams)
@ -125,11 +167,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) => {
@ -141,15 +198,55 @@ const openForm = (type: string, id?: number) => {
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
const buildIdsParam = (ids: number | number[]) => {
return Array.isArray(ids) ? ids.join(',') : String(ids)
}
const handleDelete = async (ids: number | number[]) => {
try {
//
await message.delConfirm()
//
await DeviceApi.deleteDeviceAttribute(id)
await DeviceApi.deleteDeviceAttribute(buildIdsParam(ids))
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()
//
await getList()
} catch {}
} catch { }
}
const handleBatchDelete = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要删除的数据')
return
}
await handleDelete(selectedIds.value)
}
/** 导出按钮操作 */
const handleExport = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要导出的数据')
return
}
try {
await message.exportConfirm()
exportLoading.value = true
const data = await DeviceModelAttributeApi.exportDeviceModelAttribute({ ids: selectedIds.value.join(',') })
download.excel(data, '采集设备-点位管理.xls')
} catch {
} finally {
exportLoading.value = false
}
}
onMounted(async () => {
try {
const data = await DeviceAttributeTypeApi.getDeviceAttributeTypePage({ pageNo: 1, pageSize: 10 })
typeList.value = data?.list ?? []
} catch {
typeList.value = []
}
})
</script>

@ -27,7 +27,7 @@
/>
</el-form-item>
<el-form-item label="连接状态" prop="status">
<!-- <el-form-item label="连接状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
@ -41,9 +41,9 @@
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form-item> -->
<el-form-item label="创建时间" prop="createTime">
<!-- <el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -53,7 +53,7 @@
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
</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>
@ -74,6 +74,9 @@
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
<el-button type="danger" plain @click="handleBatchDelete" v-hasPermi="['iot:device:delete']">
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
@ -81,44 +84,47 @@
<!-- 列表 -->
<ContentWrap>
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
highlight-current-row
@current-change="handleCurrentChange"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column label="设备编号" align="left" prop="deviceCode" width="150px"/>
<el-table-column label="设备名称" align="left" prop="deviceName" width="200px"/>
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column label="设备编号" align="left" prop="deviceCode"/>
<el-table-column label="设备名称" align="left" prop="deviceName"/>
<!-- <el-table-column label="设备类型" align="left" prop="deviceType" width="150px"> -->
<!-- <template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_DEVICE_TYPE" :value="scope.row.deviceType" />
</template>
</el-table-column> -->
<el-table-column label="通讯协议" align="left" prop="protocol" width="200px">
<el-table-column label="采集协议" align="left" prop="protocol" width="250px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_PROTOCOL" :value="scope.row.procotol" />
<dict-tag :type="DICT_TYPE.IOT_PROTOCOL" :value="scope.row.protocol" />
</template>
</el-table-column>
<el-table-column label="采集周期" align="left" prop="sampleCycle" width="150px"/>
<el-table-column label="状态" align="center" prop="status">
<el-table-column label="连接状态" align="center" prop="status" width="200px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_GATEWAY_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="采集周期(s)" align="left" prop="sampleCycle" width="200px"/>
<!-- <el-table-column label="读主题" align="center" prop="readTopic" />
<el-table-column label="写主题" align="center" prop="writeTopic" />
<el-table-column label="网关id" align="center" prop="gatewayId" /> -->
<!-- <el-table-column label="设备品牌id" align="center" prop="deviceBrandId" />-->
<!-- <el-table-column label="离线间隔" align="center" prop="offLineDuration" /> -->
<el-table-column
<!-- <el-table-column
label="最后上线时间"
align="center"
prop="lastOnlineTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="备注" align="center" prop="remark" />
/> -->
<!-- <el-table-column label="备注" align="center" prop="remark" /> -->
<!-- <el-table-column
label="创建时间"
align="center"
@ -126,13 +132,30 @@
:formatter="dateFormatter"
width="170px"
/> -->
<el-table-column label="是否启用" align="center" prop="isEnable" fixed="right">
<el-table-column label="是否启用" align="center" prop="isEnable" fixed="right" width="200px">
<template #default="scope">
<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="380px">
<template #default="scope">
<el-button link type="primary" @click.stop="handleShowAttribute(scope.row)">点位</el-button>
<el-button
link
type="primary"
@click="openForm('setting', scope.row.id)"
v-hasPermi="['iot:device:update']"
>
设置
</el-button>
<el-button
link
type="primary"
@click="handleCopy(scope.row.id)"
v-hasPermi="['iot:device:create']"
>
复制
</el-button>
<el-button
link
type="primary"
@ -141,6 +164,14 @@
>
编辑
</el-button>
<el-button
link
:type="isRowConnected(scope.row) ? 'warning' : 'success'"
:loading="!!connectLoadingMap[scope.row.id]"
@click.stop="handleToggleConnect(scope.row)"
>
{{ isRowConnected(scope.row) ? '断开连接' : '连接' }}
</el-button>
<el-button
link
type="danger"
@ -166,8 +197,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>
@ -177,7 +209,7 @@
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { DeviceApi, DeviceVO } from '@/api/iot/device'
import { DeviceApi, DeviceConnectParams, DeviceVO } from '@/api/iot/device'
import DeviceForm from './DeviceForm.vue'
import DeviceAttributeList from './components/DeviceAttributeList.vue'
@ -188,6 +220,8 @@ defineOptions({ name: 'Device' })
const message = useMessage() //
const { t } = useI18n() //
const tableRef = ref()
const loading = ref(true) //
const list = ref<DeviceVO[]>([]) //
const total = ref(0) //
@ -211,6 +245,11 @@ const queryParams = reactive({
const queryFormRef = ref() //
const exportLoading = ref(false) //
const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []
}
/** 查询列表 */
const getList = async () => {
loading.value = true
@ -242,26 +281,57 @@ const openForm = (type: string, id?: number) => {
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
const buildIdsParam = (ids: number | number[]) => {
return Array.isArray(ids) ? ids.join(',') : String(ids)
}
const handleDelete = async (ids: number | number[]) => {
try {
//
await message.delConfirm()
//
await DeviceApi.deleteDevice(id)
const idsParam = buildIdsParam(ids)
await DeviceApi.deleteDevice(idsParam)
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()
if (attributeDeviceId.value && idsParam.split(',').includes(String(attributeDeviceId.value))) {
attributeDeviceId.value = undefined
attributeDeviceName.value = ''
}
//
await getList()
} catch {}
}
const handleBatchDelete = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要删除的数据')
return
}
await handleDelete(selectedIds.value)
}
const handleCopy = async (id: number) => {
try {
await DeviceApi.copyDevice(id)
message.success('复制成功')
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要导出的数据')
return
}
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await DeviceApi.exportDevice(queryParams)
const data = await DeviceApi.exportDevice({ ids: selectedIds.value.join(',') })
download.excel(data, '物联设备.xls')
} catch {
} finally {
@ -269,10 +339,53 @@ 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 ?? ''
}
const connectLoadingMap = reactive<Record<number, boolean>>({})
const getRowConnectValue = (row: any) => {
return row?.isConnect ?? row?.status
}
const isRowConnected = (row: any) => {
return String(getRowConnectValue(row)) === '1'
}
const handleToggleConnect = async (row: DeviceVO) => {
const id = row?.id
if (!id) {
message.error('设备信息不完整')
return
}
const nextIsConnect = isRowConnected(row) ? '2' : '1'
const actionText = nextIsConnect === '1' ? '连接' : '断开连接'
try {
await message.confirm(`确认${actionText}设备“${row.deviceName ?? ''}”吗?`)
connectLoadingMap[id] = true
const params: DeviceConnectParams = { id: String(id), isConnect: nextIsConnect }
await DeviceApi.connectDevice(params)
message.success(`${actionText}成功`)
await getList()
} catch {
} finally {
connectLoadingMap[id] = false
}
}
let timer: any = null;

@ -13,6 +13,9 @@
<el-form-item label="分类名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入分类名称" />
</el-form-item>
<el-form-item label="分类顺序" prop="sort">
<el-input v-model="formData.sort" placeholder="请输入分类顺序" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
@ -40,11 +43,13 @@ const formData = ref({
id: undefined,
code: undefined,
name: undefined,
sort: 0,
remark: undefined,
})
const formRules = reactive({
code: [{ required: true, message: '分类编码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '分类顺序不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
@ -59,6 +64,7 @@ const open = async (type: string, id?: number) => {
formLoading.value = true
try {
formData.value = await DeviceAttributeTypeApi.getDeviceAttributeType(id)
;(formData.value as any).sort = Number((formData.value as any).sort ?? 0)
} finally {
formLoading.value = false
}
@ -75,6 +81,7 @@ const submitForm = async () => {
formLoading.value = true
try {
const data = formData.value as unknown as DeviceAttributeTypeVO
data.sort = Number((data as any).sort ?? 0)
if (formType.value === 'create') {
await DeviceAttributeTypeApi.createDeviceAttributeType(data)
message.success(t('common.createSuccess'))
@ -96,8 +103,9 @@ const resetForm = () => {
id: undefined,
code: undefined,
name: undefined,
sort: 0,
remark: undefined,
}
formRef.value?.resetFields()
}
</script>
</script>

@ -26,7 +26,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<!-- <el-form-item label="备注" prop="remark">
<el-input
v-model="queryParams.remark"
placeholder="请输入备注"
@ -34,8 +34,8 @@
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
</el-form-item> -->
<!-- <el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -45,7 +45,7 @@
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
</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>
@ -57,6 +57,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="danger"
plain
@click="handleBatchDelete"
v-hasPermi="['iot:device-attribute-type:delete']"
>
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
<el-button
type="success"
plain
@ -72,9 +80,19 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table
ref="tableRef"
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="center" prop="code" />
<el-table-column label="分类名称" align="center" prop="name" />
<el-table-column label="分类顺序" align="center" prop="sort" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column
label="创建时间"
@ -83,7 +101,7 @@
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" min-width="120px">
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
link
@ -129,6 +147,8 @@ defineOptions({ name: 'DeviceAttributeType' })
const message = useMessage() //
const { t } = useI18n() //
const tableRef = ref()
const loading = ref(true) //
const list = ref<DeviceAttributeTypeVO[]>([]) //
const total = ref(0) //
@ -143,6 +163,11 @@ const queryParams = reactive({
const queryFormRef = ref() //
const exportLoading = ref(false) //
const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []
}
/** 查询列表 */
const getList = async () => {
loading.value = true
@ -174,18 +199,32 @@ const openForm = (type: string, id?: number) => {
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
const buildIdsParam = (ids: number | number[]) => {
return Array.isArray(ids) ? ids.join(',') : String(ids)
}
const handleDelete = async (ids: number | number[]) => {
try {
//
await message.delConfirm()
//
await DeviceAttributeTypeApi.deleteDeviceAttributeType(id)
await DeviceAttributeTypeApi.deleteDeviceAttributeType(buildIdsParam(ids))
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()
//
await getList()
} catch {}
}
const handleBatchDelete = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要删除的数据')
return
}
await handleDelete(selectedIds.value)
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
@ -205,4 +244,4 @@ const handleExport = async () => {
onMounted(() => {
getList()
})
</script>
</script>

@ -12,23 +12,17 @@
</el-form-item>
<el-form-item label="点位名称" prop="attributeName">
<el-input v-model="formData.attributeName" placeholder="请输入点位名称" />
<el-form-item label="点位类型" prop="attributeType">
<el-select
v-model="formData.attributeType"
clearable
filterable
placeholder="请选择点位类型"
class="!w-180px"
>
<el-option
v-for="item in typeList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item label="点位类型" prop="attributeType">
<el-select
v-model="formData.attributeType"
clearable
filterable
placeholder="请选择点位类型"
class="!w-180px"
>
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="数据类型" prop="dataType">
<el-select v-model="formData.dataType" placeholder="请选择数据类型">
@ -44,7 +38,7 @@
<el-input v-model="formData.address" placeholder="请输入寄存器地址" />
</el-form-item>
<el-form-item label="单位" prop="dataUnit">
<el-form-item label="单位" prop="dataUnit">
<el-select v-model="formData.dataUnit" placeholder="请选择单位">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_ATTRIBUTE_UNIT)"
@ -68,8 +62,9 @@
</Dialog>
</template>
<script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { DeviceModelAttributeApi, DeviceModelAttributeVO } from '@/api/iot/devicemodelattribute'
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { DeviceModelAttributeApi } from '@/api/iot/devicemodelattribute'
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
/** 采集设备模型-点位管理 表单 */
defineOptions({ name: 'DeviceModelAttributeForm' })
@ -77,6 +72,21 @@ defineOptions({ name: 'DeviceModelAttributeForm' })
const { t } = useI18n() //
const message = useMessage() //
const typeList = ref<DeviceAttributeTypeVO[]>([])
const typeListLoaded = ref(false)
const loadTypeList = async () => {
if (typeListLoaded.value) {
return
}
try {
const data = await DeviceAttributeTypeApi.getDeviceAttributeTypePage({ pageNo: 1, pageSize: 10 })
typeList.value = data?.list ?? []
typeListLoaded.value = true
} catch {
typeList.value = []
}
}
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
@ -100,13 +110,13 @@ const formRules = reactive({
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id: number,modelId:number) => {
const open = async (type: string, id: number, modelId: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
await loadTypeList()
formData.value.deviceModelId = modelId
console.log('formData.value.deviceModelId',formData.value.deviceModelId)
//
if (id) {
formLoading.value = true
@ -159,4 +169,4 @@ const resetForm = () => {
}
formRef.value?.resetFields()
}
</script>
</script>

@ -1,48 +1,23 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="100px">
<el-form-item label="点位编码" prop="attributeCode">
<el-input
v-model="queryParams.attributeCode"
placeholder="请输入点位编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
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"
/>
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.id"
/>
</el-select>
</el-form-item>
<el-form-item label="寄存器地址" prop="address">
<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.id" />
</el-select>
</el-form-item>
<!-- <el-form-item label="寄存器地址" prop="address">
<el-input
v-model="queryParams.address"
placeholder="请输入寄存器地址"
@ -50,8 +25,8 @@
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
</el-form-item> -->
<!-- <el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -61,35 +36,37 @@
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
</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-model:create']"
>
<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-model:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['iot:device-model-attribute:export']"
>
type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['iot:device-model:export']">
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
<el-button type="danger" plain @click="handleBatchDelete" v-hasPermi="['iot:device-model:delete']">
<Icon icon="ep:delete" 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">
<el-table-column label="ID" align="center" prop="id" />
<el-table
ref="tableRef" 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="ID" align="center" prop="id" /> -->
<el-table-column label="点位编码" align="center" prop="attributeCode" />
<el-table-column label="点位名称" align="center" prop="attributeName" />
<el-table-column label="点位类型" align="center" prop="attributeType" />
@ -99,29 +76,15 @@
<el-table-column label="倍率" align="center" prop="ratio" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="采集设备模型id" align="center" prop="deviceModelId" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" min-width="120px">
<el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['iot:device-model-attribute:update']"
>
link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['iot:device-model:update']">
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['iot:device-model-attribute:delete']"
>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['iot:device-model:delete']">
删除
</el-button>
</template>
@ -129,11 +92,8 @@
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
@ -145,7 +105,7 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { DeviceModelAttributeApi, DeviceModelAttributeVO } from '@/api/iot/devicemodelattribute'
import DeviceModelAttributeForm from './DeviceModelAttributeForm.vue'
import {DeviceAttributeTypeApi, DeviceAttributeTypeVO} from "@/api/iot/deviceattributetype";
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
const props = defineProps<{
id?: number // id
@ -156,6 +116,8 @@ const typeList = ref<DeviceAttributeTypeVO[]>([]) // 列表
const message = useMessage() //
const { t } = useI18n() //
const tableRef = ref()
const loading = ref(false) //
const list = ref<DeviceModelAttributeVO[]>([]) //
const total = ref(0) //
@ -177,6 +139,11 @@ const queryParams = reactive({
const queryFormRef = ref() //
const exportLoading = ref(false) //
const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
() => props.id,
@ -187,11 +154,11 @@ watch(
queryParams.id = val
handleQuery()
},
{ immediate: true, deep: true }
{ immediate: true, deep: true }
)
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true
try {
const data = await DeviceModelAttributeApi.getDeviceModelAttributePage(queryParams)
@ -203,7 +170,7 @@ const getList = async () => {
}
/** 搜索按钮操作 */
const handleQuery = () => {
function handleQuery() {
if (!props.id) {
return
}
@ -227,31 +194,49 @@ const openForm = (type: string, id?: number) => {
message.error('请选择一个采集设备模型')
return
}
formRef.value.open(type, id,props.id)
formRef.value.open(type, id, props.id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
const buildIdsParam = (ids: number | number[]) => {
return Array.isArray(ids) ? ids.join(',') : String(ids)
}
const handleDelete = async (ids: number | number[]) => {
try {
//
await message.delConfirm()
//
await DeviceModelAttributeApi.deleteDeviceModelAttribute(id)
await DeviceModelAttributeApi.deleteDeviceModelAttribute(buildIdsParam(ids))
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()
//
await getList()
} catch {}
} catch { }
}
const handleBatchDelete = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要删除的数据')
return
}
await handleDelete(selectedIds.value)
}
/** 导出按钮操作 */
const handleExport = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要导出的数据')
return
}
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await DeviceModelAttributeApi.exportDeviceModelAttribute(queryParams)
const data = await DeviceModelAttributeApi.exportDeviceModelAttribute({ ids: selectedIds.value.join(',') })
download.excel(data, '采集设备模型-点位管理.xls')
} catch {
} finally {
@ -266,4 +251,4 @@ onMounted(async () => {
})
</script>
</script>

@ -1,49 +1,27 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form-item label="模型编码" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入模型编码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
v-model="queryParams.code" placeholder="请输入模型编码" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item label="模型名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入模型名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
v-model="queryParams.name" placeholder="请输入模型名称" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item label="通讯协议" prop="protocol">
<el-select
v-model="queryParams.protocol"
placeholder="请选择通讯协议"
clearable
class="!w-240px"
>
<el-form-item label="通讯协议" prop="protocol">
<el-select v-model="queryParams.protocol" placeholder="请选择通讯协议" clearable class="!w-240px">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_PROTOCOL)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_PROTOCOL)" :key="dict.value" :label="dict.label"
:value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<!-- <el-form-item label="备注" prop="remark">
<el-input
v-model="queryParams.remark"
placeholder="请输入备注"
@ -51,8 +29,8 @@
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
</el-form-item> -->
<!-- <el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -62,25 +40,23 @@
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
</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-model:create']"
>
<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-model:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button type="danger" plain @click="handleBatchDelete" v-hasPermi="['iot:device-model:delete']">
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['iot:device-model:export']"
>
type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['iot:device-model:export']">
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
@ -90,39 +66,30 @@
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" highlight-current-row
@current-change="handleCurrentChange">
ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"
highlight-current-row row-key="id" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column label="模型编码" align="center" prop="code" />
<el-table-column label="模型名称" align="center" prop="name" />
<el-table-column align="center" label="通讯协议" prop="protocol'" min-width="150">
<el-table-column align="center" label="通讯协议" prop="protocol" min-width="150">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_PROTOCOL" :value="scope.row.protocol" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<!-- <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" /> -->
<el-table-column label="操作" align="center" min-width="120px">
<template #default="scope">
<el-button link type="primary" @click.stop="handleShowAttribute(scope.row)">点位</el-button>
<el-button link type="primary" @click="handleCopy(scope.row.id)" v-hasPermi="['iot:device-model:create']">
复制
</el-button>
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['iot:device-model:update']"
>
link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['iot:device-model:update']">
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['iot:device-model:delete']"
>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['iot:device-model:delete']">
删除
</el-button>
</template>
@ -130,22 +97,24 @@ v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" hi
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
<!-- 采集点列表 -->
<ContentWrap>
<ModelAttributeList :id="currentRow.id" />
<el-tabs model-value="modelAttribute">
<el-tab-pane :label="modelAttributeTabLabel" name="modelAttribute">
<ModelAttributeList v-if="attributeModelId" :id="attributeModelId" />
<el-empty v-else description="请点击设备模型列表的“点位”查看采集点" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<DeviceModelForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getStrDictOptions,DICT_TYPE } from '@/utils/dict'
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { DeviceModelApi, DeviceModelVO } from '@/api/iot/devicemodel'
@ -158,6 +127,8 @@ defineOptions({ name: 'DeviceModel' })
const message = useMessage() //
const { t } = useI18n() //
const tableRef = ref()
const loading = ref(true) //
const list = ref<DeviceModelVO[]>([]) //
const total = ref(0) //
@ -173,6 +144,11 @@ const queryParams = reactive({
const queryFormRef = ref() //
const exportLoading = ref(false) //
const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []
}
/** 查询列表 */
const getList = async () => {
loading.value = true
@ -204,16 +180,30 @@ const openForm = (type: string, id?: number) => {
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
const buildIdsParam = (ids: number | number[]) => {
return Array.isArray(ids) ? ids.join(',') : String(ids)
}
const handleDelete = async (ids: number | number[]) => {
try {
//
await message.delConfirm()
//
await DeviceModelApi.deleteDeviceModel(id)
await DeviceModelApi.deleteDeviceModel(buildIdsParam(ids))
message.success(t('common.delSuccess'))
selectedIds.value = []
tableRef.value?.clearSelection?.()
//
await getList()
} catch {}
} catch { }
}
const handleBatchDelete = async () => {
if (!selectedIds.value.length) {
message.error('请选择需要删除的数据')
return
}
await handleDelete(selectedIds.value)
}
/** 导出按钮操作 */
@ -231,14 +221,31 @@ const handleExport = async () => {
}
}
/** 选中行操作 */
const currentRow = ref({}) //
const handleCurrentChange = (row) => {
currentRow.value = row
const attributeModelId = ref<number | undefined>(undefined)
const attributeModelName = ref('')
const modelAttributeTabLabel = computed(() => {
if (!attributeModelId.value) {
return '采集点'
}
return attributeModelName.value ? `采集点:${attributeModelName.value}` : '采集点'
})
const handleShowAttribute = (row: any) => {
attributeModelId.value = row?.id
attributeModelName.value = row?.name ?? ''
}
const handleCopy = async (id: number) => {
try {
await DeviceModelApi.copyDeviceModel(id)
message.success('复制成功')
await getList()
} catch { }
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
</script>

@ -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