Files
pig-farm-controller-fe/src/components/DeviceForm.vue
2025-09-30 23:52:59 +08:00

360 lines
11 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
:model-value="visible"
:title="title"
@close="handleClose"
:close-on-click-modal="false"
width="600px"
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="120px"
@submit.prevent
>
<!-- 基础信息 -->
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="formData.type" @change="handleTypeChange" :disabled="isEdit">
<el-option label="区域主控" value="area_controller" />
<el-option label="普通设备" value="device" />
</el-select>
</el-form-item>
<el-form-item label="位置描述" prop="location">
<el-input v-model="formData.location" type="textarea" />
</el-form-item>
<!-- 区域主控类型额外字段 -->
<div v-if="formData.type === 'area_controller'">
<el-form-item label="网络ID" prop="network_id">
<el-input v-model="formData.network_id" />
</el-form-item>
</div>
<!-- 普通设备类型额外字段 -->
<div v-if="formData.type === 'device'">
<el-form-item label="所属区域主控" prop="area_controller_id">
<el-select v-model="formData.area_controller_id" placeholder="请选择区域主控">
<el-option
v-for="controller in areaControllers"
:key="controller.id"
:label="controller.name"
:value="controller.id"
/>
</el-select>
</el-form-item>
<el-form-item label="设备模板" prop="device_template_id">
<el-select v-model="formData.device_template_id" placeholder="请选择设备模板">
<el-option
v-for="template in deviceTemplates"
:key="template.id"
:label="template.name"
:value="template.id"
/>
</el-select>
</el-form-item>
<el-form-item label="485总线号" prop="properties.bus_number">
<el-input-number
v-model="formData.properties.bus_number"
:min="0"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="485总线地址" prop="properties.bus_address">
<el-input-number
v-model="formData.properties.bus_address"
:min="0"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import { ref, reactive, onMounted, watch, computed, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import { AreaControllerApi, DeviceApi } from '../api/device.js';
import deviceTemplateService from '../services/deviceTemplateService.js'; // 导入设备模板服务
export default {
name: 'DeviceForm',
props: {
visible: {
type: Boolean,
default: false
},
deviceData: {
type: Object,
default: () => ({})
},
isEdit: {
type: Boolean,
default: false
}
},
emits: ['update:visible', 'success', 'cancel'],
setup(props, { emit }) {
const formRef = ref(null);
const loading = ref(false);
const areaControllers = ref([]);
const deviceTemplates = ref([]); // 新增设备模板列表状态
const initialFormData = () => ({
id: '',
name: '',
type: 'device', // 默认创建普通设备
location: '',
network_id: '', // 区域主控字段
area_controller_id: '', // 普通设备字段
device_template_id: '', // 普通设备字段
properties: { // 嵌套的properties对象
bus_number: 0,
bus_address: 0,
}
});
const formData = reactive(initialFormData());
const rules = computed(() => ({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
location: [
{ required: true, message: '请输入位置描述', trigger: 'blur' }
],
network_id: [
{ required: formData.type === 'area_controller', message: '请输入网络ID', trigger: 'blur' }
],
area_controller_id: [
{ required: formData.type === 'device', message: '请选择所属区域主控', trigger: 'change' }
],
device_template_id: [
{ required: formData.type === 'device', message: '请选择设备模板', trigger: 'change' }
],
'properties.bus_number': [
{ required: formData.type === 'device', message: '请输入485总线号', trigger: 'blur' }
],
'properties.bus_address': [
{ required: formData.type === 'device', message: '请输入485总线地址', trigger: 'blur' }
],
}));
const title = computed(() => {
return props.isEdit ? '编辑设备' : '添加设备';
});
const handleTypeChange = (value) => {
// 清除不同类型特有的字段
if (value === 'area_controller') {
formData.area_controller_id = '';
formData.device_template_id = '';
formData.properties = { bus_number: 0, bus_address: 0 };
} else {
formData.network_id = '';
}
// 触发验证规则更新
nextTick(() => {
if (formRef.value) {
formRef.value.clearValidate();
}
});
};
const loadAreaControllers = async () => {
try {
const response = await AreaControllerApi.list();
areaControllers.value = response.data || [];
} catch (error) {
console.error('获取区域主控列表失败:', error);
areaControllers.value = [];
}
};
// 新增:加载设备模板列表
const loadDeviceTemplates = async () => {
try {
const response = await deviceTemplateService.getDeviceTemplates();
deviceTemplates.value = response.data || [];
} catch (error) {
console.error('获取设备模板列表失败:', error);
deviceTemplates.value = [];
}
};
const handleClose = () => {
emit('update:visible', false);
emit('cancel');
// 重置表单
Object.assign(formData, initialFormData());
nextTick(() => {
if (formRef.value) {
formRef.value.resetFields();
}
});
};
const getSubmitData = () => {
const data = {
name: formData.name,
location: formData.location,
properties: formData.properties // properties直接作为对象传递
};
if (formData.type === 'area_controller') {
data.network_id = formData.network_id;
} else {
data.area_controller_id = formData.area_controller_id;
data.device_template_id = formData.device_template_id;
}
return data;
};
const handleSubmit = async () => {
if (!formRef.value) return;
await formRef.value.validate(async (valid) => {
if (valid) {
loading.value = true;
try {
let result;
const submitData = getSubmitData();
if (props.isEdit) {
if (formData.type === 'area_controller') {
result = await AreaControllerApi.update(formData.id, submitData);
} else {
result = await DeviceApi.update(formData.id, submitData);
}
} else {
if (formData.type === 'area_controller') {
result = await AreaControllerApi.create(submitData);
} else {
result = await DeviceApi.create(submitData);
}
}
// 适配DeviceList的树形结构添加type和parent_id
const processedResult = {
...result.data,
type: formData.type
};
if (formData.type === 'device') {
processedResult.parent_id = processedResult.area_controller_id;
}
emit('success', processedResult);
handleClose();
} catch (error) {
console.error('保存设备失败:', error);
ElMessage.error(props.isEdit ? '编辑设备失败: ' + (error.message || '未知错误') : '创建设备失败: ' + (error.message || '未知错误'));
} finally {
loading.value = false;
}
}
});
};
watch(() => props.deviceData, (newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
// 重置表单以清除旧数据和验证状态
Object.assign(formData, initialFormData());
nextTick(() => {
if (formRef.value) {
formRef.value.clearValidate();
}
});
formData.id = newVal.id;
formData.name = newVal.name;
formData.type = newVal.type;
formData.location = newVal.location;
if (newVal.type === 'area_controller') {
formData.network_id = newVal.network_id || '';
} else if (newVal.type === 'device') {
formData.area_controller_id = newVal.area_controller_id || newVal.parent_id || '';
formData.device_template_id = newVal.device_template_id || '';
}
// 处理properties对象的数据填充
if (newVal.properties) {
// 确保properties是一个对象如果API返回的是字符串则尝试解析
const propsData = typeof newVal.properties === 'string' ? JSON.parse(newVal.properties) : newVal.properties;
Object.assign(formData.properties, propsData);
}
} else {
// 如果没有传入deviceData则重置为初始状态
Object.assign(formData, initialFormData());
nextTick(() => {
if (formRef.value) {
formRef.value.clearValidate();
}
});
}
}, { immediate: true });
watch(() => props.visible, (newVal) => {
if (newVal) {
loadAreaControllers();
loadDeviceTemplates(); // 新增:对话框打开时加载设备模板
// 如果是新增确保type是默认值且清空所有字段
if (!props.isEdit) {
Object.assign(formData, initialFormData());
nextTick(() => {
if (formRef.value) {
formRef.value.clearValidate();
}
});
}
}
}, { immediate: true });
onMounted(() => {
loadAreaControllers();
loadDeviceTemplates(); // 新增:组件挂载时加载设备模板
});
return {
formRef,
loading,
areaControllers,
deviceTemplates, // 暴露设备模板列表
formData,
rules,
title,
handleTypeChange,
handleClose,
handleSubmit
};
}
};
</script>
<style scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>