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.

1218 lines
44 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>
<div class="mb-10px flex items-center justify-between text-sm text-gray-500">
<div>
{{ t('DataCollection.Device.currentDeviceLabel') }}<span class="font-medium text-gray-700">{{ attributeDeviceName || '-' }}</span>
</div>
<div>
<el-button type="primary" link @click="handleShowDeviceAlarmHistory">
{{ t('DataCollection.Device.alarmHistoryTitle') }}
</el-button>
</div>
</div>
<el-tabs v-model="deviceTabActive">
<el-tab-pane :label="deviceAttributeTabLabel" name="deviceAttribute">
<DeviceAttributeList :device-id="attributeDeviceId" />
</el-tab-pane>
<el-tab-pane :label="deviceRuleTabLabel" name="deviceRule">
<el-form
class="-mb-15px"
:model="ruleQueryParams"
ref="ruleQueryFormRef"
:inline="true"
label-width="120px"
>
<el-form-item :label="t('DataCollection.DeviceModel.ruleIdentifier')" prop="identifier">
<el-input
v-model="ruleQueryParams.identifier"
:placeholder="t('DataCollection.DeviceModel.ruleSearchIdentifierPlaceholder')"
clearable
@keyup.enter="handleRuleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleFieldName')" prop="fieldName">
<el-input
v-model="ruleQueryParams.fieldName"
:placeholder="t('DataCollection.DeviceModel.ruleSearchFieldNamePlaceholder')"
clearable
@keyup.enter="handleRuleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDefaultValue')" prop="defaultValue">
<el-input
v-model="ruleQueryParams.defaultValue"
:placeholder="t('DataCollection.DeviceModel.ruleSearchDefaultValuePlaceholder')"
clearable
@keyup.enter="handleRuleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleRuleQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('DataCollection.DeviceModel.ruleSearch') }}
</el-button>
<el-button @click="resetRuleQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('DataCollection.DeviceModel.ruleReset') }}
</el-button>
</el-form-item>
</el-form>
<div class="mb-10px flex justify-end gap-8px">
<el-button type="primary" plain @click="openCreateCountRuleForm">
新增产量
</el-button>
<el-button type="primary" @click="openCreateRuleForm">
{{ t('DataCollection.DeviceModel.ruleCreateButton') }}
</el-button>
</div>
<el-table
v-loading="ruleLoading"
:data="ruleList"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column :label="t('DataCollection.DeviceModel.ruleIdentifier')" align="center" prop="identifier" />
<el-table-column :label="t('DataCollection.DeviceModel.ruleFieldName')" align="center" prop="fieldName" sortable />
<el-table-column :label="t('DataCollection.DeviceModel.ruleFieldRule')" align="center" prop="fieldRule" />
<el-table-column :label="t('DataCollection.DeviceModel.ruleDefaultValue')" align="center" prop="defaultValue" />
<el-table-column
:label="t('DataCollection.DeviceModel.ruleCreateTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable
/>
<el-table-column :label="t('DataCollection.DeviceModel.ruleOperate')" align="center" width="160px">
<template #default="scope">
<el-button link type="primary" @click="openRuleForm(scope.row)">
{{ t('DataCollection.DeviceModel.ruleEditRuleButton') }}
</el-button>
<el-button
v-if="scope.row.identifier === 'ALARM' || scope.row.identifier === 'COUNT'"
link
type="danger"
@click="handleRuleDelete(scope.row.id)"
>
{{ t('DataCollection.DeviceModel.ruleDeleteRuleButton') }}
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="ruleTotal"
v-model:page="ruleQueryParams.pageNo"
v-model:limit="ruleQueryParams.pageSize"
@pagination="getRuleList"
/>
<el-dialog v-model="ruleDialogVisible" :title="t('DataCollection.DeviceModel.ruleDialogTitle')" width="880px" draggable>
<el-form :model="ruleForm" ref="ruleFormRef" label-width="120px">
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogIdentifier')">
<el-input v-model="ruleForm.identifier" disabled />
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogFieldName')">
<el-input v-model="ruleForm.fieldName" disabled />
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogDefaultValue')">
<el-input v-model="ruleForm.defaultValue" disabled />
</el-form-item>
<el-form-item
v-if="(ruleForm.identifier || '').toString().toUpperCase() === 'ALARM'"
:label="t('DataCollection.DeviceModel.ruleDialogAlarmLevel')"
>
<el-select
v-model="ruleForm.alarmLevel"
:placeholder="t('DataCollection.DeviceModel.ruleDialogAlarmLevelPlaceholder')"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ALARM_REGISTRATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<div class="flex flex-col w-full">
<div class="border border-gray-200 dark:border-gray-600 rounded-md py-12px">
<el-form-item
v-if="!isCountIdentifier"
:label="t('DataCollection.DeviceModel.ruleDialogFieldRule')"
>
<el-select
v-model="ruleForm.fieldRule"
:placeholder="t('DataCollection.DeviceModel.ruleDialogFieldRulePlaceholder')"
class="!w-240px"
:disabled="!currentRuleOptions.length"
>
<el-option
v-for="item in currentRuleOptions"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="isRuleDisabled(item.value, ruleForm.fieldRule)"
/>
</el-select>
</el-form-item>
<el-form-item :label="isCountIdentifier ? '四则运算规则' : t('DataCollection.DeviceModel.ruleDialogRule')">
<div v-if="isCountIdentifier" class="w-full flex flex-col gap-8px">
<div
v-for="(item, index) in countArithmeticRules"
:key="index"
class="flex items-center gap-8px"
>
<el-select
v-model="item.ruleAttributeId"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleAttributePlaceholder')"
class="!w-240px"
clearable
>
<el-option
v-for="attr in ruleAttributeOptions"
:key="attr.id"
:label="attr.attributeName || attr.attributeCode"
:value="attr.id"
/>
</el-select>
<el-select
v-model="item.ruleOperator"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleOperatorPlaceholder')"
class="!w-160px"
clearable
>
<el-option
v-for="operator in currentRuleOperatorOptions"
:key="operator.value"
:label="operator.label"
:value="operator.value"
/>
</el-select>
<el-input
v-model="item.ruleValue"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleValuePlaceholder')"
class="!w-200px"
clearable
/>
<template v-if="index < countArithmeticRules.length - 1">
<el-select
v-model="countArithmeticRules[index].bigOperator"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleOperatorPlaceholder')"
class="!w-110px"
clearable
>
<el-option
v-for="operator in countOperatorOptions"
:key="operator.value"
:label="operator.label"
:value="operator.value"
/>
</el-select>
</template>
<template v-else>
<div class="flex items-center gap-4px">
<el-button link type="primary" @click="handleAddCountArithmeticRule">
<Icon icon="ep:plus" />
</el-button>
<el-button
link
type="danger"
:disabled="countArithmeticRules.length <= 1"
@click="handleRemoveCountArithmeticRule(index)"
>
<Icon icon="ep:minus" />
</el-button>
</div>
</template>
</div>
<div class="text-12px text-[#909399] leading-20px">按“四则运算规则”添加顺序从上到下依次计算,不按乘除优先。</div>
</div>
<div v-else class="flex items-center gap-8px">
<el-select
v-model="ruleForm.ruleAttributeId"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleAttributePlaceholder')"
class="!w-240px"
clearable
>
<el-option
v-for="item in ruleAttributeOptions"
:key="item.id"
:label="item.attributeName || item.attributeCode"
:value="item.id"
/>
</el-select>
<el-select
v-model="ruleForm.ruleOperator"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleOperatorPlaceholder')"
class="!w-160px"
clearable
>
<el-option
v-for="item in currentRuleOperatorOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-if="ruleForm.ruleOperator !== 'TRUE' && ruleForm.ruleOperator !== 'FALSE'"
v-model="ruleForm.ruleValue"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleValuePlaceholder')"
class="!w-200px"
clearable
/>
</div>
</el-form-item>
</div>
<template v-if="isRunningIdentifier">
<div
v-for="(item, index) in extraPointRules"
:key="index"
class="border border-gray-200 dark:border-gray-600 rounded-md px-16px py-12px"
>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogFieldRule')">
<el-select
v-model="item.rule"
:placeholder="t('DataCollection.DeviceModel.ruleDialogFieldRulePlaceholder')"
class="!w-240px"
>
<el-option
v-for="opt in currentRuleOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
:disabled="isRuleDisabled(opt.value, item.rule)"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogRule')">
<div class="flex items-center gap-8px">
<el-select
v-model="item.id"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleAttributePlaceholder')"
class="!w-240px"
>
<el-option
v-for="attr in ruleAttributeOptions"
:key="attr.id"
:label="attr.attributeName || attr.attributeCode"
:value="attr.id"
/>
</el-select>
<el-select
v-model="item.operator"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleOperatorPlaceholder')"
class="!w-160px"
>
<el-option
v-for="opt in ruleOperatorOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
<el-input
v-if="isCountIdentifier || (item.operator !== 'TRUE' && item.operator !== 'FALSE')"
v-model="item.operatorRule"
:placeholder="t('DataCollection.DeviceModel.ruleDialogRuleValuePlaceholder')"
class="!w-200px"
/>
<el-button type="danger" link @click="handleRemovePointRule(index)">
{{ t('DataCollection.DeviceModel.ruleDeleteRuleButton') }}
</el-button>
</div>
</el-form-item>
</div>
</template>
<el-form-item v-if="isRunningIdentifier" label=" ">
<el-button type="primary" link @click="handleAddPointRule">
+ {{ t('DataCollection.DeviceModel.ruleCreateButton') }}
</el-button>
</el-form-item>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="ruleDialogVisible = false">{{ t('DataCollection.DeviceModel.dialogCancel') }}</el-button>
<el-button type="primary" :loading="ruleFormLoading" @click="handleRuleSubmit">{{ t('DataCollection.DeviceModel.dialogOk') }}</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="createRuleDialogVisible" :title="createRuleDialogTitle" width="520px" draggable>
<el-form :model="createRuleForm" ref="createRuleFormRef" label-width="120px">
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogIdentifier')">
<el-input v-model="createRuleForm.identifier" disabled />
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogFieldName')">
<el-input
v-model="createRuleForm.fieldName"
:placeholder="t('DataCollection.DeviceModel.ruleSearchFieldNamePlaceholder')"
:disabled="isCreateCountRule"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.DeviceModel.ruleDialogDefaultValue')">
<el-input v-model="createRuleForm.defaultValue" disabled />
</el-form-item>
<el-form-item v-if="!isCreateCountRule" :label="t('DataCollection.DeviceModel.ruleDialogAlarmLevel')">
<el-select
v-model="createRuleForm.alarmLevel"
:placeholder="t('DataCollection.DeviceModel.ruleDialogAlarmLevelPlaceholder')"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_ALARM_REGISTRATION)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="createRuleDialogVisible = false">{{ t('DataCollection.DeviceModel.dialogCancel') }}</el-button>
<el-button type="primary" :loading="createRuleFormLoading" @click="handleCreateRuleSubmit">
{{ t('DataCollection.DeviceModel.dialogOk') }}
</el-button>
</span>
</template>
</el-dialog>
</el-tab-pane>
</el-tabs>
<Dialog :title="t('DataCollection.Device.alarmHistoryTitle')" v-model="deviceAlarmDialogVisible" width="1200px">
<el-form
class="-mb-15px"
:model="deviceAlarmQueryParams"
ref="deviceAlarmQueryFormRef"
:inline="true"
label-width="80px"
>
<el-form-item :label="t('DataCollection.Device.alarmRuleName')" prop="ruleId">
<el-select
v-model="deviceAlarmQueryParams.ruleId"
:loading="deviceAlarmRuleLoading"
clearable
filterable
class="!w-220px"
:placeholder="t('DataCollection.DeviceModel.ruleSearchFieldNamePlaceholder')"
>
<el-option
v-for="item in deviceAlarmRuleOptions"
:key="item.id"
:label="item.fieldName || item.ruleName || item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.alarmPointName')" prop="modelId">
<el-select
v-model="deviceAlarmQueryParams.modelId"
:loading="deviceAlarmPointLoading"
clearable
filterable
class="!w-220px"
:placeholder="t('DataCollection.Device.placeholderAttributeName')"
>
<el-option
v-for="item in deviceAlarmPointOptions"
:key="item.id"
:label="item.attributeName || item.attributeCode || item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.alarmTime')" prop="createTime">
<el-date-picker
v-model="deviceAlarmQueryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:start-placeholder="t('DataCollection.HistoryData.dialogCollectionTimeStartPlaceholder')"
:end-placeholder="t('DataCollection.HistoryData.dialogCollectionTimeEndPlaceholder')"
class="!w-260px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleDeviceAlarmQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('DataCollection.Device.search') }}
</el-button>
<el-button @click="resetDeviceAlarmQuery">
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('DataCollection.Device.reset') }}
</el-button>
</el-form-item>
</el-form>
<el-table
:data="deviceAlarmList"
v-loading="deviceAlarmLoading"
:stripe="true"
:show-overflow-tooltip="true"
:max-height="700"
class="mt-10px"
>
<el-table-column :label="t('DataCollection.Device.alarmRuleName')" align="center" prop="ruleName" sortable />
<el-table-column :label="t('DataCollection.Device.alarmPointName')" align="center" prop="modelName" sortable />
<el-table-column :label="t('DataCollection.Device.alarmPointValue')" align="center" prop="addressValue" />
<el-table-column :label="t('DataCollection.Device.alarmLevel')" align="center" prop="alarmLevel">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IOT_ALARM_REGISTRATION" :value="scope.row.alarmLevel" />
</template>
</el-table-column>
<el-table-column
:label="t('DataCollection.Device.alarmTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable />
</el-table>
<div class="mt-10px pb-10px flex justify-end">
<Pagination
:total="deviceAlarmTotal"
v-model:page="deviceAlarmQueryParams.pageNo"
v-model:limit="deviceAlarmQueryParams.pageSize"
@pagination="getDeviceAlarmList"
/>
</div>
</Dialog>
</ContentWrap>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
const route = useRoute();
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import request from '@/config/axios'
import { DeviceApi, DeviceConnectParams, DeviceVO } from '@/api/iot/device'
import DeviceForm from './DeviceForm.vue'
import DeviceAttributeList from './components/DeviceAttributeList.vue'
import {
Refresh,
Grid,
Menu,
Search,
Location
} from '@element-plus/icons-vue'
import {useRouter} from "vue-router";
import DetailForm from "@/views/iot/device/detailForm.vue";
// Query ?id=123
const id = route.query.id; // string | string[]
const { t } = useI18n() //
const message = useMessage() //
const attributeDeviceName = ref(route.query.name as string);
const attributeDeviceId = ref<number | undefined>(undefined)
attributeDeviceId.value = Number(id)
const deviceAlarmDialogVisible = ref(false)
const deviceAlarmList = ref<any[]>([])
const deviceAlarmTotal = ref(0)
const ruleList = ref<DevicePointRuleVO[]>([])
const ruleTotal = ref(0)
const deviceTabActive = ref('deviceAttribute')
const deviceAttributeTabLabel = computed(() => {
return t('DataCollection.Device.deviceAttributeTabLabel')
})
const createRuleFormLoading = ref(false)
const ruleFormLoading = ref(false)
const ruleQueryFormRef = ref()
const ruleLoading = ref(false)
const deviceRuleTabLabel = computed(() => {
return t('DataCollection.Device.deviceRuleTabLabel')
})
interface DevicePointRuleVO {
id: number
identifier: string
fieldName: string
fieldRule: string
defaultValue: string
deviceId: number
createTime?: string | number | Date
ruleAttributeId?: number
ruleOperator?: string
ruleValue?: string | number
}
const ruleQueryParams = reactive({
pageNo: 1,
pageSize: 10,
identifier: undefined as string | undefined,
fieldName: undefined as string | undefined,
fieldRule: undefined as string | undefined,
defaultValue: undefined as string | undefined,
alarmLevel: undefined as string | undefined,
deviceId: undefined as number | undefined,
})
const deviceAlarmQueryParams = reactive({
pageNo: 1,
pageSize: 10,
deviceId: undefined as number | undefined,
modelId: undefined as number | undefined,
rule: undefined as string | undefined,
alarmLevel: undefined as string | undefined,
addressValue: undefined as string | undefined,
createTime: [] as string[],
ruleId: undefined as number | undefined,
})
const runningRuleOptions = [
{ value: '1', label: '运行' },
{ value: '2', label: '待机中(不运行、没故障)' },
{ value: '3', label: '故障中(故障且待机)' },
// { value: '4', label: '报警中(故障且运行)' },
]
const alarmRuleOptions = [{ value: '5', label: '报警' }]
const countOperatorOptions = [
{ value: '+', label: '+' },
{ value: '-', label: '-' },
{ value: '*', label: '*' },
{ value: '/', label: '/' },
]
const createRuleForm = reactive({
identifier: 'ALARM',
fieldName: '',
defaultValue: '报警',
alarmLevel: '',
})
const ruleForm = reactive<Partial<DevicePointRuleVO>>({
id: undefined,
identifier: '',
fieldName: '',
fieldRule: '',
defaultValue: '',
deviceId: undefined,
ruleAttributeId: undefined,
ruleOperator: undefined,
ruleValue: undefined,
alarmLevel: '',
})
type CountArithmeticRule = {
ruleAttributeId?: number
ruleOperator?: string
ruleValue?: string | number
bigOperator?: string
}
const createEmptyCountArithmeticRule = (): CountArithmeticRule => ({
ruleAttributeId: undefined,
ruleOperator: undefined,
ruleValue: undefined,
})
const extraPointRules = ref<
Array<{
id?: number
rule?: string
operator?: string
operatorRule?: string | number
}>
>([])
const ruleOperatorOptions = computed(() => getStrDictOptions('czsb_rules_conditions'))
const currentRuleOptions = computed(() => {
const id = (ruleForm.identifier || '').toString().toUpperCase()
if (id === 'RUNNING') return runningRuleOptions
if (id === 'ALARM') return alarmRuleOptions
return []
})
const isCountIdentifier = computed(() => {
return (ruleForm.identifier || '').toString().toUpperCase() === 'COUNT'
})
const currentRuleOperatorOptions = computed(() => {
if (isCountIdentifier.value) return countOperatorOptions
return ruleOperatorOptions.value
})
const handleRemovePointRule = (index: number) => {
if (index < 0 || index >= extraPointRules.value.length) return
extraPointRules.value.splice(index, 1)
}
const handleAddCountArithmeticRule = () => {
countArithmeticRules.value.push(createEmptyCountArithmeticRule())
}
const handleRemoveCountArithmeticRule = (index: number) => {
if (countArithmeticRules.value.length <= 1) return
if (index < 0 || index >= countArithmeticRules.value.length) return
countArithmeticRules.value.splice(index, 1)
const list = countArithmeticRules.value
if (list.length > 0) {
list[list.length - 1].bigOperator = undefined
}
}
const handleRuleSubmit = async () => {
if (!ruleForm.id) return
try {
ruleFormLoading.value = true
if (isCountIdentifier.value) {
const hasIncompleteRule = countArithmeticRules.value.some((item) => {
return !item.ruleAttributeId || !item.ruleOperator || String(item.ruleValue ?? '').trim() === ''
})
if (hasIncompleteRule) {
message.warning('请完整填写每一组四则运算规则')
return
}
const normalizedRules = countArithmeticRules.value.filter((item) => {
return item.ruleAttributeId && item.ruleOperator && String(item.ruleValue ?? '').trim() !== ''
})
if (!normalizedRules.length) {
message.warning('请至少填写一组四则运算规则')
return
}
for (let i = 0; i < normalizedRules.length - 1; i++) {
if (!normalizedRules[i].bigOperator) {
message.warning(`请为第${i + 1}组选择连接运算符`)
return
}
}
for (let i = 0; i < normalizedRules.length; i++) {
const item = normalizedRules[i]
if (item.ruleOperator !== '/') continue
const valueText = String(item.ruleValue ?? '').trim()
if (valueText !== '' && !Number.isNaN(Number(valueText)) && Number(valueText) === 0) {
message.warning(`${i + 1}组为除法时数值不能为0`)
return
}
}
const fieldRule = buildCountFieldRule(normalizedRules)
if (!fieldRule) {
message.warning('四则运算规则格式不正确')
return
}
const payload = {
id: ruleForm.id,
identifier: ruleForm.identifier,
fieldName: ruleForm.fieldName,
fieldRule,
defaultValue: ruleForm.defaultValue,
deviceId: ruleForm.deviceId ?? attributeDeviceId.value,
}
await request.put({ url: '/iot/device-point-rules/update', data: payload })
message.success(t('common.updateSuccess'))
ruleDialogVisible.value = false
await getRuleList()
return
}
const alarmRunningFieldRule = buildAlarmRunningFieldRule()
if (!alarmRunningFieldRule || alarmRunningFieldRule === '[]') {
message.warning('请至少填写一组规则')
return
}
const payload = {
id: ruleForm.id,
identifier: ruleForm.identifier,
fieldName: ruleForm.fieldName,
fieldRule: alarmRunningFieldRule,
defaultValue: ruleForm.defaultValue,
deviceId: ruleForm.deviceId ?? attributeDeviceId.value,
} as any
if (!isCountIdentifier.value) {
payload.alarmLevel = ruleForm.alarmLevel
}
await request.put({ url: '/iot/device-point-rules/update', data: payload })
message.success(t('common.updateSuccess'))
ruleDialogVisible.value = false
await getRuleList()
} finally {
ruleFormLoading.value = false
}
}
const buildAlarmRunningFieldRule = () => {
const list: Array<{
code: string
operator: string
operatorRule: string | number
rule: string
}> = []
if (ruleForm.ruleAttributeId && ruleForm.ruleOperator) {
const attr = ruleAttributeOptions.value.find((item) => item.id === ruleForm.ruleAttributeId)
if (attr?.attributeCode) {
list.push({
code: attr.attributeCode,
operator: String(ruleForm.ruleOperator),
operatorRule: (ruleForm.ruleValue as any) ?? '',
rule: String(ruleForm.fieldRule ?? ''),
})
}
}
extraPointRules.value.forEach((item) => {
if (!item.id || !item.operator) return
const attr = ruleAttributeOptions.value.find((opt) => opt.id === item.id)
if (!attr?.attributeCode) return
list.push({
code: attr.attributeCode,
operator: String(item.operator),
operatorRule: (item.operatorRule as any) ?? '',
rule: String(item.rule ?? ruleForm.fieldRule ?? ''),
})
})
return JSON.stringify(list)
}
const buildCountFieldRule = (rules: CountArithmeticRule[]) => {
const validRules = rules.filter((item) => {
return item.ruleAttributeId && item.ruleOperator && String(item.ruleValue ?? '').trim() !== ''
})
if (!validRules.length) return ''
const list = validRules.map((item, index) => {
const attr = ruleAttributeOptions.value.find((opt) => opt.id === item.ruleAttributeId)
if (!attr?.attributeCode) return undefined
const row: any = {
code: attr.attributeCode,
operator: String(item.ruleOperator),
value: toCountRuleValue(item.ruleValue),
}
if (index < validRules.length - 1 && item.bigOperator) {
row.bigOperator = String(item.bigOperator)
}
return row
}).filter(Boolean)
if (!list.length) return ''
return JSON.stringify(list)
}
const isCreateCountRule = computed(() => {
return createRuleForm.identifier.toString().toUpperCase() === 'COUNT'
})
const createRuleDialogTitle = computed(() => {
if (isCreateCountRule.value) return '新增产量'
return t('DataCollection.DeviceModel.ruleCreateButton')
})
const toCountRuleValue = (value: any) => {
const text = String(value ?? '').trim()
if (!text) return ''
if (/^-?\d+(\.\d+)?$/.test(text)) {
return Number(text)
}
return text
}
const handleCreateRuleSubmit = async () => {
if (!attributeDeviceId.value) return
if (!createRuleForm.fieldName) {
message.warning('请输入名称')
return
}
try {
createRuleFormLoading.value = true
const payload = {
identifier: createRuleForm.identifier,
fieldName: createRuleForm.fieldName,
defaultValue: createRuleForm.defaultValue,
deviceId: attributeDeviceId.value,
} as any
if (!isCreateCountRule.value) {
payload.alarmLevel = createRuleForm.alarmLevel
}
await request.post({ url: '/iot/device-point-rules/create', data: payload })
message.success('新增成功')
createRuleDialogVisible.value = false
await getRuleList()
} finally {
createRuleFormLoading.value = false
}
}
const handleAddPointRule = () => {
if (!isRunningIdentifier.value) return
const allOptions = currentRuleOptions.value || []
const used = new Set<string>()
if (ruleForm.fieldRule) used.add(String(ruleForm.fieldRule))
extraPointRules.value.forEach((item) => {
if (item.rule != null) used.add(String(item.rule))
})
const next = allOptions.find((opt) => !used.has(String(opt.value)))
if (!next) {
message.warning('已没有可用的点位规则可选')
return
}
extraPointRules.value.push({
rule: next.value as any,
id: undefined,
operator: undefined,
operatorRule: undefined,
})
}
const isRunningIdentifier = computed(() => {
return (ruleForm.identifier || '').toString().toUpperCase() === 'RUNNING'
})
const createRuleDialogVisible = ref(false)
const deviceAlarmLoading = ref(false)
const deviceAlarmQueryFormRef = ref()
const deviceAlarmRuleOptions = ref<any[]>([])
const deviceAlarmPointOptions = ref<any[]>([])
const deviceAlarmRuleLoading = ref(false)
const deviceAlarmPointLoading = ref(false)
const countArithmeticRules = ref<CountArithmeticRule[]>([createEmptyCountArithmeticRule()])
const ruleAttributeOptions = ref<any[]>([])
const ruleDialogVisible = ref(false)
const loadDeviceAlarmRuleOptions = async () => {
if (!attributeDeviceId.value) return
deviceAlarmRuleLoading.value = true
try {
const res = await request.get({
url: '/iot/device-point-rules/getList',
params: { id: attributeDeviceId.value },
})
const data = (res as any)?.data ?? res
const listData = Array.isArray((data as any)?.list)
? (data as any).list
: Array.isArray(data)
? data
: []
deviceAlarmRuleOptions.value = listData as any[]
} finally {
deviceAlarmRuleLoading.value = false
}
}
const openCreateRuleForm = () => {
if (!attributeDeviceId.value) {
message.error('请先选择设备')
return
}
createRuleForm.identifier = 'ALARM'
createRuleForm.fieldName = '报警'
createRuleForm.defaultValue = '报警'
createRuleForm.alarmLevel = ''
createRuleDialogVisible.value = true
}
const loadDeviceAlarmPointOptions = async () => {
if (!attributeDeviceId.value) return
deviceAlarmPointLoading.value = true
try {
const res = await request.get({
url: '/iot/device/device-attribute/list',
params: { deviceId: attributeDeviceId.value },
})
const data = (res as any)?.data ?? res
const listData = Array.isArray((data as any)?.list)
? (data as any).list
: Array.isArray(data)
? data
: []
deviceAlarmPointOptions.value = listData as any[]
} finally {
deviceAlarmPointLoading.value = false
}
}
const handleShowDeviceAlarmHistory = async () => {
if (!attributeDeviceId.value) {
message.error(t('DataCollection.Device.messageSelectDeviceRequired'))
return
}
deviceAlarmQueryParams.pageNo = 1
deviceAlarmQueryParams.pageSize = 10
deviceAlarmQueryParams.modelId = undefined
deviceAlarmQueryParams.rule = undefined
deviceAlarmQueryParams.alarmLevel = undefined
deviceAlarmQueryParams.addressValue = undefined
deviceAlarmQueryParams.createTime = []
deviceAlarmQueryParams.ruleId = undefined
deviceAlarmDialogVisible.value = true
await Promise.all([loadDeviceAlarmRuleOptions(), loadDeviceAlarmPointOptions()])
await getDeviceAlarmList()
}
const getDeviceAlarmList = async () => {
if (!attributeDeviceId.value) return
deviceAlarmLoading.value = true
try {
deviceAlarmQueryParams.deviceId = attributeDeviceId.value
const params = {
pageNo: deviceAlarmQueryParams.pageNo,
pageSize: deviceAlarmQueryParams.pageSize,
deviceId: deviceAlarmQueryParams.deviceId,
modelId: deviceAlarmQueryParams.modelId,
rule: deviceAlarmQueryParams.rule,
alarmLevel: deviceAlarmQueryParams.alarmLevel,
addressValue: deviceAlarmQueryParams.addressValue,
createTime: deviceAlarmQueryParams.createTime,
ruleId: deviceAlarmQueryParams.ruleId,
}
const data = await request.get({
url: '/iot/device-warinning-record/page',
params,
})
const listData = Array.isArray((data as any)?.list)
? (data as any).list
: Array.isArray(data)
? data
: []
const totalData = (data as any)?.total ?? listData.length
deviceAlarmList.value = listData as any[]
deviceAlarmTotal.value = totalData
} finally {
deviceAlarmLoading.value = false
}
}
const openCreateCountRuleForm = () => {
if (!attributeDeviceId.value) {
message.error('请先选择设备')
return
}
createRuleForm.identifier = 'COUNT'
createRuleForm.fieldName = '产能'
createRuleForm.defaultValue = '产能'
createRuleForm.alarmLevel = ''
createRuleDialogVisible.value = true
}
const handleRuleQuery = () => {
if (!attributeDeviceId.value) return
ruleQueryParams.pageNo = 1
getRuleList()
}
const resetRuleQuery = () => {
if (!attributeDeviceId.value) return
ruleQueryFormRef.value?.resetFields?.()
handleRuleQuery()
}
const getRuleList = async () => {
if (!attributeDeviceId.value) return
ruleLoading.value = true
try {
const params = {
...ruleQueryParams,
deviceId: attributeDeviceId.value,
}
const data = await request.get({ url: '/iot/device-point-rules/page', params })
const listData = Array.isArray((data as any)?.list) ? (data as any).list : (Array.isArray(data) ? data : [])
const totalData = (data as any)?.total ?? listData.length
ruleList.value = listData as DevicePointRuleVO[]
ruleTotal.value = totalData
} finally {
ruleLoading.value = false
}
}
const parseAlarmRunningFieldRule = (fieldRule: any) => {
if (!fieldRule) return [] as any[]
if (Array.isArray(fieldRule)) return fieldRule
if (typeof fieldRule === 'string') {
try {
const parsed = JSON.parse(fieldRule)
return Array.isArray(parsed) ? parsed : []
} catch {
return []
}
}
return []
}
const resolveRuleAttributeIdByCode = (code: any) => {
if (code == null) return undefined
const targetCode = String(code).trim().toUpperCase()
const target = ruleAttributeOptions.value.find(
(item) => String(item?.attributeCode ?? '').trim().toUpperCase() === targetCode
)
return target?.id
}
const isRuleDisabled = (value: string, selfRule?: string | number) => {
if (!value) return false
const v = String(value)
const self = selfRule != null ? String(selfRule) : undefined
if (self === v) return false
if (ruleForm.fieldRule && String(ruleForm.fieldRule) === v && self !== v) return true
if (
extraPointRules.value.some(
(item) => item.rule != null && String(item.rule) === v && String(item.rule) !== self
)
) {
return true
}
return false
}
const openRuleForm = async (row: DevicePointRuleVO & { pointRulesVOList?: any[] }) => {
const deviceId = row.deviceId || attributeDeviceId.value
if (!deviceId) {
message.error(t('DataCollection.Device.messageDeviceInfoMissingForRules'))
return
}
await loadRuleAttributeOptions(deviceId)
ruleForm.id = row.id
ruleForm.identifier = row.identifier
ruleForm.fieldName = row.fieldName
ruleForm.defaultValue = row.defaultValue
ruleForm.deviceId = row.deviceId
ruleForm.alarmLevel = (row as any).alarmLevel
countArithmeticRules.value = [createEmptyCountArithmeticRule()]
extraPointRules.value = []
const list = Array.isArray(row.pointRulesVOList) ? row.pointRulesVOList : []
const parsedAlarmRunningList = parseAlarmRunningFieldRule(row.fieldRule)
const identifier = (row.identifier || '').toString().toUpperCase()
if (identifier !== 'COUNT' && parsedAlarmRunningList.length) {
const first = parsedAlarmRunningList[0] as any
ruleForm.fieldRule = first?.rule ?? ''
ruleForm.ruleAttributeId = resolveRuleAttributeIdByCode(first?.code) as any
ruleForm.ruleOperator = first?.operator as any
ruleForm.ruleValue = first?.operatorRule as any
extraPointRules.value = parsedAlarmRunningList.slice(1).map((item: any) => ({
id: resolveRuleAttributeIdByCode(item?.code),
rule: item?.rule,
operator: item?.operator,
operatorRule: item?.operatorRule,
}))
} else if (list.length) {
const first = list[0] as any
const firstRule = first.rule ?? row.fieldRule
const firstAttrId = (() => {
if (first && first.code != null) {
const target = ruleAttributeOptions.value.find(
(item) => item.attributeCode === first.code
)
return target?.id
}
if (first && first.id != null) return first.id
return undefined
})()
ruleForm.fieldRule = firstRule as any
ruleForm.ruleAttributeId = firstAttrId as any
ruleForm.ruleOperator = first.operator as any
ruleForm.ruleValue = first.operatorRule as any
extraPointRules.value = list.slice(1).map((item: any) => ({
id: (() => {
if (item && item.code != null) {
const target = ruleAttributeOptions.value.find(
(opt) => opt.attributeCode === item.code
)
return target?.id
}
if (item && item.id != null) return item.id
return undefined
})(),
rule: item.rule,
operator: item.operator,
operatorRule: item.operatorRule,
}))
if (identifier === 'COUNT') {
const parsedFieldRule = parseCountFieldRule(row.fieldRule)
if (parsedFieldRule.length) {
countArithmeticRules.value = parsedFieldRule
} else {
const parsedList = list.map((item: any) => ({
ruleAttributeId: resolveRuleAttributeIdByCode(item?.code),
ruleOperator: item?.operator,
ruleValue: item?.operatorRule,
}))
const filteredParsedList = parsedList.filter((item) => {
return item.ruleAttributeId && item.ruleOperator && String(item.ruleValue ?? '').trim() !== ''
})
countArithmeticRules.value = filteredParsedList.length
? filteredParsedList
: [createEmptyCountArithmeticRule()]
}
}
} else {
ruleForm.fieldRule = row.fieldRule
ruleForm.ruleAttributeId = row.ruleAttributeId
ruleForm.ruleOperator = row.ruleOperator
ruleForm.ruleValue = row.ruleValue as any
if (identifier === 'COUNT') {
const parsedFieldRule = parseCountFieldRule(row.fieldRule)
countArithmeticRules.value = parsedFieldRule.length
? parsedFieldRule
: [
{
ruleAttributeId: row.ruleAttributeId,
ruleOperator: row.ruleOperator,
ruleValue: row.ruleValue as any,
},
]
}
}
const options = currentRuleOptions.value
if (options.length && !options.some((item) => item.value === ruleForm.fieldRule)) {
ruleForm.fieldRule = options[0].value
}
ruleDialogVisible.value = true
}
const parseCountFieldRule = (fieldRule: any): CountArithmeticRule[] => {
if (!fieldRule) return []
const parsed = (() => {
if (typeof fieldRule !== 'string') return fieldRule
try {
return JSON.parse(fieldRule)
} catch {
return undefined
}
})() as any
if (Array.isArray(parsed)) {
return parsed
.map((item: any) => {
const pointId = resolveRuleAttributeIdByCode(item?.code) ?? item?.pointId
return {
ruleAttributeId: pointId != null ? Number(pointId) : undefined,
ruleOperator: item?.operator,
ruleValue: item?.value ?? item?.operatorRule,
bigOperator: item?.bigOperator,
} as CountArithmeticRule
})
.filter((item) => item.ruleAttributeId || item.ruleOperator || item.ruleValue != null || item.bigOperator)
}
const root = parsed?.expr ?? parsed
if (!root || typeof root !== 'object') return []
const rows: CountArithmeticRule[] = []
const walk = (node: any) => {
if (!node || typeof node !== 'object' || !node.op) return
if (node.left && typeof node.left === 'object' && node.left.op) {
walk(node.left)
}
const attrId = resolveRuleAttributeIdByCode(node?.left?.field)
const rightValue =
node?.right?.field != null
? String(node.right.field)
: node?.right?.value != null
? node.right.value
: undefined
rows.push({
ruleAttributeId: attrId,
ruleOperator: node.op,
ruleValue: rightValue,
})
}
walk(root)
return rows.filter((item) => item.ruleOperator || item.ruleAttributeId || item.ruleValue != null)
}
const loadRuleAttributeOptions = async (deviceId: number) => {
try {
const res = await request.get({ url: '/iot/device-contact-model/list', params: { id: deviceId } })
const data = Array.isArray(res) ? res : (res as any)?.list ?? []
ruleAttributeOptions.value = data as any[]
} catch {
ruleAttributeOptions.value = []
}
}
const handleRuleDelete = async (id: number) => {
if (!id) return
try {
await message.delConfirm()
await request.delete({ url: '/iot/device-point-rules/delete?id=' + id })
message.success(t('common.delSuccess'))
await getRuleList()
} catch { }
}
onMounted(() =>{
getRuleList()
})
</script>
<style scoped>
</style>