You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/views/iot/device/components/DeviceAttributeList.vue

497 lines
15 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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