mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
Compare commits
3 Commits
7c43b0cfd0
...
099fd445f2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099fd445f2 | ||
|
|
488013bb0b | ||
|
|
233b9befab |
@ -33,7 +33,7 @@ export function FormInputs() {
|
|||||||
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||||
schema={property}
|
schema={property}
|
||||||
/>
|
/>
|
||||||
<Feedback errors={fieldState?.errors} />
|
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@ -3,7 +3,12 @@ import {
|
|||||||
provideJsonSchemaOutputs,
|
provideJsonSchemaOutputs,
|
||||||
syncVariableTitle,
|
syncVariableTitle,
|
||||||
} from '@flowgram.ai/form-materials';
|
} from '@flowgram.ai/form-materials';
|
||||||
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
|
import {
|
||||||
|
FormRenderProps,
|
||||||
|
FormMeta,
|
||||||
|
ValidateTrigger,
|
||||||
|
FeedbackLevel,
|
||||||
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
import { FlowNodeJSON } from '../typings';
|
import { FlowNodeJSON } from '../typings';
|
||||||
import { FormHeader, FormContent, FormInputs, FormOutputs } from '../form-components';
|
import { FormHeader, FormContent, FormInputs, FormOutputs } from '../form-components';
|
||||||
@ -30,7 +35,10 @@ export const defaultFormMeta: FormMeta<FlowNodeJSON['data']> = {
|
|||||||
required.includes(valuePropetyKey) &&
|
required.includes(valuePropetyKey) &&
|
||||||
(value === '' || value === undefined || value?.content === '')
|
(value === '' || value === undefined || value?.content === '')
|
||||||
) {
|
) {
|
||||||
return `${valuePropetyKey} is required`;
|
return {
|
||||||
|
message: `${valuePropetyKey} is required`,
|
||||||
|
level: FeedbackLevel.Error, // Error || Warning
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export function FormInputs() {
|
|||||||
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||||
schema={property}
|
schema={property}
|
||||||
/>
|
/>
|
||||||
<Feedback errors={fieldState?.errors} />
|
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
|
import {
|
||||||
|
FormRenderProps,
|
||||||
|
FormMeta,
|
||||||
|
ValidateTrigger,
|
||||||
|
FeedbackLevel,
|
||||||
|
} from '@flowgram.ai/free-layout-editor';
|
||||||
import {
|
import {
|
||||||
autoRenameRefEffect,
|
autoRenameRefEffect,
|
||||||
provideJsonSchemaOutputs,
|
provideJsonSchemaOutputs,
|
||||||
@ -30,7 +35,10 @@ export const defaultFormMeta: FormMeta<FlowNodeJSON> = {
|
|||||||
required.includes(valuePropetyKey) &&
|
required.includes(valuePropetyKey) &&
|
||||||
(value === '' || value === undefined || value?.content === '')
|
(value === '' || value === undefined || value?.content === '')
|
||||||
) {
|
) {
|
||||||
return `${valuePropetyKey} is required`;
|
return {
|
||||||
|
message: `${valuePropetyKey} is required`,
|
||||||
|
level: FeedbackLevel.Error, // Error || Warning
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,5 +3,6 @@
|
|||||||
"node",
|
"node",
|
||||||
"line",
|
"line",
|
||||||
"port",
|
"port",
|
||||||
"sub-canvas"
|
"sub-canvas",
|
||||||
|
"background"
|
||||||
]
|
]
|
||||||
|
|||||||
270
apps/docs/src/en/guide/advanced/free-layout/background.mdx
Normal file
270
apps/docs/src/en/guide/advanced/free-layout/background.mdx
Normal file
@ -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
Normal file
BIN
apps/docs/src/public/free-layout/background-color.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
BIN
apps/docs/src/public/free-layout/background-logo.png
Normal file
BIN
apps/docs/src/public/free-layout/background-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
BIN
apps/docs/src/public/free-layout/background-neumorphism.png
Normal file
BIN
apps/docs/src/public/free-layout/background-neumorphism.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
@ -3,5 +3,6 @@
|
|||||||
"node",
|
"node",
|
||||||
"line",
|
"line",
|
||||||
"port",
|
"port",
|
||||||
"sub-canvas"
|
"sub-canvas",
|
||||||
|
"background"
|
||||||
]
|
]
|
||||||
|
|||||||
270
apps/docs/src/zh/guide/advanced/free-layout/background.mdx
Normal file
270
apps/docs/src/zh/guide/advanced/free-layout/background.mdx
Normal file
@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
@ -53,6 +53,7 @@ export {
|
|||||||
useFieldValidate,
|
useFieldValidate,
|
||||||
useWatch,
|
useWatch,
|
||||||
ValidateTrigger,
|
ValidateTrigger,
|
||||||
|
FeedbackLevel,
|
||||||
} from '@flowgram.ai/form';
|
} from '@flowgram.ai/form';
|
||||||
export * from '@flowgram.ai/node';
|
export * from '@flowgram.ai/node';
|
||||||
export { FormModelV2 as FormModel };
|
export { FormModelV2 as FormModel };
|
||||||
|
|||||||
@ -21,7 +21,8 @@ export function createPlaygroundReactPreset<CTX extends PluginContext = PluginCo
|
|||||||
* 注册背景 (放前面插入), 默认打开
|
* 注册背景 (放前面插入), 默认打开
|
||||||
*/
|
*/
|
||||||
if (opts.background || opts.background === undefined) {
|
if (opts.background || opts.background === undefined) {
|
||||||
plugins.push(createBackgroundPlugin(opts.background || {}));
|
const backgroundOptions = typeof opts.background === 'object' ? opts.background : {};
|
||||||
|
plugins.push(createBackgroundPlugin(backgroundOptions));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 注册快捷键
|
* 注册快捷键
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export function JsonSchemaEditor(props: {
|
|||||||
value?: IJsonSchema;
|
value?: IJsonSchema;
|
||||||
onChange?: (value: IJsonSchema) => void;
|
onChange?: (value: IJsonSchema) => void;
|
||||||
config?: ConfigType;
|
config?: ConfigType;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const { value = { type: 'object' }, config = {}, onChange: onChangeProps } = props;
|
const { value = { type: 'object' }, config = {}, onChange: onChangeProps } = props;
|
||||||
const { propertyList, onAddProperty, onRemoveProperty, onEditProperty } = usePropertiesEdit(
|
const { propertyList, onAddProperty, onRemoveProperty, onEditProperty } = usePropertiesEdit(
|
||||||
@ -46,7 +47,7 @@ export function JsonSchemaEditor(props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UIContainer>
|
<UIContainer className={props.className}>
|
||||||
<UIProperties>
|
<UIProperties>
|
||||||
{propertyList.map((_property, index) => (
|
{propertyList.map((_property, index) => (
|
||||||
<PropertyEdit
|
<PropertyEdit
|
||||||
@ -63,7 +64,12 @@ export function JsonSchemaEditor(props: {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</UIProperties>
|
</UIProperties>
|
||||||
<Button size="small" style={{ marginTop: 10 }} icon={<IconPlus />} onClick={onAddProperty}>
|
<Button
|
||||||
|
size="small"
|
||||||
|
style={{ marginTop: 10, marginLeft: 16 }}
|
||||||
|
icon={<IconPlus />}
|
||||||
|
onClick={onAddProperty}
|
||||||
|
>
|
||||||
{config?.addButtonText ?? 'Add'}
|
{config?.addButtonText ?? 'Add'}
|
||||||
</Button>
|
</Button>
|
||||||
</UIContainer>
|
</UIContainer>
|
||||||
|
|||||||
@ -361,8 +361,8 @@ export class FieldModel<TValue extends FieldValue = FieldValue> implements Dispo
|
|||||||
|
|
||||||
const groupedFeedbacks = groupBy(feedbacks, 'level');
|
const groupedFeedbacks = groupBy(feedbacks, 'level');
|
||||||
|
|
||||||
warnings = warnings.concat(groupedFeedbacks[FeedbackLevel.Warning] as FieldWarning[]);
|
warnings = warnings.concat((groupedFeedbacks[FeedbackLevel.Warning] as FieldWarning[]) || []);
|
||||||
errors = errors.concat(groupedFeedbacks[FeedbackLevel.Error] as FieldError[]);
|
errors = errors.concat((groupedFeedbacks[FeedbackLevel.Error] as FieldError[]) || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { errors, warnings };
|
return { errors, warnings };
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
OnFormValuesUpdatedPayload,
|
OnFormValuesUpdatedPayload,
|
||||||
} from '../types/form';
|
} from '../types/form';
|
||||||
import { FieldName, FieldValue } from '../types/field';
|
import { FieldName, FieldValue } from '../types/field';
|
||||||
import { Errors, FormValidateReturn, Warnings } from '../types';
|
import { Errors, FeedbackLevel, FormValidateReturn, Warnings } from '../types';
|
||||||
import { createFormModelState } from '../constants';
|
import { createFormModelState } from '../constants';
|
||||||
import { getValidByErrors, mergeFeedbacks } from './utils';
|
import { getValidByErrors, mergeFeedbacks } from './utils';
|
||||||
import { Store } from './store';
|
import { Store } from './store';
|
||||||
@ -281,8 +281,14 @@ export class FormModel<TValues = any> implements Disposable {
|
|||||||
const feedback = toFeedback(result, path);
|
const feedback = toFeedback(result, path);
|
||||||
const field = this.getField(path);
|
const field = this.getField(path);
|
||||||
|
|
||||||
const errors = feedbackToFieldErrorsOrWarnings<Errors>(path, feedback);
|
const errors = feedbackToFieldErrorsOrWarnings<Errors>(
|
||||||
const warnings = feedbackToFieldErrorsOrWarnings<Warnings>(path, feedback);
|
path,
|
||||||
|
feedback?.level === FeedbackLevel.Error ? feedback : undefined
|
||||||
|
);
|
||||||
|
const warnings = feedbackToFieldErrorsOrWarnings<Warnings>(
|
||||||
|
path,
|
||||||
|
feedback?.level === FeedbackLevel.Warning ? feedback : undefined
|
||||||
|
);
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
field.state.errors = errors;
|
field.state.errors = errors;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export type {
|
|||||||
Warnings,
|
Warnings,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export { ValidateTrigger } from './types';
|
export { ValidateTrigger, FeedbackLevel } from './types';
|
||||||
export { createForm, type CreateFormOptions } from './core/create-form';
|
export { createForm, type CreateFormOptions } from './core/create-form';
|
||||||
export { Glob } from './utils';
|
export { Glob } from './utils';
|
||||||
export * from './core';
|
export * from './core';
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export interface Feedback<FeedbackLevel> {
|
|||||||
/**
|
/**
|
||||||
* Feedback message
|
* Feedback message
|
||||||
*/
|
*/
|
||||||
message: string;
|
message: string | React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FieldError = Feedback<FeedbackLevel.Error>;
|
export type FieldError = Feedback<FeedbackLevel.Error>;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Layer, observeEntity, PlaygroundConfigEntity, SCALE_WIDTH } from '@flowgram.ai/core';
|
|
||||||
import { domUtils } from '@flowgram.ai/utils';
|
import { domUtils } from '@flowgram.ai/utils';
|
||||||
|
import { Layer, observeEntity, PlaygroundConfigEntity, SCALE_WIDTH } from '@flowgram.ai/core';
|
||||||
|
|
||||||
interface BackgroundScaleUnit {
|
interface BackgroundScaleUnit {
|
||||||
realSize: number;
|
realSize: number;
|
||||||
@ -8,12 +8,67 @@ interface BackgroundScaleUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PATTERN_PREFIX = 'gedit-background-pattern-';
|
const PATTERN_PREFIX = 'gedit-background-pattern-';
|
||||||
const RENDER_SIZE = 20;
|
const DEFAULT_RENDER_SIZE = 20;
|
||||||
const DOT_SIZE = 1;
|
const DEFAULT_DOT_SIZE = 1;
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
|
||||||
export interface BackgroundLayerOptions {
|
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 网格背景
|
* dot 网格背景
|
||||||
*/
|
*/
|
||||||
@ -29,6 +84,55 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
|
|||||||
|
|
||||||
grid: HTMLElement = document.createElement('div');
|
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.grid.style.position = 'relative';
|
||||||
this.node.appendChild(this.grid);
|
this.node.appendChild(this.grid);
|
||||||
this.grid.className = 'gedit-grid-svg';
|
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;
|
const { zoom } = this;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
realSize: RENDER_SIZE, // 一个单元格代表的真实大小
|
realSize: this.gridSize, // 使用配置的网格大小
|
||||||
renderSize: Math.round(RENDER_SIZE * zoom * 100) / 100, // 一个单元格渲染的大小值
|
renderSize: Math.round(this.gridSize * zoom * 100) / 100, // 一个单元格渲染的大小值
|
||||||
zoom, // 缩放比
|
zoom, // 缩放比
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -82,7 +191,7 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
|
|||||||
left: scrollX - SCALE_WIDTH,
|
left: scrollX - SCALE_WIDTH,
|
||||||
top: scrollY - SCALE_WIDTH,
|
top: scrollY - SCALE_WIDTH,
|
||||||
});
|
});
|
||||||
this.drawGrid(scaleUnit);
|
this.drawGrid(scaleUnit, viewBoxWidth, viewBoxHeight);
|
||||||
// 设置网格
|
// 设置网格
|
||||||
this.setSVGStyle(this.grid, {
|
this.setSVGStyle(this.grid, {
|
||||||
width: viewBoxWidth,
|
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;
|
const minor = unit.renderSize;
|
||||||
if (!this.grid) {
|
if (!this.grid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const patternSize = DOT_SIZE * this.zoom;
|
const patternSize = this.dotSize * this.zoom;
|
||||||
const newContent = `
|
|
||||||
<svg width="100%" height="100%">
|
// 构建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">
|
<pattern id="${this._patternId}" width="${minor}" height="${minor}" patternUnits="userSpaceOnUse">
|
||||||
<circle
|
<circle ${circleAttributes} />
|
||||||
cx="${patternSize}"
|
|
||||||
cy="${patternSize}"
|
|
||||||
r="${patternSize}"
|
|
||||||
stroke="#eceeef"
|
|
||||||
fill-opacity="0.5"
|
|
||||||
/>
|
|
||||||
</pattern>
|
</pattern>
|
||||||
<rect width="100%" height="100%" fill="url(#${this._patternId})"/>
|
<rect width="100%" height="100%" fill="url(#${this._patternId})"/>`;
|
||||||
</svg>`;
|
|
||||||
this.grid.innerHTML = newContent;
|
// 添加Logo
|
||||||
|
const logoSVG = this.generateLogoSVG(viewBoxWidth, viewBoxHeight);
|
||||||
|
if (logoSVG) {
|
||||||
|
svgContent += logoSVG;
|
||||||
|
}
|
||||||
|
|
||||||
|
svgContent += `</svg>`;
|
||||||
|
|
||||||
|
this.grid.innerHTML = svgContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setSVGStyle(
|
protected setSVGStyle(
|
||||||
svgElement: HTMLElement | undefined,
|
svgElement: HTMLElement | undefined,
|
||||||
style: { width: number; height: number; left: number; top: number },
|
style: { width: number; height: number; left: number; top: number }
|
||||||
): void {
|
): void {
|
||||||
if (!svgElement) {
|
if (!svgElement) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user