feat: 网关管理页面 + 设备表单网关下拉联动
- 新增 src/api/gateway.js(网关 CRUD + 测试连接 + options API + 枚举映射) - 新增 src/views/config/gateway/Index.vue(网关管理页面:列表/新增/编辑/测试连接/详情抽屉+下挂设备) - 修改 src/views/config/device/Index.vue(GatewayCode 文本输入改为 GatewayId 下拉选择,列表列显示 GatewayName) - 新增 src/api/product.js、src/api/thingPoint.js(产品/物模型点 API) - 新增 src/components/thing/(PointSendForm、ThingPointTable、ThingAlarmPanel 共享组件)
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div class="point-send-form">
|
||||
<div class="ps-toolbar">
|
||||
<span class="ps-count">
|
||||
共 {{ points.length }} 个点,可下发 {{ writablePoints.length }} 个
|
||||
<span v-if="points.length" class="ps-tip">(只读点不支持下发;Int32/Float 从地址起连写 2 个寄存器)</span>
|
||||
</span>
|
||||
<el-button link type="primary" size="small" :loading="loading" @click="fetchPoints">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="points" v-loading="loading" size="small" border stripe>
|
||||
<el-table-column prop="Code" label="编码" width="120" />
|
||||
<el-table-column prop="Name" label="名称" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column label="寄存器" width="90">
|
||||
<template #default="{ row }">{{ registerLabel(row.RegisterType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="地址" width="70" align="center">
|
||||
<template #default="{ row }">0x{{ dec2hex(row.Address) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="读写" width="60" align="center">
|
||||
<template #default="{ row }">{{ rwLabel(row.Rw) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="84" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.Rw === 1" size="small" type="info">只读</el-tag>
|
||||
<el-tag v-else size="small" type="success">可下发</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下发值" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-if="row.DataType === 1"
|
||||
:model-value="row._sendValue === 1"
|
||||
active-text="开"
|
||||
@change="row._sendValue = $event ? 1 : 0"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else
|
||||
v-model="row._sendValue"
|
||||
:precision="row.DataType === 5 ? 1 : 0"
|
||||
:step="1"
|
||||
:controls="false"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" width="60" align="center">
|
||||
<template #default="{ row }">{{ row.Unit || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="70" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link type="primary"
|
||||
:disabled="row.Rw === 1"
|
||||
@click="onSend(row)"
|
||||
>下发</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态区分:无点 / 点均不可下发 -->
|
||||
<el-empty
|
||||
v-if="!loading && points.length === 0"
|
||||
description="该设备暂无物模型点,请先在「物模型点」标签页为该设备添加数据点"
|
||||
:image-size="60"
|
||||
/>
|
||||
<el-alert
|
||||
v-else-if="!loading && writablePoints.length === 0"
|
||||
title="已添加的点均为只读,暂无可下发点(可将点读写属性改为「只写/读写」)"
|
||||
type="info"
|
||||
:closable="false"
|
||||
class="ps-feedback"
|
||||
/>
|
||||
|
||||
<!-- 下发反馈 -->
|
||||
<el-alert v-if="feedback.text" :title="feedback.text" :type="feedback.type" :closable="false" class="ps-feedback" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getThingPoints, registerLabel, rwLabel } from '@/api/thingPoint'
|
||||
import { sendDeviceCommand } from '@/api/device'
|
||||
|
||||
const props = defineProps({
|
||||
deviceId: { type: String, required: true }
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const points = ref([])
|
||||
const feedback = ref({ text: '', type: 'success' })
|
||||
|
||||
// 只读(1)的点不能下发;只写/读写都可以(Int32/Float 走 FC16 连写 2 个寄存器,已支持)
|
||||
const writablePoints = computed(() => points.value.filter(p => p.Rw !== 1))
|
||||
|
||||
function dec2hex(n) {
|
||||
return (Number(n) || 0).toString(16).toUpperCase().padStart(4, '0')
|
||||
}
|
||||
|
||||
async function fetchPoints() {
|
||||
loading.value = true
|
||||
try {
|
||||
const pts = (await getThingPoints(2, props.deviceId)) || []
|
||||
// 显示设备全部点(含只读),便于与「物模型点」标签页对照
|
||||
points.value = pts.map(p => ({ ...p, _sendValue: p.DataType === 1 ? 1 : 0 }))
|
||||
} catch (e) {
|
||||
points.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onSend(row) {
|
||||
try {
|
||||
await sendDeviceCommand(props.deviceId, {
|
||||
Id: props.deviceId,
|
||||
PointId: row.Id,
|
||||
Value: row._sendValue ?? 0,
|
||||
IsSimulated: false
|
||||
})
|
||||
// res 为后端返回的 Data(无数据时返回 undefined),消息提示走统一处理
|
||||
feedback.value = { text: `已下发至「${row.Name || row.Code}」`, type: 'success' }
|
||||
ElMessage.success(`指令已下发:${row.Name || row.Code}`)
|
||||
} catch (e) {
|
||||
feedback.value = { text: e?.message || '下发失败', type: 'error' }
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.deviceId, (val) => { if (val) fetchPoints() })
|
||||
onMounted(() => { if (props.deviceId) fetchPoints() })
|
||||
|
||||
defineExpose({ refresh: fetchPoints })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ps-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ps-count {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
.ps-tip {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
.ps-feedback {
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="thing-alarm-panel">
|
||||
<div class="tp-toolbar">
|
||||
<el-button type="primary" size="small" icon="Plus" @click="openAdd">新增告警规则</el-button>
|
||||
<span v-if="rules.length" class="tp-count">共 {{ rules.length }} 条规则</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="rules" v-loading="loading" size="small" border stripe>
|
||||
<el-table-column prop="RuleName" label="规则名称" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="绑定点" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.PointCode" size="small" type="info">{{ row.PointCode }}</el-tag>
|
||||
<span v-else>整{{ ownerTypeLabel }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="触发条件" min-width="140">
|
||||
<template #default="{ row }">
|
||||
{{ metricLabel(row.Metric) }} {{ row.Operator }} {{ row.Threshold }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="级别" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="levelTagType(row.AlarmLevel)" size="small">{{ row.AlarmLevel }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="启用" width="70" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch :model-value="row.Enabled" size="small" @change="onToggle(row, $event)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="form.Id ? '编辑告警规则' : '新增告警规则'" width="520px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="rulesForm" label-width="100px">
|
||||
<el-form-item label="规则名称" prop="RuleName">
|
||||
<el-input v-model="form.RuleName" placeholder="如:温度超限报警" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="监测指标" prop="Metric">
|
||||
<el-select v-model="form.Metric" placeholder="请选择该归属下的点" style="width: 100%">
|
||||
<el-option v-for="p in pointOptions" :key="p.value" :label="p.label" :value="p.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="触发条件" required>
|
||||
<div style="display: flex; gap: 8px; width: 100%">
|
||||
<el-select v-model="form.Operator" placeholder="运算符" style="width: 45%">
|
||||
<el-option v-for="op in operators" :key="op" :label="op" :value="op" />
|
||||
</el-select>
|
||||
<el-input-number v-model="form.Threshold" :precision="1" :step="1" style="width: 55%" placeholder="阈值" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警级别" prop="AlarmLevel">
|
||||
<el-select v-model="form.AlarmLevel" style="width: 100%">
|
||||
<el-option v-for="l in levels" :key="l" :label="l" :value="l" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="form.Enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.Remark" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getOwnerAlertRules, addAlertRule, updateAlertRule, deleteAlertRule, setAlertRuleEnabled } from '@/api/alertRule'
|
||||
import { getThingPoints } from '@/api/thingPoint'
|
||||
|
||||
const props = defineProps({
|
||||
ownerType: { type: Number, required: true }, // 1=产品 2=设备
|
||||
ownerId: { type: String, required: true }
|
||||
})
|
||||
|
||||
const ownerTypeLabel = computed(() => (props.ownerType === 1 ? '产品' : '设备'))
|
||||
|
||||
const operators = ['>', '>=', '<', '<=', '=', '≠']
|
||||
const levels = ['信息', '警告', '紧急']
|
||||
const levelTagType = (l) => ({ '信息': 'info', '警告': 'warning', '紧急': 'danger' }[l] || 'info')
|
||||
|
||||
const loading = ref(false)
|
||||
const rules = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const saving = ref(false)
|
||||
const formRef = ref(null)
|
||||
const pointOptions = ref([])
|
||||
|
||||
const defaultForm = () => ({
|
||||
Id: '', RuleName: '', DeviceType: '', Metric: '', Operator: '>',
|
||||
Threshold: null, AlarmLevel: '警告', Enabled: true, Remark: '',
|
||||
OwnerType: props.ownerType, OwnerId: props.ownerId, PointCode: ''
|
||||
})
|
||||
const form = reactive(defaultForm())
|
||||
|
||||
const rulesForm = {
|
||||
RuleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||
Metric: [{ required: true, message: '请选择监测指标', trigger: 'change' }],
|
||||
AlarmLevel: [{ required: true, message: '请选择告警级别', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const metricLabel = (m) => pointOptions.value.find(p => p.value === m)?.label || m || '-'
|
||||
|
||||
async function fetchRules() {
|
||||
loading.value = true
|
||||
try {
|
||||
rules.value = (await getOwnerAlertRules(props.ownerType, props.ownerId)) || []
|
||||
} catch (e) {
|
||||
rules.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPoints() {
|
||||
try {
|
||||
const pts = (await getThingPoints(props.ownerType, props.ownerId)) || []
|
||||
pointOptions.value = pts.map(p => ({ value: p.Code, label: `${p.Name || p.Code} (${p.Code})` }))
|
||||
} catch (e) {
|
||||
pointOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
Object.assign(form, defaultForm())
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
Object.assign(form, defaultForm(), row)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
await formRef.value.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
...form,
|
||||
OwnerType: props.ownerType,
|
||||
OwnerId: Number(props.ownerId) || 0,
|
||||
// 从点选 Metric 时同步 PointCode(点编码即绑定编码)
|
||||
PointCode: form.Metric || null
|
||||
}
|
||||
if (payload.Id) {
|
||||
await updateAlertRule(payload)
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
delete payload.Id
|
||||
await addAlertRule(payload)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchRules()
|
||||
} catch (e) { /* 拦截器已提示 */ } finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onToggle(row, enabled) {
|
||||
try {
|
||||
await setAlertRuleEnabled(row.Id, enabled)
|
||||
row.Enabled = enabled
|
||||
ElMessage.success(enabled ? '已启用' : '已停用')
|
||||
} catch (e) { /* 拦截器已提示 */ }
|
||||
}
|
||||
|
||||
async function onDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除规则「${row.RuleName}」吗?`, '提示', { type: 'warning' })
|
||||
try {
|
||||
await deleteAlertRule(row.Id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchRules()
|
||||
} catch (e) { /* 拦截器已提示 */ }
|
||||
}
|
||||
|
||||
watch(() => props.ownerId, (val) => { if (val) { fetchPoints(); fetchRules() } })
|
||||
onMounted(() => { if (props.ownerId) { fetchPoints(); fetchRules() } })
|
||||
|
||||
defineExpose({ refresh: fetchRules })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tp-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tp-count {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div class="thing-point-table">
|
||||
<div class="tp-toolbar">
|
||||
<el-button type="primary" size="small" icon="Plus" @click="openAdd">新增点</el-button>
|
||||
<span v-if="points.length" class="tp-count">共 {{ points.length }} 个点</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="points" v-loading="loading" size="small" border stripe>
|
||||
<el-table-column prop="Code" label="编码" width="120" />
|
||||
<el-table-column prop="Name" label="名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="寄存器" width="90">
|
||||
<template #default="{ row }">{{ registerLabel(row.RegisterType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="读写" width="70" align="center">
|
||||
<template #default="{ row }">{{ rwLabel(row.Rw) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="80" align="center">
|
||||
<template #default="{ row }">{{ dataLabel(row.DataType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="地址" width="80" align="center">
|
||||
<template #default="{ row }">0x{{ dec2hex(row.Address) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="倍率" width="70" align="center">
|
||||
<template #default="{ row }">{{ row.Scale }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="Unit" label="单位" width="60" align="center">
|
||||
<template #default="{ row }">{{ row.Unit || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="启用" width="70" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.Enabled ? 'success' : 'info'" size="small">{{ row.Enabled ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="form.Id ? '编辑点' : '新增点'" width="560px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="点编码" prop="Code">
|
||||
<el-input v-model="form.Code" placeholder="如 TempPV / PowerOn" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点名称" prop="Name">
|
||||
<el-input v-model="form.Name" placeholder="如 温度PV" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="寄存器类型" prop="RegisterType">
|
||||
<el-select v-model="form.RegisterType" style="width: 100%">
|
||||
<el-option v-for="o in registerOptions" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="读写权限" prop="Rw">
|
||||
<el-select v-model="form.Rw" style="width: 100%">
|
||||
<el-option v-for="o in rwOptions" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="数据类型" prop="DataType">
|
||||
<el-select v-model="form.DataType" style="width: 100%">
|
||||
<el-option v-for="o in dataOptions" :key="o.value" :label="o.label" :value="o.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="寄存器地址" prop="Address">
|
||||
<el-input-number v-model="form.Address" :min="0" :max="65535" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="倍率 Scale">
|
||||
<el-input-number v-model="form.Scale" :min="0.0001" :precision="4" :step="1" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="偏移 Offset">
|
||||
<el-input-number v-model="form.Offset" :precision="4" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位">
|
||||
<el-input v-model="form.Unit" placeholder="如 ℃ / %" maxlength="16" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序号">
|
||||
<el-input-number v-model="form.Sort" :min="0" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="是否启用">
|
||||
<el-switch v-model="form.Enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.Remark" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getThingPoints, addThingPoint, updateThingPoint, deleteThingPoint,
|
||||
rwOptions, rwLabel, registerOptions, registerLabel, dataOptions, dataLabel
|
||||
} from '@/api/thingPoint'
|
||||
|
||||
const props = defineProps({
|
||||
ownerType: { type: Number, required: true }, // 1=产品 2=设备
|
||||
ownerId: { type: String, required: true }
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const points = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const saving = ref(false)
|
||||
const formRef = ref(null)
|
||||
|
||||
const defaultForm = () => ({
|
||||
Id: '', Code: '', Name: '', OwnerType: props.ownerType, OwnerId: props.ownerId,
|
||||
RegisterType: 2, Rw: 1, DataType: 3, Address: 0,
|
||||
Scale: 1, Offset: 0, Unit: '', Enabled: true, Sort: 0, Remark: ''
|
||||
})
|
||||
const form = reactive(defaultForm())
|
||||
|
||||
const rules = {
|
||||
Code: [{ required: true, message: '请输入点编码', trigger: 'blur' }],
|
||||
Name: [{ required: true, message: '请输入点名称', trigger: 'blur' }],
|
||||
RegisterType: [{ required: true, message: '请选择寄存器类型', trigger: 'change' }],
|
||||
Rw: [{ required: true, message: '请选择读写权限', trigger: 'change' }],
|
||||
DataType: [{ required: true, message: '请选择数据类型', trigger: 'change' }]
|
||||
}
|
||||
|
||||
function dec2hex(n) {
|
||||
return (Number(n) || 0).toString(16).toUpperCase().padStart(4, '0')
|
||||
}
|
||||
|
||||
async function fetchPoints() {
|
||||
loading.value = true
|
||||
try {
|
||||
points.value = (await getThingPoints(props.ownerType, props.ownerId)) || []
|
||||
} catch (e) {
|
||||
points.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
Object.assign(form, defaultForm(), { OwnerType: props.ownerType, OwnerId: props.ownerId })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
Object.assign(form, defaultForm(), {
|
||||
Id: row.Id, Code: row.Code, Name: row.Name,
|
||||
RegisterType: row.RegisterType, Rw: row.Rw, DataType: row.DataType,
|
||||
Address: row.Address, Scale: row.Scale, Offset: row.Offset,
|
||||
Unit: row.Unit || '', Enabled: row.Enabled, Sort: row.Sort, Remark: row.Remark || ''
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
await formRef.value.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = { ...form, OwnerType: props.ownerType, OwnerId: props.ownerId }
|
||||
if (payload.Id) {
|
||||
await updateThingPoint(payload)
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
delete payload.Id
|
||||
await addThingPoint(payload)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchPoints()
|
||||
} catch (e) { /* 拦截器已提示 */ } finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除点「${row.Code}」吗?`, '提示', { type: 'warning' })
|
||||
try {
|
||||
await deleteThingPoint(row.Id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchPoints()
|
||||
} catch (e) { /* 拦截器已提示 */ }
|
||||
}
|
||||
|
||||
watch(() => props.ownerId, (val) => { if (val) fetchPoints() })
|
||||
onMounted(() => { if (props.ownerId) fetchPoints() })
|
||||
|
||||
defineExpose({ refresh: fetchPoints })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tp-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tp-count {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user