1. 修复错误

This commit is contained in:
2025-09-08 18:54:41 +08:00
parent 7112a16ca8
commit 5ce0848929
7 changed files with 1152 additions and 31 deletions

242
frontend/dashboard.html Normal file
View File

@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猪场管理系统 - 控制台</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f5f7fa;
min-height: 100vh;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 24px;
display: flex;
align-items: center;
gap: 10px;
}
.user-info {
display: flex;
align-items: center;
gap: 15px;
}
.logout-btn {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
}
.logout-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.container {
max-width: 1200px;
margin: 30px auto;
padding: 0 20px;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: white;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
padding: 25px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
}
.card h3 {
color: #333;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.card p {
color: #666;
line-height: 1.6;
}
.device-control {
background: white;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
padding: 25px;
margin-bottom: 30px;
}
.control-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.control-item {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
text-align: center;
}
.control-item h4 {
margin-bottom: 15px;
color: #333;
}
.control-buttons {
display: flex;
gap: 10px;
justify-content: center;
}
.control-btn {
padding: 8px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.on-btn {
background: #28a745;
color: white;
}
.off-btn {
background: #dc3545;
color: white;
}
.control-btn:hover {
opacity: 0.9;
transform: translateY(-2px);
}
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
.header {
flex-direction: column;
gap: 15px;
text-align: center;
}
}
</style>
</head>
<body>
<div class="header">
<h1>🐷 猪场管理系统</h1>
<div class="user-info">
<span id="username">用户</span>
<button class="logout-btn" id="logoutBtn">退出登录</button>
</div>
</div>
<!-- 移除了导航栏 -->
<div class="container">
<div class="dashboard-grid">
<div class="card">
<h3>📊 系统状态</h3>
<p>当前系统运行正常</p>
<p>连接设备: <span id="deviceCount">0</span></p>
<p>今日操作: <span id="operationCount">0</span></p>
</div>
<div class="card">
<h3>🌡️ 环境监控</h3>
<p>温度: <span id="temperature">25.6</span>°C</p>
<p>湿度: <span id="humidity">65</span>%</p>
<p>空气质量: 良好</p>
</div>
<div class="card">
<h3>📡 设备连接</h3>
<p>中继设备: <span class="status-indicator status-online"></span>在线</p>
<p>风扇设备: <span class="status-indicator status-online"></span>在线</p>
<p>水帘设备: <span class="status-indicator status-offline"></span>离线</p>
</div>
</div>
<div class="device-control">
<h2>⚙️ 设备控制</h2>
</div>
</div>
<script>
// 检查用户是否已登录
document.addEventListener('DOMContentLoaded', function() {
const token = localStorage.getItem('authToken');
const username = localStorage.getItem('username');
if (!token) {
// 未登录,跳转到登录页面
window.location.href = '/';
return;
}
// 显示用户名
if (username) {
document.getElementById('username').textContent = username;
}
});
// 退出登录
function logout() {
// 清除本地存储的认证信息
localStorage.removeItem('authToken');
localStorage.removeItem('userId');
localStorage.removeItem('username');
// 跳转到登录页面
window.location.href = '/';
}
// 控制设备
function controlDevice(deviceType, action) {
alert(`正在${action === 'on' ? '开启' : '关闭'}${deviceType === 'fan' ? '风机' : '水帘'}`);
// 这里应该调用实际的设备控制API
}
</script>
</body>
</html>

812
frontend/device.html Normal file
View File

@@ -0,0 +1,812 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猪场管理系统 - 设备管理</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f5f7fa;
min-height: 100vh;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 24px;
display: flex;
align-items: center;
gap: 10px;
}
.user-info {
display: flex;
align-items: center;
gap: 15px;
}
.logout-btn {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
}
.logout-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.nav {
background: white;
padding: 15px 20px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.nav ul {
display: flex;
list-style: none;
gap: 20px;
}
.nav a {
text-decoration: none;
color: #666;
padding: 8px 15px;
border-radius: 5px;
transition: all 0.3s ease;
}
.nav a:hover, .nav a.active {
background: #667eea;
color: white;
}
.container {
max-width: 1200px;
margin: 30px auto;
padding: 0 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-header h2 {
color: #333;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
}
.device-tree {
background: white;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
padding: 25px;
margin-bottom: 30px;
}
.tree-node {
margin-bottom: 15px;
border-left: 2px solid #e1e1e1;
padding-left: 20px;
}
.relay-node {
border-left-color: #667eea;
}
.controller-node {
border-left-color: #764ba2;
}
.device-node {
border-left-color: #ffa500;
}
.node-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background: #f8f9fa;
border-radius: 5px;
margin-bottom: 10px;
}
.relay-node > .node-header {
background: rgba(102, 126, 234, 0.1);
}
.controller-node > .node-header {
background: rgba(118, 75, 162, 0.1);
}
.device-node > .node-header {
background: rgba(255, 165, 0, 0.1);
}
.node-title {
font-weight: bold;
display: flex;
align-items: center;
gap: 10px;
}
.node-type {
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
color: white;
}
.relay-type {
background: #667eea;
}
.controller-type {
background: #764ba2;
}
.device-type {
background: #ffa500;
}
.node-actions {
display: flex;
gap: 10px;
}
.action-btn {
padding: 5px 10px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.edit-btn {
background: #ffc107;
color: #333;
}
.delete-btn {
background: #dc3545;
color: white;
}
.children {
margin-left: 20px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
border-radius: 15px;
width: 100%;
max-width: 500px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.modal-header {
padding: 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.modal-body {
padding: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-group input, .form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.modal-footer {
padding: 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.status-active {
color: #28a745;
font-weight: bold;
}
.status-inactive {
color: #dc3545;
font-weight: bold;
}
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
.header {
flex-direction: column;
gap: 15px;
}
.page-header {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
}
</style>
</head>
<body>
<div class="header">
<h1>🐷 猪场管理系统</h1>
<div class="user-info">
<span id="username">管理员</span>
<button class="logout-btn" onclick="logout()">退出登录</button>
</div>
</div>
<div class="nav">
<ul>
<li><a href="dashboard.html">控制台</a></li>
<li><a href="device.html" class="active">设备管理</a></li>
</ul>
</div>
<div class="container">
<div class="page-header">
<h2>设备管理</h2>
<button class="btn btn-primary" onclick="openAddDeviceModal()">添加设备</button>
</div>
<div class="device-tree" id="deviceTree">
<!-- 设备树将通过JavaScript动态生成 -->
</div>
</div>
<!-- 添加/编辑设备模态框 -->
<div class="modal" id="deviceModal">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">添加设备</h3>
<button class="close-btn" onclick="closeDeviceModal()">&times;</button>
</div>
<div class="modal-body">
<form id="deviceForm">
<input type="hidden" id="deviceId">
<div class="form-group">
<label for="deviceName">设备名称</label>
<input type="text" id="deviceName" required>
</div>
<div class="form-group">
<label for="deviceType">设备类型</label>
<select id="deviceType" required onchange="toggleParentField()">
<option value="">请选择设备类型</option>
<option value="relay">中继设备</option>
<option value="pig_pen_controller">猪舍主控</option>
<option value="feed_mill_controller">做料车间主控</option>
<option value="fan">风机</option>
<option value="water_curtain">水帘</option>
</select>
</div>
<div class="form-group" id="parentField" style="display: none;">
<label for="parentId">上级设备</label>
<select id="parentId">
<option value="">请选择上级设备</option>
</select>
</div>
<div class="form-group">
<label for="deviceStatus">设备状态</label>
<select id="deviceStatus" required>
<option value="active">启用</option>
<option value="inactive">停用</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeDeviceModal()">取消</button>
<button class="btn btn-primary" onclick="saveDevice()">保存</button>
</div>
</div>
</div>
<script>
// 检查用户是否已登录
document.addEventListener('DOMContentLoaded', function() {
const token = localStorage.getItem('authToken');
const username = localStorage.getItem('username');
if (!token) {
// 未登录,跳转到登录页面
window.location.href = '/';
return;
}
// 显示用户名
if (username) {
document.getElementById('username').textContent = username;
}
// 加载设备列表
loadDevices();
});
// 当前设备列表
let devices = [];
// 获取设备列表
function loadDevices() {
// 这里应该调用后端API获取设备列表
// 暂时使用模拟数据
fetch('/api/v1/device/list')
.then(response => response.json())
.then(data => {
if (data.code === 0) {
devices = data.data.devices;
renderDeviceTree();
} else {
// 如果无法获取数据,使用模拟数据
mockDevices();
renderDeviceTree();
}
})
.catch(error => {
console.error('获取设备列表失败:', error);
// 如果无法获取数据,使用模拟数据
mockDevices();
renderDeviceTree();
});
}
// 模拟设备数据
function mockDevices() {
devices = [
{
id: 1,
name: "一号中继器",
type: "relay",
parent_id: null,
status: "active",
created_at: "2023-01-01T00:00:00Z"
},
{
id: 2,
name: "A区主控",
type: "pig_pen_controller",
parent_id: 1,
status: "active",
created_at: "2023-01-02T00:00:00Z"
},
{
id: 3,
name: "A区1号风机",
type: "fan",
parent_id: 2,
status: "active",
created_at: "2023-01-03T00:00:00Z"
},
{
id: 4,
name: "A区2号风机",
type: "fan",
parent_id: 2,
status: "inactive",
created_at: "2023-01-03T00:00:00Z"
},
{
id: 5,
name: "B区主控",
type: "pig_pen_controller",
parent_id: 1,
status: "active",
created_at: "2023-01-02T00:00:00Z"
},
{
id: 6,
name: "B区水帘",
type: "water_curtain",
parent_id: 5,
status: "active",
created_at: "2023-01-04T00:00:00Z"
}
];
}
// 渲染设备树
function renderDeviceTree() {
const treeContainer = document.getElementById('deviceTree');
// 获取所有中继设备(顶级节点)
const relays = devices.filter(device => device.type === 'relay');
let treeHTML = '';
relays.forEach(relay => {
treeHTML += renderRelayNode(relay);
});
treeContainer.innerHTML = treeHTML || '<p>暂无设备数据</p>';
}
// 渲染中继节点
function renderRelayNode(relay) {
const controllers = devices.filter(device => device.parent_id === relay.id);
let html = `
<div class="tree-node relay-node">
<div class="node-header">
<div class="node-title">
<span>${relay.name}</span>
<span class="node-type relay-type">中继</span>
<span class="status-${relay.status}">${relay.status === 'active' ? '启用' : '停用'}</span>
</div>
<div class="node-actions">
<button class="action-btn edit-btn" onclick="editDevice(${relay.id})">编辑</button>
<button class="action-btn delete-btn" onclick="deleteDevice(${relay.id})">删除</button>
</div>
</div>
`;
if (controllers.length > 0) {
html += '<div class="children">';
controllers.forEach(controller => {
html += renderControllerNode(controller);
});
html += '</div>';
}
html += '</div>';
return html;
}
// 渲染控制器节点
function renderControllerNode(controller) {
const childDevices = devices.filter(device => device.parent_id === controller.id);
let typeText = '';
switch(controller.type) {
case 'pig_pen_controller':
typeText = '猪舍主控';
break;
case 'feed_mill_controller':
typeText = '做料车间主控';
break;
default:
typeText = controller.type;
}
let html = `
<div class="tree-node controller-node">
<div class="node-header">
<div class="node-title">
<span>${controller.name}</span>
<span class="node-type controller-type">${typeText}</span>
<span class="status-${controller.status}">${controller.status === 'active' ? '启用' : '停用'}</span>
</div>
<div class="node-actions">
<button class="action-btn edit-btn" onclick="editDevice(${controller.id})">编辑</button>
<button class="action-btn delete-btn" onclick="deleteDevice(${controller.id})">删除</button>
</div>
</div>
`;
if (childDevices.length > 0) {
html += '<div class="children">';
childDevices.forEach(device => {
html += renderDeviceNode(device);
});
html += '</div>';
}
html += '</div>';
return html;
}
// 渲染设备节点
function renderDeviceNode(device) {
let typeText = '';
switch(device.type) {
case 'fan':
typeText = '风机';
break;
case 'water_curtain':
typeText = '水帘';
break;
default:
typeText = device.type;
}
return `
<div class="tree-node device-node">
<div class="node-header">
<div class="node-title">
<span>${device.name}</span>
<span class="node-type device-type">${typeText}</span>
<span class="status-${device.status}">${device.status === 'active' ? '启用' : '停用'}</span>
</div>
<div class="node-actions">
<button class="action-btn edit-btn" onclick="editDevice(${device.id})">编辑</button>
<button class="action-btn delete-btn" onclick="deleteDevice(${device.id})">删除</button>
</div>
</div>
</div>
`;
}
// 打开添加设备模态框
function openAddDeviceModal() {
document.getElementById('modalTitle').textContent = '添加设备';
document.getElementById('deviceForm').reset();
document.getElementById('deviceId').value = '';
toggleParentField();
loadParentDevices();
document.getElementById('deviceModal').style.display = 'flex';
}
// 编辑设备
function editDevice(deviceId) {
const device = devices.find(d => d.id === deviceId);
if (!device) return;
document.getElementById('modalTitle').textContent = '编辑设备';
document.getElementById('deviceId').value = device.id;
document.getElementById('deviceName').value = device.name;
document.getElementById('deviceType').value = device.type;
document.getElementById('deviceStatus').value = device.status;
toggleParentField();
loadParentDevices(device.type);
if (device.parent_id) {
document.getElementById('parentId').value = device.parent_id;
}
document.getElementById('deviceModal').style.display = 'flex';
}
// 关闭模态框
function closeDeviceModal() {
document.getElementById('deviceModal').style.display = 'none';
}
// 根据设备类型切换上级设备字段显示
function toggleParentField() {
const type = document.getElementById('deviceType').value;
const parentField = document.getElementById('parentField');
if (type === 'relay' || !type) {
parentField.style.display = 'none';
document.getElementById('parentId').innerHTML = '<option value="">请选择上级设备</option>';
} else {
parentField.style.display = 'block';
loadParentDevices(type);
}
}
// 加载上级设备选项
function loadParentDevices(currentType) {
const parentIdSelect = document.getElementById('parentId');
parentIdSelect.innerHTML = '<option value="">请选择上级设备</option>';
let parentDevices = [];
if (currentType === 'pig_pen_controller' || currentType === 'feed_mill_controller') {
// 控制器的上级是中继设备
parentDevices = devices.filter(device => device.type === 'relay');
} else if (currentType === 'fan' || currentType === 'water_curtain') {
// 设备的上级是控制器
parentDevices = devices.filter(device =>
device.type === 'pig_pen_controller' || device.type === 'feed_mill_controller');
}
parentDevices.forEach(device => {
const option = document.createElement('option');
option.value = device.id;
option.textContent = device.name;
parentIdSelect.appendChild(option);
});
}
// 保存设备
function saveDevice() {
const deviceId = document.getElementById('deviceId').value;
const name = document.getElementById('deviceName').value;
const type = document.getElementById('deviceType').value;
const parentId = document.getElementById('parentId').value;
const status = document.getElementById('deviceStatus').value;
if (!name || !type) {
alert('请填写必填字段');
return;
}
const deviceData = {
name: name,
type: type,
status: status,
parent_id: parentId ? parseInt(parentId) : null
};
if (deviceId) {
// 编辑设备
deviceData.id = parseInt(deviceId);
fetch('/api/v1/device/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify(deviceData)
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
loadDevices(); // 重新加载设备列表
closeDeviceModal();
} else {
alert('更新设备失败: ' + data.message);
}
})
.catch(error => {
console.error('更新设备失败:', error);
alert('更新设备失败,请查看控制台了解详情');
});
} else {
// 添加设备
fetch('/api/v1/device/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify(deviceData)
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
loadDevices(); // 重新加载设备列表
closeDeviceModal();
} else {
alert('创建设备失败: ' + data.message);
}
})
.catch(error => {
console.error('创建设备失败:', error);
alert('创建设备失败,请查看控制台了解详情');
});
}
}
// 删除设备
function deleteDevice(deviceId) {
if (!confirm('确定要删除这个设备吗?')) {
return;
}
fetch('/api/v1/device/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('authToken')
},
body: JSON.stringify({id: deviceId})
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
loadDevices(); // 重新加载设备列表
} else {
alert('删除设备失败: ' + data.message);
}
})
.catch(error => {
console.error('删除设备失败:', error);
alert('删除设备失败,请查看控制台了解详情');
});
}
// 退出登录
function logout() {
// 清除本地存储的认证信息
localStorage.removeItem('authToken');
localStorage.removeItem('userId');
localStorage.removeItem('username');
// 跳转到登录页面
window.location.href = '/';
}
// 点击模态框外部关闭模态框
window.onclick = function(event) {
const modal = document.getElementById('deviceModal');
if (event.target === modal) {
closeDeviceModal();
}
};
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猪场管理系统</title> <title>猪场管理系统</title>
<script type="module" crossorigin src="/assets/index.3aaa7f29.js"></script> <script type="module" crossorigin src="/assets/index.390b7904.js"></script>
<link rel="stylesheet" href="/assets/index.4e0b2087.css"> <link rel="stylesheet" href="/assets/index.c407adbd.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -123,13 +123,6 @@
</option> </option>
</select> </select>
</div> </div>
<div class="form-group">
<label for="deviceStatus">设备状态</label>
<select id="deviceStatus" v-model="deviceForm.status" required>
<option value="active">启用</option>
<option value="inactive">停用</option>
</select>
</div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@@ -154,8 +147,7 @@ export default {
id: null, id: null,
name: '', name: '',
type: '', type: '',
parent_id: null, parent_id: null
status: 'active'
} }
} }
}, },
@@ -248,8 +240,7 @@ export default {
id: null, id: null,
name: '', name: '',
type: '', type: '',
parent_id: null, parent_id: null
status: 'active'
} }
this.showModal = true this.showModal = true
}, },
@@ -258,6 +249,7 @@ export default {
editDevice(device) { editDevice(device) {
this.editingDevice = device this.editingDevice = device
this.deviceForm = { ...device } this.deviceForm = { ...device }
delete this.deviceForm.status
this.showModal = true this.showModal = true
}, },
@@ -292,8 +284,7 @@ export default {
id: this.deviceForm.id, id: this.deviceForm.id,
name: this.deviceForm.name, name: this.deviceForm.name,
type: this.deviceForm.type, type: this.deviceForm.type,
parent_id: this.deviceForm.parent_id, parent_id: this.deviceForm.parent_id
status: this.deviceForm.status
}) })
}) })
} else { } else {
@@ -307,8 +298,7 @@ export default {
body: JSON.stringify({ body: JSON.stringify({
name: this.deviceForm.name, name: this.deviceForm.name,
type: this.deviceForm.type, type: this.deviceForm.type,
parent_id: this.deviceForm.parent_id, parent_id: this.deviceForm.parent_id
status: this.deviceForm.status
}) })
}) })
} }

View File

@@ -3,6 +3,9 @@
package device package device
import ( import (
"encoding/json"
"strconv"
"git.huangwc.com/pig/pig-farm-controller/internal/api/middleware" "git.huangwc.com/pig/pig-farm-controller/internal/api/middleware"
"git.huangwc.com/pig/pig-farm-controller/internal/controller" "git.huangwc.com/pig/pig-farm-controller/internal/controller"
"git.huangwc.com/pig/pig-farm-controller/internal/logs" "git.huangwc.com/pig/pig-farm-controller/internal/logs"
@@ -19,10 +22,49 @@ type ListResponse struct {
// DeviceRequest 设备创建/更新请求结构体 // DeviceRequest 设备创建/更新请求结构体
type DeviceRequest struct { type DeviceRequest struct {
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"` // 设备名称,必填
Type model.DeviceType `json:"type" binding:"required"` Type model.DeviceType `json:"type" binding:"required"` // 设备类型,必填
ParentID *uint `json:"parent_id"` ParentID *uint `json:"parent_id,omitempty"` // 父设备ID可选
Status string `json:"status" binding:"required"` }
// BindAndValidate 绑定并验证请求数据
func (req *DeviceRequest) BindAndValidate(data []byte) error {
// 创建一个map来解析原始JSON
raw := make(map[string]interface{})
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
// 解析已知字段
if name, ok := raw["name"].(string); ok {
req.Name = name
}
if typ, ok := raw["type"].(string); ok {
req.Type = model.DeviceType(typ)
}
// 特殊处理parent_id字段
if parentIDVal, exists := raw["parent_id"]; exists && parentIDVal != nil {
switch v := parentIDVal.(type) {
case float64:
// JSON数字默认是float64类型
if v >= 0 {
parentID := uint(v)
req.ParentID = &parentID
}
case string:
// 如果是字符串尝试转换为uint
if v != "" && v != "null" {
if parentID, err := strconv.ParseUint(v, 10, 32); err == nil {
parentIDUint := uint(parentID)
req.ParentID = &parentIDUint
}
}
}
}
return nil
} }
// Controller 设备控制控制器 // Controller 设备控制控制器
@@ -58,16 +100,26 @@ func (c *Controller) List(ctx *gin.Context) {
// Create 创建设备 // Create 创建设备
func (c *Controller) Create(ctx *gin.Context) { func (c *Controller) Create(ctx *gin.Context) {
var req DeviceRequest var req DeviceRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
// 直接使用绑定和验证方法处理JSON数据
rawData, err := ctx.GetRawData()
if err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "无法读取请求数据: "+err.Error())
return
}
if err := req.BindAndValidate(rawData); err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error()) controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error())
return return
} }
// TODO: 设备状态应该由系统自动获取,而不是由用户指定
// 这里设置默认状态为active后续需要实现自动状态检测
device := &model.Device{ device := &model.Device{
Name: req.Name, Name: req.Name,
Type: req.Type, Type: req.Type,
ParentID: req.ParentID, ParentID: req.ParentID,
Status: req.Status, Status: "active", // 默认设置为active状态
} }
if err := c.deviceRepo.Create(device); err != nil { if err := c.deviceRepo.Create(device); err != nil {
@@ -86,7 +138,30 @@ func (c *Controller) Update(ctx *gin.Context) {
DeviceRequest DeviceRequest
} }
if err := ctx.ShouldBindJSON(&req); err != nil { // 直接使用绑定和验证方法处理JSON数据
rawData, err := ctx.GetRawData()
if err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "无法读取请求数据: "+err.Error())
return
}
// 先解析ID
var raw map[string]interface{}
if json.Unmarshal(rawData, &raw) == nil {
if idVal, ok := raw["id"]; ok {
switch id := idVal.(type) {
case float64:
req.ID = uint(id)
case string:
if idUint, err := strconv.ParseUint(id, 10, 32); err == nil {
req.ID = uint(idUint)
}
}
}
}
// 再解析DeviceRequest部分
if err := req.DeviceRequest.BindAndValidate(rawData); err != nil {
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error()) controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误: "+err.Error())
return return
} }
@@ -101,7 +176,9 @@ func (c *Controller) Update(ctx *gin.Context) {
device.Name = req.Name device.Name = req.Name
device.Type = req.Type device.Type = req.Type
device.ParentID = req.ParentID device.ParentID = req.ParentID
device.Status = req.Status // TODO: 设备状态应该由系统自动获取,而不是由用户指定
// 这里保持设备原有状态,后续需要实现自动状态检测
// device.Status = req.Status
if err := c.deviceRepo.Update(device); err != nil { if err := c.deviceRepo.Update(device); err != nil {
c.logger.Error("更新设备失败: " + err.Error()) c.logger.Error("更新设备失败: " + err.Error())