10.1 项目概述

10.1.1 项目介绍

我们将构建一个完整的任务管理应用,它包含以下功能:

  • 用户认证和权限管理
  • 任务的增删改查
  • 项目分组管理
  • 实时协作功能
  • 数据同步和离线支持
  • 性能监控和错误追踪
graph TD
    A[用户登录] --> B[主界面]
    B --> C[项目管理]
    B --> D[任务管理]
    B --> E[团队协作]
    
    C --> C1[创建项目]
    C --> C2[编辑项目]
    C --> C3[删除项目]
    
    D --> D1[添加任务]
    D --> D2[编辑任务]
    D --> D3[完成任务]
    D --> D4[任务筛选]
    
    E --> E1[邀请成员]
    E --> E2[实时更新]
    E --> E3[消息通知]

10.1.2 技术架构

graph TB
    subgraph "前端层"
        A[Sciter-JS UI]
        B[组件系统]
        C[状态管理]
    end
    
    subgraph "业务层"
        D[用户管理]
        E[项目管理]
        F[任务管理]
        G[协作管理]
    end
    
    subgraph "数据层"
        H[本地存储]
        I[网络请求]
        J[数据同步]
    end
    
    subgraph "服务层"
        K[认证服务]
        L[API 服务]
        M[WebSocket 服务]
    end
    
    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G
    G --> H
    H --> I
    I --> J
    J --> K
    K --> L
    L --> M

10.2 项目结构设计

10.2.1 目录结构

task-manager/
├── src/
│   ├── components/          # 组件目录
│   │   ├── common/         # 通用组件
│   │   ├── auth/           # 认证组件
│   │   ├── project/        # 项目组件
│   │   ├── task/           # 任务组件
│   │   └── collaboration/  # 协作组件
│   ├── services/           # 服务层
│   │   ├── auth.js
│   │   ├── project.js
│   │   ├── task.js
│   │   └── websocket.js
│   ├── store/              # 状态管理
│   │   ├── index.js
│   │   ├── auth.js
│   │   ├── project.js
│   │   └── task.js
│   ├── utils/              # 工具函数
│   │   ├── api.js
│   │   ├── storage.js
│   │   ├── validation.js
│   │   └── helpers.js
│   ├── styles/             # 样式文件
│   │   ├── global.css
│   │   ├── components.css
│   │   └── themes.css
│   ├── assets/             # 静态资源
│   │   ├── icons/
│   │   ├── images/
│   │   └── fonts/
│   └── main.js             # 入口文件
├── tests/                  # 测试文件
├── docs/                   # 文档
├── build/                  # 构建脚本
├── package.json
└── README.md

10.2.2 应用入口

<!-- main.html -->
<!DOCTYPE html>
<html>
<head>
    <title>任务管理器</title>
    <meta charset="utf-8">
    <style src="styles/global.css"></style>
    <style src="styles/components.css"></style>
    <style src="styles/themes.css"></style>
</head>
<body>
    <div id="app">
        <div id="loading" class="loading-screen">
            <div class="spinner"></div>
            <p>加载中...</p>
        </div>
    </div>
    
    <script type="module" src="main.js"></script>
</body>
</html>
// main.js
import { App } from './components/App.js';
import { Store } from './store/index.js';
import { AuthService } from './services/auth.js';
import { WebSocketService } from './services/websocket.js';
import { ErrorTracker } from './utils/error-tracker.js';
import { PerformanceMonitor } from './utils/performance-monitor.js';

class TaskManagerApp {
    constructor() {
        this.store = new Store();
        this.authService = new AuthService(this.store);
        this.wsService = new WebSocketService(this.store);
        this.errorTracker = new ErrorTracker();
        this.performanceMonitor = new PerformanceMonitor();
        
        this.app = null;
        this.isInitialized = false;
    }
    
    // 初始化应用
    async initialize() {
        try {
            console.log('初始化任务管理器应用...');
            
            // 启动性能监控
            this.performanceMonitor.start();
            
            // 初始化错误追踪
            this.errorTracker.initialize();
            
            // 检查用户认证状态
            await this.checkAuthStatus();
            
            // 创建应用实例
            this.app = new App({
                store: this.store,
                authService: this.authService,
                wsService: this.wsService
            });
            
            // 渲染应用
            await this.renderApp();
            
            // 设置全局事件监听
            this.setupGlobalEventListeners();
            
            this.isInitialized = true;
            console.log('应用初始化完成');
            
        } catch (error) {
            console.error('应用初始化失败:', error);
            this.errorTracker.captureError(error);
            this.showErrorScreen(error);
        }
    }
    
    // 检查认证状态
    async checkAuthStatus() {
        const token = localStorage.getItem('auth_token');
        
        if (token) {
            try {
                const user = await this.authService.validateToken(token);
                this.store.dispatch('auth/setUser', user);
                this.store.dispatch('auth/setAuthenticated', true);
            } catch (error) {
                console.warn('Token 验证失败:', error.message);
                localStorage.removeItem('auth_token');
            }
        }
    }
    
    // 渲染应用
    async renderApp() {
        const appContainer = document.getElementById('app');
        const loadingScreen = document.getElementById('loading');
        
        // 渲染主应用
        await this.app.render(appContainer);
        
        // 隐藏加载屏幕
        loadingScreen.style.display = 'none';
    }
    
    // 设置全局事件监听
    setupGlobalEventListeners() {
        // 窗口关闭前保存数据
        window.addEventListener('beforeunload', () => {
            this.store.dispatch('app/saveState');
        });
        
        // 网络状态变化
        window.addEventListener('online', () => {
            this.store.dispatch('app/setOnlineStatus', true);
            this.wsService.reconnect();
        });
        
        window.addEventListener('offline', () => {
            this.store.dispatch('app/setOnlineStatus', false);
        });
        
        // 全局错误处理
        window.addEventListener('error', (event) => {
            this.errorTracker.captureError(event.error);
        });
        
        window.addEventListener('unhandledrejection', (event) => {
            this.errorTracker.captureError(event.reason);
        });
    }
    
    // 显示错误屏幕
    showErrorScreen(error) {
        const appContainer = document.getElementById('app');
        appContainer.innerHTML = `
            <div class="error-screen">
                <h1>应用启动失败</h1>
                <p>抱歉,应用无法正常启动。请刷新页面重试。</p>
                <details>
                    <summary>错误详情</summary>
                    <pre>${error.stack || error.message}</pre>
                </details>
                <button onclick="location.reload()">刷新页面</button>
            </div>
        `;
    }
    
    // 销毁应用
    destroy() {
        if (this.app) {
            this.app.destroy();
        }
        
        this.performanceMonitor.stop();
        this.wsService.disconnect();
        
        console.log('应用已销毁');
    }
}

// 启动应用
const taskManager = new TaskManagerApp();
taskManager.initialize();

// 导出全局实例
window.TaskManager = taskManager;

10.3 核心组件实现

10.3.1 主应用组件

// components/App.js
import { LoginForm } from './auth/LoginForm.js';
import { Dashboard } from './Dashboard.js';
import { Router } from '../utils/router.js';
import { Component } from '../utils/component.js';

export class App extends Component {
    constructor(options) {
        super();
        
        this.store = options.store;
        this.authService = options.authService;
        this.wsService = options.wsService;
        
        this.router = new Router();
        this.currentView = null;
        
        this.setupRoutes();
        this.setupStoreSubscriptions();
    }
    
    // 设置路由
    setupRoutes() {
        this.router.addRoute('/', () => this.renderDashboard());
        this.router.addRoute('/login', () => this.renderLogin());
        this.router.addRoute('/projects/:id', (params) => this.renderProject(params.id));
        this.router.addRoute('/tasks/:id', (params) => this.renderTask(params.id));
        
        // 路由守卫
        this.router.beforeEach((to, from, next) => {
            const isAuthenticated = this.store.getState().auth.isAuthenticated;
            
            if (!isAuthenticated && to !== '/login') {
                next('/login');
            } else if (isAuthenticated && to === '/login') {
                next('/');
            } else {
                next();
            }
        });
    }
    
    // 设置状态订阅
    setupStoreSubscriptions() {
        this.store.subscribe('auth/isAuthenticated', (isAuthenticated) => {
            if (isAuthenticated) {
                this.wsService.connect();
                this.router.push('/');
            } else {
                this.wsService.disconnect();
                this.router.push('/login');
            }
        });
    }
    
    // 渲染应用
    async render(container) {
        this.container = container;
        
        // 创建应用结构
        this.container.innerHTML = `
            <div class="app">
                <header id="app-header" class="app-header"></header>
                <nav id="app-nav" class="app-nav"></nav>
                <main id="app-main" class="app-main"></main>
                <footer id="app-footer" class="app-footer"></footer>
            </div>
        `;
        
        // 获取容器元素
        this.headerContainer = this.container.querySelector('#app-header');
        this.navContainer = this.container.querySelector('#app-nav');
        this.mainContainer = this.container.querySelector('#app-main');
        this.footerContainer = this.container.querySelector('#app-footer');
        
        // 启动路由
        this.router.start();
    }
    
    // 渲染登录页面
    async renderLogin() {
        this.hideNavigation();
        
        if (this.currentView) {
            this.currentView.destroy();
        }
        
        this.currentView = new LoginForm({
            authService: this.authService,
            onLogin: (user) => {
                this.store.dispatch('auth/setUser', user);
                this.store.dispatch('auth/setAuthenticated', true);
            }
        });
        
        await this.currentView.render(this.mainContainer);
    }
    
    // 渲染仪表板
    async renderDashboard() {
        this.showNavigation();
        
        if (this.currentView) {
            this.currentView.destroy();
        }
        
        this.currentView = new Dashboard({
            store: this.store,
            router: this.router
        });
        
        await this.currentView.render(this.mainContainer);
    }
    
    // 渲染项目详情
    async renderProject(projectId) {
        this.showNavigation();
        
        const { ProjectDetail } = await import('./project/ProjectDetail.js');
        
        if (this.currentView) {
            this.currentView.destroy();
        }
        
        this.currentView = new ProjectDetail({
            projectId,
            store: this.store,
            router: this.router
        });
        
        await this.currentView.render(this.mainContainer);
    }
    
    // 渲染任务详情
    async renderTask(taskId) {
        this.showNavigation();
        
        const { TaskDetail } = await import('./task/TaskDetail.js');
        
        if (this.currentView) {
            this.currentView.destroy();
        }
        
        this.currentView = new TaskDetail({
            taskId,
            store: this.store,
            router: this.router
        });
        
        await this.currentView.render(this.mainContainer);
    }
    
    // 显示导航
    showNavigation() {
        this.renderHeader();
        this.renderNavigation();
        this.renderFooter();
    }
    
    // 隐藏导航
    hideNavigation() {
        this.headerContainer.innerHTML = '';
        this.navContainer.innerHTML = '';
        this.footerContainer.innerHTML = '';
    }
    
    // 渲染头部
    renderHeader() {
        const user = this.store.getState().auth.user;
        
        this.headerContainer.innerHTML = `
            <div class="header-content">
                <div class="logo">
                    <h1>任务管理器</h1>
                </div>
                <div class="user-menu">
                    <span class="user-name">${user.name}</span>
                    <button class="logout-btn" onclick="this.handleLogout()">退出</button>
                </div>
            </div>
        `;
        
        // 绑定事件
        this.headerContainer.querySelector('.logout-btn').addEventListener('click', () => {
            this.handleLogout();
        });
    }
    
    // 渲染导航
    renderNavigation() {
        this.navContainer.innerHTML = `
            <ul class="nav-menu">
                <li><a href="/" class="nav-link">仪表板</a></li>
                <li><a href="/projects" class="nav-link">项目</a></li>
                <li><a href="/tasks" class="nav-link">任务</a></li>
                <li><a href="/team" class="nav-link">团队</a></li>
                <li><a href="/settings" class="nav-link">设置</a></li>
            </ul>
        `;
        
        // 绑定导航事件
        this.navContainer.addEventListener('click', (event) => {
            if (event.target.classList.contains('nav-link')) {
                event.preventDefault();
                const href = event.target.getAttribute('href');
                this.router.push(href);
            }
        });
    }
    
    // 渲染底部
    renderFooter() {
        const appState = this.store.getState().app;
        
        this.footerContainer.innerHTML = `
            <div class="footer-content">
                <div class="status-info">
                    <span class="online-status ${appState.isOnline ? 'online' : 'offline'}">
                        ${appState.isOnline ? '在线' : '离线'}
                    </span>
                    <span class="sync-status">
                        ${appState.isSyncing ? '同步中...' : '已同步'}
                    </span>
                </div>
                <div class="app-info">
                    <span>版本 1.0.0</span>
                </div>
            </div>
        `;
    }
    
    // 处理退出登录
    async handleLogout() {
        try {
            await this.authService.logout();
            this.store.dispatch('auth/clearUser');
            this.store.dispatch('auth/setAuthenticated', false);
        } catch (error) {
            console.error('退出登录失败:', error);
        }
    }
    
    // 销毁组件
    destroy() {
        if (this.currentView) {
            this.currentView.destroy();
        }
        
        this.router.destroy();
        super.destroy();
    }
}

10.3.2 登录组件

// components/auth/LoginForm.js
import { Component } from '../../utils/component.js';
import { Validator } from '../../utils/validation.js';

export class LoginForm extends Component {
    constructor(options) {
        super();
        
        this.authService = options.authService;
        this.onLogin = options.onLogin;
        
        this.validator = new Validator();
        this.isLoading = false;
        
        this.setupValidationRules();
    }
    
    // 设置验证规则
    setupValidationRules() {
        this.validator.addRule('email', {
            required: true,
            email: true,
            message: '请输入有效的邮箱地址'
        });
        
        this.validator.addRule('password', {
            required: true,
            minLength: 6,
            message: '密码至少需要6个字符'
        });
    }
    
    // 渲染组件
    async render(container) {
        this.container = container;
        
        this.container.innerHTML = `
            <div class="login-container">
                <div class="login-form">
                    <div class="login-header">
                        <h2>登录</h2>
                        <p>欢迎回到任务管理器</p>
                    </div>
                    
                    <form id="login-form" class="form">
                        <div class="form-group">
                            <label for="email">邮箱</label>
                            <input 
                                type="email" 
                                id="email" 
                                name="email" 
                                placeholder="请输入邮箱"
                                required
                            >
                            <div class="error-message" id="email-error"></div>
                        </div>
                        
                        <div class="form-group">
                            <label for="password">密码</label>
                            <input 
                                type="password" 
                                id="password" 
                                name="password" 
                                placeholder="请输入密码"
                                required
                            >
                            <div class="error-message" id="password-error"></div>
                        </div>
                        
                        <div class="form-group">
                            <label class="checkbox-label">
                                <input type="checkbox" id="remember" name="remember">
                                <span class="checkmark"></span>
                                记住我
                            </label>
                        </div>
                        
                        <div class="form-actions">
                            <button 
                                type="submit" 
                                class="btn btn-primary btn-block"
                                id="login-btn"
                            >
                                <span class="btn-text">登录</span>
                                <span class="btn-spinner" style="display: none;"></span>
                            </button>
                        </div>
                        
                        <div class="form-footer">
                            <a href="#" class="forgot-password">忘记密码?</a>
                            <span class="divider">|</span>
                            <a href="#" class="register-link">注册账号</a>
                        </div>
                    </form>
                    
                    <div class="demo-account">
                        <p>演示账号:</p>
                        <button class="btn btn-secondary" id="demo-login">
                            使用演示账号登录
                        </button>
                    </div>
                </div>
            </div>
        `;
        
        this.bindEvents();
    }
    
    // 绑定事件
    bindEvents() {
        const form = this.container.querySelector('#login-form');
        const demoBtn = this.container.querySelector('#demo-login');
        
        form.addEventListener('submit', (event) => {
            event.preventDefault();
            this.handleLogin();
        });
        
        demoBtn.addEventListener('click', () => {
            this.handleDemoLogin();
        });
        
        // 实时验证
        const inputs = form.querySelectorAll('input[type="email"], input[type="password"]');
        inputs.forEach(input => {
            input.addEventListener('blur', () => {
                this.validateField(input.name, input.value);
            });
            
            input.addEventListener('input', () => {
                this.clearFieldError(input.name);
            });
        });
    }
    
    // 处理登录
    async handleLogin() {
        const formData = this.getFormData();
        
        // 验证表单
        const validation = this.validator.validate(formData);
        if (!validation.isValid) {
            this.showValidationErrors(validation.errors);
            return;
        }
        
        this.setLoading(true);
        
        try {
            const user = await this.authService.login({
                email: formData.email,
                password: formData.password,
                remember: formData.remember
            });
            
            this.onLogin(user);
            
        } catch (error) {
            this.showError(error.message || '登录失败,请重试');
        } finally {
            this.setLoading(false);
        }
    }
    
    // 处理演示登录
    async handleDemoLogin() {
        this.setLoading(true);
        
        try {
            const user = await this.authService.demoLogin();
            this.onLogin(user);
        } catch (error) {
            this.showError('演示登录失败,请重试');
        } finally {
            this.setLoading(false);
        }
    }
    
    // 获取表单数据
    getFormData() {
        const form = this.container.querySelector('#login-form');
        const formData = new FormData(form);
        
        return {
            email: formData.get('email'),
            password: formData.get('password'),
            remember: formData.has('remember')
        };
    }
    
    // 验证字段
    validateField(fieldName, value) {
        const validation = this.validator.validateField(fieldName, value);
        
        if (!validation.isValid) {
            this.showFieldError(fieldName, validation.message);
        } else {
            this.clearFieldError(fieldName);
        }
        
        return validation.isValid;
    }
    
    // 显示验证错误
    showValidationErrors(errors) {
        Object.keys(errors).forEach(fieldName => {
            this.showFieldError(fieldName, errors[fieldName]);
        });
    }
    
    // 显示字段错误
    showFieldError(fieldName, message) {
        const errorElement = this.container.querySelector(`#${fieldName}-error`);
        const inputElement = this.container.querySelector(`#${fieldName}`);
        
        if (errorElement) {
            errorElement.textContent = message;
            errorElement.style.display = 'block';
        }
        
        if (inputElement) {
            inputElement.classList.add('error');
        }
    }
    
    // 清除字段错误
    clearFieldError(fieldName) {
        const errorElement = this.container.querySelector(`#${fieldName}-error`);
        const inputElement = this.container.querySelector(`#${fieldName}`);
        
        if (errorElement) {
            errorElement.style.display = 'none';
        }
        
        if (inputElement) {
            inputElement.classList.remove('error');
        }
    }
    
    // 显示通用错误
    showError(message) {
        // 可以显示在表单顶部或使用通知组件
        const form = this.container.querySelector('#login-form');
        
        let errorContainer = form.querySelector('.form-error');
        if (!errorContainer) {
            errorContainer = document.createElement('div');
            errorContainer.className = 'form-error';
            form.insertBefore(errorContainer, form.firstChild);
        }
        
        errorContainer.innerHTML = `
            <div class="alert alert-error">
                <span class="alert-icon">⚠</span>
                <span class="alert-message">${message}</span>
                <button class="alert-close" onclick="this.parentElement.remove()">&times;</button>
            </div>
        `;
    }
    
    // 设置加载状态
    setLoading(loading) {
        this.isLoading = loading;
        
        const loginBtn = this.container.querySelector('#login-btn');
        const btnText = loginBtn.querySelector('.btn-text');
        const btnSpinner = loginBtn.querySelector('.btn-spinner');
        
        if (loading) {
            loginBtn.disabled = true;
            btnText.style.display = 'none';
            btnSpinner.style.display = 'inline-block';
        } else {
            loginBtn.disabled = false;
            btnText.style.display = 'inline';
            btnSpinner.style.display = 'none';
        }
    }
}

10.3.3 仪表板组件

// components/Dashboard.js
import { Component } from '../utils/component.js';
import { ProjectCard } from './project/ProjectCard.js';
import { TaskList } from './task/TaskList.js';
import { StatsWidget } from './common/StatsWidget.js';

export class Dashboard extends Component {
    constructor(options) {
        super();
        
        this.store = options.store;
        this.router = options.router;
        
        this.widgets = [];
        this.projects = [];
        this.recentTasks = [];
        
        this.setupStoreSubscriptions();
    }
    
    // 设置状态订阅
    setupStoreSubscriptions() {
        this.store.subscribe('projects/list', (projects) => {
            this.projects = projects;
            this.renderProjects();
        });
        
        this.store.subscribe('tasks/recent', (tasks) => {
            this.recentTasks = tasks;
            this.renderRecentTasks();
        });
        
        this.store.subscribe('stats/overview', (stats) => {
            this.renderStats(stats);
        });
    }
    
    // 渲染组件
    async render(container) {
        this.container = container;
        
        this.container.innerHTML = `
            <div class="dashboard">
                <div class="dashboard-header">
                    <h1>仪表板</h1>
                    <div class="dashboard-actions">
                        <button class="btn btn-primary" id="new-project-btn">
                            <span class="icon">+</span>
                            新建项目
                        </button>
                        <button class="btn btn-secondary" id="new-task-btn">
                            <span class="icon">+</span>
                            新建任务
                        </button>
                    </div>
                </div>
                
                <div class="dashboard-content">
                    <div class="stats-section">
                        <h2>概览统计</h2>
                        <div class="stats-grid" id="stats-grid"></div>
                    </div>
                    
                    <div class="projects-section">
                        <div class="section-header">
                            <h2>最近项目</h2>
                            <a href="/projects" class="view-all-link">查看全部</a>
                        </div>
                        <div class="projects-grid" id="projects-grid"></div>
                    </div>
                    
                    <div class="tasks-section">
                        <div class="section-header">
                            <h2>最近任务</h2>
                            <a href="/tasks" class="view-all-link">查看全部</a>
                        </div>
                        <div class="tasks-container" id="tasks-container"></div>
                    </div>
                    
                    <div class="activity-section">
                        <h2>最近活动</h2>
                        <div class="activity-feed" id="activity-feed"></div>
                    </div>
                </div>
            </div>
        `;
        
        this.bindEvents();
        await this.loadData();
    }
    
    // 绑定事件
    bindEvents() {
        const newProjectBtn = this.container.querySelector('#new-project-btn');
        const newTaskBtn = this.container.querySelector('#new-task-btn');
        
        newProjectBtn.addEventListener('click', () => {
            this.handleNewProject();
        });
        
        newTaskBtn.addEventListener('click', () => {
            this.handleNewTask();
        });
    }
    
    // 加载数据
    async loadData() {
        try {
            // 并行加载数据
            await Promise.all([
                this.store.dispatch('projects/fetchRecent'),
                this.store.dispatch('tasks/fetchRecent'),
                this.store.dispatch('stats/fetchOverview'),
                this.store.dispatch('activity/fetchRecent')
            ]);
        } catch (error) {
            console.error('加载仪表板数据失败:', error);
            this.showError('数据加载失败,请刷新页面重试');
        }
    }
    
    // 渲染统计信息
    renderStats(stats) {
        const statsGrid = this.container.querySelector('#stats-grid');
        
        const statsData = [
            {
                title: '总项目数',
                value: stats.totalProjects,
                icon: '📁',
                color: 'blue',
                trend: stats.projectsTrend
            },
            {
                title: '总任务数',
                value: stats.totalTasks,
                icon: '📋',
                color: 'green',
                trend: stats.tasksTrend
            },
            {
                title: '已完成',
                value: stats.completedTasks,
                icon: '✅',
                color: 'success',
                trend: stats.completedTrend
            },
            {
                title: '进行中',
                value: stats.inProgressTasks,
                icon: '🔄',
                color: 'warning',
                trend: stats.inProgressTrend
            }
        ];
        
        statsGrid.innerHTML = '';
        
        statsData.forEach(stat => {
            const widget = new StatsWidget(stat);
            const widgetElement = document.createElement('div');
            widget.render(widgetElement);
            statsGrid.appendChild(widgetElement);
            this.widgets.push(widget);
        });
    }
    
    // 渲染项目
    renderProjects() {
        const projectsGrid = this.container.querySelector('#projects-grid');
        
        if (this.projects.length === 0) {
            projectsGrid.innerHTML = `
                <div class="empty-state">
                    <div class="empty-icon">📁</div>
                    <h3>暂无项目</h3>
                    <p>创建您的第一个项目开始管理任务</p>
                    <button class="btn btn-primary" onclick="this.handleNewProject()">
                        创建项目
                    </button>
                </div>
            `;
            return;
        }
        
        projectsGrid.innerHTML = '';
        
        this.projects.slice(0, 6).forEach(project => {
            const card = new ProjectCard({
                project,
                onClick: (projectId) => {
                    this.router.push(`/projects/${projectId}`);
                }
            });
            
            const cardElement = document.createElement('div');
            card.render(cardElement);
            projectsGrid.appendChild(cardElement);
            this.widgets.push(card);
        });
    }
    
    // 渲染最近任务
    renderRecentTasks() {
        const tasksContainer = this.container.querySelector('#tasks-container');
        
        if (this.recentTasks.length === 0) {
            tasksContainer.innerHTML = `
                <div class="empty-state">
                    <div class="empty-icon">📋</div>
                    <h3>暂无任务</h3>
                    <p>创建您的第一个任务开始工作</p>
                    <button class="btn btn-primary" onclick="this.handleNewTask()">
                        创建任务
                    </button>
                </div>
            `;
            return;
        }
        
        const taskList = new TaskList({
            tasks: this.recentTasks.slice(0, 10),
            compact: true,
            onTaskClick: (taskId) => {
                this.router.push(`/tasks/${taskId}`);
            },
            onTaskUpdate: (taskId, updates) => {
                this.store.dispatch('tasks/update', { id: taskId, ...updates });
            }
        });
        
        taskList.render(tasksContainer);
        this.widgets.push(taskList);
    }
    
    // 处理新建项目
    handleNewProject() {
        // 可以打开模态框或跳转到新建项目页面
        this.router.push('/projects/new');
    }
    
    // 处理新建任务
    handleNewTask() {
        // 可以打开模态框或跳转到新建任务页面
        this.router.push('/tasks/new');
    }
    
    // 显示错误
    showError(message) {
        // 可以使用通知组件显示错误
        console.error(message);
    }
    
    // 销毁组件
    destroy() {
        this.widgets.forEach(widget => {
            if (widget.destroy) {
                widget.destroy();
            }
        });
        
        this.widgets = [];
        super.destroy();
    }
}

10.4 状态管理实现

10.4.1 核心状态管理器

// store/index.js
import { AuthStore } from './auth.js';
import { ProjectStore } from './project.js';
import { TaskStore } from './task.js';
import { AppStore } from './app.js';
import { EventEmitter } from '../utils/event-emitter.js';

export class Store extends EventEmitter {
    constructor() {
        super();
        
        // 初始化子状态管理器
        this.modules = {
            auth: new AuthStore(this),
            projects: new ProjectStore(this),
            tasks: new TaskStore(this),
            app: new AppStore(this)
        };
        
        // 合并状态
        this.state = {};
        this.updateState();
        
        // 设置持久化
        this.setupPersistence();
    }
    
    // 更新状态
    updateState() {
        const newState = {};
        
        Object.keys(this.modules).forEach(moduleName => {
            newState[moduleName] = this.modules[moduleName].getState();
        });
        
        this.state = newState;
    }
    
    // 获取状态
    getState() {
        return this.state;
    }
    
    // 分发动作
    async dispatch(action, payload) {
        const [moduleName, actionName] = action.split('/');
        
        if (!this.modules[moduleName]) {
            throw new Error(`Unknown module: ${moduleName}`);
        }
        
        const module = this.modules[moduleName];
        
        if (typeof module[actionName] !== 'function') {
            throw new Error(`Unknown action: ${action}`);
        }
        
        try {
            const result = await module[actionName](payload);
            this.updateState();
            
            // 触发状态变化事件
            this.emit('stateChange', {
                action,
                payload,
                state: this.state
            });
            
            // 触发特定模块的状态变化事件
            this.emit(`${moduleName}/stateChange`, this.state[moduleName]);
            
            return result;
            
        } catch (error) {
            console.error(`Action ${action} failed:`, error);
            throw error;
        }
    }
    
    // 订阅状态变化
    subscribe(path, callback) {
        const [moduleName, statePath] = path.split('/');
        
        const handler = (moduleState) => {
            if (statePath) {
                const value = this.getNestedValue(moduleState, statePath);
                callback(value);
            } else {
                callback(moduleState);
            }
        };
        
        this.on(`${moduleName}/stateChange`, handler);
        
        // 立即调用一次以获取当前值
        if (this.state[moduleName]) {
            handler(this.state[moduleName]);
        }
        
        // 返回取消订阅函数
        return () => {
            this.off(`${moduleName}/stateChange`, handler);
        };
    }
    
    // 获取嵌套值
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => {
            return current && current[key];
        }, obj);
    }
    
    // 设置持久化
    setupPersistence() {
        // 从本地存储恢复状态
        this.restoreState();
        
        // 监听状态变化并保存
        this.on('stateChange', () => {
            this.saveState();
        });
        
        // 监听页面卸载事件
        window.addEventListener('beforeunload', () => {
            this.saveState();
        });
    }
    
    // 保存状态到本地存储
    saveState() {
        try {
            const persistentState = {
                auth: {
                    user: this.state.auth.user,
                    isAuthenticated: this.state.auth.isAuthenticated
                },
                app: {
                    theme: this.state.app.theme,
                    language: this.state.app.language,
                    preferences: this.state.app.preferences
                }
            };
            
            localStorage.setItem('app_state', JSON.stringify(persistentState));
        } catch (error) {
            console.warn('保存状态失败:', error);
        }
    }
    
    // 从本地存储恢复状态
    restoreState() {
        try {
            const savedState = localStorage.getItem('app_state');
            
            if (savedState) {
                const parsedState = JSON.parse(savedState);
                
                // 恢复各模块状态
                Object.keys(parsedState).forEach(moduleName => {
                    if (this.modules[moduleName] && this.modules[moduleName].restoreState) {
                        this.modules[moduleName].restoreState(parsedState[moduleName]);
                    }
                });
                
                this.updateState();
            }
        } catch (error) {
            console.warn('恢复状态失败:', error);
        }
    }
    
    // 清除持久化状态
    clearPersistedState() {
        localStorage.removeItem('app_state');
    }
    
    // 重置所有状态
    reset() {
        Object.values(this.modules).forEach(module => {
            if (module.reset) {
                module.reset();
            }
        });
        
        this.updateState();
        this.clearPersistedState();
    }
}

10.4.2 任务状态管理

// store/task.js
import { TaskService } from '../services/task.js';

export class TaskStore {
    constructor(rootStore) {
        this.rootStore = rootStore;
        this.taskService = new TaskService();
        
        this.state = {
            list: [],
            recent: [],
            current: null,
            filters: {
                status: 'all',
                priority: 'all',
                assignee: 'all',
                project: 'all'
            },
            loading: false,
            error: null
        };
    }
    
    // 获取状态
    getState() {
        return { ...this.state };
    }
    
    // 获取任务列表
    async fetchTasks(options = {}) {
        this.state.loading = true;
        this.state.error = null;
        
        try {
            const tasks = await this.taskService.getTasks(options);
            this.state.list = tasks;
            return tasks;
        } catch (error) {
            this.state.error = error.message;
            throw error;
        } finally {
            this.state.loading = false;
        }
    }
    
    // 获取最近任务
    async fetchRecent() {
        try {
            const recentTasks = await this.taskService.getRecentTasks();
            this.state.recent = recentTasks;
            return recentTasks;
        } catch (error) {
            console.error('获取最近任务失败:', error);
            throw error;
        }
    }
    
    // 获取单个任务
    async fetchTask(taskId) {
        this.state.loading = true;
        
        try {
            const task = await this.taskService.getTask(taskId);
            this.state.current = task;
            
            // 更新列表中的任务
            const index = this.state.list.findIndex(t => t.id === taskId);
            if (index !== -1) {
                this.state.list[index] = task;
            }
            
            return task;
        } catch (error) {
            this.state.error = error.message;
            throw error;
        } finally {
            this.state.loading = false;
        }
    }
    
    // 创建任务
    async create(taskData) {
        try {
            const newTask = await this.taskService.createTask(taskData);
            
            // 添加到列表开头
            this.state.list.unshift(newTask);
            
            // 添加到最近任务
            this.state.recent.unshift(newTask);
            if (this.state.recent.length > 10) {
                this.state.recent.pop();
            }
            
            return newTask;
        } catch (error) {
            this.state.error = error.message;
            throw error;
        }
    }
    
    // 更新任务
    async update(updates) {
        const { id, ...data } = updates;
        
        try {
            const updatedTask = await this.taskService.updateTask(id, data);
            
            // 更新列表中的任务
            const listIndex = this.state.list.findIndex(t => t.id === id);
            if (listIndex !== -1) {
                this.state.list[listIndex] = updatedTask;
            }
            
            // 更新最近任务
            const recentIndex = this.state.recent.findIndex(t => t.id === id);
            if (recentIndex !== -1) {
                this.state.recent[recentIndex] = updatedTask;
            }
            
            // 更新当前任务
            if (this.state.current && this.state.current.id === id) {
                this.state.current = updatedTask;
            }
            
            return updatedTask;
        } catch (error) {
            this.state.error = error.message;
            throw error;
        }
    }
    
    // 删除任务
    async delete(taskId) {
        try {
            await this.taskService.deleteTask(taskId);
            
            // 从列表中移除
            this.state.list = this.state.list.filter(t => t.id !== taskId);
            
            // 从最近任务中移除
            this.state.recent = this.state.recent.filter(t => t.id !== taskId);
            
            // 清除当前任务
            if (this.state.current && this.state.current.id === taskId) {
                this.state.current = null;
            }
            
            return true;
        } catch (error) {
            this.state.error = error.message;
            throw error;
        }
    }
    
    // 完成任务
    async complete(taskId) {
        return this.update({
            id: taskId,
            status: 'completed',
            completedAt: new Date().toISOString()
        });
    }
    
    // 重新打开任务
    async reopen(taskId) {
        return this.update({
            id: taskId,
            status: 'in_progress',
            completedAt: null
        });
    }
    
    // 分配任务
    async assign(taskId, assigneeId) {
        return this.update({
            id: taskId,
            assigneeId: assigneeId
        });
    }
    
    // 设置优先级
    async setPriority(taskId, priority) {
        return this.update({
            id: taskId,
            priority: priority
        });
    }
    
    // 设置截止日期
    async setDueDate(taskId, dueDate) {
        return this.update({
            id: taskId,
            dueDate: dueDate
        });
    }
    
    // 添加评论
    async addComment(taskId, comment) {
        try {
            const newComment = await this.taskService.addComment(taskId, comment);
            
            // 更新任务的评论列表
            const task = this.state.list.find(t => t.id === taskId) || this.state.current;
            if (task) {
                if (!task.comments) {
                    task.comments = [];
                }
                task.comments.push(newComment);
            }
            
            return newComment;
        } catch (error) {
            this.state.error = error.message;
            throw error;
        }
    }
    
    // 设置过滤器
    setFilter(filterType, value) {
        this.state.filters[filterType] = value;
        
        // 重新获取任务列表
        this.fetchTasks({ filters: this.state.filters });
    }
    
    // 清除过滤器
    clearFilters() {
        this.state.filters = {
            status: 'all',
            priority: 'all',
            assignee: 'all',
            project: 'all'
        };
        
        this.fetchTasks();
    }
    
    // 获取过滤后的任务
    getFilteredTasks() {
        let filteredTasks = [...this.state.list];
        
        // 按状态过滤
        if (this.state.filters.status !== 'all') {
            filteredTasks = filteredTasks.filter(task => 
                task.status === this.state.filters.status
            );
        }
        
        // 按优先级过滤
        if (this.state.filters.priority !== 'all') {
            filteredTasks = filteredTasks.filter(task => 
                task.priority === this.state.filters.priority
            );
        }
        
        // 按分配人过滤
        if (this.state.filters.assignee !== 'all') {
            filteredTasks = filteredTasks.filter(task => 
                task.assigneeId === this.state.filters.assignee
            );
        }
        
        // 按项目过滤
        if (this.state.filters.project !== 'all') {
            filteredTasks = filteredTasks.filter(task => 
                task.projectId === this.state.filters.project
            );
        }
        
        return filteredTasks;
    }
    
    // 搜索任务
    searchTasks(query) {
        if (!query.trim()) {
            return this.getFilteredTasks();
        }
        
        const lowercaseQuery = query.toLowerCase();
        
        return this.getFilteredTasks().filter(task => 
            task.title.toLowerCase().includes(lowercaseQuery) ||
            task.description.toLowerCase().includes(lowercaseQuery) ||
            (task.tags && task.tags.some(tag => 
                tag.toLowerCase().includes(lowercaseQuery)
            ))
        );
    }
    
    // 获取任务统计
    getStats() {
        const tasks = this.state.list;
        
        return {
            total: tasks.length,
            completed: tasks.filter(t => t.status === 'completed').length,
            inProgress: tasks.filter(t => t.status === 'in_progress').length,
            pending: tasks.filter(t => t.status === 'pending').length,
            overdue: tasks.filter(t => {
                return t.dueDate && new Date(t.dueDate) < new Date() && t.status !== 'completed';
            }).length
        };
    }
    
    // 重置状态
    reset() {
        this.state = {
            list: [],
            recent: [],
            current: null,
            filters: {
                status: 'all',
                priority: 'all',
                assignee: 'all',
                project: 'all'
            },
            loading: false,
            error: null
        };
    }
}

10.5 服务层实现

10.5.1 认证服务

// services/auth.js
import { ApiClient } from '../utils/api.js';
import { TokenManager } from '../utils/token-manager.js';

export class AuthService {
    constructor(store) {
        this.store = store;
        this.apiClient = new ApiClient();
        this.tokenManager = new TokenManager();
        
        this.setupInterceptors();
    }
    
    // 设置请求拦截器
    setupInterceptors() {
        // 请求拦截器 - 添加认证头
        this.apiClient.interceptors.request.use((config) => {
            const token = this.tokenManager.getToken();
            if (token) {
                config.headers.Authorization = `Bearer ${token}`;
            }
            return config;
        });
        
        // 响应拦截器 - 处理认证错误
        this.apiClient.interceptors.response.use(
            (response) => response,
            (error) => {
                if (error.status === 401) {
                    this.handleAuthError();
                }
                return Promise.reject(error);
            }
        );
    }
    
    // 用户登录
    async login(credentials) {
        try {
            const response = await this.apiClient.post('/auth/login', credentials);
            
            const { user, token, refreshToken } = response.data;
            
            // 保存令牌
            this.tokenManager.setTokens(token, refreshToken);
            
            // 如果选择记住我,设置持久化
            if (credentials.remember) {
                this.tokenManager.setPersistent(true);
            }
            
            return user;
            
        } catch (error) {
            throw new Error(error.message || '登录失败');
        }
    }
    
    // 演示登录
    async demoLogin() {
        try {
            const response = await this.apiClient.post('/auth/demo-login');
            
            const { user, token } = response.data;
            
            // 保存令牌(演示账号不持久化)
            this.tokenManager.setTokens(token);
            
            return user;
            
        } catch (error) {
            throw new Error('演示登录失败');
        }
    }
    
    // 用户注册
    async register(userData) {
        try {
            const response = await this.apiClient.post('/auth/register', userData);
            
            const { user, token, refreshToken } = response.data;
            
            // 保存令牌
            this.tokenManager.setTokens(token, refreshToken);
            
            return user;
            
        } catch (error) {
            throw new Error(error.message || '注册失败');
        }
    }
    
    // 用户登出
    async logout() {
        try {
            const refreshToken = this.tokenManager.getRefreshToken();
            
            if (refreshToken) {
                await this.apiClient.post('/auth/logout', { refreshToken });
            }
        } catch (error) {
            console.warn('服务器登出失败:', error.message);
        } finally {
            // 清除本地令牌
            this.tokenManager.clearTokens();
        }
    }
    
    // 刷新令牌
    async refreshToken() {
        try {
            const refreshToken = this.tokenManager.getRefreshToken();
            
            if (!refreshToken) {
                throw new Error('No refresh token available');
            }
            
            const response = await this.apiClient.post('/auth/refresh', {
                refreshToken
            });
            
            const { token: newToken, refreshToken: newRefreshToken } = response.data;
            
            // 更新令牌
            this.tokenManager.setTokens(newToken, newRefreshToken);
            
            return newToken;
            
        } catch (error) {
            // 刷新失败,清除令牌
            this.tokenManager.clearTokens();
            throw new Error('Token refresh failed');
        }
    }
    
    // 验证令牌
    async validateToken(token) {
        try {
            const response = await this.apiClient.get('/auth/validate', {
                headers: { Authorization: `Bearer ${token}` }
            });
            
            return response.data.user;
            
        } catch (error) {
            throw new Error('Token validation failed');
        }
    }
    
    // 获取当前用户信息
    async getCurrentUser() {
        try {
            const response = await this.apiClient.get('/auth/me');
            return response.data.user;
        } catch (error) {
            throw new Error('Failed to get current user');
        }
    }
    
    // 更新用户信息
    async updateProfile(userData) {
        try {
            const response = await this.apiClient.put('/auth/profile', userData);
            return response.data.user;
        } catch (error) {
            throw new Error(error.message || '更新用户信息失败');
        }
    }
    
    // 修改密码
    async changePassword(passwordData) {
        try {
            await this.apiClient.put('/auth/password', passwordData);
            return true;
        } catch (error) {
            throw new Error(error.message || '修改密码失败');
        }
    }
    
    // 忘记密码
    async forgotPassword(email) {
        try {
            await this.apiClient.post('/auth/forgot-password', { email });
            return true;
        } catch (error) {
            throw new Error(error.message || '发送重置邮件失败');
        }
    }
    
    // 重置密码
    async resetPassword(token, newPassword) {
        try {
            await this.apiClient.post('/auth/reset-password', {
                token,
                password: newPassword
            });
            return true;
        } catch (error) {
            throw new Error(error.message || '重置密码失败');
        }
    }
    
    // 处理认证错误
    handleAuthError() {
        // 清除令牌
        this.tokenManager.clearTokens();
        
        // 更新状态
        if (this.store) {
            this.store.dispatch('auth/clearUser');
            this.store.dispatch('auth/setAuthenticated', false);
        }
        
        // 可以显示登录提示
        console.warn('Authentication failed, please login again');
    }
    
    // 检查是否已认证
    isAuthenticated() {
        return this.tokenManager.hasValidToken();
    }
}

10.5.2 WebSocket 服务

// services/websocket.js
import { EventEmitter } from '../utils/event-emitter.js';

export class WebSocketService extends EventEmitter {
    constructor(store) {
        super();
        
        this.store = store;
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
        this.reconnectDelay = 1000;
        this.isConnecting = false;
        this.isManualDisconnect = false;
        
        this.messageHandlers = new Map();
        this.setupMessageHandlers();
    }
    
    // 设置消息处理器
    setupMessageHandlers() {
        this.messageHandlers.set('task_updated', (data) => {
            this.store.dispatch('tasks/update', data.task);
        });
        
        this.messageHandlers.set('task_created', (data) => {
            this.store.dispatch('tasks/add', data.task);
        });
        
        this.messageHandlers.set('task_deleted', (data) => {
            this.store.dispatch('tasks/remove', data.taskId);
        });
        
        this.messageHandlers.set('project_updated', (data) => {
            this.store.dispatch('projects/update', data.project);
        });
        
        this.messageHandlers.set('user_joined', (data) => {
            this.emit('userJoined', data.user);
        });
        
        this.messageHandlers.set('user_left', (data) => {
            this.emit('userLeft', data.user);
        });
        
        this.messageHandlers.set('notification', (data) => {
            this.emit('notification', data);
        });
    }
    
    // 连接 WebSocket
    async connect() {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            return;
        }
        
        if (this.isConnecting) {
            return;
        }
        
        this.isConnecting = true;
        this.isManualDisconnect = false;
        
        try {
            const token = localStorage.getItem('auth_token');
            if (!token) {
                throw new Error('No authentication token');
            }
            
            const wsUrl = `${this.getWebSocketUrl()}?token=${token}`;
            
            this.ws = new WebSocket(wsUrl);
            
            this.ws.onopen = () => {
                console.log('WebSocket connected');
                this.isConnecting = false;
                this.reconnectAttempts = 0;
                this.emit('connected');
            };
            
            this.ws.onmessage = (event) => {
                this.handleMessage(event.data);
            };
            
            this.ws.onclose = (event) => {
                console.log('WebSocket disconnected:', event.code, event.reason);
                this.isConnecting = false;
                this.emit('disconnected', event);
                
                if (!this.isManualDisconnect) {
                    this.scheduleReconnect();
                }
            };
            
            this.ws.onerror = (error) => {
                console.error('WebSocket error:', error);
                this.isConnecting = false;
                this.emit('error', error);
            };
            
        } catch (error) {
            this.isConnecting = false;
            console.error('WebSocket connection failed:', error);
            this.emit('error', error);
        }
    }
    
    // 断开连接
    disconnect() {
        this.isManualDisconnect = true;
        
        if (this.ws) {
            this.ws.close();
            this.ws = null;
        }
    }
    
    // 重新连接
    async reconnect() {
        this.disconnect();
        await new Promise(resolve => setTimeout(resolve, 100));
        await this.connect();
    }
    
    // 安排重连
    scheduleReconnect() {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error('Max reconnect attempts reached');
            this.emit('maxReconnectAttemptsReached');
            return;
        }
        
        this.reconnectAttempts++;
        const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
        
        console.log(`Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
        
        setTimeout(() => {
            if (!this.isManualDisconnect) {
                this.connect();
            }
        }, delay);
    }
    
    // 发送消息
    send(type, data) {
        if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
            console.warn('WebSocket is not connected');
            return false;
        }
        
        try {
            const message = JSON.stringify({ type, data });
            this.ws.send(message);
            return true;
        } catch (error) {
            console.error('Failed to send WebSocket message:', error);
            return false;
        }
    }
    
    // 处理接收到的消息
    handleMessage(rawMessage) {
        try {
            const message = JSON.parse(rawMessage);
            const { type, data } = message;
            
            // 查找对应的处理器
            const handler = this.messageHandlers.get(type);
            if (handler) {
                handler(data);
            } else {
                console.warn('Unknown message type:', type);
            }
            
            // 触发通用消息事件
            this.emit('message', message);
            
        } catch (error) {
            console.error('Failed to parse WebSocket message:', error);
        }
    }
    
    // 获取 WebSocket URL
    getWebSocketUrl() {
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
        const host = window.location.host;
        return `${protocol}//${host}/ws`;
    }
    
    // 订阅项目更新
    subscribeToProject(projectId) {
        this.send('subscribe_project', { projectId });
    }
    
    // 取消订阅项目更新
    unsubscribeFromProject(projectId) {
        this.send('unsubscribe_project', { projectId });
    }
    
    // 订阅任务更新
    subscribeToTask(taskId) {
        this.send('subscribe_task', { taskId });
    }
    
    // 取消订阅任务更新
    unsubscribeFromTask(taskId) {
        this.send('unsubscribe_task', { taskId });
    }
    
    // 发送用户活动
    sendUserActivity(activity) {
        this.send('user_activity', activity);
    }
    
    // 获取连接状态
    getConnectionState() {
        if (!this.ws) return 'disconnected';
        
        switch (this.ws.readyState) {
            case WebSocket.CONNECTING:
                return 'connecting';
            case WebSocket.OPEN:
                return 'connected';
            case WebSocket.CLOSING:
                return 'closing';
            case WebSocket.CLOSED:
                return 'disconnected';
            default:
                return 'unknown';
        }
    }
}

10.6 实践练习

10.6.1 创建完整的任务组件

// components/task/TaskCard.js
import { Component } from '../../utils/component.js';
import { formatDate, getRelativeTime } from '../../utils/date.js';

export class TaskCard extends Component {
    constructor(options) {
        super();
        
        this.task = options.task;
        this.onClick = options.onClick;
        this.onUpdate = options.onUpdate;
        this.compact = options.compact || false;
    }
    
    // 渲染组件
    async render(container) {
        this.container = container;
        
        const priorityClass = this.getPriorityClass(this.task.priority);
        const statusClass = this.getStatusClass(this.task.status);
        const isOverdue = this.isOverdue(this.task.dueDate, this.task.status);
        
        this.container.innerHTML = `
            <div class="task-card ${statusClass} ${priorityClass} ${this.compact ? 'compact' : ''}">
                <div class="task-header">
                    <div class="task-checkbox">
                        <input 
                            type="checkbox" 
                            ${this.task.status === 'completed' ? 'checked' : ''}
                            onchange="this.handleStatusChange(event)"
                        >
                    </div>
                    <div class="task-title">
                        <h3 onclick="this.handleClick()">${this.task.title}</h3>
                        ${this.task.description ? `<p class="task-description">${this.task.description}</p>` : ''}
                    </div>
                    <div class="task-actions">
                        <button class="btn-icon" onclick="this.handleEdit()" title="编辑">
                            ✏️
                        </button>
                        <button class="btn-icon" onclick="this.handleDelete()" title="删除">
                            🗑️
                        </button>
                    </div>
                </div>
                
                ${!this.compact ? this.renderTaskDetails() : ''}
                
                <div class="task-footer">
                    <div class="task-meta">
                        ${this.task.priority ? `<span class="priority-badge ${priorityClass}">${this.getPriorityText(this.task.priority)}</span>` : ''}
                        ${this.task.dueDate ? `<span class="due-date ${isOverdue ? 'overdue' : ''}">${formatDate(this.task.dueDate)}</span>` : ''}
                        ${this.task.assignee ? `<span class="assignee">${this.task.assignee.name}</span>` : ''}
                    </div>
                    <div class="task-stats">
                        ${this.task.comments ? `<span class="comment-count">💬 ${this.task.comments.length}</span>` : ''}
                        ${this.task.attachments ? `<span class="attachment-count">📎 ${this.task.attachments.length}</span>` : ''}
                    </div>
                </div>
            </div>
        `;
        
        this.bindEvents();
    }
    
    // 渲染任务详情
    renderTaskDetails() {
        return `
            <div class="task-details">
                ${this.task.tags && this.task.tags.length > 0 ? `
                    <div class="task-tags">
                        ${this.task.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
                    </div>
                ` : ''}
                
                ${this.task.progress !== undefined ? `
                    <div class="task-progress">
                        <div class="progress-bar">
                            <div class="progress-fill" style="width: ${this.task.progress}%"></div>
                        </div>
                        <span class="progress-text">${this.task.progress}%</span>
                    </div>
                ` : ''}
            </div>
        `;
    }
    
    // 绑定事件
    bindEvents() {
        const checkbox = this.container.querySelector('input[type="checkbox"]');
        const title = this.container.querySelector('.task-title h3');
        const editBtn = this.container.querySelector('.btn-icon[title="编辑"]');
        const deleteBtn = this.container.querySelector('.btn-icon[title="删除"]');
        
        checkbox.addEventListener('change', (event) => {
            this.handleStatusChange(event);
        });
        
        title.addEventListener('click', () => {
            this.handleClick();
        });
        
        editBtn.addEventListener('click', (event) => {
            event.stopPropagation();
            this.handleEdit();
        });
        
        deleteBtn.addEventListener('click', (event) => {
            event.stopPropagation();
            this.handleDelete();
        });
    }
    
    // 处理状态变化
    handleStatusChange(event) {
        const isCompleted = event.target.checked;
        const newStatus = isCompleted ? 'completed' : 'in_progress';
        
        if (this.onUpdate) {
            this.onUpdate(this.task.id, { status: newStatus });
        }
    }
    
    // 处理点击
    handleClick() {
        if (this.onClick) {
            this.onClick(this.task.id);
        }
    }
    
    // 处理编辑
    handleEdit() {
        // 可以打开编辑模态框或跳转到编辑页面
        console.log('Edit task:', this.task.id);
    }
    
    // 处理删除
    handleDelete() {
        if (confirm('确定要删除这个任务吗?')) {
            if (this.onUpdate) {
                this.onUpdate(this.task.id, { deleted: true });
            }
        }
    }
    
    // 获取优先级样式类
    getPriorityClass(priority) {
        const classes = {
            high: 'priority-high',
            medium: 'priority-medium',
            low: 'priority-low'
        };
        return classes[priority] || '';
    }
    
    // 获取状态样式类
    getStatusClass(status) {
        const classes = {
            pending: 'status-pending',
            in_progress: 'status-in-progress',
            completed: 'status-completed'
        };
        return classes[status] || '';
    }
    
    // 获取优先级文本
    getPriorityText(priority) {
        const texts = {
            high: '高',
            medium: '中',
            low: '低'
        };
        return texts[priority] || '';
    }
    
    // 检查是否过期
    isOverdue(dueDate, status) {
        if (!dueDate || status === 'completed') {
            return false;
        }
        return new Date(dueDate) < new Date();
    }
    
    // 更新任务数据
     updateTask(newTask) {
         this.task = newTask;
         this.render(this.container);
     }
 }

10.6.2 样式系统实现

/* styles/global.css */
:root {
    /* 颜色变量 */
    --primary-color: #007bff;
    --secondary-color: #6c757d;
    --success-color: #28a745;
    --warning-color: #ffc107;
    --danger-color: #dc3545;
    --info-color: #17a2b8;
    
    /* 背景颜色 */
    --bg-primary: #ffffff;
    --bg-secondary: #f8f9fa;
    --bg-dark: #343a40;
    
    /* 文本颜色 */
    --text-primary: #212529;
    --text-secondary: #6c757d;
    --text-muted: #868e96;
    
    /* 边框颜色 */
    --border-color: #dee2e6;
    --border-light: #e9ecef;
    
    /* 阴影 */
    --shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
    --shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    --shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
    
    /* 字体 */
    --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-size-base: 14px;
    --line-height-base: 1.5;
    
    /* 间距 */
    --spacing-xs: 0.25rem;
    --spacing-sm: 0.5rem;
    --spacing-md: 1rem;
    --spacing-lg: 1.5rem;
    --spacing-xl: 3rem;
    
    /* 圆角 */
    --border-radius: 0.375rem;
    --border-radius-sm: 0.25rem;
    --border-radius-lg: 0.5rem;
    
    /* 过渡 */
    --transition: all 0.15s ease-in-out;
}

/* 基础样式重置 */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: var(--font-family);
    font-size: var(--font-size-base);
    line-height: var(--line-height-base);
    color: var(--text-primary);
    background-color: var(--bg-secondary);
}

/* 应用布局 */
.app {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.app-header {
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-color);
    padding: var(--spacing-md);
    box-shadow: var(--shadow-sm);
}

.app-nav {
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-color);
    padding: var(--spacing-sm) var(--spacing-md);
}

.app-main {
    flex: 1;
    padding: var(--spacing-lg);
    overflow-y: auto;
}

.app-footer {
    background: var(--bg-primary);
    border-top: 1px solid var(--border-color);
    padding: var(--spacing-sm) var(--spacing-md);
    font-size: 0.875rem;
    color: var(--text-secondary);
}

/* 按钮样式 */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--spacing-sm) var(--spacing-md);
    font-size: var(--font-size-base);
    font-weight: 500;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition);
    gap: var(--spacing-xs);
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.btn-primary {
    color: white;
    background-color: var(--primary-color);
    border-color: var(--primary-color);
}

.btn-primary:hover:not(:disabled) {
    background-color: #0056b3;
    border-color: #0056b3;
}

.btn-secondary {
    color: var(--text-primary);
    background-color: var(--bg-secondary);
    border-color: var(--border-color);
}

.btn-secondary:hover:not(:disabled) {
    background-color: #e2e6ea;
    border-color: #dae0e5;
}

.btn-icon {
    padding: var(--spacing-xs);
    background: none;
    border: none;
    cursor: pointer;
    border-radius: var(--border-radius-sm);
    transition: var(--transition);
}

.btn-icon:hover {
    background-color: var(--bg-secondary);
}

/* 表单样式 */
.form-group {
    margin-bottom: var(--spacing-md);
}

.form-group label {
    display: block;
    margin-bottom: var(--spacing-xs);
    font-weight: 500;
    color: var(--text-primary);
}

.form-group input,
.form-group textarea,
.form-group select {
    width: 100%;
    padding: var(--spacing-sm);
    font-size: var(--font-size-base);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    transition: var(--transition);
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}

.form-group input.error,
.form-group textarea.error,
.form-group select.error {
    border-color: var(--danger-color);
}

.error-message {
    color: var(--danger-color);
    font-size: 0.875rem;
    margin-top: var(--spacing-xs);
    display: none;
}

/* 任务卡片样式 */
.task-card {
    background: var(--bg-primary);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    padding: var(--spacing-md);
    margin-bottom: var(--spacing-md);
    box-shadow: var(--shadow-sm);
    transition: var(--transition);
}

.task-card:hover {
    box-shadow: var(--shadow);
    transform: translateY(-1px);
}

.task-card.compact {
    padding: var(--spacing-sm);
    margin-bottom: var(--spacing-sm);
}

.task-header {
    display: flex;
    align-items: flex-start;
    gap: var(--spacing-sm);
    margin-bottom: var(--spacing-sm);
}

.task-checkbox input {
    width: 18px;
    height: 18px;
    margin-top: 2px;
}

.task-title {
    flex: 1;
}

.task-title h3 {
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: var(--spacing-xs);
    cursor: pointer;
    color: var(--text-primary);
}

.task-title h3:hover {
    color: var(--primary-color);
}

.task-description {
    color: var(--text-secondary);
    font-size: 0.875rem;
    line-height: 1.4;
}

.task-actions {
    display: flex;
    gap: var(--spacing-xs);
}

.task-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: var(--spacing-sm);
    padding-top: var(--spacing-sm);
    border-top: 1px solid var(--border-light);
}

.task-meta {
    display: flex;
    gap: var(--spacing-sm);
    align-items: center;
}

.priority-badge {
    padding: 2px 6px;
    border-radius: var(--border-radius-sm);
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
}

.priority-high {
    background-color: #fee;
    color: var(--danger-color);
    border-left: 3px solid var(--danger-color);
}

.priority-medium {
    background-color: #fff3cd;
    color: var(--warning-color);
    border-left: 3px solid var(--warning-color);
}

.priority-low {
    background-color: #d1ecf1;
    color: var(--info-color);
    border-left: 3px solid var(--info-color);
}

.status-completed {
    opacity: 0.7;
}

.status-completed .task-title h3 {
    text-decoration: line-through;
}

.due-date {
    font-size: 0.875rem;
    color: var(--text-secondary);
}

.due-date.overdue {
    color: var(--danger-color);
    font-weight: 600;
}

/* 仪表板样式 */
.dashboard {
    max-width: 1200px;
    margin: 0 auto;
}

.dashboard-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--spacing-xl);
}

.dashboard-header h1 {
    font-size: 2rem;
    font-weight: 700;
    color: var(--text-primary);
}

.dashboard-actions {
    display: flex;
    gap: var(--spacing-sm);
}

.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: var(--spacing-lg);
    margin-bottom: var(--spacing-xl);
}

.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--spacing-lg);
    margin-bottom: var(--spacing-xl);
}

/* 响应式设计 */
@media (max-width: 768px) {
    .app-main {
        padding: var(--spacing-md);
    }
    
    .dashboard-header {
        flex-direction: column;
        gap: var(--spacing-md);
        align-items: stretch;
    }
    
    .dashboard-actions {
        justify-content: center;
    }
    
    .stats-grid {
        grid-template-columns: 1fr;
    }
    
    .projects-grid {
        grid-template-columns: 1fr;
    }
    
    .task-header {
        flex-direction: column;
        gap: var(--spacing-xs);
    }
    
    .task-actions {
        align-self: flex-end;
    }
}

/* 深色主题 */
@media (prefers-color-scheme: dark) {
    :root {
        --bg-primary: #1a1a1a;
        --bg-secondary: #2d2d2d;
        --bg-dark: #000000;
        
        --text-primary: #ffffff;
        --text-secondary: #b3b3b3;
        --text-muted: #666666;
        
        --border-color: #404040;
        --border-light: #333333;
    }
}

10.6.3 构建和部署配置

// build/build.js
import { build } from 'esbuild';
import { copy } from 'esbuild-plugin-copy';
import { clean } from 'esbuild-plugin-clean';
import fs from 'fs';
import path from 'path';

const isDev = process.argv.includes('--dev');
const isWatch = process.argv.includes('--watch');

// 构建配置
const buildConfig = {
    entryPoints: ['src/main.js'],
    bundle: true,
    outfile: 'dist/main.js',
    format: 'esm',
    target: 'es2020',
    sourcemap: isDev,
    minify: !isDev,
    define: {
        'process.env.NODE_ENV': isDev ? '"development"' : '"production"'
    },
    plugins: [
        clean({
            patterns: ['dist/*']
        }),
        copy({
            resolveFrom: 'cwd',
            assets: [
                {
                    from: ['src/assets/**/*'],
                    to: ['dist/assets']
                },
                {
                    from: ['src/styles/**/*'],
                    to: ['dist/styles']
                },
                {
                    from: ['main.html'],
                    to: ['dist/index.html']
                }
            ]
        })
    ]
};

// 开发服务器配置
const devServerConfig = {
    ...buildConfig,
    outdir: 'dist',
    write: false,
    plugins: [
        ...buildConfig.plugins,
        {
            name: 'dev-server',
            setup(build) {
                build.onEnd(result => {
                    if (result.errors.length === 0) {
                        console.log('✅ Build completed successfully');
                    } else {
                        console.error('❌ Build failed:', result.errors);
                    }
                });
            }
        }
    ]
};

// 执行构建
async function runBuild() {
    try {
        console.log(`🚀 Starting ${isDev ? 'development' : 'production'} build...`);
        
        if (isWatch) {
            const ctx = await build({
                ...devServerConfig,
                watch: {
                    onRebuild(error, result) {
                        if (error) {
                            console.error('❌ Rebuild failed:', error);
                        } else {
                            console.log('✅ Rebuild completed');
                        }
                    }
                }
            });
            
            console.log('👀 Watching for changes...');
            
            // 优雅关闭
            process.on('SIGINT', () => {
                console.log('\n🛑 Stopping build watcher...');
                ctx.dispose();
                process.exit(0);
            });
            
        } else {
            await build(buildConfig);
            console.log('✅ Build completed successfully!');
            
            // 生成构建报告
            generateBuildReport();
        }
        
    } catch (error) {
        console.error('❌ Build failed:', error);
        process.exit(1);
    }
}

// 生成构建报告
function generateBuildReport() {
    const distPath = path.resolve('dist');
    const files = getAllFiles(distPath);
    
    const report = {
        buildTime: new Date().toISOString(),
        files: files.map(file => {
            const stats = fs.statSync(file);
            return {
                path: path.relative(distPath, file),
                size: stats.size,
                sizeFormatted: formatBytes(stats.size)
            };
        }),
        totalSize: files.reduce((total, file) => {
            return total + fs.statSync(file).size;
        }, 0)
    };
    
    report.totalSizeFormatted = formatBytes(report.totalSize);
    
    fs.writeFileSync(
        path.join(distPath, 'build-report.json'),
        JSON.stringify(report, null, 2)
    );
    
    console.log('📊 Build Report:');
    console.log(`   Total size: ${report.totalSizeFormatted}`);
    console.log(`   Files: ${report.files.length}`);
}

// 获取所有文件
function getAllFiles(dir) {
    const files = [];
    
    function traverse(currentDir) {
        const items = fs.readdirSync(currentDir);
        
        for (const item of items) {
            const fullPath = path.join(currentDir, item);
            const stats = fs.statSync(fullPath);
            
            if (stats.isDirectory()) {
                traverse(fullPath);
            } else {
                files.push(fullPath);
            }
        }
    }
    
    traverse(dir);
    return files;
}

// 格式化字节大小
function formatBytes(bytes) {
    if (bytes === 0) return '0 Bytes';
    
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

// 启动构建
runBuild();
// package.json
{
  "name": "task-manager",
  "version": "1.0.0",
  "description": "基于 Sciter-JS 的任务管理应用",
  "main": "src/main.js",
  "type": "module",
  "scripts": {
    "dev": "node build/build.js --dev --watch",
    "build": "node build/build.js",
    "build:dev": "node build/build.js --dev",
    "preview": "sciter dist/index.html",
    "test": "node tests/run-tests.js",
    "lint": "eslint src/**/*.js",
    "format": "prettier --write src/**/*.{js,css,html}"
  },
  "dependencies": {
    "sciter-js": "^5.0.0"
  },
  "devDependencies": {
    "esbuild": "^0.19.0",
    "esbuild-plugin-copy": "^2.1.1",
    "esbuild-plugin-clean": "^1.0.1",
    "eslint": "^8.50.0",
    "prettier": "^3.0.0"
  },
  "keywords": [
    "sciter-js",
    "task-management",
    "desktop-app",
    "javascript"
  ],
  "author": "Your Name",
  "license": "MIT"
}

10.7 本章小结

10.7.1 核心概念回顾

通过本章的实战项目,我们学习了:

  1. 项目架构设计

    • 模块化的目录结构
    • 分层的架构设计
    • 组件化的开发模式
  2. 状态管理实践

    • 集中式状态管理
    • 模块化的状态设计
    • 状态持久化策略
  3. 服务层设计

    • 认证服务的完整实现
    • WebSocket 实时通信
    • 错误处理和重连机制
  4. 组件开发

    • 可复用组件设计
    • 事件处理机制
    • 生命周期管理

10.7.2 技术要点总结

  1. 应用架构

    graph TB
       A[应用入口] --> B[路由管理]
       B --> C[页面组件]
       C --> D[业务组件]
       D --> E[基础组件]
    
    
       F[状态管理] --> G[认证状态]
       F --> H[业务状态]
       F --> I[应用状态]
    
    
       J[服务层] --> K[API 服务]
       J --> L[WebSocket 服务]
       J --> M[存储服务]
    
    1. 开发流程
    • 需求分析和架构设计
    • 组件拆分和接口定义
    • 核心功能实现
    • 测试和优化
    • 构建和部署
    1. 最佳实践
    • 模块化开发

    • 组件复用

    • 状态管理

    • 错误处理

    • 性能优化

      10.7.3 扩展建议

    1. 功能扩展
    • 添加文件上传功能
    • 实现消息通知系统
    • 集成第三方服务
    • 添加数据可视化
    1. 技术优化
    • 实现虚拟滚动
    • 添加离线支持
    • 优化包大小
    • 提升加载性能
    1. 用户体验
    • 添加动画效果

    • 实现主题切换

    • 支持键盘快捷键

    • 优化移动端体验

      10.7.4 学习建议

    1. 深入理解
    • 仔细研究项目架构
    • 理解各模块的职责
    • 掌握状态管理模式
    1. 动手实践
    • 完整实现项目功能
    • 尝试添加新功能
    • 优化现有代码
    1. 持续改进
    • 收集用户反馈
    • 监控应用性能
    • 定期重构代码 — 恭喜! 🎉 您已经完成了 Sciter-JS 教程的全部内容。通过这个完整的实战项目,您应该已经掌握了使用 Sciter-JS 开发桌面应用的核心技能。 下一步建议:
    • 继续完善这个任务管理应用
    • 尝试开发其他类型的应用
    • 深入学习 Sciter-JS 的高级特性
    • 参与开源项目或分享您的经验 祝您在 Sciter-JS 开发之路上越走越远! 🚀