WebDesktop Interface: Web版桌面管理(仿IOS操作模式)

本项目实现了一套Web桌面功能,整个项目采用B+C的架构,Shell部分采用了Awesomium,用户操作接口采用HTML5 SPA技术开发,涵盖了桌面的基本操作功能及文件夹管理、应用中心、复合应用、桌面组件等功能。

效果截图:

SHELL封装: Awesomium 演示程序:NoPublic


技术架构:Backbone + Sea.js 的模块化 SPA

在项目开发中,选择 Backbone.js 作为 MVC 框架、Sea.js 作为模块加载器,是一个兼顾开发效率和代码组织的方案。 具体使用的技术有Backbone,Seajs,PubSubJS,Webkit等。

架构概览

┌─────────────────────────────────────────┐
│         Web Desktop Application          │
├─────────────────────────────────────────┤
│  UI Layer (Backbone Views)              │
│  ├── DesktopContainerView               │
│  ├── DesktopLayersView                  │
│  ├── DesktopElementView                 │
│  └── Application/Folder/Widget Views    │
├─────────────────────────────────────────┤
│  Data Layer (Backbone Models)           │
│  ├── DesktopElement (Base)              │
│  ├── DesktopApplication                 │
│  ├── DesktopFolder                      │
│  └── DesktopWidget                      │
├─────────────────────────────────────────┤
│  Event System (PubSubJS)                │
│  ├── Scoped Events [layerId.eventType]  │
│  └── Global Events                      │
├─────────────────────────────────────────┤
│  Module Loader (Sea.js CMD)             │
│  ├── Synchronous require()              │
│  ├── Async seajs.use()                  │
│  └── Template Modules                   │
├─────────────────────────────────────────┤
│  Native Bridge (MVP Platform)           │
│  └── Bidirectional Message Passing      │
└─────────────────────────────────────────┘

Sea.js 模块系统

CMD(Common Module Definition)模块定义

define(function(require, exports, module) {
    // 同步依赖
    var $ = require('jquery');
    var Backbone = require('backbone');
    var application_tpl = require('application_tpl');

    // 定义模块内容
    var DesktopApplicationView = Backbone.View.extend({
        template: _.template(application_tpl),
        // ...
    });

    // 导出模块
    module.exports = DesktopApplicationView;
});

模块配置 (app/main.js):

seajs.config({
    base: './base/',
    alias: {
        'jquery': 'jquery-module.js',
        'backbone': 'backbone-module.js',
        'underscore': 'underscore-module.js',
        'pubsub': 'pubsub-js-module.js'
    },
    map: [
        [/.js$/, '.js?v=' + version]  // 版本号防缓存
    ]
});

// 入口模块
seajs.use(['app/init'], function(init) {
    init.bootstrap();
});

优势

  • 按需加载:只加载当前页面需要的模块
  • 依赖管理:自动处理模块依赖关系
  • 版本控制:通过 map 配置实现缓存更新
  • 开发友好:支持调试模式,便于开发定位

Backbone.js MVC 架构

Model 层:桌面元素的数据模型

// 基础元素模型
var DesktopElement = Backbone.Model.extend({
    defaults: {
        id: '',
        appType: 0,        // 0:应用, 1:组件, 5:文件夹, 6:复合应用
        name: '',
        icon: '',
        position: { top: 0, left: 0 },
        size: { width: 100, height: 100 },
        zIndex: 1,
        isLock: false,
        cellInfo: { row: -1, column: -1 }
    }
});

// 应用模型继承
var DesktopApplication = DesktopElement.extend({
    defaults: _.extend({}, DesktopElement.prototype.defaults, {
        appId: '',
        openType: 0,       // 0:BS, 1:CS, 2:单机, 3:内部, 99:IE
        systemId: '',
        appCommand: ''
    });
});

// Collection 管理
var DesktopElements = Backbone.Collection.extend({
    model: DesktopElement,
    comparator: function(item) {
        return item.get('index');
    }
});

View 层:桌面元素的视图渲染

// 基础视图
var DesktopElementView = Backbone.View.extend({
    className: 'desktop-element',

    events: {
        'dblclick': 'onDoubleClick',
        'contextmenu': 'onContextMenu',
        'dragstart': 'onDragStart'
    },

    render: function() {
        var html = this.template(this.model.toJSON());
        this.$el.html(html);
        this.$el.css(this.model.get('position'));
        return this;
    },

    onDoubleClick: function() {
        PubSub.publishSync(config.OpenAppEvent, this.model);
    }
});

// 应用视图继承
var DesktopApplicationView = DesktopElementView.extend({
    template: _.template(application_tpl),

    render: function() {
        DesktopElementView.prototype.render.call(this);
        this.$el.find('.icon').css({
            'background-image': 'url(' + this.model.get('icon') + ')'
        });
        return this;
    }
});

虚拟桌面系统

var DesktopLayersView = Backbone.View.extend({
    el: '#desktop-layers',

    initialize: function() {
        this.currentLayer = 0;
        this.layers = [];
        this.initLayers(4);  // 4个虚拟桌面
        this.bindEvents();
    },

    initLayers: function(count) {
        for (var i = 0; i < count; i++) {
            this.layers.push(new DesktopLayer({
                id: 'layer-' + i,
                index: i
            }));
        }
    },

    switchToLayer: function(layerIndex) {
        var direction = layerIndex > this.currentLayer ? 'left' : 'right';
        this.$el.find('.layer')
            .removeClass('active')
            .eq(layerIndex)
            .addClass('active')
            .addClass('slide-' + direction);

        this.currentLayer = layerIndex;
        PubSub.publishSync(config.LayerChangedEvent, layerIndex);
    }
});

事件驱动架构 (PubSub)

分层事件系统

// 作用域事件:格式为 [layerId, eventType].join(".")
PubSub.publishSync(
    [layerId, config.CreateDesktopElementEvent].join("."),
    appElement
);

// 示例:订阅指定图层的事件
PubSub.subscribe(
    ['layer-0', config.CreateDesktopElementEvent].join("."),
    function(msg, element) {
        // 处理图层 0 的元素创建
        addElementToLayer(element);
    }
);

// 全局事件
PubSub.subscribe(config.DesktopIsChangedEvent, function() {
    // 桌面变更时触发自动保存(防抖 500ms)
    debounce(saveDesktopConfig, 500)();
});

常用事件定义

var config = {
    // 桌面元素事件
    CreateDesktopElementEvent: 'CreateDesktopElement',
    RemoveDesktopElementEvent: 'RemoveDesktopElement',
    MoveDesktopElementEvent: 'MoveDesktopElement',

    // 系统事件
    DesktopIsChangedEvent: 'DesktopIsChanged',
    DesktopConfigSaveEvent: 'DesktopConfigSave',
    LayerChangedEvent: 'LayerChanged',

    // UI 事件
    ShowWarnMessageEvent: 'ShowWarnMessage',
    HideDesktopPopThings: 'HideDesktopPopThings',
    OpenAppStoreEvent: 'OpenAppStore'
};

MVP 平台桥接(原生集成)

双向通信机制

// Web 调用原生方法
var MvpService = {
    openApp: function(appData) {
        PubSub.publishSync(config.CallNativeMethod, {
            method: 'OpenApp',
            params: [appData]
        });
    },

    selectFile: function(callback) {
        PubSub.subscribe(config.FileSelectedEvent, function(msg, filePath) {
            callback(filePath);
        });
        PubSub.publishSync(config.CallNativeMethod, {
            method: 'ShowFileDialog',
            params: []
        });
    }
};

// 接收原生回调
PubSub.subscribe(config.NativeCallbackEvent, function(msg, data) {
    switch(data.method) {
        case 'OpenApp':
            handleAppOpenResult(data.result);
            break;
        case 'GetConfig':
            loadDesktopConfig(data.result);
            break;
    }
});

模块化模板

模板作为模块导出

// static/templates/application.js
define(function() {
    return `
<div class="desktop-element application" id="<%= id %>">
    <div class="icon" style="background-image: url(<%= icon %>)"></div>
    <div class="name"><%= name %></div>
</div>`;
});

在 View 中使用

define(function(require, exports, module) {
    var Backbone = require('backbone');
    var application_tpl = require('application_tpl');

    var DesktopApplicationView = Backbone.View.extend({
        template: _.template(application_tpl),
        // ...
    });

    module.exports = DesktopApplicationView;
});

项目结构

WebDesktop/
├── index.htm                    # 入口页面
├── app/
│   ├── main.js                  # Sea.js 配置
│   ├── init.js                  # 应用初始化
│   ├── config.js                # 配置常量
│   ├── util.js                  # 工具函数
│   ├── service/                 # 业务服务层
│   │   ├── desktopService.js
│   │   ├── appStoreService.js
│   │   └── mvpService.js
│   └── desktop/                 # 桌面组件
│       ├── desktopContainer.js  # 桌面容器
│       ├── desktopLayer.js      # 虚拟桌面
│       ├── desktopElBase.js     # 基础元素
│       ├── desktopApplication.js
│       ├── desktopFolder.js
│       └── desktopWidget.js
├── static/
│   ├── templates/               # Underscore 模板
│   └── style/                   # 样式文件
└── base/                        # 第三方库
    ├── jquery.js
    ├── backbone.js
    └── sea.js

技术要点总结

特性 实现方案
模块化 Sea.js CMD 规范
MVC 架构 Backbone.js Models/Collections/Views
事件通信 PubSubJS 事件总线
模板引擎 Underscore.js Templates
拖拽交互 jQuery UI Draggable/Droppable
原生桥接 PubSub 双向通信
状态持久化 JSON 序列化 + 防抖保存
虚拟桌面 多图层容器 + CSS 动画

这套架构在 2014 年是一个相对成熟的 SPA 解决方案,通过 Backbone.js 的 MVC 模式和 Sea.js 的模块化能力,实现了代码的可维护性和可扩展性,同时也很好地支持了与原生客户端的集成。

深入理解 Backbone.js 的架构设计理念,有助于更好地掌握前端工程化的核心思想,下面结合项目实践详细介绍其架构体系。


Backbone.js 架构体系详解

整体架构

flowchart TB
    subgraph View["View 层 - 视图渲染"]
        V1[DesktopContainerView]
        V2[DesktopLayersView]
        V3[DesktopElementView]
        V4[ApplicationView]
    end
    
    subgraph Model["Model 层 - 数据模型"]
        M1[DesktopElement]
        M2[DesktopApplication]
        M3[DesktopFolder]
        M4[DesktopWidget]
        M5[DesktopElements Collection]
    end
    
    subgraph Controller["Controller - 路由控制"]
        C1[Backbone.Router]
        C2[DesktopRouter]
    end
    
    subgraph Event["Event 事件系统"]
        E1[Backbone.Events]
        E2[PubSubJS]
    end
    
    subgraph Storage["数据持久化"]
        S1[localStorage]
        S2[Server API]
    end
    
    V1 -->|监听| E1
    V2 -->|监听| E1
    V3 -->|绑定| M1
    M1 -->|触发| E1
    M1 -->|同步| S1
    C1 -->|导航| V1
    E1 <-->|全局事件| E2

MVC 数据流向

sequenceDiagram
    participant U as User
    participant V as View
    participant M as Model
    participant C as Collection
    participant S as Server
    
    U->>V: 用户交互 (点击/拖拽)
    V->>V: 事件处理
    V->>M: 修改数据 Model.set()
    M->>C: Collection 更新
    C->>M: 触发 change 事件
    M->>V: 触发 change:attr 事件
    V->>V: 重新渲染视图
    M->>S: 同步到服务器
    S-->>M: 返回确认

事件驱动架构

flowchart LR
    subgraph 事件类型
        Global[全局事件]
        Scoped[作用域事件]
        Model[Model 事件]
        View[View 事件]
    end
    
    subgraph PubSubJS 实现
        PS[PubSub]
    end
    
    Global --> PS
    Scoped --> PS
    Model --> PS
    View --> PS
    
    PS -->|layer-0.CreateElement| L0[图层0处理器]
    PS -->|layer-1.CreateElement| L1[图层1处理器]
    PS -->|DesktopChanged| Save[自动保存]

模块依赖关系

graph TD
    subgraph main["入口 main.js"]
        M[seajs.use]
    end
    
    subgraph init["初始化模块"]
        M --> I[app/init.js]
    end
    
    subgraph config["配置模块"]
        I --> C[app/config.js]
    end
    
    subgraph core["核心模块"]
        I --> Backbone[jquery & backbone]
        I --> PubSub[pubsub-js]
    end
    
    subgraph desktop["桌面模块"]
        I --> DV[desktopContainer.js]
        DV --> DL[desktopLayer.js]
        DV --> DE[desktopElBase.js]
        DE --> DA[desktopApplication.js]
        DE --> DF[desktopFolder.js]
        DE --> DW[desktopWidget.js]
    end
    
    subgraph templates["模板模块"]
        T1[application_tpl]
        T2[folder_tpl]
        T3[widget_tpl]
        T1 --> DA
        T2 --> DF
        T3 --> DW
    end
    
    subgraph service["服务模块"]
        I --> S1[desktopService.js]
        I --> S2[appStoreService.js]
        I --> S3[mvpService.js]
    end

Model 与 Collection 的关系

classDiagram
    class DesktopElement {
        +string id
        +number appType
        +string name
        +string icon
        +object position
        +object size
        +number zIndex
        +get()
        +set()
        +toJSON()
    }
    
    class DesktopApplication {
        +string appId
        +number openType
        +string systemId
    }
    
    class DesktopFolder {
        +string parentId
        +array children
    }
    
    class DesktopWidget {
        +string widgetType
        +object config
    }
    
    class DesktopElements {
        +model DesktopElement
        +add()
        +remove()
        +getById()
        +sort()
    }
    
    DesktopElement <|-- DesktopApplication
    DesktopElement <|-- DesktopFolder
    DesktopElement <|-- DesktopWidget
    DesktopElements --> DesktopElement : contains

View 继承体系

classDiagram
    class Backbone_View {
        +el
        +$el
        +template
        +render()
        +remove()
        +setElement()
    }
    
    class DesktopElementView {
        +className
        +events
        +onDoubleClick()
        +onContextMenu()
        +onDragStart()
    }
    
    class DesktopApplicationView {
        +template
        +render()
    }
    
    class DesktopFolderView {
        +openFolder()
        +closeFolder()
    }
    
    class DesktopWidgetView {
        +refresh()
        +getConfig()
    }
    
    Backbone_View <|-- DesktopElementView
    DesktopElementView <|-- DesktopApplicationView
    DesktopElementView <|-- DesktopFolderView
    DesktopElementView <|-- DesktopWidgetView

状态管理流程

stateDiagram-v2
    [*] --> Initializing: 应用启动
    Initializing --> LoadingConfig: 加载配置
    LoadingConfig --> LoadingElements: 加载桌面元素
    LoadingElements --> Rendering: 渲染视图
    Rendering --> Ready: 桌面就绪
    
    Ready --> Dragging: 拖拽元素
    Dragging --> Ready: 拖拽结束
    
    Ready --> OpeningApp: 打开应用
    OpeningApp --> RunningApp: 应用运行中
    RunningApp --> ClosingApp: 关闭应用
    ClosingApp --> Ready
    
    Ready --> Changed: 元素变更
    Changed --> Saving: 防抖保存
    Saving --> Ready: 保存完成
    
    Ready --> Switching: 切换图层
    Switching --> Ready: 切换完成

数据同步机制

flowchart TB
    subgraph 本地操作
        U[用户操作]
        V[View]
        M[Model]
    end
    
    subgraph 缓存层
        L[localStorage]
    end
    
    subgraph 服务器
        API[Server API]
        DB[(Database)]
    end
    
    U --> V
    V --> M
    M --> L
    M --> API
    API --> DB
    
    DB ---> API
    API --->|响应| M
    L -.->|恢复| M

Backbone.js 的这种架构设计,使得应用在保持轻量的同时,具备了良好的代码组织能力和扩展性。通过 Model-View-Presenter 的变体配合 PubSubJS,实现了复杂的前端交互逻辑。