|
|
<template>
|
|
|
<ContentWrap>
|
|
|
<el-form
|
|
|
class="-mb-15px"
|
|
|
:model="queryParams"
|
|
|
ref="queryFormRef"
|
|
|
:inline="true"
|
|
|
label-width="68px"
|
|
|
>
|
|
|
<el-form-item :label="t('DataCollection.Device.attributeCode')" prop="attributeCode">
|
|
|
<el-input
|
|
|
v-model="queryParams.attributeCode"
|
|
|
:placeholder="t('DataCollection.Device.placeholderAttributeCode')"
|
|
|
clearable
|
|
|
@keyup.enter="handleQuery"
|
|
|
class="!w-240px"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item :label="t('DataCollection.Device.attributeName')" prop="attributeName">
|
|
|
<el-input
|
|
|
v-model="queryParams.attributeName"
|
|
|
:placeholder="t('DataCollection.Device.placeholderAttributeName')"
|
|
|
clearable
|
|
|
@keyup.enter="handleQuery"
|
|
|
class="!w-240px"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item :label="t('DataCollection.Device.attributeType')" prop="attributeType">
|
|
|
<el-select
|
|
|
v-model="queryParams.attributeType"
|
|
|
clearable
|
|
|
filterable
|
|
|
:placeholder="t('DataCollection.Device.placeholderAttributeType')"
|
|
|
class="!w-240px"
|
|
|
>
|
|
|
<el-option v-for="item in typeList" :key="item.id" :label="item.code + '-' + item.name" :value="item.id" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
|
<el-button @click="handleQuery">
|
|
|
<Icon icon="ep:search" class="mr-5px" /> {{ t('DataCollection.Device.search') }}
|
|
|
</el-button>
|
|
|
<el-button @click="resetQuery">
|
|
|
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('DataCollection.Device.reset') }}
|
|
|
</el-button>
|
|
|
<el-button
|
|
|
type="primary"
|
|
|
plain
|
|
|
@click="openForm('create')"
|
|
|
v-hasPermi="['iot:device:create']"
|
|
|
>
|
|
|
<Icon icon="ep:plus" class="mr-5px" /> {{ t('DataCollection.Device.create') }}
|
|
|
</el-button>
|
|
|
<el-button
|
|
|
type="warning"
|
|
|
plain
|
|
|
@click="handleImport"
|
|
|
v-hasPermi="['iot:device:export']"
|
|
|
>
|
|
|
<Icon icon="ep:upload" /> {{ t('DataCollection.Device.import') }}
|
|
|
</el-button>
|
|
|
<el-button
|
|
|
type="success"
|
|
|
plain
|
|
|
@click="handleExport"
|
|
|
:loading="exportLoading"
|
|
|
v-hasPermi="['iot:device:export']"
|
|
|
>
|
|
|
<Icon icon="ep:download" class="mr-5px" /> {{ t('DataCollection.Device.export') }}
|
|
|
</el-button>
|
|
|
<el-button
|
|
|
type="danger"
|
|
|
plain
|
|
|
@click="handleBatchDelete"
|
|
|
v-hasPermi="['iot:device:delete']"
|
|
|
>
|
|
|
<Icon icon="ep:delete" class="mr-5px" /> {{ t('DataCollection.Device.batchDelete') }}
|
|
|
</el-button>
|
|
|
<el-button type="primary" plain @click="openBatchEdit" v-hasPermi="['iot:device:update']">
|
|
|
<Icon icon="ep:edit" 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" :max-height="planTableMaxHeight" row-key="id"
|
|
|
@selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" width="55" reserve-selection />
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.attributeCode')"
|
|
|
align="left"
|
|
|
prop="attributeCode"
|
|
|
width="150px"
|
|
|
sortable
|
|
|
/>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.attributeName')"
|
|
|
align="left"
|
|
|
prop="attributeName"
|
|
|
width="150px"
|
|
|
sortable
|
|
|
/>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.attributeType')"
|
|
|
align="center"
|
|
|
prop="typeName"
|
|
|
width="140px"
|
|
|
sortable />
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.dataType')"
|
|
|
align="center"
|
|
|
prop="dataType"
|
|
|
width="120px"
|
|
|
sortable>
|
|
|
<template #default="scope">
|
|
|
<dict-tag :type="DICT_TYPE.IOT_DEVICE_DATA_TYPE" :value="scope.row.dataType" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.address')"
|
|
|
align="center"
|
|
|
prop="address"
|
|
|
min-width="140px"
|
|
|
/>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.attributeLatestValue')"
|
|
|
align="center"
|
|
|
prop="addressValue"
|
|
|
min-width="120px"
|
|
|
sortable
|
|
|
>
|
|
|
<template #default="scope">
|
|
|
{{ formatAddressValue(scope.row.addressValue) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.dataUnit')"
|
|
|
align="center"
|
|
|
prop="dataUnit"
|
|
|
width="80px"
|
|
|
>
|
|
|
<template #default="scope">
|
|
|
{{ formatDataUnit(scope.row.dataUnit) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.ratio')"
|
|
|
align="center"
|
|
|
prop="ratio"
|
|
|
width="80px"
|
|
|
/>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.attributeLatestCollectionTime')"
|
|
|
align="center"
|
|
|
prop="latestCollectionTime"
|
|
|
:formatter="dateFormatter"
|
|
|
width="170px"
|
|
|
sortable
|
|
|
/>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.remark')"
|
|
|
align="center"
|
|
|
prop="remark"
|
|
|
min-width="160px"
|
|
|
/>
|
|
|
<el-table-column
|
|
|
:label="t('DataCollection.Device.operate')"
|
|
|
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']"
|
|
|
>
|
|
|
{{ t('action.edit') }}
|
|
|
</el-button>
|
|
|
<el-button
|
|
|
link
|
|
|
type="danger"
|
|
|
@click="handleDelete(scope.row.id)"
|
|
|
v-hasPermi="['iot:device:delete']"
|
|
|
>
|
|
|
{{ t('action.delete') }}
|
|
|
</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
<Pagination
|
|
|
ref="planPaginationRef"
|
|
|
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
|
|
@pagination="getList" />
|
|
|
</ContentWrap>
|
|
|
|
|
|
<DeviceAttributeForm ref="formRef" @success="getList" />
|
|
|
|
|
|
<!-- 单位导入对话框 -->
|
|
|
<Dialog title="批量编辑" v-model="batchEditVisible">
|
|
|
<el-form ref="batchEditFormRef" :model="batchEditForm" label-width="120px">
|
|
|
<el-form-item :label="t('DataCollection.Device.attributeType')">
|
|
|
<el-select v-model="batchEditForm.attributeType" clearable filterable :placeholder="t('DataCollection.Device.placeholderAttributeType')" class="w-1/1">
|
|
|
<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="t('DataCollection.Device.dataUnit')">
|
|
|
<el-select v-model="batchEditForm.dataUnit" clearable :placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')" class="w-1/1">
|
|
|
<el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item :label="t('DataCollection.Device.ratio')">
|
|
|
<el-input-number v-model="batchEditForm.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0" :precision="2" :step="0.01" class="!w-full" />
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
<el-button @click="batchEditVisible = false">{{ t('common.cancel') }}</el-button>
|
|
|
<el-button type="primary" :loading="batchEditLoading" @click="handleBatchEdit">{{ t('common.ok') }}</el-button>
|
|
|
</template>
|
|
|
</Dialog>
|
|
|
<DeviceAttributeImportForm ref="importFormRef" @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 DeviceAttributeForm from './DeviceAttributeForm.vue'
|
|
|
import DeviceAttributeImportForm from './DeviceAttributeImportForm.vue'
|
|
|
import { DeviceAttributeTypeApi, DeviceAttributeTypeVO } from '@/api/iot/deviceattributetype'
|
|
|
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
|
|
|
|
|
|
const { t } = useI18n() // 国际化
|
|
|
const message = useMessage() // 消息弹窗
|
|
|
|
|
|
const tableRef = ref()
|
|
|
const planTableMaxHeight = ref(520)
|
|
|
const planPaginationRef = ref<HTMLElement>()
|
|
|
|
|
|
const updatePlanTableMaxHeight = async () => {
|
|
|
await nextTick()
|
|
|
const tableTop = tableRef.value?.$el?.getBoundingClientRect().top ?? 0
|
|
|
const paginationHeight = planPaginationRef.value?.$el?.getBoundingClientRect().height ?? 72
|
|
|
const pageBottomGap = 56
|
|
|
const availableHeight = window.innerHeight - tableTop - paginationHeight - pageBottomGap
|
|
|
planTableMaxHeight.value = Math.min(window.innerHeight, Math.max(240, Math.floor(availableHeight)))
|
|
|
}
|
|
|
|
|
|
const props = defineProps<{
|
|
|
deviceId?: number // 设备id(主表的关联字段)
|
|
|
}>()
|
|
|
|
|
|
const typeList = ref<DeviceAttributeTypeVO[]>([])
|
|
|
const unitList = ref<ProductUnitVO[]>([])
|
|
|
|
|
|
const loading = ref(false) // 列表的加载中
|
|
|
const list = ref([]) // 列表的数据
|
|
|
const total = ref(0) // 列表的总页数
|
|
|
const queryParams = reactive({
|
|
|
pageNo: 1,
|
|
|
pageSize: 10,
|
|
|
deviceId: undefined as number | undefined,
|
|
|
attributeCode: undefined,
|
|
|
attributeName: undefined,
|
|
|
attributeType: undefined
|
|
|
})
|
|
|
|
|
|
const queryFormRef = ref()
|
|
|
|
|
|
const exportLoading = ref(false)
|
|
|
|
|
|
const formatAddressValue = (value: unknown) => {
|
|
|
if (value === null || value === undefined) {
|
|
|
return '-'
|
|
|
}
|
|
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
|
return value
|
|
|
}
|
|
|
return ''
|
|
|
}
|
|
|
|
|
|
const loadUnitList = async () => {
|
|
|
if (unitList.value.length) {
|
|
|
return
|
|
|
}
|
|
|
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
|
|
|
}
|
|
|
|
|
|
const formatDataUnit = (value: unknown) => {
|
|
|
if (value === null || value === undefined || value === '') {
|
|
|
return '-'
|
|
|
}
|
|
|
return unitList.value.find((unit) => String(unit.id) === String(value))?.name ?? String(value)
|
|
|
}
|
|
|
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,
|
|
|
(val: number) => {
|
|
|
if (!val) {
|
|
|
return
|
|
|
}
|
|
|
queryParams.deviceId = val
|
|
|
selectedIds.value = []
|
|
|
tableRef.value?.clearSelection?.()
|
|
|
handleQuery()
|
|
|
},
|
|
|
{ immediate: true, deep: true }
|
|
|
)
|
|
|
|
|
|
/** 查询列表 */
|
|
|
async function getList() {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
const data = await DeviceApi.getDeviceAttributePage(queryParams)
|
|
|
list.value = data.list
|
|
|
total.value = data.total
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
updatePlanTableMaxHeight()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** 搜索按钮操作 */
|
|
|
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) => {
|
|
|
if (!props.deviceId) {
|
|
|
message.error(t('DataCollection.Device.messageSelectDeviceRequired'))
|
|
|
return
|
|
|
}
|
|
|
formRef.value.open(type, id, props.deviceId)
|
|
|
}
|
|
|
|
|
|
/** 删除按钮操作 */
|
|
|
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(buildIdsParam(ids))
|
|
|
message.success(t('common.delSuccess'))
|
|
|
selectedIds.value = []
|
|
|
tableRef.value?.clearSelection?.()
|
|
|
// 刷新列表
|
|
|
await getList()
|
|
|
} catch { }
|
|
|
}
|
|
|
|
|
|
const handleBatchDelete = async () => {
|
|
|
if (!selectedIds.value.length) {
|
|
|
message.error(t('common.delNoData'))
|
|
|
return
|
|
|
}
|
|
|
await handleDelete(selectedIds.value)
|
|
|
}
|
|
|
|
|
|
const batchEditVisible = ref(false)
|
|
|
const batchEditLoading = ref(false)
|
|
|
const batchEditFormRef = ref()
|
|
|
const batchEditForm = reactive({
|
|
|
attributeType: undefined as number | undefined,
|
|
|
dataUnit: undefined as string | undefined,
|
|
|
ratio: undefined as number | undefined
|
|
|
})
|
|
|
|
|
|
const resetBatchEditForm = () => {
|
|
|
batchEditForm.attributeType = undefined
|
|
|
batchEditForm.dataUnit = undefined
|
|
|
batchEditForm.ratio = undefined
|
|
|
batchEditFormRef.value?.resetFields()
|
|
|
}
|
|
|
|
|
|
const openBatchEdit = async () => {
|
|
|
if (!selectedIds.value.length) {
|
|
|
message.error('请选择需要编辑的数据')
|
|
|
return
|
|
|
}
|
|
|
resetBatchEditForm()
|
|
|
await loadUnitList()
|
|
|
|
|
|
batchEditVisible.value = true
|
|
|
}
|
|
|
|
|
|
const handleBatchEdit = async () => {
|
|
|
try {
|
|
|
batchEditLoading.value = true
|
|
|
await DeviceApi.batchUpdateDeviceAttribute({
|
|
|
ids: selectedIds.value,
|
|
|
attributeType: batchEditForm.attributeType,
|
|
|
dataUnit: batchEditForm.dataUnit,
|
|
|
ratio: batchEditForm.ratio
|
|
|
})
|
|
|
message.success(t('common.updateSuccess'))
|
|
|
batchEditVisible.value = false
|
|
|
selectedIds.value = []
|
|
|
tableRef.value?.clearSelection?.()
|
|
|
await getList()
|
|
|
} finally {
|
|
|
batchEditLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
/** 导入 */
|
|
|
const importFormRef = ref()
|
|
|
const handleImport = () => {
|
|
|
if (!props.deviceId) {
|
|
|
message.error(t('DataCollection.Device.messageSelectDeviceRequired'))
|
|
|
return
|
|
|
}
|
|
|
importFormRef.value.open(props.deviceId)
|
|
|
}
|
|
|
|
|
|
/** 导出按钮操作 */
|
|
|
const handleExport = async () => {
|
|
|
if (!props.deviceId) {
|
|
|
message.error(t('DataCollection.Device.messageSelectDeviceRequired'))
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
await message.exportConfirm()
|
|
|
exportLoading.value = true
|
|
|
const params: any = {
|
|
|
deviceId: props.deviceId
|
|
|
}
|
|
|
if (selectedIds.value.length) {
|
|
|
params.ids = selectedIds.value.join(',')
|
|
|
}
|
|
|
const data = await DeviceApi.exportDeviceContactModel(params)
|
|
|
download.excel(data, t('DataCollection.Device.attributeExportFilename'))
|
|
|
} catch {
|
|
|
} finally {
|
|
|
exportLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
onMounted(async () => {
|
|
|
try {
|
|
|
const data = await DeviceAttributeTypeApi.getDeviceAttributeTypeList()
|
|
|
typeList.value = data ?? []
|
|
|
} catch {
|
|
|
typeList.value = []
|
|
|
}
|
|
|
try {
|
|
|
await loadUnitList()
|
|
|
} catch {
|
|
|
unitList.value = []
|
|
|
}
|
|
|
updatePlanTableMaxHeight()
|
|
|
window.addEventListener('resize', updatePlanTableMaxHeight)
|
|
|
})
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
window.removeEventListener('resize', updatePlanTableMaxHeight)
|
|
|
})
|
|
|
</script>
|