Python项目-智能家居控制系统的设计与实现

1. 引言

随着物联网技术的快速发展,智能家居系统已经成为现代家庭生活的重要组成部分。本文将详细介绍一个基于Python的智能家居控制系统的设计与实现过程,该系统能够实现对家庭设备的集中管理和智能控制,提升家居生活的便捷性和舒适度。

2. 系统架构设计

2.1 总体架构

智能家居控制系统采用分层架构设计,主要包括以下几个层次:

  • 设备层:各种智能家居设备,如灯光、空调、窗帘、安防设备等
  • 通信层:负责设备与控制中心之间的数据传输,支持WiFi、蓝牙、Zigbee等多种通信协议
  • 控制层:系统的核心,处理来自用户的指令并转发给相应的设备
  • 应用层:提供Web界面和移动应用,供用户进行交互操作
  • 智能层:基于机器学习算法,实现系统的自动化决策和智能场景控制

2.2 核心模块设计

系统主要包含以下核心模块:

  1. 设备管理模块:负责设备的注册、状态监控和控制
  2. 用户管理模块:处理用户认证、权限管理等功能
  3. 场景控制模块:实现自定义场景的创建和执行
  4. 数据分析模块:收集和分析用户行为数据,为智能决策提供支持
  5. API接口模块:提供RESTful API,支持第三方应用集成

3. 技术选型

3.1 编程语言与框架

  • 后端:Python 3.9
  • Web框架:Flask 2.0.1
  • WebSocket:Flask-SocketIO
  • 数据库:SQLite(开发环境)/ PostgreSQL(生产环境)
  • ORM:SQLAlchemy
  • 前端:HTML5 + CSS3 + JavaScript
  • 前端框架:Vue.js 3.0
  • UI组件库:Element Plus

3.2 硬件通信技术

  • WiFi通信:使用MQTT协议
  • 蓝牙通信:使用PyBluez库
  • Zigbee通信:通过串口与Zigbee协调器通信

3.3 智能算法

  • 用户行为分析:基于Scikit-learn的聚类算法
  • 场景推荐:基于协同过滤的推荐算法
  • 异常检测:基于统计和机器学习的异常检测算法

4. 系统实现

4.1 设备管理模块实现

设备管理是系统的基础模块,负责管理所有智能设备。以下是设备基类的实现:

# device_manager.py
from abc import ABC, abstractmethod
import uuid
import time

class Device(ABC):
    """智能设备基类"""
    
    def __init__(self, name, location, device_type):
        self.device_id = str(uuid.uuid4())
        self.name = name
        self.location = location
        self.type = device_type
        self.status = "offline"
        self.last_updated = time.time()
        
    @abstractmethod
    def turn_on(self):
        pass
        
    @abstractmethod
    def turn_off(self):
        pass
    
    def get_status(self):
        return {
            "device_id": self.device_id,
            "name": self.name,
            "location": self.location,
            "type": self.type,
            "status": self.status,
            "last_updated": self.last_updated
        }
    
    def update_status(self, status):
        self.status = status
        self.last_updated = time.time()


class LightDevice(Device):
    """灯光设备类"""
    
    def __init__(self, name, location, brightness=100):
        super().__init__(name, location, "light")
        self.brightness = brightness
        self.color = "white"
        
    def turn_on(self):
        self.update_status("on")
        return True
        
    def turn_off(self):
        self.update_status("off")
        return True
        
    def set_brightness(self, brightness):
        if 0 <= brightness <= 100:
            self.brightness = brightness
            return True
        return False
        
    def set_color(self, color):
        self.color = color
        return True
        
    def get_status(self):
        status = super().get_status()
        status.update({
            "brightness": self.brightness,
            "color": self.color
        })
        return status


class DeviceManager:
    """设备管理器"""
    
    def __init__(self):
        self.devices = {}
        
    def add_device(self, device):
        self.devices[device.device_id] = device
        return device.device_id
        
    def remove_device(self, device_id):
        if device_id in self.devices:
            del self.devices[device_id]
            return True
        return False
        
    def get_device(self, device_id):
        return self.devices.get(device_id)
        
    def get_all_devices(self):
        return [device.get_status() for device in self.devices.values()]
        
    def get_devices_by_type(self, device_type):
        return [device.get_status() for device in self.devices.values() 
                if device.type == device_type]
        
    def get_devices_by_location(self, location):
        return [device.get_status() for device in self.devices.values() 
                if device.location == location]

4.2 场景控制模块实现

场景控制模块允许用户创建自定义场景,实现多设备的联动控制:

# scene_controller.py
import time
import json

class Scene:
    """场景类"""
    
    def __init__(self, name, description=""):
        self.scene_id = str(uuid.uuid4())
        self.name = name
        self.description = description
        self.actions = []
        self.created_at = time.time()
        self.last_executed = None
        
    def add_action(self, device_id, action, params=None):
        if params is None:
            params = {}
        self.actions.append({
            "device_id": device_id,
            "action": action,
            "params": params
        })
        
    def remove_action(self, index):
        if 0 <= index < len(self.actions):
            self.actions.pop(index)
            return True
        return False
        
    def get_details(self):
        return {
            "scene_id": self.scene_id,
            "name": self.name,
            "description": self.description,
            "actions": self.actions,
            "created_at": self.created_at,
            "last_executed": self.last_executed
        }


class SceneController:
    """场景控制器"""
    
    def __init__(self, device_manager):
        self.scenes = {}
        self.device_manager = device_manager
        
    def create_scene(self, name, description=""):
        scene = Scene(name, description)
        self.scenes[scene.scene_id] = scene
        return scene.scene_id
        
    def delete_scene(self, scene_id):
        if scene_id in self.scenes:
            del self.scenes[scene_id]
            return True
        return False
        
    def get_scene(self, scene_id):
        return self.scenes.get(scene_id)
        
    def get_all_scenes(self):
        return [scene.get_details() for scene in self.scenes.values()]
        
    def execute_scene(self, scene_id):
        scene = self.scenes.get(scene_id)
        if not scene:
            return False
            
        results = []
        for action in scene.actions:
            device = self.device_manager.get_device(action["device_id"])
            if device:
                if action["action"] == "turn_on":
                    result = device.turn_on()
                elif action["action"] == "turn_off":
                    result = device.turn_off()
                elif action["action"] == "set_brightness" and hasattr(device, "set_brightness"):
                    result = device.set_brightness(action["params"].get("brightness", 100))
                elif action["action"] == "set_color" and hasattr(device, "set_color"):
                    result = device.set_color(action["params"].get("color", "white"))
                else:
                    result = False
                    
                results.append({
                    "device_id": action["device_id"],
                    "action": action["action"],
                    "success": result
                })
                
        scene.last_executed = time.time()
        return results		
		

4.3 API接口实现

使用Flask框架实现RESTful API接口:

# app.py
from flask import Flask, request, jsonify
from flask_socketio import SocketIO
from device_manager import DeviceManager, LightDevice
from scene_controller import SceneController
import json

app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*")

# 初始化设备管理器和场景控制器
device_manager = DeviceManager()
scene_controller = SceneController(device_manager)

# 设备管理API
@app.route('/api/devices', methods=['GET'])
def get_devices():
    return jsonify(device_manager.get_all_devices())

@app.route('/api/devices', methods=['POST'])
def add_device():
    data = request.json
    if data.get('type') == 'light':
        device = LightDevice(
            name=data.get('name', 'New Light'),
            location=data.get('location', 'Living Room'),
            brightness=data.get('brightness', 100)
        )
        device_id = device_manager.add_device(device)
        return jsonify({"device_id": device_id}), 201
    return jsonify({"error": "Unsupported device type"}), 400

@app.route('/api/devices/<device_id>', methods=['GET'])
def get_device(device_id):
    device = device_manager.get_device(device_id)
    if device:
        return jsonify(device.get_status())
    return jsonify({"error": "Device not found"}), 404

@app.route('/api/devices/<device_id>/control', methods=['POST'])
def control_device(device_id):
    device = device_manager.get_device(device_id)
    if not device:
        return jsonify({"error": "Device not found"}), 404
        
    data = request.json
    action = data.get('action')
    
    if action == 'turn_on':
        result = device.turn_on()
    elif action == 'turn_off':
        result = device.turn_off()
    elif action == 'set_brightness' and hasattr(device, 'set_brightness'):
        result = device.set_brightness(data.get('brightness', 100))
    elif action == 'set_color' and hasattr(device, 'set_color'):
        result = device.set_color(data.get('color', 'white'))
    else:
        return jsonify({"error": "Invalid action"}), 400
        
    # 通过WebSocket广播设备状态变化
    socketio.emit('device_update', device.get_status())
    
    return jsonify({"success": result})

# 场景管理API
@app.route('/api/scenes', methods=['GET'])
def get_scenes():
    return jsonify(scene_controller.get_all_scenes())

@app.route('/api/scenes', methods=['POST'])
def create_scene():
    data = request.json
    scene_id = scene_controller.create_scene(
        name=data.get('name', 'New Scene'),
        description=data.get('description', '')
    )
    
    # 添加场景动作
    scene = scene_controller.get_scene(scene_id)
    for action in data.get('actions', []):
        scene.add_action(
            device_id=action.get('device_id'),
            action=action.get('action'),
            params=action.get('params', {})
        )
        
    return jsonify({"scene_id": scene_id}), 201

@app.route('/api/scenes/<scene_id>/execute', methods=['POST'])
def execute_scene(scene_id):
    results = scene_controller.execute_scene(scene_id)
    if results is False:
        return jsonify({"error": "Scene not found"}), 404
    return jsonify({"results": results})

# 主程序入口
if __name__ == '__main__':
    # 添加一些测试设备
    living_room_light = LightDevice("客厅主灯", "客厅")
    bedroom_light = LightDevice("卧室灯", "卧室")
    
    device_manager.add_device(living_room_light)
    device_manager.add_device(bedroom_light)
    
    # 创建一个测试场景
    night_mode_id = scene_controller.create_scene("夜间模式", "睡前自动设置的场景")
    night_mode = scene_controller.get_scene(night_mode_id)
    night_mode.add_action(living_room_light.device_id, "turn_off")
    night_mode.add_action(bedroom_light.device_id, "set_brightness", {"brightness": 30})
    
    # 启动服务器
    socketio.run(app, host='0.0.0.0', port=5000, debug=True)

4.4 前端界面实现

使用Vue.js和Element Plus构建用户界面:

<!-- static/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能家居控制系统</title>
    <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <div id="app">
        <el-container>
            <el-header>
                <h1>智能家居控制系统</h1>
            </el-header>
            <el-container>
                <el-aside width="250px">
                    <el-menu default-active="devices">
                        <el-menu-item index="devices" @click="activeTab = 'devices'">
                            <i class="el-icon-cpu"></i>
                            <span>设备管理</span>
                        </el-menu-item>
                        <el-menu-item index="scenes" @click="activeTab = 'scenes'">
                            <i class="el-icon-magic-stick"></i>
                            <span>场景控制</span>
                        </el-menu-item>
                        <el-menu-item index="statistics" @click="activeTab = 'statistics'">
                            <i class="el-icon-data-line"></i>
                            <span>数据统计</span>
                        </el-menu-item>
                        <el-menu-item index="settings" @click="activeTab = 'settings'">
                            <i class="el-icon-setting"></i>
                            <span>系统设置</span>
                        </el-menu-item>
                    </el-menu>
                </el-aside>
                <el-main>
                    <!-- 设备管理页面 -->
                    <div v-if="activeTab === 'devices'">
                        <el-row :gutter="20">
                            <el-col :span="24">
                                <el-card>
                                    <template #header>
                                        <div class="card-header">
                                            <span>设备列表</span>
                                            <el-button type="primary" size="small" @click="showAddDeviceDialog">添加设备</el-button>
                                        </div>
                                    </template>
                                    <el-table :data="devices" style="width: 100%">
                                        <el-table-column prop="name" label="设备名称"></el-table-column>
                                        <el-table-column prop="location" label="位置"></el-table-column>
                                        <el-table-column prop="type" label="类型"></el-table-column>
                                        <el-table-column prop="status" label="状态">
                                            <template #default="scope">
                                                <el-tag :type="scope.row.status === 'on' ? 'success' : 'info'">
                                                    {{ scope.row.status === 'on' ? '开启' : '关闭' }}
                                                </el-tag>
                                            </template>
                                        </el-table-column>
                                        <el-table-column label="操作">
                                            <template #default="scope">
                                                <el-button 
                                                    size="small" 
                                                    :type="scope.row.status === 'on' ? 'danger' : 'success'"
                                                    @click="controlDevice(scope.row.device_id, scope.row.status === 'on' ? 'turn_off' : 'turn_on')"
                                                >
                                                    {{ scope.row.status === 'on' ? '关闭' : '开启' }}
                                                </el-button>
                                                <el-button 
                                                    size="small" 
                                                    type="primary" 
                                                    @click="showDeviceDetailDialog(scope.row)"
                                                >
                                                    详情
                                                </el-button>
                                            </template>
                                        </el-table-column>
                                    </el-table>
                                </el-card>
                            </el-col>
                        </el-row>
                    </div>
                    
                    <!-- 场景控制页面 -->
                    <div v-if="activeTab === 'scenes'">
                        <el-row :gutter="20">
                            <el-col :span="24">
                                <el-card>
                                    <template #header>
                                        <div class="card-header">
                                            <span>场景列表</span>
                                            <el-button type="primary" size="small" @click="showAddSceneDialog">创建场景</el-button>
                                        </div>
                                    </template>
                                    <el-table :data="scenes" style="width: 100%">
                                        <el-table-column prop="name" label="场景名称"></el-table-column>
                                        <el-table-column prop="description" label="描述"></el-table-column>
                                        <el-table-column label="操作">
                                            <template #default="scope">
                                                <el-button 
                                                    size="small" 
                                                    type="success"
                                                    @click="executeScene(scope.row.scene_id)"
                                                >
                                                    执行
                                                </el-button>
                                                <el-button 
                                                    size="small" 
                                                    type="primary" 
                                                    @click="showSceneDetailDialog(scope.row)"
                                                >
                                                    详情
                                                </el-button>
                                            </template>
                                        </el-table-column>
                                    </el-table>
                                </el-card>
                            </el-col>
                        </el-row>
                    </div>
                </el-main>
            </el-container>
        </el-container>
        
        <!-- 添加设备对话框 -->
        <el-dialog title="添加设备" v-model="addDeviceDialogVisible">
            <el-form :model="newDevice" label-width="100px">
                <el-form-item label="设备名称">
                    <el-input v-model="newDevice.name"></el-input>
                </el-form-item>
                <el-form-item label="位置">
                    <el-input v-model="newDevice.location"></el-input>
                </el-form-item>
                <el-form-item label="设备类型">
                    <el-select v-model="newDevice.type" placeholder="请选择设备类型">
                        <el-option label="灯光" value="light"></el-option>
                        <el-option label="空调" value="ac" disabled></el-option>
                        <el-option label="窗帘" value="curtain" disabled></el-option>
                    </el-select>
                </el-form-item>
            </el-form>
            <template #footer>
                <span class="dialog-footer">
                    <el-button @click="addDeviceDialogVisible = false">取消</el-button>
                    <el-button type="primary" @click="addDevice">确定</el-button>
                </span>
            </template>
        </el-dialog>
    </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/element-plus"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://cdn.socket.io/4.4.1/socket.io.min.js"></script>
    <script src="/static/js/app.js"></script>
</body>
</html>
// static/js/app.js
const { createApp, ref, onMounted } = Vue;

const app = createApp({
    setup() {
        const activeTab = ref('devices');
        const devices = ref([]);
        const scenes = ref([]);
        const addDeviceDialogVisible = ref(false);
        const newDevice = ref({
            name: '',
            location: '',
            type: 'light'
        });
        
        // 初始化WebSocket连接
        const socket = io();
        
        // 监听设备状态更新
        socket.on('device_update', (data) => {
            const index = devices.value.findIndex(d => d.device_id === data.device_id);
            if (index !== -1) {
                devices.value[index] = data;
            }
        });
        
        // 获取所有设备
        const fetchDevices = async () => {
            try {
                const response = await axios.get('/api/devices');
                devices.value = response.data;
            } catch (error) {
                console.error('获取设备列表失败:', error);
                ElMessage.error('获取设备列表失败');
            }
        };
        
        // 获取所有场景
        const fetchScenes = async () => {
            try {
                const response = await axios.get('/api/scenes');
                scenes.value = response.data;
            } catch (error) {
                console.error('获取场景列表失败:', error);
                ElMessage.error('获取场景列表失败');
            }
        };
        
        // 控制设备
        const controlDevice = async (deviceId, action, params = {}) => {
            try {
                await axios.post(`/api/devices/${deviceId}/control`, {
                    action,
                    ...params
                });
                ElMessage.success(`设备操作成功: ${action}`);
            } catch (error) {
                console.error('设备操作失败:', error);
                ElMessage.error('设备操作失败');
            }
        };
        
        // 执行场景
        const executeScene = async (sceneId) => {
            try {
                const response = await axios.post(`/api/scenes/${sceneId}/execute`);
                ElMessage.success('场景执行成功');
            } catch (error) {
                console.error('场景执行失败:', error);
                ElMessage.error('场景执行失败');
            }
        };
        
        // 添加设备
        const addDevice = async () => {
            try {
                await axios.post('/api/devices', newDevice.value);
                ElMessage.success('设备添加成功');
                addDeviceDialogVisible.value = false;
                fetchDevices();
                
                // 重置表单
                newDevice.value = {
                    name: '',
                    location: '',
                    type: 'light'
                };
            } catch (error) {
                console.error('添加设备失败:', error);
                ElMessage.error('添加设备失败');
            }
        };
        
        // 显示添加设备对话框
        const showAddDeviceDialog = () => {
            addDeviceDialogVisible.value = true;
        };
        
        // 显示设备详情对话框
        const showDeviceDetailDialog = (device) => {
            // 实现详情对话框逻辑
            console.log('设备详情:', device);
        };
        
        // 显示添加场景对话框
        const showAddSceneDialog = () => {
            // 实现添加场景对话框逻辑
            console.log('添加场景');
        };
        
        // 显示场景详情对话框
        const showSceneDetailDialog = (scene) => {
            // 实现场景详情对话框逻辑
            console.log('场景详情:', scene);
        };
        
        // 页面加载时获取数据
        onMounted(() => {
            fetchDevices();
            fetchScenes();
        });
        
        return {
            activeTab,
            devices,
            scenes,
            addDeviceDialogVisible,
            newDevice,
            controlDevice,
            executeScene,
            addDevice,
            showAddDeviceDialog,
            showDeviceDetailDialog,
            showAddSceneDialog,
            showSceneDetailDialog
        };
    }
});

app.use(ElementPlus);
app.mount('#app');

5. 系统测试

5.1 单元测试

使用Python的unittest框架对各个模块进行单元测试:

# tests/test_device_manager.py
import unittest
from device_manager import DeviceManager, LightDevice

class TestDeviceManager(unittest.TestCase):
    
    def setUp(self):
        self.device_manager = DeviceManager()
        self.light = LightDevice("测试灯", "测试房间")
        
    def test_add_device(self):
        device_id = self.device_manager.add_device(self.light)
        self.assertIsNotNone(device_id)
        self.assertEqual(len(self.device_manager.devices), 1)
        
    def test_get_device(self):
        device_id = self.device_manager.add_device(self.light)
        device = self.device_manager.get_device(device_id)
        self.assertEqual(device, self.light)
        
    def test_remove_device(self):
        device_id = self.device_manager.add_device(self.light)
        result = self.device_manager.remove_device(device_id)
        self.assertTrue(result)
        self.assertEqual(len(self.device_manager.devices), 0)
        
    def test_get_all_devices(self):
        self.device_manager.add_device(self.light)
        devices = self.device_manager.get_all_devices()
        self.assertEqual(len(devices), 1)
        self.assertEqual(devices[0]["name"], "测试灯")
        
    def test_light_device_functions(self):
        self.light.turn_on()
        self.assertEqual(self.light.status, "on")
        
        self.light.set_brightness(50)
        self.assertEqual(self.light.brightness, 50)
        
        self.light.set_color("blue")
        self.assertEqual(self.light.color, "blue")
        
        self.light.turn_off()
        self.assertEqual(self.light.status, "off")

if __name__ == '__main__':
    unittest.main()

6. 系统扩展与优化

6.1 安全性增强

  1. 用户认证与授权:实现JWT认证机制,确保API接口的安全访问

  2. HTTPS支持:配置SSL证书,启用HTTPS加密通信

  3. 设备通信加密:对设备与控制中心之间的通信进行加密

  4. 日志审计:记录所有关键操作,便于安全审计

6.2 功能扩展

  1. 语音控制:集成语音识别模块,支持语音指令控制

  2. 移动应用:开发配套的移动应用,实现随时随地控制

  3. 智能算法优化:引入更先进的机器学习算法,提升系统智能化水平

  4. 多协议支持:扩展对更多设备通信协议的支持,如Z-Wave、KNX等

6.3 性能优化

  1. 异步处理:使用异步框架处理设备通信,提高系统响应速度

  2. 缓存机制:引入Redis缓存,减轻数据库负担

  3. 分布式部署:实现系统的分布式部署,提高可扩展性和可用性

  4. 微服务架构:将系统拆分为多个微服务,便于独立扩展和维护

7. 总结与展望

本文详细介绍了一个基于Python的智能家居控制系统的设计与实现过程。该系统采用分层架构设计,实现了设备管理、场景控制、用户界面等核心功能,并通过多种通信协议支持各类智能设备的接入和控制。

系统的主要特点包括:

  1. 模块化设计:系统各模块职责明确,便于扩展和维护

  2. 多协议支持:支持WiFi、蓝牙、Zigbee等多种通信协议

  3. 智能化控制:基于机器学习算法实现智能场景推荐和自动化控制

  4. 友好的用户界面:提供Web界面和移动应用,操作简单直观

未来,系统可以在以下方面进行进一步的发展:

  1. 边缘计算:将部分计算任务下放到边缘设备,减轻中心服务器负担

  2. AI增强:引入深度学习和强化学习算法,提升系统智能化水平

  3. 生态系统集成:与主流智能家居生态系统(如Apple HomeKit、Google Home、Amazon Alexa等)进行集成

  4. 能源管理:增加能源消耗监控和优化功能,实现绿色节能

智能家居作为物联网的重要应用场景,将随着技术的发展不断演进。本系统为智能家居的实现提供了一种可行的解决方案,希望能为相关领域的研究和实践提供参考。

源代码

源代码(已更新)下载:
https://download.youkuaiyun.com/download/exlink2012/90976680

Directory Content Summary

Source Directory: ./smart_home_system

Directory Structure

smart_home_system/
  README.md
  requirements.txt
  run.py
  app/
    database.py
    device_manager.py
    scene_manager.py
    __init__.py
    routes/
      api.py
      __init__.py
  config/
  static/
    css/
      styles.css
    js/
      app.js
  templates/
    index.html

File Contents

README.md

# 智能家居控制系统

这是一个基于Python的智能家居控制系统,实现了对家庭设备的集中管理和智能控制。

## 功能特点

- 设备管理:添加、编辑、删除和控制各种智能设备
- 位置管理:管理设备所在的位置
- 数据统计:查看设备状态和分布统计
- 设备历史:记录设备状态变化历史
- 响应式界面:适配不同尺寸的屏幕

## 支持的设备类型

- 灯光设备:控制开关、亮度和颜色
- 温控设备:控制温度和工作模式

## 技术栈

### 后端
- Python 3.9+
- Flask Web框架
- SQLite数据库

### 前端
- Vue.js 3
- Bootstrap 5
- Axios HTTP客户端

## 安装与运行

1. 克隆或下载项目代码

2. 安装依赖
```bash
pip install -r requirements.txt
运行应用
python run.py
在浏览器中访问
http://localhost:5000
项目结构
smart_home_system/
├── app/                    # 应用代码
│   ├── __init__.py         # 应用初始化
│   ├── database.py         # 数据库模块
│   ├── device_manager.py   # 设备管理模块
│   └── routes/             # API路由
│       ├── __init__.py
│       └── api.py          # API接口定义
├── config/                 # 配置文件
├── static/                 # 静态资源
│   ├── css/                # CSS样式
│   │   └── styles.css      # 主样式表
│   └── js/                 # JavaScript代码
│       └── app.js          # 前端应用
├── templates/              # HTML模板
│   └── index.html          # 主页面
├── README.md               # 项目说明
├── requirements.txt        # 依赖列表
└── run.py                  # 应用入口
API接口
设备管理
GET /api/devices - 获取所有设备
GET /api/devices/<device_id> - 获取单个设备
POST /api/devices - 添加设备
PUT /api/devices/<device_id> - 更新设备
DELETE /api/devices/<device_id> - 删除设备
POST /api/devices/<device_id>/control - 控制设备
GET /api/devices/<device_id>/history - 获取设备历史
位置管理
GET /api/locations - 获取所有位置
POST /api/locations - 添加位置
设备类型
GET /api/device-types - 获取所有设备类型
扩展与自定义
添加新设备类型
在 device_manager.py 中创建新的设备类型类,继承 DeviceType 基类
实现必要的方法:create_device, get_properties_schema, validate_command, execute_command
在 DeviceFactory 中注册新设备类型
自定义数据库
默认使用SQLite数据库,如需使用其他数据库:

修改 database.py 中的数据库连接和操作代码
更新 requirements.txt 添加相应的数据库驱动



requirements.txt

Flask==2.2.3
Flask-Cors==3.0.10
Werkzeug==2.2.3
Jinja2==3.1.2
MarkupSafe==2.1.2
itsdangerous==2.1.2
click==8.1.3
colorama==0.4.6
5. README File

run.py

"""
Main entry point for Smart Home System
"""
from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

app\database.py

"""
Database module for Smart Home System
Handles database operations for device management
"""
import sqlite3
import json
import os
import datetime
from pathlib import Path

class Database:
    def __init__(self, db_path=None):
        """Initialize database connection"""
        if db_path is None:
            # Create database in the project directory
            base_dir = Path(__file__).resolve().parent.parent
            db_path = os.path.join(base_dir, 'smart_home.db')
        
        self.db_path = db_path
        self.connection = None
        self.cursor = None
        self._connect()
        self._create_tables()
    
    def _connect(self):
        """Connect to the SQLite database"""
        try:
            self.connection = sqlite3.connect(self.db_path)
            self.connection.row_factory = sqlite3.Row  # Return rows as dictionaries
            self.cursor = self.connection.cursor()
        except sqlite3.Error as e:
            print(f"Database connection error: {e}")
            raise
    
    def _create_tables(self):
        """Create necessary tables if they don't exist"""
        try:
            # Devices table
            self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS devices (
                id TEXT PRIMARY KEY,
                name TEXT NOT NULL,
                type TEXT NOT NULL,
                location TEXT,
                status TEXT DEFAULT 'offline',
                properties TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            ''')
            
            # Device history table for logging state changes
            self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS device_history (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                device_id TEXT NOT NULL,
                status TEXT NOT NULL,
                properties TEXT,
                timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (device_id) REFERENCES devices (id)
            )
            ''')
            
            # Locations table
            self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS locations (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE,
                description TEXT
            )
            ''')
            
            # Scenes table
            self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS scenes (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE,
                description TEXT,
                icon TEXT DEFAULT 'lightbulb',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            ''')
            
            # Scene actions table - stores device states for each scene
            self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS scene_actions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                scene_id INTEGER NOT NULL,
                device_id TEXT NOT NULL,
                action_type TEXT NOT NULL,
                action_params TEXT,
                FOREIGN KEY (scene_id) REFERENCES scenes (id) ON DELETE CASCADE,
                FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
            )
            ''')
            
            # Insert default locations if they don't exist
            default_locations = ['Living Room', 'Bedroom', 'Kitchen', 'Bathroom', 'Office', 'Hallway']
            for location in default_locations:
                self.cursor.execute('''
                INSERT OR IGNORE INTO locations (name) VALUES (?)
                ''', (location,))
            
            self.connection.commit()
        except sqlite3.Error as e:
            print(f"Error creating tables: {e}")
            raise
    
    def close(self):
        """Close the database connection"""
        if self.connection:
            self.connection.close()
    
    # Device operations
    def add_device(self, device_data):
        """Add a new device to the database"""
        try:
            # Convert properties dict to JSON string
            if 'properties' in device_data and isinstance(device_data['properties'], dict):
                device_data['properties'] = json.dumps(device_data['properties'])
            
            # Get current timestamp
            current_time = datetime.datetime.now().isoformat()
            
            self.cursor.execute('''
            INSERT INTO devices (id, name, type, location, status, properties, created_at, last_updated)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            ''', (
                device_data['id'],
                device_data['name'],
                device_data['type'],
                device_data.get('location', 'Unknown'),
                device_data.get('status', 'offline'),
                device_data.get('properties', '{}'),
                current_time,
                current_time
            ))
            
            self.connection.commit()
            return True
        except sqlite3.Error as e:
            print(f"Error adding device: {e}")
            self.connection.rollback()
            return False
    
    def update_device(self, device_id, update_data):
        """Update device information"""
        try:
            # Get current device data
            current_device = self.get_device(device_id)
            if not current_device:
                return False
            
            # Prepare update fields
            update_fields = []
            update_values = []
            
            for key, value in update_data.items():
                if key == 'properties' and isinstance(value, dict):
                    # Merge with existing properties
                    current_props = json.loads(current_device['properties']) if current_device['properties'] else {}
                    current_props.update(value)
                    value = json.dumps(current_props)
                
                if key in ['name', 'type', 'location', 'status', 'properties']:
                    update_fields.append(f"{key} = ?")
                    update_values.append(value)
            
            # Add last_updated timestamp
            update_fields.append("last_updated = ?")
            update_values.append(datetime.datetime.now().isoformat())
            
            # Add device_id for WHERE clause
            update_values.append(device_id)
            
            # Execute update query
            if update_fields:
                query = f'''
                UPDATE devices SET {', '.join(update_fields)}
                WHERE id = ?
                '''
                self.cursor.execute(query, update_values)
                
                # Log state change if status or properties changed
                if 'status' in update_data or 'properties' in update_data:
                    self._log_device_history(device_id, 
                                            update_data.get('status', current_device['status']),
                                            update_data.get('properties', current_device['properties']))
                
                self.connection.commit()
                return True
            
            return False
        except sqlite3.Error as e:
            print(f"Error updating device: {e}")
            self.connection.rollback()
            return False
    
    def delete_device(self, device_id):
        """Delete a device from the database"""
        try:
            self.cursor.execute('DELETE FROM devices WHERE id = ?', (device_id,))
            self.cursor.execute('DELETE FROM device_history WHERE device_id = ?', (device_id,))
            self.cursor.execute('DELETE FROM scene_actions WHERE device_id = ?', (device_id,))
            self.connection.commit()
            return True
        except sqlite3.Error as e:
            print(f"Error deleting device: {e}")
            self.connection.rollback()
            return False
    
    def get_device(self, device_id):
        """Get a device by ID"""
        try:
            self.cursor.execute('SELECT * FROM devices WHERE id = ?', (device_id,))
            device = self.cursor.fetchone()
            
            if device:
                device_dict = dict(device)
                # Parse properties JSON
                if device_dict['properties']:
                    device_dict['properties'] = json.loads(device_dict['properties'])
                return device_dict
            
            return None
        except sqlite3.Error as e:
            print(f"Error getting device: {e}")
            return None
    
    def get_all_devices(self):
        """Get all devices"""
        try:
            self.cursor.execute('SELECT * FROM devices ORDER BY name')
            devices = self.cursor.fetchall()
            
            result = []
            for device in devices:
                device_dict = dict(device)
                # Parse properties JSON
                if device_dict['properties']:
                    device_dict['properties'] = json.loads(device_dict['properties'])
                result.append(device_dict)
            
            return result
        except sqlite3.Error as e:
            print(f"Error getting devices: {e}")
            return []
    
    def get_devices_by_type(self, device_type):
        """Get devices by type"""
        try:
            self.cursor.execute('SELECT * FROM devices WHERE type = ? ORDER BY name', (device_type,))
            devices = self.cursor.fetchall()
            
            result = []
            for device in devices:
                device_dict = dict(device)
                # Parse properties JSON
                if device_dict['properties']:
                    device_dict['properties'] = json.loads(device_dict['properties'])
                result.append(device_dict)
            
            return result
        except sqlite3.Error as e:
            print(f"Error getting devices by type: {e}")
            return []
    
    def get_devices_by_location(self, location):
        """Get devices by location"""
        try:
            self.cursor.execute('SELECT * FROM devices WHERE location = ? ORDER BY name', (location,))
            devices = self.cursor.fetchall()
            
            result = []
            for device in devices:
                device_dict = dict(device)
                # Parse properties JSON
                if device_dict['properties']:
                    device_dict['properties'] = json.loads(device_dict['properties'])
                result.append(device_dict)
            
            return result
        except sqlite3.Error as e:
            print(f"Error getting devices by location: {e}")
            return []
    
    def _log_device_history(self, device_id, status, properties):
        """Log device state change to history"""
        try:
            if isinstance(properties, dict):
                properties = json.dumps(properties)
            
            self.cursor.execute('''
            INSERT INTO device_history (device_id, status, properties)
            VALUES (?, ?, ?)
            ''', (device_id, status, properties))
            
            return True
        except sqlite3.Error as e:
            print(f"Error logging device history: {e}")
            return False
    
    def get_device_history(self, device_id, limit=50):
        """Get device history"""
        try:
            self.cursor.execute('''
            SELECT * FROM device_history
            WHERE device_id = ?
            ORDER BY timestamp DESC
            LIMIT ?
            ''', (device_id, limit))
            
            history = self.cursor.fetchall()
            
            result = []
            for entry in history:
                entry_dict = dict(entry)
                # Parse properties JSON
                if entry_dict['properties']:
                    entry_dict['properties'] = json.loads(entry_dict['properties'])
                result.append(entry_dict)
            
            return result
        except sqlite3.Error as e:
            print(f"Error getting device history: {e}")
            return []
    
    # Location operations
    def get_all_locations(self):
        """Get all locations"""
        try:
            self.cursor.execute('SELECT * FROM locations ORDER BY name')
            locations = self.cursor.fetchall()
            return [dict(location) for location in locations]
        except sqlite3.Error as e:
            print(f"Error getting locations: {e}")
            return []
    
    def add_location(self, name, description=None):
        """Add a new location"""
        try:
            self.cursor.execute('''
            INSERT INTO locations (name, description)
            VALUES (?, ?)
            ''', (name, description))
            
            self.connection.commit()
            return self.cursor.lastrowid
        except sqlite3.Error as e:
            print(f"Error adding location: {e}")
            self.connection.rollback()
            return None
    
    # Scene operations
    def add_scene(self, scene_data):
        """Add a new scene"""
        try:
            name = scene_data.get('name')
            description = scene_data.get('description')
            icon = scene_data.get('icon', 'lightbulb')
            
            current_time = datetime.datetime.now().isoformat()
            
            self.cursor.execute('''
            INSERT INTO scenes (name, description, icon, created_at, last_updated)
            VALUES (?, ?, ?, ?, ?)
            ''', (name, description, icon, current_time, current_time))
            
            scene_id = self.cursor.lastrowid
            
            # Add scene actions if provided
            actions = scene_data.get('actions', [])
            for action in actions:
                self.add_scene_action(scene_id, action)
            
            self.connection.commit()
            return scene_id
        except sqlite3.Error as e:
            print(f"Error adding scene: {e}")
            self.connection.rollback()
            return None
    
    def update_scene(self, scene_id, update_data):
        """Update scene information"""
        try:
            update_fields = []
            update_values = []
            
            for key, value in update_data.items():
                if key in ['name', 'description', 'icon']:
                    update_fields.append(f"{key} = ?")
                    update_values.append(value)
            
            # Add last_updated timestamp
            update_fields.append("last_updated = ?")
            update_values.append(datetime.datetime.now().isoformat())
            
            # Add scene_id for WHERE clause
            update_values.append(scene_id)
            
            # Execute update query
            if update_fields:
                query = f'''
                UPDATE scenes SET {', '.join(update_fields)}
                WHERE id = ?
                '''
                self.cursor.execute(query, update_values)
                
                # Update scene actions if provided
                if 'actions' in update_data:
                    # Delete existing actions
                    self.cursor.execute('DELETE FROM scene_actions WHERE scene_id = ?', (scene_id,))
                    
                    # Add new actions
                    for action in update_data['actions']:
                        self.add_scene_action(scene_id, action)
                
                self.connection.commit()
                return True
            
            return False
        except sqlite3.Error as e:
            print(f"Error updating scene: {e}")
            self.connection.rollback()
            return False
    
    def delete_scene(self, scene_id):
        """Delete a scene"""
        try:
            self.cursor.execute('DELETE FROM scenes WHERE id = ?', (scene_id,))
            self.cursor.execute('DELETE FROM scene_actions WHERE scene_id = ?', (scene_id,))
            self.connection.commit()
            return True
        except sqlite3.Error as e:
            print(f"Error deleting scene: {e}")
            self.connection.rollback()
            return False
    
    def get_scene(self, scene_id):
        """Get a scene by ID"""
        try:
            self.cursor.execute('SELECT * FROM scenes WHERE id = ?', (scene_id,))
            scene = self.cursor.fetchone()
            
            if not scene:
                return None
            
            scene_dict = dict(scene)
            
            # Get scene actions
            scene_dict['actions'] = self.get_scene_actions(scene_id)
            
            return scene_dict
        except sqlite3.Error as e:
            print(f"Error getting scene: {e}")
            return None
    
    def get_all_scenes(self):
        """Get all scenes"""
        try:
            self.cursor.execute('SELECT * FROM scenes ORDER BY name')
            scenes = self.cursor.fetchall()
            
            result = []
            for scene in scenes:
                scene_dict = dict(scene)
                scene_dict['actions'] = self.get_scene_actions(scene_dict['id'])
                result.append(scene_dict)
            
            return result
        except sqlite3.Error as e:
            print(f"Error getting scenes: {e}")
            return []
    
    def add_scene_action(self, scene_id, action_data):
        """Add a scene action"""
        try:
            device_id = action_data.get('device_id')
            action_type = action_data.get('action_type')
            action_params = action_data.get('action_params', {})
            
            if isinstance(action_params, dict):
                action_params = json.dumps(action_params)
            
            self.cursor.execute('''
            INSERT INTO scene_actions (scene_id, device_id, action_type, action_params)
            VALUES (?, ?, ?, ?)
            ''', (scene_id, device_id, action_type, action_params))
            
            return self.cursor.lastrowid
        except sqlite3.Error as e:
            print(f"Error adding scene action: {e}")
            return None
    
    def get_scene_actions(self, scene_id):
        """Get actions for a scene"""
        try:
            self.cursor.execute('''
            SELECT * FROM scene_actions WHERE scene_id = ?
            ''', (scene_id,))
            
            actions = self.cursor.fetchall()
            
            result = []
            for action in actions:
                action_dict = dict(action)
                
                # Parse action_params JSON
                if action_dict['action_params']:
                    action_dict['action_params'] = json.loads(action_dict['action_params'])
                
                result.append(action_dict)
            
            return result
        except sqlite3.Error as e:
            print(f"Error getting scene actions: {e}")
            return []

app\device_manager.py

"""
Device Manager Module for Smart Home System
Handles device operations and communication
"""
import uuid
import time
import json
from .database import Database

class DeviceManager:
    """
    Device Manager class for handling device operations
    """
    def __init__(self, db=None):
        """Initialize the device manager"""
        self.db = db if db else Database()
        self.devices = {}  # In-memory cache of devices
        self._load_devices_from_db()
    
    def _load_devices_from_db(self):
        """Load devices from database into memory"""
        devices = self.db.get_all_devices()
        for device in devices:
            self.devices[device['id']] = device
    
    def register_device(self, name, device_type, location=None, properties=None):
        """Register a new device"""
        if properties is None:
            properties = {}
        
        device_id = str(uuid.uuid4())
        
        device_data = {
            'id': device_id,
            'name': name,
            'type': device_type,
            'location': location,
            'status': 'offline',
            'properties': properties
        }
        
        # Add to database
        if self.db.add_device(device_data):
            # Add to in-memory cache
            self.devices[device_id] = device_data
            return device_id
        
        return None
    
    def update_device_status(self, device_id, status, properties=None):
        """Update device status and properties"""
        if device_id not in self.devices:
            return False
        
        update_data = {'status': status}
        
        if properties:
            update_data['properties'] = properties
        
        # Update database
        if self.db.update_device(device_id, update_data):
            # Update in-memory cache
            self.devices[device_id].update(update_data)
            self.devices[device_id]['last_updated'] = time.time()
            return True
        
        return False
    
    def get_device(self, device_id):
        """Get device by ID"""
        if device_id in self.devices:
            return self.devices[device_id]
        
        # Try to get from database
        device = self.db.get_device(device_id)
        if device:
            self.devices[device_id] = device
            return device
        
        return None
    
    def get_all_devices(self):
        """Get all devices"""
        return list(self.devices.values())
    
    def get_devices_by_type(self, device_type):
        """Get devices by type"""
        return [device for device in self.devices.values() if device['type'] == device_type]
    
    def get_devices_by_location(self, location):
        """Get devices by location"""
        return [device for device in self.devices.values() if device['location'] == location]
    
    def delete_device(self, device_id):
        """Delete a device"""
        if device_id not in self.devices:
            return False
        
        # Delete from database
        if self.db.delete_device(device_id):
            # Delete from in-memory cache
            del self.devices[device_id]
            return True
        
        return False
    
    def update_device_properties(self, device_id, properties):
        """Update device properties"""
        if device_id not in self.devices:
            return False
        
        # Get current properties
        current_props = self.devices[device_id].get('properties', {})
        
        # Merge with new properties
        if isinstance(properties, dict):
            current_props.update(properties)
        
        # Update database
        update_data = {'properties': current_props}
        if self.db.update_device(device_id, update_data):
            # Update in-memory cache
            self.devices[device_id]['properties'] = current_props
            self.devices[device_id]['last_updated'] = time.time()
            return True
        
        return False
    
    def get_device_history(self, device_id, limit=50):
        """Get device history"""
        return self.db.get_device_history(device_id, limit)
    
    def get_all_locations(self):
        """Get all locations"""
        return self.db.get_all_locations()
    
    def add_location(self, name, description=None):
        """Add a new location"""
        return self.db.add_location(name, description)


# Device type implementations
class DeviceType:
    """Base class for device types"""
    
    @staticmethod
    def create_device(device_manager, name, location, **kwargs):
        """Create a device of this type"""
        raise NotImplementedError("Subclasses must implement this method")
    
    @staticmethod
    def get_properties_schema():
        """Get JSON schema for device properties"""
        raise NotImplementedError("Subclasses must implement this method")
    
    @staticmethod
    def validate_command(command, params):
        """Validate a command for this device type"""
        raise NotImplementedError("Subclasses must implement this method")
    
    @staticmethod
    def execute_command(device, command, params):
        """Execute a command on a device"""
        raise NotImplementedError("Subclasses must implement this method")


class LightDevice(DeviceType):
    """Light device type implementation"""
    
    @staticmethod
    def create_device(device_manager, name, location, **kwargs):
        """Create a light device"""
        brightness = kwargs.get('brightness', 100)
        color = kwargs.get('color', 'white')
        
        properties = {
            'brightness': brightness,
            'color': color,
            'supported_commands': ['turn_on', 'turn_off', 'set_brightness', 'set_color']
        }
        
        return device_manager.register_device(name, 'light', location, properties)
    
    @staticmethod
    def get_properties_schema():
        """Get JSON schema for light properties"""
        return {
            "type": "object",
            "properties": {
                "brightness": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100,
                    "description": "Brightness level (0-100)"
                },
                "color": {
                    "type": "string",
                    "description": "Light color (name or hex code)"
                }
            }
        }
    
    @staticmethod
    def validate_command(command, params):
        """Validate a command for light device"""
        if command == 'turn_on' or command == 'turn_off':
            return True
        
        if command == 'set_brightness':
            if 'brightness' not in params:
                return False
            brightness = params.get('brightness')
            return isinstance(brightness, int) and 0 <= brightness <= 100
        
        if command == 'set_color':
            return 'color' in params
        
        return False
    
    @staticmethod
    def execute_command(device, command, params):
        """Execute a command on a light device"""
        device_id = device['id']
        device_manager = DeviceManager()
        
        if command == 'turn_on':
            return device_manager.update_device_status(device_id, 'on')
        
        if command == 'turn_off':
            return device_manager.update_device_status(device_id, 'off')
        
        if command == 'set_brightness':
            brightness = params.get('brightness', 100)
            properties = {'brightness': brightness}
            return device_manager.update_device_properties(device_id, properties)
        
        if command == 'set_color':
            color = params.get('color', 'white')
            properties = {'color': color}
            return device_manager.update_device_properties(device_id, properties)
        
        return False


class ThermostatDevice(DeviceType):
    """Thermostat device type implementation"""
    
    @staticmethod
    def create_device(device_manager, name, location, **kwargs):
        """Create a thermostat device"""
        current_temp = kwargs.get('current_temp', 22)
        target_temp = kwargs.get('target_temp', 22)
        mode = kwargs.get('mode', 'off')  # off, heat, cool, auto
        
        properties = {
            'current_temp': current_temp,
            'target_temp': target_temp,
            'mode': mode,
            'supported_commands': ['set_temperature', 'set_mode']
        }
        
        return device_manager.register_device(name, 'thermostat', location, properties)
    
    @staticmethod
    def get_properties_schema():
        """Get JSON schema for thermostat properties"""
        return {
            "type": "object",
            "properties": {
                "current_temp": {
                    "type": "number",
                    "description": "Current temperature in Celsius"
                },
                "target_temp": {
                    "type": "number",
                    "minimum": 5,
                    "maximum": 35,
                    "description": "Target temperature in Celsius"
                },
                "mode": {
                    "type": "string",
                    "enum": ["off", "heat", "cool", "auto"],
                    "description": "Thermostat mode"
                }
            }
        }
    
    @staticmethod
    def validate_command(command, params):
        """Validate a command for thermostat device"""
        if command == 'set_temperature':
            if 'temperature' not in params:
                return False
            temp = params.get('temperature')
            return isinstance(temp, (int, float)) and 5 <= temp <= 35
        
        if command == 'set_mode':
            if 'mode' not in params:
                return False
            mode = params.get('mode')
            return mode in ['off', 'heat', 'cool', 'auto']
        
        return False
    
    @staticmethod
    def execute_command(device, command, params):
        """Execute a command on a thermostat device"""
        device_id = device['id']
        device_manager = DeviceManager()
        
        if command == 'set_temperature':
            temperature = params.get('temperature')
            properties = {'target_temp': temperature}
            
            # If device is off, turn it on in heat or cool mode
            if device['properties'].get('mode') == 'off':
                properties['mode'] = 'auto'
            
            return device_manager.update_device_properties(device_id, properties)
        
        if command == 'set_mode':
            mode = params.get('mode')
            properties = {'mode': mode}
            
            # Update status based on mode
            status = 'on' if mode != 'off' else 'off'
            device_manager.update_device_status(device_id, status)
            
            return device_manager.update_device_properties(device_id, properties)
        
        return False


# Factory for creating devices of different types
class DeviceFactory:
    """Factory class for creating devices of different types"""
    
    _device_types = {
        'light': LightDevice,
        'thermostat': ThermostatDevice
    }
    
    @classmethod
    def register_device_type(cls, type_name, device_class):
        """Register a new device type"""
        cls._device_types[type_name] = device_class
    
    @classmethod
    def create_device(cls, device_manager, type_name, name, location, **kwargs):
        """Create a device of the specified type"""
        if type_name not in cls._device_types:
            raise ValueError(f"Unknown device type: {type_name}")
        
        device_class = cls._device_types[type_name]
        return device_class.create_device(device_manager, name, location, **kwargs)
    
    @classmethod
    def get_device_types(cls):
        """Get all registered device types"""
        return list(cls._device_types.keys())
    
    @classmethod
    def get_properties_schema(cls, type_name):
        """Get properties schema for a device type"""
        if type_name not in cls._device_types:
            raise ValueError(f"Unknown device type: {type_name}")
        
        device_class = cls._device_types[type_name]
        return device_class.get_properties_schema()
    
    @classmethod
    def execute_command(cls, device, command, params):
        """Execute a command on a device"""
        device_type = device['type']
        
        if device_type not in cls._device_types:
            raise ValueError(f"Unknown device type: {device_type}")
        
        device_class = cls._device_types[device_type]
        
        # Validate command
        if not device_class.validate_command(command, params):
            return False
        
        # Execute command
        return device_class.execute_command(device, command, params)

app\scene_manager.py

"""
Scene Manager for Smart Home System
Handles scene operations and execution
"""
import uuid
from .device_manager import DeviceManager

class SceneManager:
    def __init__(self, db=None, device_manager=None):
        """Initialize Scene Manager"""
        from .database import Database
        self.db = db if db else Database()
        self.device_manager = device_manager if device_manager else DeviceManager(self.db)
    
    def create_scene(self, name, description=None, icon=None, actions=None):
        """Create a new scene"""
        if not name:
            raise ValueError("Scene name is required")
        
        scene_data = {
            'name': name,
            'description': description,
            'icon': icon or 'lightbulb',
            'actions': actions or []
        }
        
        scene_id = self.db.add_scene(scene_data)
        if scene_id:
            return self.get_scene(scene_id)
        return None
    
    def update_scene(self, scene_id, update_data):
        """Update an existing scene"""
        if not scene_id:
            return False
        
        # Validate scene exists
        scene = self.get_scene(scene_id)
        if not scene:
            return False
        
        # Update scene
        if self.db.update_scene(scene_id, update_data):
            return self.get_scene(scene_id)
        return None
    
    def delete_scene(self, scene_id):
        """Delete a scene"""
        return self.db.delete_scene(scene_id)
    
    def get_scene(self, scene_id):
        """Get a scene by ID"""
        return self.db.get_scene(scene_id)
    
    def get_all_scenes(self):
        """Get all scenes"""
        return self.db.get_all_scenes()
    
    def activate_scene(self, scene_id):
        """Activate a scene by executing all its actions"""
        scene = self.get_scene(scene_id)
        if not scene:
            return False
        
        results = []
        for action in scene['actions']:
            device_id = action['device_id']
            action_type = action['action_type']
            action_params = action['action_params']
            
            # Get device
            device = self.device_manager.get_device(device_id)
            if not device:
                results.append({
                    'device_id': device_id,
                    'success': False,
                    'message': 'Device not found'
                })
                continue
            
            # Execute action
            try:
                if action_type == 'set_status':
                    status = action_params.get('status')
                    if status:
                        self.device_manager.update_device_status(device_id, status)
                        results.append({
                            'device_id': device_id,
                            'success': True,
                            'action': 'set_status',
                            'value': status
                        })
                
                elif action_type == 'set_property':
                    property_name = action_params.get('property')
                    property_value = action_params.get('value')
                    
                    if property_name and property_value is not None:
                        properties = {property_name: property_value}
                        self.device_manager.update_device_properties(device_id, properties)
                        results.append({
                            'device_id': device_id,
                            'success': True,
                            'action': 'set_property',
                            'property': property_name,
                            'value': property_value
                        })
                
                elif action_type == 'execute_command':
                    command = action_params.get('command')
                    params = action_params.get('params', {})
                    
                    if command:
                        # This would call the device-specific command execution
                        # For now, we'll just update the device properties
                        self.device_manager.update_device_properties(device_id, params)
                        results.append({
                            'device_id': device_id,
                            'success': True,
                            'action': 'execute_command',
                            'command': command,
                            'params': params
                        })
                
                else:
                    results.append({
                        'device_id': device_id,
                        'success': False,
                        'message': f'Unsupported action type: {action_type}'
                    })
            
            except Exception as e:
                results.append({
                    'device_id': device_id,
                    'success': False,
                    'message': str(e)
                })
        
        return {
            'scene_id': scene_id,
            'scene_name': scene['name'],
            'success': any(result['success'] for result in results),
            'results': results
        }
    
    def add_device_to_scene(self, scene_id, device_id, action_type, action_params):
        """Add a device action to a scene"""
        scene = self.get_scene(scene_id)
        if not scene:
            return False
        
        # Validate device exists
        device = self.device_manager.get_device(device_id)
        if not device:
            return False
        
        # Create action
        action = {
            'device_id': device_id,
            'action_type': action_type,
            'action_params': action_params
        }
        
        # Add action to scene
        action_id = self.db.add_scene_action(scene_id, action)
        return action_id is not None
    
    def remove_device_from_scene(self, scene_id, action_id):
        """Remove a device action from a scene"""
        # This would require adding a method to delete a specific action
        # For now, we'll update the scene with all actions except the one to remove
        scene = self.get_scene(scene_id)
        if not scene:
            return False
        
        # Filter out the action to remove
        updated_actions = [action for action in scene['actions'] if action['id'] != action_id]
        
        # Update scene with new actions
        return self.db.update_scene(scene_id, {'actions': updated_actions})

app_init_.py

"""
Smart Home System Application
"""
from flask import Flask
from flask_cors import CORS

def create_app():
    """Create and configure the Flask application"""
    app = Flask(__name__, 
                static_folder='../static',
                template_folder='../templates')
    
    # Enable CORS
    CORS(app)
    
    # Load configuration
    app.config.from_mapping(
        SECRET_KEY='dev',
        DATABASE=None,
    )
    
    # Register blueprints
    from .routes import api
    app.register_blueprint(api.bp)
    
    return app

app\routes\api.py

"""
API routes for Smart Home System
"""
from flask import Blueprint, request, jsonify, current_app
from ..device_manager import DeviceManager, DeviceFactory
from ..scene_manager import SceneManager

bp = Blueprint('api', __name__, url_prefix='/api')

# Initialize device manager
device_manager = DeviceManager()
# Initialize scene manager
scene_manager = SceneManager(device_manager=device_manager)

@bp.route('/devices', methods=['GET'])
def get_devices():
    """Get all devices or filter by type/location"""
    device_type = request.args.get('type')
    location = request.args.get('location')
    
    if device_type:
        devices = device_manager.get_devices_by_type(device_type)
    elif location:
        devices = device_manager.get_devices_by_location(location)
    else:
        devices = device_manager.get_all_devices()
    
    return jsonify(devices)

@bp.route('/devices/<device_id>', methods=['GET'])
def get_device(device_id):
    """Get a device by ID"""
    device = device_manager.get_device(device_id)
    
    if not device:
        return jsonify({'error': 'Device not found'}), 404
    
    return jsonify(device)

@bp.route('/devices', methods=['POST'])
def create_device():
    """Create a new device"""
    data = request.json
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    # Required fields
    name = data.get('name')
    device_type = data.get('type')
    location = data.get('location')
    
    if not name or not device_type:
        return jsonify({'error': 'Name and type are required'}), 400
    
    # Check if device type is supported
    if device_type not in DeviceFactory.get_device_types():
        return jsonify({'error': f'Unsupported device type: {device_type}'}), 400
    
    # Create device
    try:
        device_id = DeviceFactory.create_device(
            device_manager, 
            device_type, 
            name, 
            location, 
            **data.get('properties', {})
        )
        
        if not device_id:
            return jsonify({'error': 'Failed to create device'}), 500
        
        # Get the created device
        device = device_manager.get_device(device_id)
        
        return jsonify(device), 201
    
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@bp.route('/devices/<device_id>', methods=['PUT'])
def update_device(device_id):
    """Update a device"""
    device = device_manager.get_device(device_id)
    
    if not device:
        return jsonify({'error': 'Device not found'}), 404
    
    data = request.json
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    # Update name
    if 'name' in data:
        device['name'] = data['name']
    
    # Update location
    if 'location' in data:
        device['location'] = data['location']
    
    # Update properties
    if 'properties' in data and isinstance(data['properties'], dict):
        device_manager.update_device_properties(device_id, data['properties'])
    
    # Update in database
    device_manager.db.update_device(device_id, {
        'name': device['name'],
        'location': device['location']
    })
    
    return jsonify(device_manager.get_device(device_id))

@bp.route('/devices/<device_id>', methods=['DELETE'])
def delete_device(device_id):
    """Delete a device"""
    device = device_manager.get_device(device_id)
    
    if not device:
        return jsonify({'error': 'Device not found'}), 404
    
    if device_manager.delete_device(device_id):
        return jsonify({'message': 'Device deleted successfully'})
    
    return jsonify({'error': 'Failed to delete device'}), 500

@bp.route('/devices/<device_id>/control', methods=['POST'])
def control_device(device_id):
    """Control a device"""
    device = device_manager.get_device(device_id)
    
    if not device:
        return jsonify({'error': 'Device not found'}), 404
    
    data = request.json
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    command = data.get('command')
    params = data.get('params', {})
    
    if not command:
        return jsonify({'error': 'Command is required'}), 400
    
    # Check if command is supported by device
    supported_commands = device.get('properties', {}).get('supported_commands', [])
    if command not in supported_commands:
        return jsonify({'error': f'Unsupported command: {command}'}), 400
    
    # Execute command
    try:
        result = DeviceFactory.execute_command(device, command, params)
        
        if result:
            # Get updated device
            updated_device = device_manager.get_device(device_id)
            return jsonify({
                'message': 'Command executed successfully',
                'device': updated_device
            })
        
        return jsonify({'error': 'Failed to execute command'}), 500
    
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@bp.route('/devices/<device_id>/history', methods=['GET'])
def get_device_history(device_id):
    """Get device history"""
    device = device_manager.get_device(device_id)
    
    if not device:
        return jsonify({'error': 'Device not found'}), 404
    
    limit = request.args.get('limit', 50, type=int)
    history = device_manager.get_device_history(device_id, limit)
    
    return jsonify(history)

@bp.route('/device-types', methods=['GET'])
def get_device_types():
    """Get all supported device types"""
    device_types = DeviceFactory.get_device_types()
    
    # Get schema for each device type
    result = {}
    for device_type in device_types:
        try:
            schema = DeviceFactory.get_properties_schema(device_type)
            result[device_type] = {
                'name': device_type,
                'schema': schema
            }
        except Exception:
            result[device_type] = {
                'name': device_type,
                'schema': {}
            }
    
    return jsonify(result)

@bp.route('/locations', methods=['GET'])
def get_locations():
    """Get all locations"""
    locations = device_manager.db.get_all_locations()
    return jsonify(locations)

@bp.route('/locations', methods=['POST'])
def add_location():
    """Add a new location"""
    data = request.json
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    name = data.get('name')
    description = data.get('description')
    
    if not name:
        return jsonify({'error': 'Name is required'}), 400
    
    location_id = device_manager.db.add_location(name, description)
    
    if location_id:
        return jsonify({
            'id': location_id,
            'name': name,
            'description': description
        }), 201
    
    return jsonify({'error': 'Failed to add location'}), 500

# Scene routes
@bp.route('/scenes', methods=['GET'])
def get_scenes():
    """Get all scenes"""
    scenes = scene_manager.get_all_scenes()
    return jsonify(scenes)

@bp.route('/scenes/<scene_id>', methods=['GET'])
def get_scene(scene_id):
    """Get a scene by ID"""
    scene = scene_manager.get_scene(scene_id)
    
    if not scene:
        return jsonify({'error': 'Scene not found'}), 404
    
    return jsonify(scene)

@bp.route('/scenes', methods=['POST'])
def create_scene():
    """Create a new scene"""
    data = request.json
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    name = data.get('name')
    description = data.get('description')
    icon = data.get('icon')
    actions = data.get('actions', [])
    
    if not name:
        return jsonify({'error': 'Name is required'}), 400
    
    try:
        scene = scene_manager.create_scene(name, description, icon, actions)
        
        if scene:
            return jsonify(scene), 201
        
        return jsonify({'error': 'Failed to create scene'}), 500
    
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@bp.route('/scenes/<scene_id>', methods=['PUT'])
def update_scene(scene_id):
    """Update a scene"""
    scene = scene_manager.get_scene(scene_id)
    
    if not scene:
        return jsonify({'error': 'Scene not found'}), 404
    
    data = request.json
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    try:
        updated_scene = scene_manager.update_scene(scene_id, data)
        
        if updated_scene:
            return jsonify(updated_scene)
        
        return jsonify({'error': 'Failed to update scene'}), 500
    
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@bp.route('/scenes/<scene_id>', methods=['DELETE'])
def delete_scene(scene_id):
    """Delete a scene"""
    scene = scene_manager.get_scene(scene_id)
    
    if not scene:
        return jsonify({'error': 'Scene not found'}), 404
    
    if scene_manager.delete_scene(scene_id):
        return jsonify({'message': 'Scene deleted successfully'})
    
    return jsonify({'error': 'Failed to delete scene'}), 500

@bp.route('/scenes/<scene_id>/activate', methods=['POST'])
def activate_scene(scene_id):
    """Activate a scene"""
    scene = scene_manager.get_scene(scene_id)
    
    if not scene:
        return jsonify({'error': 'Scene not found'}), 404
    
    result = scene_manager.activate_scene(scene_id)
    
    if result and result.get('success'):
        return jsonify(result)
    
    return jsonify({'error': 'Failed to activate scene', 'details': result}), 500

@bp.route('/scenes/<scene_id>/devices', methods=['POST'])
def add_device_to_scene(scene_id):
    """Add a device to a scene"""
    scene = scene_manager.get_scene(scene_id)
    
    if not scene:
        return jsonify({'error': 'Scene not found'}), 404
    
    data = request.json
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    device_id = data.get('device_id')
    action_type = data.get('action_type')
    action_params = data.get('action_params', {})
    
    if not device_id or not action_type:
        return jsonify({'error': 'Device ID and action type are required'}), 400
    
    # Check if device exists
    device = device_manager.get_device(device_id)
    if not device:
        return jsonify({'error': 'Device not found'}), 404
    
    if scene_manager.add_device_to_scene(scene_id, device_id, action_type, action_params):
        return jsonify({'message': 'Device added to scene successfully'})
    
    return jsonify({'error': 'Failed to add device to scene'}), 500

app\routes_init_.py


static\css\styles.css

/* Smart Home System CSS */

/* General Styles */
body {
    font-family: 'Microsoft YaHei', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f8f9fa;
}

.card {
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
    transition: all 0.3s ease;
}

.card:hover {
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
}

/* Device Card Styles */
.card.border-success {
    border-width: 2px;
}

.card.border-danger {
    border-width: 2px;
}

/* Color Button Styles */
.color-btn {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 1px solid #dee2e6;
}

.color-btn:hover {
    transform: scale(1.1);
}

/* Status Badge Styles */
.badge.bg-success {
    background-color: #28a745 !important;
}

.badge.bg-danger {
    background-color: #dc3545 !important;
}

.badge.bg-secondary {
    background-color: #6c757d !important;
}

/* Progress Bar Animation */
.progress-bar {
    transition: width 1s ease;
    animation: progress-bar-stripes 1s linear infinite;
}

@keyframes progress-bar-stripes {
    0% {
        background-position: 1rem 0;
    }
    100% {
        background-position: 0 0;
    }
}

/* Custom Scrollbar */
::-webkit-scrollbar {
    width: 8px;
}

::-webkit-scrollbar-track {
    background: #f1f1f1;
}

::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
    background: #555;
}

/* Modal Animation */
.modal.fade .modal-dialog {
    transition: transform 0.3s ease-out;
    transform: translateY(-50px);
}

.modal.show .modal-dialog {
    transform: none;
}

/* Toast Notification */
.toast {
    opacity: 1 !important;
}

/* Responsive Adjustments */
@media (max-width: 768px) {
    .card-footer {
        flex-direction: column;
        gap: 10px;
        align-items: center;
    }
    
    .btn-group {
        width: 100%;
    }
}
3. JavaScript Application

static\js\app.js

/**
 * Smart Home System - Frontend Application
 */

// API Base URL
const API_BASE_URL = '/api';

// Bootstrap components
let toastElement;
let addDeviceModal;
let editDeviceModal;
let deviceHistoryModal;
let addLocationModal;
let editLocationModal;

// Create Vue application
const app = Vue.createApp({
    data() {
        return {
            // UI state
            activeTab: 'devices',
            
            // Data
            devices: [],
            locations: [],
            deviceTypes: [],
            deviceHistory: [],
            
            // Filters
            deviceTypeFilter: '',
            locationFilter: '',
            statusFilter: '',
            
            // Selected items
            selectedDevice: null,
            editingDevice: null,
            editingLocation: null,
            
            // Form data
            newDevice: {
                name: '',
                type: 'light',
                location: '',
                properties: {
                    brightness: 100,
                    color: 'white',
                    current_temp: 22,
                    target_temp: 22,
                    mode: 'off'
                }
            },
            newLocation: {
                name: '',
                description: ''
            }
        };
    },
    
    computed: {
        filteredDevices() {
            return this.devices.filter(device => {
                let matchesType = true;
                let matchesLocation = true;
                let matchesStatus = true;
                
                if (this.deviceTypeFilter) {
                    matchesType = device.type === this.deviceTypeFilter;
                }
                
                if (this.locationFilter) {
                    matchesLocation = device.location === this.locationFilter;
                }
                
                if (this.statusFilter) {
                    matchesStatus = device.status === this.statusFilter;
                }
                
                return matchesType && matchesLocation && matchesStatus;
            });
        }
    },
    
    methods: {
        // Device operations
        async loadDevices() {
            try {
                const response = await axios.get(`${API_BASE_URL}/devices`);
                this.devices = response.data;
            } catch (error) {
                this.showToast('加载设备失败', 'error');
                console.error('Error loading devices:', error);
            }
        },
        
        async loadDeviceTypes() {
            try {
                const response = await axios.get(`${API_BASE_URL}/device-types`);
                this.deviceTypes = Object.keys(response.data);
            } catch (error) {
                this.showToast('加载设备类型失败', 'error');
                console.error('Error loading device types:', error);
            }
        },
        
        async addDevice() {
            try {
                // Prepare properties based on device type
                let properties = {};
                
                if (this.newDevice.type === 'light') {
                    properties = {
                        brightness: parseInt(this.newDevice.properties.brightness),
                        color: this.newDevice.properties.color
                    };
                } else if (this.newDevice.type === 'thermostat') {
                    properties = {
                        current_temp: parseFloat(this.newDevice.properties.current_temp),
                        target_temp: parseFloat(this.newDevice.properties.target_temp),
                        mode: this.newDevice.properties.mode
                    };
                }
                
                const response = await axios.post(`${API_BASE_URL}/devices`, {
                    name: this.newDevice.name,
                    type: this.newDevice.type,
                    location: this.newDevice.location,
                    properties: properties
                });
                
                // Add new device to list
                this.devices.push(response.data);
                
                // Reset form
                this.resetNewDeviceForm();
                
                // Close modal
                addDeviceModal.hide();
                
                this.showToast('设备添加成功');
            } catch (error) {
                this.showToast('添加设备失败', 'error');
                console.error('Error adding device:', error);
            }
        },
        
        async updateDevice() {
            if (!this.editingDevice) return;
            
            try {
                const deviceId = this.editingDevice.id;
                
                await axios.put(`${API_BASE_URL}/devices/${deviceId}`, {
                    name: this.editingDevice.name,
                    location: this.editingDevice.location
                });
                
                // Update device in list
                const index = this.devices.findIndex(d => d.id === deviceId);
                if (index !== -1) {
                    this.devices[index].name = this.editingDevice.name;
                    this.devices[index].location = this.editingDevice.location;
                }
                
                // Close modal
                editDeviceModal.hide();
                
                this.showToast('设备更新成功');
            } catch (error) {
                this.showToast('更新设备失败', 'error');
                console.error('Error updating device:', error);
            }
        },
        
        async deleteDevice(device) {
            if (!confirm(`确定要删除设备 "${device.name}" 吗?`)) return;
            
            try {
                await axios.delete(`${API_BASE_URL}/devices/${device.id}`);
                
                // Remove device from list
                this.devices = this.devices.filter(d => d.id !== device.id);
                
                this.showToast('设备删除成功');
            } catch (error) {
                this.showToast('删除设备失败', 'error');
                console.error('Error deleting device:', error);
            }
        },
        
        async toggleDevice(device) {
            const command = device.status === 'on' ? 'turn_off' : 'turn_on';
            
            try {
                const response = await axios.post(`${API_BASE_URL}/devices/${device.id}/control`, {
                    command: command
                });
                
                // Update device in list
                const index = this.devices.findIndex(d => d.id === device.id);
                if (index !== -1) {
                    this.devices[index] = response.data.device;
                }
                
                this.showToast(`设备${command === 'turn_on' ? '开启' : '关闭'}成功`);
            } catch (error) {
                this.showToast(`设备${command === 'turn_on' ? '开启' : '关闭'}失败`, 'error');
                console.error('Error controlling device:', error);
            }
        },
        
        async setBrightness(deviceId, brightness) {
            try {
                const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {
                    command: 'set_brightness',
                    params: {
                        brightness: parseInt(brightness)
                    }
                });
                
                // Update device in list
                const index = this.devices.findIndex(d => d.id === deviceId);
                if (index !== -1) {
                    this.devices[index] = response.data.device;
                }
            } catch (error) {
                this.showToast('设置亮度失败', 'error');
                console.error('Error setting brightness:', error);
            }
        },
        
        async setColor(deviceId, color) {
            try {
                const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {
                    command: 'set_color',
                    params: {
                        color: color
                    }
                });
                
                // Update device in list
                const index = this.devices.findIndex(d => d.id === deviceId);
                if (index !== -1) {
                    this.devices[index] = response.data.device;
                }
            } catch (error) {
                this.showToast('设置颜色失败', 'error');
                console.error('Error setting color:', error);
            }
        },
        
        async adjustTemperature(deviceId, delta) {
            const device = this.devices.find(d => d.id === deviceId);
            if (!device) return;
            
            const currentTemp = device.properties.target_temp;
            const newTemp = Math.min(Math.max(currentTemp + delta, 5), 35);
            
            try {
                const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {
                    command: 'set_temperature',
                    params: {
                        temperature: newTemp
                    }
                });
                
                // Update device in list
                const index = this.devices.findIndex(d => d.id === deviceId);
                if (index !== -1) {
                    this.devices[index] = response.data.device;
                }
            } catch (error) {
                this.showToast('设置温度失败', 'error');
                console.error('Error setting temperature:', error);
            }
        },
        
        async setThermostatMode(deviceId, mode) {
            try {
                const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {
                    command: 'set_mode',
                    params: {
                        mode: mode
                    }
                });
                
                // Update device in list
                const index = this.devices.findIndex(d => d.id === deviceId);
                if (index !== -1) {
                    this.devices[index] = response.data.device;
                }
            } catch (error) {
                this.showToast('设置模式失败', 'error');
                console.error('Error setting mode:', error);
            }
        },
        
        async loadDeviceHistory(deviceId) {
            try {
                const response = await axios.get(`${API_BASE_URL}/devices/${deviceId}/history`);
                this.deviceHistory = response.data;
            } catch (error) {
                this.showToast('加载设备历史记录失败', 'error');
                console.error('Error loading device history:', error);
            }
        },
        
        // Location operations
        async loadLocations() {
            try {
                const response = await axios.get(`${API_BASE_URL}/locations`);
                this.locations = response.data;
            } catch (error) {
                this.showToast('加载位置失败', 'error');
                console.error('Error loading locations:', error);
            }
        },
        
        async addLocation() {
            try {
                await axios.post(`${API_BASE_URL}/locations`, {
                    name: this.newLocation.name,
                    description: this.newLocation.description
                });
                
                // Reload locations
                await this.loadLocations();
                
                // Reset form
                this.newLocation = {
                    name: '',
                    description: ''
                };
                
                // Close modal
                addLocationModal.hide();
                
                this.showToast('位置添加成功');
            } catch (error) {
                this.showToast('添加位置失败', 'error');
                console.error('Error adding location:', error);
            }
        },
        
        // UI operations
        showAddDeviceModal() {
            this.resetNewDeviceForm();
            addDeviceModal.show();
        },
        
        showEditDeviceModal(device) {
            this.editingDevice = { ...device };
            editDeviceModal.show();
        },
        
        showDeviceHistory(device) {
            this.selectedDevice = device;
            this.deviceHistory = [];
            deviceHistoryModal.show();
            this.loadDeviceHistory(device.id);
        },
        
        showAddLocationModal() {
            this.newLocation = {
                name: '',
                description: ''
            };
            addLocationModal.show();
        },
        
        showEditLocationModal(location) {
            this.editingLocation = { ...location };
            editLocationModal.show();
        },
        
        resetNewDeviceForm() {
            this.newDevice = {
                name: '',
                type: 'light',
                location: '',
                properties: {
                    brightness: 100,
                    color: 'white',
                    current_temp: 22,
                    target_temp: 22,
                    mode: 'off'
                }
            };
        },
        
        filterDevices() {
            // This method is called when filters change
            // The actual filtering is done in the computed property
        },
        
        getDeviceCountByType(type) {
            return this.devices.filter(device => device.type === type).length;
        },
        
        getDeviceCountByLocation(location) {
            return this.devices.filter(device => device.location === location).length;
        },
        
        getStatusBadgeClass(status) {
            if (status === 'on') return 'bg-success';
            if (status === 'off') return 'bg-secondary';
            return 'bg-danger';
        },
        
        getStatusText(status) {
            if (status === 'on') return '开启';
            if (status === 'off') return '关闭';
            return '离线';
        },
        
        formatDate(dateString) {
            if (!dateString) return '';
            
            const date = new Date(dateString);
            return date.toLocaleString('zh-CN', {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                second: '2-digit'
            });
        },
        
        showToast(message, type = 'success') {
            const toastEl = document.getElementById('toast');
            const toastMessageEl = document.getElementById('toastMessage');
            
            toastMessageEl.textContent = message;
            toastEl.classList.remove('bg-success', 'bg-danger');
            toastEl.classList.add(type === 'success' ? 'bg-success' : 'bg-danger');
            
            const toast = new bootstrap.Toast(toastEl);
            toast.show();
        }
    },
    
    mounted() {
        // Initialize Bootstrap components
        toastElement = document.getElementById('toast');
        addDeviceModal = new bootstrap.Modal(document.getElementById('addDeviceModal'));
        editDeviceModal = new bootstrap.Modal(document.getElementById('editDeviceModal'));
        deviceHistoryModal = new bootstrap.Modal(document.getElementById('deviceHistoryModal'));
        addLocationModal = new bootstrap.Modal(document.getElementById('addLocationModal'));
        editLocationModal = new bootstrap.Modal(document.getElementById('editLocationModal'));
        
        // Load data
        this.loadDevices();
        this.loadDeviceTypes();
        this.loadLocations();
    }
});

// Mount Vue application
app.mount('#app');
4. Requirements File

templates\index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能家居控制系统 - 设备管理</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
    <link rel="stylesheet" href="/static/css/styles.css">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">
                    <i class="bi bi-house-gear-fill me-2"></i>
                    智能家居控制系统
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav">
                        <li class="nav-item">
                            <a class="nav-link active" href="#" @click="activeTab = 'devices'">
                                <i class="bi bi-grid-3x3-gap-fill me-1"></i>设备管理
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" @click="activeTab = 'locations'">
                                <i class="bi bi-geo-alt-fill me-1"></i>位置管理
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#" @click="activeTab = 'statistics'">
                                <i class="bi bi-bar-chart-fill me-1"></i>数据统计
                            </a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>

        <div class="container mt-4">
            <!-- Devices Tab -->
            <div v-if="activeTab === 'devices'">
                <div class="d-flex justify-content-between align-items-center mb-4">
                    <h2>设备管理</h2>
                    <button class="btn btn-primary" @click="showAddDeviceModal">
                        <i class="bi bi-plus-lg me-1"></i>添加设备
                    </button>
                </div>

                <!-- Filters -->
                <div class="row mb-4">
                    <div class="col-md-4">
                        <div class="input-group">
                            <span class="input-group-text">类型</span>
                            <select class="form-select" v-model="deviceTypeFilter" @change="filterDevices">
                                <option value="">全部</option>
                                <option v-for="type in deviceTypes" :value="type">{{ type }}</option>
                            </select>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="input-group">
                            <span class="input-group-text">位置</span>
                            <select class="form-select" v-model="locationFilter" @change="filterDevices">
                                <option value="">全部</option>
                                <option v-for="location in locations" :value="location.name">{{ location.name }}</option>
                            </select>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="input-group">
                            <span class="input-group-text">状态</span>
                            <select class="form-select" v-model="statusFilter" @change="filterDevices">
                                <option value="">全部</option>
                                <option value="on">开启</option>
                                <option value="off">关闭</option>
                                <option value="offline">离线</option>
                            </select>
                        </div>
                    </div>
                </div>

                <!-- Devices List -->
                <div class="row" v-if="filteredDevices.length > 0">
                    <div class="col-md-4 mb-4" v-for="device in filteredDevices" :key="device.id">
                        <div class="card h-100" :class="{'border-success': device.status === 'on', 'border-danger': device.status === 'offline'}">
                            <div class="card-header d-flex justify-content-between align-items-center">
                                <h5 class="card-title mb-0">{{ device.name }}</h5>
                                <span class="badge" :class="getStatusBadgeClass(device.status)">
                                    {{ getStatusText(device.status) }}
                                </span>
                            </div>
                            <div class="card-body">
                                <p class="card-text">
                                    <i class="bi bi-tag-fill me-2"></i>类型: {{ device.type }}
                                </p>
                                <p class="card-text">
                                    <i class="bi bi-geo-alt-fill me-2"></i>位置: {{ device.location || '未设置' }}
                                </p>
                                
                                <!-- Light Device Controls -->
                                <div v-if="device.type === 'light' && device.status !== 'offline'">
                                    <div class="mb-3">
                                        <label class="form-label">亮度: {{ device.properties.brightness }}%</label>
                                        <input type="range" class="form-range" min="0" max="100" step="1"
                                            :value="device.properties.brightness"
                                            @change="setBrightness(device.id, $event.target.value)">
                                    </div>
                                    <div class="mb-3">
                                        <label class="form-label">颜色:</label>
                                        <div class="d-flex">
                                            <button class="btn btn-sm color-btn me-1" 
                                                v-for="color in ['white', 'red', 'green', 'blue', 'yellow']"
                                                :style="{backgroundColor: color}"
                                                @click="setColor(device.id, color)">
                                            </button>
                                        </div>
                                    </div>
                                </div>
                                
                                <!-- Thermostat Device Controls -->
                                <div v-if="device.type === 'thermostat' && device.status !== 'offline'">
                                    <div class="mb-3">
                                        <label class="form-label">当前温度: {{ device.properties.current_temp }}°C</label>
                                    </div>
                                    <div class="mb-3">
                                        <label class="form-label">目标温度: {{ device.properties.target_temp }}°C</label>
                                        <div class="d-flex align-items-center">
                                            <button class="btn btn-sm btn-outline-primary me-2" 
                                                @click="adjustTemperature(device.id, -1)">
                                                <i class="bi bi-dash"></i>
                                            </button>
                                            <span class="fs-5">{{ device.properties.target_temp }}°C</span>
                                            <button class="btn btn-sm btn-outline-primary ms-2" 
                                                @click="adjustTemperature(device.id, 1)">
                                                <i class="bi bi-plus"></i>
                                            </button>
                                        </div>
                                    </div>
                                    <div class="mb-3">
                                        <label class="form-label">模式:</label>
                                        <div class="btn-group w-100">
                                            <button class="btn btn-sm" 
                                                :class="device.properties.mode === 'off' ? 'btn-primary' : 'btn-outline-primary'"
                                                @click="setThermostatMode(device.id, 'off')">关闭</button>
                                            <button class="btn btn-sm" 
                                                :class="device.properties.mode === 'heat' ? 'btn-primary' : 'btn-outline-primary'"
                                                @click="setThermostatMode(device.id, 'heat')">制热</button>
                                            <button class="btn btn-sm" 
                                                :class="device.properties.mode === 'cool' ? 'btn-primary' : 'btn-outline-primary'"
                                                @click="setThermostatMode(device.id, 'cool')">制冷</button>
                                            <button class="btn btn-sm" 
                                                :class="device.properties.mode === 'auto' ? 'btn-primary' : 'btn-outline-primary'"
                                                @click="setThermostatMode(device.id, 'auto')">自动</button>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="card-footer d-flex justify-content-between">
                                <button class="btn btn-sm" 
                                    :class="device.status === 'on' ? 'btn-danger' : 'btn-success'"
                                    @click="toggleDevice(device)"
                                    :disabled="device.status === 'offline'">
                                    {{ device.status === 'on' ? '关闭' : '开启' }}
                                </button>
                                <div>
                                    <button class="btn btn-sm btn-info me-1" @click="showDeviceHistory(device)">
                                        <i class="bi bi-clock-history"></i>
                                    </button>
                                    <button class="btn btn-sm btn-primary me-1" @click="showEditDeviceModal(device)">
                                        <i class="bi bi-pencil"></i>
                                    </button>
                                    <button class="btn btn-sm btn-danger" @click="deleteDevice(device)">
                                        <i class="bi bi-trash"></i>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                
                <div class="alert alert-info" v-else>
                    没有找到符合条件的设备。
                </div>
            </div>

            <!-- Locations Tab -->
            <div v-if="activeTab === 'locations'">
                <div class="d-flex justify-content-between align-items-center mb-4">
                    <h2>位置管理</h2>
                    <button class="btn btn-primary" @click="showAddLocationModal">
                        <i class="bi bi-plus-lg me-1"></i>添加位置
                    </button>
                </div>

                <div class="row">
                    <div class="col-md-4 mb-4" v-for="location in locations" :key="location.id">
                        <div class="card h-100">
                            <div class="card-header">
                                <h5 class="card-title mb-0">{{ location.name }}</h5>
                            </div>
                            <div class="card-body">
                                <p class="card-text" v-if="location.description">{{ location.description }}</p>
                                <p class="card-text" v-else>无描述</p>
                                
                                <div class="mt-3">
                                    <h6>设备数量:</h6>
                                    <p>{{ getDeviceCountByLocation(location.name) }} 个设备</p>
                                </div>
                            </div>
                            <div class="card-footer d-flex justify-content-end">
                                <button class="btn btn-sm btn-primary me-1" @click="showEditLocationModal(location)">
                                    <i class="bi bi-pencil"></i>
                                </button>
                                <button class="btn btn-sm btn-danger" 
                                    @click="deleteLocation(location)"
                                    :disabled="getDeviceCountByLocation(location.name) > 0">
                                    <i class="bi bi-trash"></i>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Statistics Tab -->
            <div v-if="activeTab === 'statistics'">
                <h2 class="mb-4">数据统计</h2>
                
                <div class="row">
                    <div class="col-md-4 mb-4">
                        <div class="card h-100">
                            <div class="card-header bg-primary text-white">
                                <h5 class="card-title mb-0">设备总览</h5>
                            </div>
                            <div class="card-body">
                                <div class="d-flex justify-content-between mb-3">
                                    <span>总设备数:</span>
                                    <span class="fw-bold">{{ devices.length }}</span>
                                </div>
                                <div class="d-flex justify-content-between mb-3">
                                    <span>在线设备:</span>
                                    <span class="fw-bold">{{ devices.filter(d => d.status !== 'offline').length }}</span>
                                </div>
                                <div class="d-flex justify-content-between mb-3">
                                    <span>开启设备:</span>
                                    <span class="fw-bold">{{ devices.filter(d => d.status === 'on').length }}</span>
                                </div>
                                <div class="d-flex justify-content-between">
                                    <span>离线设备:</span>
                                    <span class="fw-bold">{{ devices.filter(d => d.status === 'offline').length }}</span>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <div class="col-md-4 mb-4">
                        <div class="card h-100">
                            <div class="card-header bg-success text-white">
                                <h5 class="card-title mb-0">设备类型分布</h5>
                            </div>
                            <div class="card-body">
                                <div v-for="type in deviceTypes" :key="type" class="mb-3">
                                    <div class="d-flex justify-content-between mb-1">
                                        <span>{{ type }}:</span>
                                        <span class="fw-bold">{{ getDeviceCountByType(type) }}</span>
                                    </div>
                                    <div class="progress">
                                        <div class="progress-bar" role="progressbar"
                                            :style="{width: (getDeviceCountByType(type) / devices.length * 100) + '%'}"
                                            :aria-valuenow="getDeviceCountByType(type)"
                                            aria-valuemin="0" :aria-valuemax="devices.length">
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <div class="col-md-4 mb-4">
                        <div class="card h-100">
                            <div class="card-header bg-info text-white">
                                <h5 class="card-title mb-0">位置分布</h5>
                            </div>
                            <div class="card-body">
                                <div v-for="location in locations" :key="location.id" class="mb-3">
                                    <div class="d-flex justify-content-between mb-1">
                                        <span>{{ location.name }}:</span>
                                        <span class="fw-bold">{{ getDeviceCountByLocation(location.name) }}</span>
                                    </div>
                                    <div class="progress">
                                        <div class="progress-bar bg-info" role="progressbar"
                                            :style="{width: (getDeviceCountByLocation(location.name) / devices.length * 100) + '%'}"
                                            :aria-valuenow="getDeviceCountByLocation(location.name)"
                                            aria-valuemin="0" :aria-valuemax="devices.length">
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Add Device Modal -->
        <div class="modal fade" id="addDeviceModal" tabindex="-1" aria-hidden="true">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">添加设备</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <form @submit.prevent="addDevice">
                            <div class="mb-3">
                                <label class="form-label">设备名称</label>
                                <input type="text" class="form-control" v-model="newDevice.name" required>
                            </div>
                            <div class="mb-3">
                                <label class="form-label">设备类型</label>
                                <select class="form-select" v-model="newDevice.type" required>
                                    <option v-for="type in deviceTypes" :value="type">{{ type }}</option>
                                </select>
                            </div>
                            <div class="mb-3">
                                <label class="form-label">位置</label>
                                <select class="form-select" v-model="newDevice.location">
                                    <option value="">未设置</option>
                                    <option v-for="location in locations" :value="location.name">{{ location.name }}</option>
                                </select>
                            </div>
                            
                            <!-- Light device properties -->
                            <div v-if="newDevice.type === 'light'">
                                <div class="mb-3">
                                    <label class="form-label">亮度</label>
                                    <input type="range" class="form-range" min="0" max="100" step="1" v-model="newDevice.properties.brightness">
                                    <div class="text-center">{{ newDevice.properties.brightness }}%</div>
                                </div>
                                <div class="mb-3">
                                    <label class="form-label">颜色</label>
                                    <select class="form-select" v-model="newDevice.properties.color">
                                        <option value="white">白色</option>
                                        <option value="red">红色</option>
                                        <option value="green">绿色</option>
                                        <option value="blue">蓝色</option>
                                        <option value="yellow">黄色</option>
                                    </select>
                                </div>
                            </div>
                            
                            <!-- Thermostat device properties -->
                            <div v-if="newDevice.type === 'thermostat'">
                                <div class="mb-3">
                                    <label class="form-label">当前温度 (°C)</label>
                                    <input type="number" class="form-control" v-model="newDevice.properties.current_temp" min="0" max="40">
                                </div>
                                <div class="mb-3">
                                    <label class="form-label">目标温度 (°C)</label>
                                    <input type="number" class="form-control" v-model="newDevice.properties.target_temp" min="5" max="35">
                                </div>
                                <div class="mb-3">
                                    <label class="form-label">模式</label>
                                    <select class="form-select" v-model="newDevice.properties.mode">
                                        <option value="off">关闭</option>
                                        <option value="heat">制热</option>
                                        <option value="cool">制冷</option>
                                        <option value="auto">自动</option>
                                    </select>
                                </div>
                            </div>
                            
                            <div class="text-end">
                                <button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button>
                                <button type="submit" class="btn btn-primary">添加</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <!-- Edit Device Modal -->
        <div class="modal fade" id="editDeviceModal" tabindex="-1" aria-hidden="true">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">编辑设备</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <form @submit.prevent="updateDevice" v-if="editingDevice">
                            <div class="mb-3">
                                <label class="form-label">设备名称</label>
                                <input type="text" class="form-control" v-model="editingDevice.name" required>
                            </div>
                            <div class="mb-3">
                                <label class="form-label">位置</label>
                                <select class="form-select" v-model="editingDevice.location">
                                    <option value="">未设置</option>
                                    <option v-for="location in locations" :value="location.name">{{ location.name }}</option>
                                </select>
                            </div>
                            <div class="text-end">
                                <button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button>
                                <button type="submit" class="btn btn-primary">保存</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <!-- Device History Modal -->
        <div class="modal fade" id="deviceHistoryModal" tabindex="-1" aria-hidden="true">
            <div class="modal-dialog modal-lg">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">设备历史记录</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <h6 v-if="selectedDevice">{{ selectedDevice.name }}</h6>
                        <div class="table-responsive">
                            <table class="table table-striped">
                                <thead>
                                    <tr>
                                        <th>时间</th>
                                        <th>状态</th>
                                        <th>属性</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr v-for="entry in deviceHistory" :key="entry.id">
                                        <td>{{ formatDate(entry.timestamp) }}</td>
                                        <td>
                                            <span class="badge" :class="getStatusBadgeClass(entry.status)">
                                                {{ getStatusText(entry.status) }}
                                            </span>
                                        </td>
                                        <td>
                                            <pre v-if="entry.properties" class="mb-0">{{ JSON.stringify(entry.properties, null, 2) }}</pre>
                                            <span v-else>-</span>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                        <div class="alert alert-info" v-if="deviceHistory.length === 0">
                            没有历史记录。
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Add Location Modal -->
        <div class="modal fade" id="addLocationModal" tabindex="-1" aria-hidden="true">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">添加位置</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <form @submit.prevent="addLocation">
                            <div class="mb-3">
                                <label class="form-label">位置名称</label>
                                <input type="text" class="form-control" v-model="newLocation.name" required>
                            </div>
                            <div class="mb-3">
                                <label class="form-label">描述</label>
                                <textarea class="form-control" v-model="newLocation.description" rows="3"></textarea>
                            </div>
                            <div class="text-end">
                                <button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button>
                                <button type="submit" class="btn btn-primary">添加</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <!-- Edit Location Modal -->
        <div class="modal fade" id="editLocationModal" tabindex="-1" aria-hidden="true">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">编辑位置</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <form @submit.prevent="updateLocation" v-if="editingLocation">
                            <div class="mb-3">
                                <label class="form-label">位置名称</label>
                                <input type="text" class="form-control" v-model="editingLocation.name" required>
                            </div>
                            <div class="mb-3">
                                <label class="form-label">描述</label>
                                <textarea class="form-control" v-model="editingLocation.description" rows="3"></textarea>
                            </div>
                            <div class="text-end">
                                <button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button>
                                <button type="submit" class="btn btn-primary">保存</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <!-- Toast Notifications -->
        <div class="toast-container position-fixed bottom-0 end-0 p-3">
            <div id="toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
                <div class="toast-header">
                    <strong class="me-auto">通知</strong>
                    <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
                </div>
                <div class="toast-body" id="toastMessage"></div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue@3.2.45/dist/vue.global.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="/static/js/app.js"></script>
</body>
</html>






评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值