Skip to content

项目架构 ​

本文档介绍 UEditor Plus Designer 的整体架构设计。

架构概览 ​

┌─────────────────────────────────────────┐
│           User Interface                │
│  (Vue Components / React Wrapper)      │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│         Composables Layer               │
│  (useMaterial, useSection, etc.)       │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│           Core Layer                    │
│        (DesignerCore class)            │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│         UEditor Integration             │
│      (UEditor Plus Editor)             │
└─────────────────────────────────────────┘

分层设计 ​

1. 核心层 (Core Layer) ​

文件: src/core/DesignerCore.ts

框架无关的业务逻辑层,封装编辑器核心功能。

职责:

  • UEditor 生命周期管理
  • Section 管理和操作
  • 事件系统
  • 编辑器配置

关键类:

typescript
class DesignerCore {
  private editor: any
  private eventBus: EventEmitter
  
  async init(): Promise<void>
  getEditor(): any
  insertSection(html: string): void
  on(event: string, callback: Function): void
  off(event: string, callback?: Function): void
  destroy(): void
}

2. 组合式函数层 (Composables Layer) ​

目录: src/composables/

Vue 3 特定的状态管理和逻辑复用。

模块:

useMaterial ​

文件: src/composables/useMaterial.ts

素材加载和管理。

typescript
export function useMaterial(config: DesignerConfig) {
  // 数据
  const materials = ref<MaterialItem[]>([])
  const categories = ref<MaterialCategory[]>([])
  const loading = ref(false)
  
  // 方法
  const loadCategories = async () => {}
  const loadMaterials = async (params) => {}
  
  return {
    materials,
    categories,
    loading,
    loadCategories,
    loadMaterials
  }
}

useSection ​

文件: src/composables/useSection.ts

Section 状态管理。

typescript
export function useSection(designerCore: DesignerCore) {
  // 数据
  const currentSection = ref<HTMLElement | null>(null)
  const sectionData = ref<SectionData | null>(null)
  
  // 方法
  const selectSection = (element: HTMLElement) => {}
  const updateSection = (data: Partial<SectionData>) => {}
  
  return {
    currentSection,
    sectionData,
    selectSection,
    updateSection
  }
}

useSectionActions ​

文件: src/composables/useSectionActions.ts

Section 操作逻辑。

typescript
export function useSectionActions(
  currentSection: Ref<HTMLElement | null>,
  designerCore: DesignerCore
) {
  const moveUp = () => {}
  const moveDown = () => {}
  const duplicate = () => {}
  const remove = () => {}
  
  return {
    moveUp,
    moveDown,
    duplicate,
    remove
  }
}

3. 组件层 (Components Layer) ​

目录: src/components/

Vue UI 组件实现。

Designer.vue ​

主编辑器组件,整合所有功能。

vue
<template>
  <div class="designer-container">
    <MaterialPanel />
    <div class="editor-wrapper">
      <div ref="editorContainer"></div>
      <SectionTools v-if="currentSection" />
    </div>
  </div>
</template>

MaterialPanel.vue ​

素材面板,显示分类和素材列表。

vue
<template>
  <div class="material-panel">
    <div class="categories">...</div>
    <div class="materials">...</div>
  </div>
</template>

SectionTools.vue ​

Section 工具栏,提供编辑操作。

vue
<template>
  <div class="section-tools">
    <button @click="moveUp">上移</button>
    <button @click="moveDown">下移</button>
    <!-- ... -->
  </div>
</template>

4. 工具层 (Utils Layer) ​

目录: src/utils/

纯函数和工具模块。

config.ts ​

配置常量和默认值。

typescript
export const Config = {
  DefaultCategoryLoader: () => Promise.resolve([]),
  DefaultStyleLoader: () => Promise.resolve({ records: [], total: 0 }),
  SectionClass: 'pb-section',
  SectionActiveClass: 'pb-section-active'
}

ueditor-loader.ts ​

UEditor 动态加载。

typescript
export async function loadUEditor(path: string): Promise<void> {
  // 加载 UEditor 脚本
}

style-processor.ts ​

样式处理工具。

typescript
export class StyleProcessor {
  static setWidth(element: HTMLElement, width: number): void {}
  static setRotate(element: HTMLElement, rotate: number): void {}
  static setOpacity(element: HTMLElement, opacity: number): void {}
}

数据流 ​

素材加载流程 ​

用户打开素材面板
  ↓
MaterialPanel 组件
  ↓
useMaterial.loadMaterials()
  ↓
调用 config.styleLoader
  ↓
API 请求
  ↓
更新 materials ref
  ↓
组件自动更新

Section 操作流程 ​

用户点击 Section
  ↓
Designer 组件捕获事件
  ↓
useSection.selectSection()
  ↓
更新 currentSection ref
  ↓
SectionTools 组件显示
  ↓
用户操作 Section
  ↓
useSectionActions.xxx()
  ↓
DesignerCore 执行操作
  ↓
触发 change 事件

关键设计模式 ​

1. 单一职责原则 ​

每个模块只负责一个功能:

  • DesignerCore - 编辑器核心逻辑
  • useMaterial - 素材管理
  • useSection - Section 状态

2. 依赖注入 ​

通过 props 和参数传递依赖:

typescript
export function useSection(designerCore: DesignerCore) {
  // designerCore 作为依赖注入
}

3. 发布-订阅模式 ​

事件系统实现组件通信:

typescript
designerCore.on('change', (content) => {
  // 监听变化
})

designerCore.emit('change', content)

4. 策略模式 ​

可替换的加载器:

typescript
interface DesignerConfig {
  categoryLoader?: () => Promise<MaterialCategory[]>
  styleLoader?: (params) => Promise<StyleListData>
}

扩展点 ​

1. 自定义加载器 ​

实现自定义素材加载逻辑:

typescript
const config = {
  categoryLoader: async () => {
    // 自定义实现
  },
  styleLoader: async (params) => {
    // 自定义实现
  }
}

2. 自定义组件 ​

替换默认组件:

vue
<template>
  <Designer>
    <template #material-panel>
      <CustomMaterialPanel />
    </template>
  </Designer>
</template>

3. 样式覆盖 ​

通过 CSS 变量或类名覆盖:

css
.designer-container {
  --primary-color: #42b983;
}

性能优化 ​

1. 懒加载 ​

按需加载 UEditor 和素材:

typescript
// 只在需要时加载
const loadUEditor = async () => {
  if (!window.UE) {
    await loadUEditorScript()
  }
}

2. 虚拟滚动 ​

素材列表使用虚拟滚动(计划中):

vue
<virtual-list :items="materials" :item-height="100">
  <template #default="{ item }">
    <MaterialItem :material="item" />
  </template>
</virtual-list>

3. 防抖节流 ​

对频繁操作进行防抖:

typescript
const debouncedSave = debounce((content) => {
  saveContent(content)
}, 1000)

测试策略 ​

单元测试 ​

测试工具函数和 composables:

typescript
describe('StyleProcessor', () => {
  it('should set width correctly', () => {
    const element = document.createElement('div')
    StyleProcessor.setWidth(element, 50)
    expect(element.style.width).toBe('50%')
  })
})

组件测试 ​

测试 Vue 组件:

typescript
describe('MaterialPanel', () => {
  it('should render materials', async () => {
    const wrapper = mount(MaterialPanel, {
      props: { config }
    })
    await nextTick()
    expect(wrapper.findAll('.material-item')).toHaveLength(10)
  })
})

E2E 测试 ​

测试完整用户流程(计划中)。

未来规划 ​

  • [ ] 插件系统
  • [ ] 更多内置素材模板
  • [ ] 实时协作编辑
  • [ ] 版本历史和撤销/重做
  • [ ] 移动端优化

相关文档 ​

Released under the Apache License 2.0. 压缩版免费使用,商用或源码需授权。