Browse Source

feat(background-plugin): 增强背景插件, 支持自定义背景、Logo显示和新拟态效果 (#404)

* feat(background-plugin): enhance background plugin with logo support and neumorphism effects

- Add comprehensive logo support (text and image)
- Implement neumorphism visual effects for modern UI
- Add customizable background colors and dot patterns
- Fix logo position jumping during canvas scrolling
- Add complete Chinese and English documentation
- Add visual examples and type definitions
- Update navigation metadata for documentation

Features:
- Logo positioning with 5 preset locations
- Neumorphism effects with configurable shadows
- Custom background colors and dot styling
- Smooth logo rendering during viewport changes
- Comprehensive documentation with examples

* fix(playground-react): fix background plugin type compatibility

- Fix TypeScript error when background option is boolean
- Ensure proper type handling for BackgroundLayerOptions

* fix(docs): remove incorrect number from sub-canvas plugin heading

---------

Co-authored-by: husky-dot <xiaozhi@172-0-8-36.lightspeed.rcsntx.sbcglobal.net>
小智 6 months ago
parent
commit
488013bb0b

+ 2 - 1
apps/docs/src/en/guide/advanced/free-layout/_meta.json

@@ -3,5 +3,6 @@
   "node",
   "line",
   "port",
-  "sub-canvas"
+  "sub-canvas",
+  "background"
 ]

+ 270 - 0
apps/docs/src/en/guide/advanced/free-layout/background.mdx

@@ -0,0 +1,270 @@
+# Background
+
+The background plugin is used to customize canvas background effects, supporting dot patterns, logo display, and neumorphism visual effects.
+
+## Background Configuration
+
+The background plugin is provided through `BackgroundPlugin`, configuration options include:
+
+### Basic Configuration
+
+<img loading="lazy" className="invert-img" src="/free-layout/background-color.png"/>
+
+```ts pure
+{
+  // Background color
+  backgroundColor: '#1a1a1a',
+
+  // Dot color
+  dotColor: '#ffffff',
+
+  // Dot size (pixels)
+  dotSize: 1,
+
+  // Grid spacing (pixels)
+  gridSize: 20,
+
+  // Dot opacity (0-1)
+  dotOpacity: 0.5,
+
+  // Dot fill color
+  dotFillColor: '#ffffff'
+}
+```
+
+### Logo Configuration
+
+Supports both text and image logo types:
+
+<img loading="lazy" className="invert-img" src="/free-layout/background-logo.png"/>
+
+```ts pure
+{
+  logo: {
+    // Logo text
+    text: 'FLOWGRAM.AI',
+
+    // Image URL (optional, higher priority than text)
+    imageUrl: 'https://example.com/logo.png',
+
+    // Position: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
+    position: 'center',
+
+    // Size
+    size: 200,
+
+    // Opacity (0-1)
+    opacity: 0.25,
+
+    // Color
+    color: '#ffffff',
+
+    // Font family
+    fontFamily: 'Arial, sans-serif',
+
+    // Font weight
+    fontWeight: 'bold',
+
+    // Custom offset
+    offset: { x: 0, y: 0 }
+  }
+}
+```
+
+### Neumorphism Effect
+
+Neumorphism is a modern visual design style that creates depth through dual soft shadows:
+
+<img loading="lazy" className="invert-img" src="/free-layout/background-neumorphism.png"/>
+
+```ts pure
+{
+  logo: {
+    neumorphism: {
+      // Enable neumorphism effect
+      enabled: true,
+
+      // Text color
+      textColor: '#E0E0E0',
+
+      // Light shadow color
+      lightShadowColor: 'rgba(255,255,255,0.9)',
+
+      // Dark shadow color
+      darkShadowColor: 'rgba(0,0,0,0.15)',
+
+      // Shadow offset distance
+      shadowOffset: 6,
+
+      // Shadow blur radius
+      shadowBlur: 12,
+
+      // Shadow intensity
+      intensity: 0.6,
+
+      // Raised effect (true=raised, false=inset)
+      raised: true
+    }
+  }
+}
+```
+
+## Usage Example
+
+```tsx pure
+// Use background property directly in editor configuration
+const editorProps = {
+  // Background configuration
+  background: {
+    // Dark theme background
+    backgroundColor: '#1a1a1a',
+    dotColor: '#ffffff',
+    dotSize: 1,
+    gridSize: 20,
+    dotOpacity: 0.3,
+
+    // Brand logo
+    logo: {
+      text: 'FLOWGRAM.AI',
+      position: 'center',
+      size: 200,
+      opacity: 0.25,
+      color: '#ffffff',
+      fontFamily: 'Arial, sans-serif',
+      fontWeight: 'bold',
+
+      // Neumorphism effect
+      neumorphism: {
+        enabled: true,
+        textColor: '#E0E0E0',
+        lightShadowColor: 'rgba(255,255,255,0.9)',
+        darkShadowColor: 'rgba(0,0,0,0.15)',
+        shadowOffset: 6,
+        shadowBlur: 12,
+        intensity: 0.6,
+        raised: true
+      }
+    }
+  }
+}
+```
+
+## Preset Styles
+
+### Classic Dark Theme
+
+```tsx pure
+const editorProps = {
+  background: {
+    backgroundColor: '#1a1a1a',
+    dotColor: '#ffffff',
+    dotSize: 1,
+    gridSize: 20,
+    dotOpacity: 0.3,
+    logo: {
+      text: 'Your Brand',
+      position: 'center',
+      size: 200,
+      opacity: 0.25,
+      color: '#ffffff',
+      neumorphism: {
+        enabled: true,
+        textColor: '#E0E0E0',
+        lightShadowColor: 'rgba(255,255,255,0.9)',
+        darkShadowColor: 'rgba(0,0,0,0.15)',
+        shadowOffset: 6,
+        shadowBlur: 12,
+        intensity: 0.6,
+        raised: true
+      }
+    }
+  }
+}
+```
+
+### Minimal White Theme
+
+```tsx pure
+const editorProps = {
+  background: {
+    backgroundColor: '#ffffff',
+    dotColor: '#000000',
+    dotSize: 1,
+    gridSize: 20,
+    dotOpacity: 0.1,
+    logo: {
+      text: 'Your Brand',
+      position: 'center',
+      size: 200,
+      opacity: 0.1,
+      color: '#000000'
+    }
+  }
+}
+```
+
+## Notes
+
+1. **Color Matching**: Ensure sufficient contrast between logo color and background color
+2. **Opacity Settings**: Logo opacity should not be too high to avoid affecting content readability
+3. **Neumorphism Effect**: Shadow parameters should be adjusted reasonably, overly strong effects may distract attention
+4. **Performance Considerations**: Complex shadow effects may impact rendering performance, consider simplifying on low-end devices
+
+## Type Definitions
+
+```ts
+interface BackgroundLayerOptions {
+  /** Grid spacing, default 20px */
+  gridSize?: number;
+  /** Dot size, default 1px */
+  dotSize?: number;
+  /** Dot color, default "#eceeef" */
+  dotColor?: string;
+  /** Dot opacity, default 0.5 */
+  dotOpacity?: number;
+  /** Background color, default transparent */
+  backgroundColor?: string;
+  /** Dot fill color, default same as stroke color */
+  dotFillColor?: string;
+  /** Logo configuration */
+  logo?: {
+    /** Logo text content */
+    text?: string;
+    /** Logo image URL */
+    imageUrl?: string;
+    /** Logo position, default 'center' */
+    position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
+    /** Logo size, default 'medium' */
+    size?: 'small' | 'medium' | 'large' | number;
+    /** Logo opacity, default 0.1 */
+    opacity?: number;
+    /** Logo color (text only), default "#cccccc" */
+    color?: string;
+    /** Logo font family (text only), default 'Arial, sans-serif' */
+    fontFamily?: string;
+    /** Logo font weight (text only), default 'normal' */
+    fontWeight?: 'normal' | 'bold' | 'lighter' | number;
+    /** Custom offset */
+    offset?: { x: number; y: number };
+    /** Neumorphism effect configuration */
+    neumorphism?: {
+      /** Enable neumorphism effect */
+      enabled: boolean;
+      /** Text color */
+      textColor?: string;
+      /** Light shadow color */
+      lightShadowColor?: string;
+      /** Dark shadow color */
+      darkShadowColor?: string;
+      /** Shadow offset distance */
+      shadowOffset?: number;
+      /** Shadow blur radius */
+      shadowBlur?: number;
+      /** Shadow intensity */
+      intensity?: number;
+      /** Raised effect (true=raised, false=inset) */
+      raised?: boolean;
+    };
+  };
+}
+```

BIN
apps/docs/src/public/free-layout/background-color.png


BIN
apps/docs/src/public/free-layout/background-logo.png


BIN
apps/docs/src/public/free-layout/background-neumorphism.png


+ 2 - 1
apps/docs/src/zh/guide/advanced/free-layout/_meta.json

@@ -3,5 +3,6 @@
   "node",
   "line",
   "port",
-  "sub-canvas"
+  "sub-canvas",
+  "background"
 ]

+ 270 - 0
apps/docs/src/zh/guide/advanced/free-layout/background.mdx

@@ -0,0 +1,270 @@
+# 背景
+
+背景插件用于自定义画布的背景效果,支持点阵背景、Logo显示和新拟态(Neumorphism)视觉效果。
+
+## 背景配置
+
+背景插件通过 `BackgroundPlugin` 提供,配置项包括:
+
+### 基础配置
+
+<img loading="lazy" className="invert-img" src="/free-layout/background-color.png"/>
+
+```ts pure
+{
+  // 背景颜色
+  backgroundColor: '#1a1a1a',
+
+  // 点的颜色
+  dotColor: '#ffffff',
+
+  // 点的大小(像素)
+  dotSize: 1,
+
+  // 网格间距(像素)
+  gridSize: 20,
+
+  // 点的透明度(0-1)
+  dotOpacity: 0.5,
+
+  // 点的填充颜色
+  dotFillColor: '#ffffff'
+}
+```
+
+### Logo配置
+
+支持文本和图片两种Logo类型:
+
+<img loading="lazy" className="invert-img" src="/free-layout/background-logo.png"/>
+
+```ts pure
+{
+  logo: {
+    // Logo文本
+    text: 'FLOWGRAM.AI',
+
+    // 图片URL (可选,优先级高于文本)
+    imageUrl: 'https://example.com/logo.png',
+
+    // 位置:'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
+    position: 'center',
+
+    // 大小
+    size: 200,
+
+    // 透明度(0-1)
+    opacity: 0.25,
+
+    // 颜色
+    color: '#ffffff',
+
+    // 字体
+    fontFamily: 'Arial, sans-serif',
+
+    // 字体粗细
+    fontWeight: 'bold',
+
+    // 自定义偏移量
+    offset: { x: 0, y: 0 }
+  }
+}
+```
+
+### 新拟态效果
+
+新拟态(Neumorphism)是一种现代化的视觉设计风格,通过双层柔和阴影创造立体感:
+
+<img loading="lazy" className="invert-img" src="/free-layout/background-neumorphism.png"/>
+
+```ts pure
+{
+  logo: {
+    neumorphism: {
+      // 启用新拟态效果
+      enabled: true,
+
+      // 文字颜色
+      textColor: '#E0E0E0',
+
+      // 高光阴影颜色
+      lightShadowColor: 'rgba(255,255,255,0.9)',
+
+      // 暗色阴影颜色
+      darkShadowColor: 'rgba(0,0,0,0.15)',
+
+      // 阴影偏移距离
+      shadowOffset: 6,
+
+      // 阴影模糊半径
+      shadowBlur: 12,
+
+      // 阴影强度
+      intensity: 0.6,
+
+      // 凸起效果(true=凸起, false=凹陷)
+      raised: true
+    }
+  }
+}
+```
+
+## 使用示例
+
+```tsx pure
+// 在编辑器配置中直接使用 background 属性
+const editorProps = {
+  // 背景配置
+  background: {
+    // 深色主题背景
+    backgroundColor: '#1a1a1a',
+    dotColor: '#ffffff',
+    dotSize: 1,
+    gridSize: 20,
+    dotOpacity: 0.3,
+
+    // 品牌Logo
+    logo: {
+      text: 'FLOWGRAM.AI',
+      position: 'center',
+      size: 200,
+      opacity: 0.25,
+      color: '#ffffff',
+      fontFamily: 'Arial, sans-serif',
+      fontWeight: 'bold',
+
+      // 新拟态效果
+      neumorphism: {
+        enabled: true,
+        textColor: '#E0E0E0',
+        lightShadowColor: 'rgba(255,255,255,0.9)',
+        darkShadowColor: 'rgba(0,0,0,0.15)',
+        shadowOffset: 6,
+        shadowBlur: 12,
+        intensity: 0.6,
+        raised: true
+      }
+    }
+  }
+}
+```
+
+## 预设样式
+
+### 经典黑色主题
+
+```tsx pure
+const editorProps = {
+  background: {
+    backgroundColor: '#1a1a1a',
+    dotColor: '#ffffff',
+    dotSize: 1,
+    gridSize: 20,
+    dotOpacity: 0.3,
+    logo: {
+      text: '您的品牌',
+      position: 'center',
+      size: 200,
+      opacity: 0.25,
+      color: '#ffffff',
+      neumorphism: {
+        enabled: true,
+        textColor: '#E0E0E0',
+        lightShadowColor: 'rgba(255,255,255,0.9)',
+        darkShadowColor: 'rgba(0,0,0,0.15)',
+        shadowOffset: 6,
+        shadowBlur: 12,
+        intensity: 0.6,
+        raised: true
+      }
+    }
+  }
+}
+```
+
+### 简约白色主题
+
+```tsx pure
+const editorProps = {
+  background: {
+    backgroundColor: '#ffffff',
+    dotColor: '#000000',
+    dotSize: 1,
+    gridSize: 20,
+    dotOpacity: 0.1,
+    logo: {
+      text: '您的品牌',
+      position: 'center',
+      size: 200,
+      opacity: 0.1,
+      color: '#000000'
+    }
+  }
+}
+```
+
+## 注意事项
+
+1. **颜色搭配**:确保Logo颜色与背景色有足够的对比度
+2. **透明度设置**:Logo透明度不宜过高,以免影响内容可读性
+3. **新拟态效果**:需要合理调整阴影参数,过强的效果可能分散注意力
+4. **性能考虑**:复杂的阴影效果可能影响渲染性能,建议在低端设备上适当简化
+
+## 类型定义
+
+```ts
+interface BackgroundLayerOptions {
+  /** 网格间距,默认 20px */
+  gridSize?: number;
+  /** 点的大小,默认 1px */
+  dotSize?: number;
+  /** 点的颜色,默认 "#eceeef" */
+  dotColor?: string;
+  /** 点的透明度,默认 0.5 */
+  dotOpacity?: number;
+  /** 背景颜色,默认透明 */
+  backgroundColor?: string;
+  /** 点的填充颜色,默认与stroke颜色相同 */
+  dotFillColor?: string;
+  /** Logo 配置 */
+  logo?: {
+    /** Logo 文本内容 */
+    text?: string;
+    /** Logo 图片 URL */
+    imageUrl?: string;
+    /** Logo 位置,默认 'center' */
+    position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
+    /** Logo 大小,默认 'medium' */
+    size?: 'small' | 'medium' | 'large' | number;
+    /** Logo 透明度,默认 0.1 */
+    opacity?: number;
+    /** Logo 颜色(仅文本),默认 "#cccccc" */
+    color?: string;
+    /** Logo 字体家族(仅文本),默认 'Arial, sans-serif' */
+    fontFamily?: string;
+    /** Logo 字体粗细(仅文本),默认 'normal' */
+    fontWeight?: 'normal' | 'bold' | 'lighter' | number;
+    /** 自定义偏移 */
+    offset?: { x: number; y: number };
+    /** 新拟态效果配置 */
+    neumorphism?: {
+      /** 启用新拟态效果 */
+      enabled: boolean;
+      /** 文字颜色 */
+      textColor?: string;
+      /** 高光阴影颜色 */
+      lightShadowColor?: string;
+      /** 暗色阴影颜色 */
+      darkShadowColor?: string;
+      /** 阴影偏移距离 */
+      shadowOffset?: number;
+      /** 阴影模糊半径 */
+      shadowBlur?: number;
+      /** 阴影强度 */
+      intensity?: number;
+      /** 凸起效果(true=凸起, false=凹陷) */
+      raised?: boolean;
+    };
+  };
+}
+```

+ 2 - 1
packages/client/playground-react/src/preset/playground-react-preset.ts

@@ -21,7 +21,8 @@ export function createPlaygroundReactPreset<CTX extends PluginContext = PluginCo
      * 注册背景 (放前面插入), 默认打开
      */
     if (opts.background || opts.background === undefined) {
-      plugins.push(createBackgroundPlugin(opts.background || {}));
+      const backgroundOptions = typeof opts.background === 'object' ? opts.background : {};
+      plugins.push(createBackgroundPlugin(backgroundOptions));
     }
     /**
      * 注册快捷键

+ 391 - 22
packages/plugins/background-plugin/src/background-layer.tsx

@@ -1,5 +1,5 @@
-import { Layer, observeEntity, PlaygroundConfigEntity, SCALE_WIDTH } from '@flowgram.ai/core';
 import { domUtils } from '@flowgram.ai/utils';
+import { Layer, observeEntity, PlaygroundConfigEntity, SCALE_WIDTH } from '@flowgram.ai/core';
 
 interface BackgroundScaleUnit {
   realSize: number;
@@ -8,12 +8,67 @@ interface BackgroundScaleUnit {
 }
 
 const PATTERN_PREFIX = 'gedit-background-pattern-';
-const RENDER_SIZE = 20;
-const DOT_SIZE = 1;
+const DEFAULT_RENDER_SIZE = 20;
+const DEFAULT_DOT_SIZE = 1;
 let id = 0;
+
 export interface BackgroundLayerOptions {
-  // 预留配置项目
+  /** 网格间距,默认 20px */
+  gridSize?: number;
+  /** 点的大小,默认 1px */
+  dotSize?: number;
+  /** 点的颜色,默认 "#eceeef" */
+  dotColor?: string;
+  /** 点的透明度,默认 0.5 */
+  dotOpacity?: number;
+  /** 背景颜色,默认透明 */
+  backgroundColor?: string;
+  /** 点的填充颜色,默认与stroke颜色相同 */
+  dotFillColor?: string;
+  /** Logo 配置 */
+  logo?: {
+    /** Logo 文本内容 */
+    text?: string;
+    /** Logo 图片 URL */
+    imageUrl?: string;
+    /** Logo 位置,默认 'center' */
+    position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
+    /** Logo 大小,默认 'medium' */
+    size?: 'small' | 'medium' | 'large' | number;
+    /** Logo 透明度,默认 0.1 */
+    opacity?: number;
+    /** Logo 颜色(仅文本),默认 "#cccccc" */
+    color?: string;
+    /** Logo 字体大小(仅文本),默认根据 size 计算 */
+    fontSize?: number;
+    /** Logo 字体家族(仅文本),默认 'Arial, sans-serif' */
+    fontFamily?: string;
+    /** Logo 字体粗细(仅文本),默认 'normal' */
+    fontWeight?: 'normal' | 'bold' | 'lighter' | number;
+    /** 自定义偏移 */
+    offset?: { x: number; y: number };
+    /** 新拟态(Neumorphism)效果配置 */
+    neumorphism?: {
+      /** 是否启用新拟态效果,默认 false */
+      enabled?: boolean;
+      /** 主要文字颜色,应该与背景色接近,默认自动计算 */
+      textColor?: string;
+      /** 亮色阴影颜色,默认自动计算(背景色的亮色版本) */
+      lightShadowColor?: string;
+      /** 暗色阴影颜色,默认自动计算(背景色的暗色版本) */
+      darkShadowColor?: string;
+      /** 阴影偏移距离,默认 6 */
+      shadowOffset?: number;
+      /** 阴影模糊半径,默认 12 */
+      shadowBlur?: number;
+      /** 效果强度(0-1),影响阴影的透明度,默认 0.3 */
+      intensity?: number;
+      /** 凸起效果(true)还是凹陷效果(false),默认 true */
+      raised?: boolean;
+    };
+  };
 }
+
 /**
  * dot 网格背景
  */
@@ -29,6 +84,55 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
 
   grid: HTMLElement = document.createElement('div');
 
+  /**
+   * 获取网格大小配置
+   */
+  private get gridSize(): number {
+    return this.options.gridSize ?? DEFAULT_RENDER_SIZE;
+  }
+
+  /**
+   * 获取点大小配置
+   */
+  private get dotSize(): number {
+    return this.options.dotSize ?? DEFAULT_DOT_SIZE;
+  }
+
+  /**
+   * 获取点颜色配置
+   */
+  private get dotColor(): string {
+    return this.options.dotColor ?? '#eceeef';
+  }
+
+  /**
+   * 获取点透明度配置
+   */
+  private get dotOpacity(): number {
+    return this.options.dotOpacity ?? 0.5;
+  }
+
+  /**
+   * 获取背景颜色配置
+   */
+  private get backgroundColor(): string {
+    return this.options.backgroundColor ?? 'transparent';
+  }
+
+  /**
+   * 获取点填充颜色配置
+   */
+  private get dotFillColor(): string {
+    return this.options.dotFillColor ?? this.dotColor;
+  }
+
+  /**
+   * 获取Logo配置
+   */
+  private get logoConfig() {
+    return this.options.logo;
+  }
+
   /**
    * 当前缩放比
    */
@@ -50,6 +154,11 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
     this.grid.style.position = 'relative';
     this.node.appendChild(this.grid);
     this.grid.className = 'gedit-grid-svg';
+
+    // 设置背景颜色
+    if (this.backgroundColor !== 'transparent') {
+      this.node.style.backgroundColor = this.backgroundColor;
+    }
   }
 
   /**
@@ -59,8 +168,8 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
     const { zoom } = this;
 
     return {
-      realSize: RENDER_SIZE, // 一个单元格代表的真实大小
-      renderSize: Math.round(RENDER_SIZE * zoom * 100) / 100, // 一个单元格渲染的大小值
+      realSize: this.gridSize, // 使用配置的网格大小
+      renderSize: Math.round(this.gridSize * zoom * 100) / 100, // 一个单元格渲染的大小值
       zoom, // 缩放比
     };
   }
@@ -82,7 +191,7 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
       left: scrollX - SCALE_WIDTH,
       top: scrollY - SCALE_WIDTH,
     });
-    this.drawGrid(scaleUnit);
+    this.drawGrid(scaleUnit, viewBoxWidth, viewBoxHeight);
     // 设置网格
     this.setSVGStyle(this.grid, {
       width: viewBoxWidth,
@@ -92,34 +201,294 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
     });
   }
 
+  /**
+   * 计算Logo位置
+   */
+  private calculateLogoPosition(
+    viewBoxWidth: number,
+    viewBoxHeight: number
+  ): { x: number; y: number } {
+    if (!this.logoConfig) return { x: 0, y: 0 };
+
+    const { position = 'center', offset = { x: 0, y: 0 } } = this.logoConfig;
+    const playgroundConfig = this.playgroundConfigEntity.config;
+    const scaleUnit = this.getScaleUnit();
+    const mod = scaleUnit.renderSize * 10;
+
+    // 计算SVG内的相对位置,使Logo相对于可视区域固定
+    const { scrollX, scrollY } = playgroundConfig;
+    const scrollXDelta = this.getScrollDelta(scrollX, mod);
+    const scrollYDelta = this.getScrollDelta(scrollY, mod);
+
+    // 可视区域的基准点(相对于SVG坐标系)
+    const visibleLeft = mod + scrollXDelta;
+    const visibleTop = mod + scrollYDelta;
+    const visibleCenterX = visibleLeft + playgroundConfig.width / 2;
+    const visibleCenterY = visibleTop + playgroundConfig.height / 2;
+
+    let x = 0,
+      y = 0;
+
+    switch (position) {
+      case 'center':
+        x = visibleCenterX;
+        y = visibleCenterY;
+        break;
+      case 'top-left':
+        x = visibleLeft + 100;
+        y = visibleTop + 100;
+        break;
+      case 'top-right':
+        x = visibleLeft + playgroundConfig.width - 100;
+        y = visibleTop + 100;
+        break;
+      case 'bottom-left':
+        x = visibleLeft + 100;
+        y = visibleTop + playgroundConfig.height - 100;
+        break;
+      case 'bottom-right':
+        x = visibleLeft + playgroundConfig.width - 100;
+        y = visibleTop + playgroundConfig.height - 100;
+        break;
+    }
+
+    return { x: x + offset.x, y: y + offset.y };
+  }
+
+  /**
+   * 获取Logo大小
+   */
+  private getLogoSize(): number {
+    if (!this.logoConfig) return 0;
+
+    const { size = 'medium' } = this.logoConfig;
+
+    if (typeof size === 'number') {
+      return size;
+    }
+
+    switch (size) {
+      case 'small':
+        return 24;
+      case 'medium':
+        return 48;
+      case 'large':
+        return 72;
+      default:
+        return 48;
+    }
+  }
+
+  /**
+   * 颜色工具函数:将十六进制颜色转换为RGB
+   */
+  private hexToRgb(hex: string): { r: number; g: number; b: number } | null {
+    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+    return result
+      ? {
+          r: parseInt(result[1], 16),
+          g: parseInt(result[2], 16),
+          b: parseInt(result[3], 16),
+        }
+      : null;
+  }
+
+  /**
+   * 颜色工具函数:调整颜色亮度
+   */
+  private adjustBrightness(hex: string, percent: number): string {
+    const rgb = this.hexToRgb(hex);
+    if (!rgb) return hex;
+
+    const adjust = (value: number) => {
+      const adjusted = Math.round(value + (255 - value) * percent);
+      return Math.max(0, Math.min(255, adjusted));
+    };
+
+    return `#${adjust(rgb.r).toString(16).padStart(2, '0')}${adjust(rgb.g)
+      .toString(16)
+      .padStart(2, '0')}${adjust(rgb.b).toString(16).padStart(2, '0')}`;
+  }
+
+  /**
+   * 生成新拟态阴影滤镜
+   */
+  private generateNeumorphismFilter(
+    filterId: string,
+    lightShadow: string,
+    darkShadow: string,
+    offset: number,
+    blur: number,
+    intensity: number,
+    raised: boolean
+  ): string {
+    const lightOffset = raised ? -offset : offset;
+    const darkOffset = raised ? offset : -offset;
+
+    return `
+      <defs>
+        <filter id="${filterId}" x="-50%" y="-50%" width="200%" height="200%">
+          <feDropShadow dx="${lightOffset}" dy="${lightOffset}" stdDeviation="${blur}" flood-color="${lightShadow}" flood-opacity="${intensity}"/>
+          <feDropShadow dx="${darkOffset}" dy="${darkOffset}" stdDeviation="${blur}" flood-color="${darkShadow}" flood-opacity="${intensity}"/>
+        </filter>
+      </defs>`;
+  }
+
+  /**
+   * 绘制Logo SVG内容
+   */
+  private generateLogoSVG(viewBoxWidth: number, viewBoxHeight: number): string {
+    if (!this.logoConfig) return '';
+
+    const {
+      text,
+      imageUrl,
+      opacity = 0.1,
+      color = '#cccccc',
+      fontSize,
+      fontFamily = 'Arial, sans-serif',
+      fontWeight = 'normal',
+      neumorphism,
+    } = this.logoConfig;
+    const position = this.calculateLogoPosition(viewBoxWidth, viewBoxHeight);
+    const logoSize = this.getLogoSize();
+
+    let logoSVG = '';
+
+    if (imageUrl) {
+      // 图片Logo(暂不支持3D效果)
+      logoSVG = `
+        <image
+          href="${imageUrl}"
+          x="${position.x - logoSize / 2}"
+          y="${position.y - logoSize / 2}"
+          width="${logoSize}"
+          height="${logoSize}"
+          opacity="${opacity}"
+        />`;
+    } else if (text) {
+      // 文本Logo
+      const actualFontSize = fontSize ?? Math.max(logoSize / 2, 12);
+
+      // 检查是否启用新拟态效果
+      if (neumorphism?.enabled) {
+        const {
+          textColor,
+          lightShadowColor,
+          darkShadowColor,
+          shadowOffset = 6,
+          shadowBlur = 12,
+          intensity = 0.3,
+          raised = true,
+        } = neumorphism;
+
+        // 自动计算颜色(如果未提供)
+        const bgColor = this.backgroundColor !== 'transparent' ? this.backgroundColor : '#f0f0f0';
+        const finalTextColor = textColor || bgColor;
+        const finalLightShadow = lightShadowColor || this.adjustBrightness(bgColor, 0.2);
+        const finalDarkShadow = darkShadowColor || this.adjustBrightness(bgColor, -0.2);
+
+        const filterId = `neumorphism-${this._patternId}`;
+
+        // 添加新拟态滤镜定义
+        logoSVG += this.generateNeumorphismFilter(
+          filterId,
+          finalLightShadow,
+          finalDarkShadow,
+          shadowOffset,
+          shadowBlur,
+          intensity,
+          raised
+        );
+
+        // 创建新拟态文本
+        logoSVG += `
+          <text
+            x="${position.x}"
+            y="${position.y}"
+            font-family="${fontFamily}"
+            font-size="${actualFontSize}"
+            font-weight="${fontWeight}"
+            fill="${finalTextColor}"
+            opacity="${opacity}"
+            text-anchor="middle"
+            dominant-baseline="middle"
+            filter="url(#${filterId})"
+          >${text}</text>`;
+      } else {
+        // 普通文本(无3D效果)
+        logoSVG = `
+          <text
+            x="${position.x}"
+            y="${position.y}"
+            font-family="${fontFamily}"
+            font-size="${actualFontSize}"
+            font-weight="${fontWeight}"
+            fill="${color}"
+            opacity="${opacity}"
+            text-anchor="middle"
+            dominant-baseline="middle"
+          >${text}</text>`;
+      }
+    }
+
+    return logoSVG;
+  }
+
   /**
    * 绘制网格
    */
-  protected drawGrid(unit: BackgroundScaleUnit): void {
+  protected drawGrid(unit: BackgroundScaleUnit, viewBoxWidth: number, viewBoxHeight: number): void {
     const minor = unit.renderSize;
     if (!this.grid) {
       return;
     }
-    const patternSize = DOT_SIZE * this.zoom;
-    const newContent = `
-    <svg width="100%" height="100%">
+    const patternSize = this.dotSize * this.zoom;
+
+    // 构建SVG内容,根据是否有背景颜色决定是否添加背景矩形
+    let svgContent = `<svg width="100%" height="100%">`;
+
+    // 如果设置了背景颜色,先绘制背景矩形
+    if (this.backgroundColor !== 'transparent') {
+      svgContent += `<rect width="100%" height="100%" fill="${this.backgroundColor}"/>`;
+    }
+
+    // 添加点阵图案
+    // 构建圆圈属性,保持与原始实现的兼容性
+    const circleAttributes = [
+      `cx="${patternSize}"`,
+      `cy="${patternSize}"`,
+      `r="${patternSize}"`,
+      `stroke="${this.dotColor}"`,
+      // 只有当 dotFillColor 被明确设置且与 dotColor 不同时才添加 fill 属性
+      this.options.dotFillColor && this.dotFillColor !== this.dotColor
+        ? `fill="${this.dotFillColor}"`
+        : '',
+      `fill-opacity="${this.dotOpacity}"`,
+    ]
+      .filter(Boolean)
+      .join(' ');
+
+    svgContent += `
       <pattern id="${this._patternId}" width="${minor}" height="${minor}" patternUnits="userSpaceOnUse">
-        <circle
-          cx="${patternSize}"
-          cy="${patternSize}"
-          r="${patternSize}"
-          stroke="#eceeef"
-          fill-opacity="0.5"
-        />
+        <circle ${circleAttributes} />
       </pattern>
-      <rect width="100%" height="100%" fill="url(#${this._patternId})"/>
-    </svg>`;
-    this.grid.innerHTML = newContent;
+      <rect width="100%" height="100%" fill="url(#${this._patternId})"/>`;
+
+    // 添加Logo
+    const logoSVG = this.generateLogoSVG(viewBoxWidth, viewBoxHeight);
+    if (logoSVG) {
+      svgContent += logoSVG;
+    }
+
+    svgContent += `</svg>`;
+
+    this.grid.innerHTML = svgContent;
   }
 
   protected setSVGStyle(
     svgElement: HTMLElement | undefined,
-    style: { width: number; height: number; left: number; top: number },
+    style: { width: number; height: number; left: number; top: number }
   ): void {
     if (!svgElement) {
       return;