Files
pqs-9100_client/frontend/src/views/machine/device/components/devicePopup.vue
2025-10-30 11:09:50 +08:00

684 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" @close="close" v-bind="dialogBig" align-center>
<el-tabs type="border-card" v-model="activeTab" @tab-change="onTabChange">
<el-tab-pane label="设备台账信息">
<div ref="deviceInfoContainer">
<el-form
:model="formContent"
ref="dialogFormRef"
:rules="rules"
:disabled="false"
label-width="auto"
class="form-three"
:validate-on-rule-change="false"
>
<el-divider>设备信息</el-divider>
<el-form-item label="装置编号" prop="createId">
<el-input
v-model="formContent.createId"
placeholder="请输入装置编号"
@input="handleInput"
maxlength="32"
show-word-limit
/>
</el-form-item>
<el-form-item label="设备名称" prop="name">
<el-input
v-model="formContent.name"
placeholder="请输入设备名称"
:disabled="scene === '1' || formContent.importFlag == 1"
maxlength="32"
show-word-limit
/>
</el-form-item>
<el-form-item label="设备类型" prop="devType">
<el-select
v-model="formContent.devType"
filterable
clearable
placeholder="请选择设备类型"
@change="handleDevTypeChange"
:disabled="formContent.importFlag == 1"
>
<el-option
v-for="item in devTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="预投计划" prop="preinvestmentPlan" v-if="scene === '1'">
<el-select v-model="formContent.preinvestmentPlan" clearable placeholder="请选择预投计划">
<el-option
v-for="item in dictStore.getDictData('Preinvestment_Plan')"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="设备厂家" prop="manufacturer" v-if="scene != '1'">
<el-select
v-model="formContent.manufacturer"
clearable
placeholder="请选择设备厂家"
:disabled="formContent.importFlag == 1"
>
<el-option
v-for="item in dictStore.getDictData('Dev_Manufacturers')"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="createDateTitle" prop="createDate" v-if="scene === '0'">
<el-date-picker
v-model="formContent.createDate"
:placeholder="`请选择${createDateTitle}`"
:disabled-date="disabledDate"
:disabled="formContent.importFlag == 1"
/>
</el-form-item>
<el-form-item label="固件版本" prop="hardwareVersion" v-if="scene === '0'">
<el-input v-model="formContent.hardwareVersion" clearable placeholder="请输入固件版本" />
</el-form-item>
<el-form-item label="软件版本" prop="softwareVersion" v-if="scene === '0'">
<el-input v-model="formContent.softwareVersion" clearable placeholder="请输入软件版本" />
</el-form-item>
<el-form-item label="定检日期" prop="inspectDate" v-if="MonIsShow">
<el-date-picker
v-model="formContent.inspectDate"
placeholder="请选择定检日期"
:disabled-date="disabledDate"
:disabled="formContent.importFlag == 1"
/>
</el-form-item>
<el-divider>参数信息</el-divider>
<el-form-item label="通道数" prop="devChns" v-if="DevIsShow">
<el-input v-model="formContent.devChns" :disabled="true" />
</el-form-item>
<el-form-item label="额定电压(V)" prop="devVolt" v-if="DevIsShow">
<el-input v-model="formContent.devVolt" :disabled="true" />
</el-form-item>
<el-form-item label="额定电流(A)" prop="devCurr" v-if="DevIsShow">
<el-input v-model="formContent.devCurr" :disabled="true" />
</el-form-item>
<el-form-item label="被检通道" prop="inspectChannel" v-if="MonIsShow">
<el-select
v-model="formContent.inspectChannel"
multiple
collapse-tags
:disabled="true"
:max-collapse-tags="4"
placeholder="请选择被检通道"
clearable
>
<el-option
v-for="(option, index) in pqChannelArray"
:key="index"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item label="通讯协议" prop="protocol">
<el-select
v-model="formContent.protocol"
clearable
placeholder="请选择通讯协议"
:disabled="formContent.importFlag == 1"
>
<el-option
v-for="item in dictStore.getDictData('Protocol')"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="IP地址" prop="ip" placeholder="请输入IP地址">
<el-input v-model="formContent.ip" :disabled="formContent.importFlag == 1" />
</el-form-item>
<el-form-item label="端口号" prop="port" placeholder="请输入端口号">
<el-input v-model="formContent.port" :disabled="formContent.importFlag == 1" />
</el-form-item>
<el-form-item label="是否加密" prop="encryptionFlag">
<el-select
v-model="formContent.encryptionFlag"
clearable
placeholder="请选择是否加密"
:disabled="formContent.importFlag == 1"
>
<el-option label="是" :value="1"></el-option>
<el-option label="否" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="识别码" prop="series" clearable v-if="formContent.encryptionFlag">
<el-input
v-model="formContent.series"
placeholder="请输入识别码"
show-password
:disabled="formContent.importFlag == 1"
/>
</el-form-item>
<el-form-item label="密钥" prop="devKey" clearable v-if="formContent.encryptionFlag">
<el-input
v-model="formContent.devKey"
placeholder="请输入密钥"
show-password
:disabled="formContent.importFlag == 1"
/>
</el-form-item>
<el-form-item label="样品编号" prop="sampleId" clearable v-if="DevIsShow && scene === '0'">
<el-input v-model="formContent.sampleId" placeholder="请输入样品编号" />
</el-form-item>
<el-form-item label="送样日期" prop="arrivedDate" v-if="scene === '0'">
<el-date-picker
v-model="formContent.arrivedDate"
placeholder="请选择送样日期"
:disabled-date="disabledDate"
/>
</el-form-item>
<el-form-item label="委托方" prop="delegate" v-if="scene === '0'">
<el-select v-model="formContent.delegate" clearable placeholder="请选择委托方">
<el-option
v-for="item in dictStore.getDictData('Delegate')"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="所属地市" prop="cityName" v-if="MonIsShow">
<el-input
v-model="formContent.cityName"
clearable
placeholder="请输入所属地市"
:disabled="formContent.importFlag == 1"
/>
</el-form-item>
<el-form-item label="所属供电公司" prop="gdName" v-if="MonIsShow">
<el-input
v-model="formContent.gdName"
clearable
placeholder="请输入所属供电公司"
:disabled="formContent.importFlag == 1"
/>
</el-form-item>
<el-form-item label="所属电站" prop="subName" v-if="MonIsShow">
<el-input
v-model="formContent.subName"
clearable
placeholder="请输入所属电站"
:disabled="formContent.importFlag == 1"
/>
</el-form-item>
<el-form-item
v-auth.device="'factorFlag'"
label="是否支持系数校准"
prop="factorFlag"
v-if="scene === '1'"
>
<el-radio-group v-model="formContent.factorFlag">
<el-radio :value="1"></el-radio>
<el-radio :value="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="scene === '1'"
label="谐波系统设备id"
prop="harmSysId"
placeholder="请输入谐波系统设备id"
>
<el-input v-model="formContent.harmSysId" :disabled="formContent.importFlag == 1" />
</el-form-item>
</el-form>
</div>
</el-tab-pane>
<el-tab-pane label="监测点台账信息" v-if="MonIsShow">
<!-- 监测点台账信息 tab pane -->
<MonitorTable
@getParameter="getParameter"
:DevFormContent="formContent"
:tableHeight="monitorTableHeight"
:selectOptions="selectOptions"
></MonitorTable>
</el-tab-pane>
</el-tabs>
<template #footer>
<div>
<el-button @click="close()"> </el-button>
<el-button type="primary" @click="save()">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { dialogBig } from '@/utils/elementBind'
import { type Device } from '@/api/device/interface/device'
import { ElMessage, type FormItemRule } from 'element-plus'
import { addPqDev, getSelectOptions, updatePqDev } from '@/api/device/device'
import { computed, reactive, ref } from 'vue'
import { useDictStore } from '@/stores/modules/dict'
// 使用 dayjs 库格式化
import dayjs from 'dayjs'
import MonitorTable from '@/views/machine/device/components/monitorTab.vue'
import { useAppSceneStore } from '@/stores/modules/mode'
import { generateUUID } from '@/utils'
import { type Monitor } from '@/api/device/interface/monitor'
const AppSceneStore = useAppSceneStore()
const MonIsShow = ref(false)
const DevIsShow = ref(false)
// 存储设备类型选项
const devTypeOptions = ref<Device.ResDev[]>([])
const monitor = ref<Monitor.ResPqMon[]>([])
// const IsPasswordShow = ref(false)
const dictStore = useDictStore()
const mode = ref()
const scene = ref('')
// 定义弹出组件元信息
const dialogFormRef = ref()
const createDateTitle = ref('')
const activeTab = ref('0') // '0' 对应第一个 tab
const monitorTableHeight = ref(375) // 默认高度
const selectOptions = ref<Record<string, Device.SelectOption[]>>({})
const pqChannelArray = ref([
{
value: '1',
label: '1'
}
])
const disabledDate = (time: Date) => {
return time.getTime() > Date.now()
}
const onTabChange = (tabName: string | number) => {
const name = tabName.toString() // 统一转为字符串处理
if (name === '1') {
const formElement = dialogFormRef.value.$el as HTMLElement
const formHeight = formElement.offsetHeight
monitorTableHeight.value = formHeight - 88
}
}
function useMetaInfo() {
const dialogVisible = ref(false)
const titleType = ref('add')
const formContent = reactive<Device.ResPqDev>({
id: '',
name: '',
pattern: mode.value,
devType: '',
devChns: 1,
devVolt: 57.74,
devCurr: 5,
manufacturer: '',
createDate: dayjs().format('YYYY-MM-DD'),
createId: '',
hardwareVersion: '',
softwareVersion: '',
protocol: 'MMS',
ip: '',
port: 102,
encryptionFlag: 0,
recheckNum: 0,
state: 1,
factorFlag: 1,
icdId: '',
power: '',
preinvestmentPlan: '',
delegate: '',
inspectDate: dayjs().format('YYYY-MM-DD'),
cityName: '',
gdName: '',
subName: '',
importFlag: 0,
inspectChannel: [],
monitorList: []
})
return { dialogVisible, titleType, formContent }
}
const { dialogVisible, titleType, formContent } = useMetaInfo()
// 清空formContent
const resetFormContent = () => {
Object.assign(formContent, {
id: '',
name: '',
pattern: mode.value,
devType: '',
devChns: 1,
devVolt: 57.74,
devCurr: 5,
manufacturer: '',
createDate: dayjs().format('YYYY-MM-DD'),
createId: '',
hardwareVersion: '',
softwareVersion: '',
protocol: 'MMS',
ip: '',
port: 102,
encryptionFlag: 0,
recheckNum: 0,
state: 1,
factorFlag: 1,
icdId: '',
power: '',
preinvestmentPlan: '',
delegate: '',
inspectDate: dayjs().format('YYYY-MM-DD'),
cityName: '',
gdName: '',
subName: '',
importFlag: 0,
inspectChannel: [],
monitorList: []
})
}
let dialogTitle = computed(() => {
return titleType.value === 'add' ? '新增被检设备' : '编辑被检设备'
})
// 定义表单校验规则
const baseRules: Record<string, Array<FormItemRule>> = {
devType: [{ required: true, message: '设备类型必选!', trigger: 'change' }],
pattern: [{ required: true, message: '设备模式必填!', trigger: 'blur' }],
devChns: [
{ required: true, message: '设备通道数必填!', trigger: 'blur' },
{ pattern: /^[1-9]\d*$/, message: '设备通道数为大于1的整数', trigger: 'blur' }
],
devVolt: [
{ required: true, message: '额定电压必填!', trigger: 'blur' },
{ pattern: /^\d+(\.\d+)?$/, message: '额定电压格式错误', trigger: 'blur' }
],
devCurr: [
{ required: true, message: '额定电流必填!', trigger: 'blur' },
{ pattern: /^\d+(\.\d+)?$/, message: '额定电流格式错误', trigger: 'blur' }
],
createId: [{ required: true, message: '装置编号必填!', trigger: 'blur' }],
ip: [
{ required: true, message: 'IP地址必填', trigger: 'blur' },
{
pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
message: 'IP地址格式错误',
trigger: 'blur'
}
],
port: [
{ required: true, message: '端口号必填!', trigger: 'blur' },
{
pattern: /^(6553[0-5]|655[0-2][0-9]|64[0-9]{3}|[1-5]?[0-9]{1,4})$/,
message: '端口号范围0到65535的整数',
trigger: 'blur'
}
],
encryptionFlag: [{ required: true, message: '是否加密必选!', trigger: 'change' }],
protocol: [{ required: true, message: '通讯协议必选!', trigger: 'change' }],
series: [{ required: true, message: '请输入识别码', trigger: 'blur' }],
devKey: [{ required: true, message: '请输入密钥', trigger: 'blur' }]
}
// 使用计算属性根据 scene 动态生成规则
const rules = computed(() => {
const dynamicRules = { ...baseRules }
if (scene.value === '0') {
//只有电科院需要展示
dynamicRules.createDate = [{ required: true, message: createDateTitle.value + '必填!', trigger: 'blur' }]
}
if (scene.value === '1') {
//只有楼下出厂需要展示
dynamicRules.preinvestmentPlan = [{ required: true, message: '预投计划必选!', trigger: 'change' }]
dynamicRules.createId = [
{ required: true, message: '装置编号必填!', trigger: 'blur' },
{ pattern: /^\d+$/, message: '装置编号为数字', trigger: 'blur' }
]
}
if (scene.value !== '0') {
dynamicRules.name = [{ required: true, message: '设备名称必填!', trigger: 'blur' }]
// dynamicRules.hardwareVersion = [{ required: true, message: '固件版本必选!', trigger: 'change' }]
// dynamicRules.softwareVersion = [{ required: true, message: '软件版本必选!', trigger: 'change' }]
dynamicRules.hardwareVersion = [{ required: true, message: '固件版本必填!', trigger: 'blur' }]
dynamicRules.softwareVersion = [{ required: true, message: '软件版本必填!', trigger: 'blur' }]
dynamicRules.manufacturer = [{ required: true, message: '生产厂家必选!', trigger: 'change' }]
}
if (scene.value !== '2') {
dynamicRules.name = [{ required: true, message: '设备名称必填!', trigger: 'blur' }]
dynamicRules.manufacturer = [{ required: true, message: '生产厂家必选!', trigger: 'change' }]
}
if (mode.value === '比对式') {
dynamicRules.inspectDate = [{ required: true, message: '定检日期必填!', trigger: 'blur' }]
// dynamicRules.cityName = [{ required: true, message: '所属地市必选!', trigger: 'change' }]
// dynamicRules.gdName = [{ required: true, message: '所属供电公司必选!', trigger: 'change' }]
// dynamicRules.subName = [{ required: true, message: '所属电站必选!', trigger: 'change' }]
dynamicRules.cityName = [{ required: true, message: '所属地市必填!', trigger: 'blur' }]
dynamicRules.gdName = [{ required: true, message: '所属供电公司必填!', trigger: 'blur' }]
dynamicRules.subName = [{ required: true, message: '所属电站必填!', trigger: 'blur' }]
}
return dynamicRules
})
// 关闭弹窗
const close = () => {
dialogVisible.value = false
// 清空dialogForm中的值
resetFormContent()
// 重置表单
dialogFormRef.value?.resetFields()
}
//子组件监测点变化通知父组件设备台账
const getParameter = (data: Monitor.ResPqMon[]) => {
console.log('子组件监测点变化通知父组件设备台账', data)
monitor.value = data
//保存监测点信息自动更新设备台账的被检通道
formContent.inspectChannel = monitor.value
.map(item => item.num.toString())
.sort((a, b) => parseFloat(a) - parseFloat(b))
}
// 保存数据
const save = () => {
try {
dialogFormRef.value?.validate(async (valid: boolean) => {
if (formContent.encryptionFlag === 0) {
formContent.series = ''
formContent.devKey = ''
}
if (valid) {
formContent.createDate = dayjs(formContent.createDate).format('YYYY-MM-DD')
if (AppSceneStore.currentScene === '1') {
formContent.name = formContent.createId
}
if (formContent.arrivedDate) {
formContent.arrivedDate = dayjs(formContent.arrivedDate).format('YYYY-MM-DD')
}
if (mode.value == '比对式') {
if (!monitor.value || monitor.value.length === 0) {
activeTab.value = '1'
ElMessage.warning({ message: '请添加监测点台账信息!' })
return
}
if (formContent.inspectDate) {
formContent.inspectDate = dayjs(formContent.inspectDate).format('YYYY-MM-DD')
}
//可检通道转为字符串逗号分隔
// 确保 inspectChannel 是数组再执行 join
if (Array.isArray(formContent.inspectChannel)) {
formContent.inspectChannel = formContent.inspectChannel
.map(Number) // 将值转为数字以保证正确排序
.sort((a, b) => a - b) // 数字升序排序
.map(String) // 恢复为字符串用于保存
.join(',')
}
} else {
formContent.inspectChannel = null
}
//保存时判是否加密,把识别码密钥字段清空
if (formContent.encryptionFlag === 0) {
formContent.series = null
formContent.devKey = null
}
formContent.monitorList = monitor.value
if (titleType.value != 'add') {
await updatePqDev(formContent)
ElMessage.success({ message: `${dialogTitle.value}成功!` })
} else {
// 新增需要把设备模式转成字典ID
const patternItem = dictStore.getDictData('Pattern').find(item => item.name === formContent.pattern)
if (patternItem) {
formContent.pattern = patternItem.id
}
// 新增需要把通讯协议转成字典ID
const protocolItem = dictStore
.getDictData('Protocol')
.find(item => item.name === formContent.protocol)
if (protocolItem) {
formContent.protocol = protocolItem.id
}
await addPqDev(formContent)
ElMessage.success({ message: `${dialogTitle.value}成功!` })
}
close()
// 刷新表格
await props.refreshTable!()
} else {
if (activeTab.value === '1') {
activeTab.value = '0'
ElMessage.warning({ message: '设备台账信息填写有误!' })
}
}
})
} catch (err) {
console.error('验证过程中出现错误', err)
}
}
// 打开弹窗,可能是新增,也可能是编辑
const open = async (
sign: string,
data: Device.ResPqDev,
currentMode: string,
currentScene: string,
devType: Device.ResDev[]
) => {
dialogFormRef.value?.resetFields()
devTypeOptions.value = devType
mode.value = currentMode
scene.value = currentScene
titleType.value = sign
dialogVisible.value = true
activeTab.value = '0' // 强制回到第一个 tab
if (currentMode === '比对式') {
const patternItem = dictStore.getDictData('Pattern').find(item => item.name === currentMode)
if (patternItem) {
const { data } = (await getSelectOptions({ pattern: patternItem.id })) as { data: Record<string, any[]> }
// 遍历 data 的所有字段并映射为 { label, value }
for (const key in data) {
if (Array.isArray(data[key])) {
selectOptions.value[key] = data[key].map((value: string) => ({
label: value,
value: value
}))
}
}
}
createDateTitle.value = '投运日期'
DevIsShow.value = false
MonIsShow.value = true
} else {
createDateTitle.value = '出厂日期'
DevIsShow.value = true
MonIsShow.value = false
}
if (data.id) {
updateDeviceByType(data.devType)
Object.assign(formContent, { ...data })
if (typeof formContent.inspectChannel === 'string') {
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean)
}
monitor.value = data.monitorList
} else {
resetFormContent()
//只有比对式设备ID由前端传
if (currentMode === '比对式') formContent.id = generateUUID().replaceAll('-', '')
if (scene.value === '2') {
const manufacturers = dictStore.getDictData('Dev_Manufacturers')
const nanjingCanxuan = manufacturers.find(item => item.name === '南京灿能')
if (nanjingCanxuan) {
formContent.manufacturer = nanjingCanxuan.id
}
}
// 设置默认设备参数
updateDeviceByType('') // 可传空或默认类型
}
}
const updateDeviceByType = (devTypeId: string) => {
const dev = devTypeOptions.value.find(t => t.id === devTypeId)
if (dev) {
formContent.devChns = dev.devChns
formContent.devCurr = dev.devCurr
formContent.devVolt = dev.devVolt
formContent.icdId = dev.icd
formContent.power = dev.power
// 动态设置 pqChannelArray 从 1 到 dev.devChns.length
pqChannelArray.value = Array.from({ length: dev.devChns }, (_, i) => ({
value: String(i + 1),
label: String(i + 1)
}))
} else {
// 默认值处理
formContent.devChns = 1
formContent.devCurr = 1
formContent.devVolt = 57.74
pqChannelArray.value = [{ value: '1', label: '1' }]
}
}
const handleDevTypeChange = (value: string) => {
updateDeviceByType(value)
const maxChannel = formContent.devChns
// 过滤掉超出新通道数范围的选项
if (Array.isArray(formContent.inspectChannel)) {
formContent.inspectChannel = formContent.inspectChannel.filter(channel => parseInt(channel, 10) <= maxChannel)
}
//切换设备类型时,删除超出最大通道数的监测点台账
monitor.value = monitor.value.filter(monitor => parseInt(monitor.num.toString(), 10) <= maxChannel)
formContent.monitorList = monitor.value
}
const handleInput = (value: string) => {
// 在这里处理选中事件的逻辑
//if (scene.value === '1') {
formContent.name = value
//}
}
// 对外映射
defineExpose({ open })
const props = defineProps<{
refreshTable: (() => Promise<void>) | undefined
}>()
</script>