组件库是低代码平台的核心资产,它决定了平台的功能边界和开发效率。本章将详细介绍如何设计和实现一个完整的组件库系统。
5.1 组件库架构设计
5.1.1 整体架构
graph TB
A[组件库管理器] --> B[组件注册中心]
A --> C[组件加载器]
A --> D[组件版本管理]
B --> E[内置组件]
B --> F[第三方组件]
B --> G[自定义组件]
C --> H[动态加载]
C --> I[懒加载]
C --> J[预加载]
D --> K[版本控制]
D --> L[依赖管理]
D --> M[兼容性检查]
E --> N[基础组件]
E --> O[布局组件]
E --> P[表单组件]
E --> Q[图表组件]
5.1.2 组件库管理器
// 组件库管理器
class ComponentLibraryManager {
private libraries: Map<string, ComponentLibrary> = new Map();
private componentRegistry: ComponentRegistry;
private versionManager: VersionManager;
private loader: ComponentLoader;
private eventBus: EventBus;
constructor(
componentRegistry: ComponentRegistry,
eventBus: EventBus
) {
this.componentRegistry = componentRegistry;
this.eventBus = eventBus;
this.versionManager = new VersionManager();
this.loader = new ComponentLoader(this.eventBus);
this.initializeBuiltinLibraries();
}
// 注册组件库
async registerLibrary(library: ComponentLibrary): Promise<void> {
try {
// 验证组件库
await this.validateLibrary(library);
// 检查版本兼容性
this.versionManager.checkCompatibility(library);
// 加载组件库
await this.loader.loadLibrary(library);
// 注册组件
library.components.forEach(component => {
this.componentRegistry.registerComponent(component);
});
// 存储组件库
this.libraries.set(library.id, library);
// 触发事件
this.eventBus.emit('library:registered', library);
console.log(`Component library "${library.name}" registered successfully`);
} catch (error) {
console.error(`Failed to register library "${library.name}":`, error);
throw error;
}
}
// 卸载组件库
async unregisterLibrary(libraryId: string): Promise<void> {
const library = this.libraries.get(libraryId);
if (!library) {
throw new Error(`Library "${libraryId}" not found`);
}
try {
// 检查依赖
this.checkLibraryDependencies(libraryId);
// 卸载组件
library.components.forEach(component => {
this.componentRegistry.unregisterComponent(component.type);
});
// 卸载资源
await this.loader.unloadLibrary(library);
// 移除组件库
this.libraries.delete(libraryId);
// 触发事件
this.eventBus.emit('library:unregistered', library);
console.log(`Component library "${library.name}" unregistered successfully`);
} catch (error) {
console.error(`Failed to unregister library "${library.name}":`, error);
throw error;
}
}
// 获取组件库列表
getLibraries(): ComponentLibrary[] {
return Array.from(this.libraries.values());
}
// 获取组件库
getLibrary(libraryId: string): ComponentLibrary | undefined {
return this.libraries.get(libraryId);
}
// 搜索组件库
searchLibraries(keyword: string): ComponentLibrary[] {
const lowerKeyword = keyword.toLowerCase();
return Array.from(this.libraries.values())
.filter(library =>
library.name.toLowerCase().includes(lowerKeyword) ||
library.description.toLowerCase().includes(lowerKeyword) ||
library.tags.some(tag => tag.toLowerCase().includes(lowerKeyword))
);
}
// 更新组件库
async updateLibrary(libraryId: string, newVersion: string): Promise<void> {
const library = this.libraries.get(libraryId);
if (!library) {
throw new Error(`Library "${libraryId}" not found`);
}
try {
// 获取新版本信息
const newLibrary = await this.fetchLibraryVersion(libraryId, newVersion);
// 检查兼容性
this.versionManager.checkUpdateCompatibility(library, newLibrary);
// 备份当前版本
const backup = { ...library };```
## 5.3 组件注册与使用
### 5.3.1 组件注册
```typescript
// 注册内置组件
class ComponentRegistryInitializer {
static async initializeBuiltinComponents(registry: ComponentRegistry): Promise<void> {
try {
// 注册基础组件
await registry.registerComponent(TextComponent);
await registry.registerComponent(ButtonComponent);
await registry.registerComponent(InputComponent);
// 注册布局组件
await registry.registerComponent(ContainerComponent);
await registry.registerComponent(GridComponent);
console.log('内置组件注册完成');
} catch (error) {
console.error('组件注册失败:', error);
throw error;
}
}
static async loadExternalComponents(registry: ComponentRegistry, componentUrls: string[]): Promise<void> {
const loadPromises = componentUrls.map(async (url) => {
try {
const response = await fetch(url);
const componentDefinition = await response.json();
await registry.registerComponent(componentDefinition);
console.log(`外部组件加载成功: ${componentDefinition.name}`);
} catch (error) {
console.error(`外部组件加载失败: ${url}`, error);
}
});
await Promise.allSettled(loadPromises);
}
}
// 使用示例
const initializeComponentSystem = async () => {
const registry = new ComponentRegistry();
const loader = new ComponentLoader();
// 初始化内置组件
await ComponentRegistryInitializer.initializeBuiltinComponents(registry);
// 加载外部组件
const externalComponents = [
'/api/components/chart-component.json',
'/api/components/table-component.json',
'/api/components/form-component.json'
];
await ComponentRegistryInitializer.loadExternalComponents(registry, externalComponents);
return { registry, loader };
};
5.3.2 组件使用示例
// 组件使用管理器
class ComponentUsageManager {
private registry: ComponentRegistry;
private loader: ComponentLoader;
constructor(registry: ComponentRegistry, loader: ComponentLoader) {
this.registry = registry;
this.loader = loader;
}
// 创建组件实例
async createComponentInstance(componentId: string, props: any = {}, container?: HTMLElement): Promise<ComponentInstance> {
const definition = this.registry.getComponent(componentId);
if (!definition) {
throw new Error(`组件未找到: ${componentId}`);
}
const instance: ComponentInstance = {
id: this.generateInstanceId(),
componentId,
definition,
props: { ...definition.defaultProps, ...props },
element: null,
children: [],
parent: null,
events: {},
state: {}
};
// 渲染组件
if (container) {
await this.renderComponent(instance, container);
}
return instance;
}
// 渲染组件
async renderComponent(instance: ComponentInstance, container: HTMLElement): Promise<void> {
const { definition, props, events } = instance;
if (definition.renderer && definition.renderer.render) {
// 使用自定义渲染器
instance.element = definition.renderer.render(props, events);
} else {
// 使用默认渲染器
instance.element = this.createDefaultElement(definition, props);
}
if (instance.element) {
container.appendChild(instance.element);
// 应用样式
if (definition.styles) {
this.applyStyles(definition.styles, instance.element);
}
}
}
// 更新组件属性
updateComponentProps(instance: ComponentInstance, newProps: any): void {
instance.props = { ...instance.props, ...newProps };
if (instance.definition.renderer && instance.definition.renderer.update && instance.element) {
instance.definition.renderer.update(instance.element, instance.props);
} else {
// 重新渲染
this.rerenderComponent(instance);
}
}
// 重新渲染组件
private rerenderComponent(instance: ComponentInstance): void {
if (!instance.element || !instance.element.parentElement) {
return;
}
const container = instance.element.parentElement;
container.removeChild(instance.element);
this.renderComponent(instance, container);
}
// 创建默认元素
private createDefaultElement(definition: ComponentDefinition, props: any): HTMLElement {
const element = document.createElement('div');
element.className = `component-${definition.type}`;
element.textContent = props.text || definition.name;
return element;
}
// 应用样式
private applyStyles(styles: string, element: HTMLElement): void {
const styleId = `component-styles-${Date.now()}`;
let styleElement = document.getElementById(styleId) as HTMLStyleElement;
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = styleId;
document.head.appendChild(styleElement);
}
styleElement.textContent = styles;
}
// 生成实例ID
private generateInstanceId(): string {
return `component-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// 销毁组件实例
destroyComponent(instance: ComponentInstance): void {
if (instance.element && instance.element.parentElement) {
instance.element.parentElement.removeChild(instance.element);
}
// 清理事件监听器
if (instance.events) {
Object.keys(instance.events).forEach(eventName => {
if (instance.element) {
instance.element.removeEventListener(eventName, instance.events[eventName]);
}
});
}
// 递归销毁子组件
instance.children.forEach(child => {
this.destroyComponent(child);
});
instance.children = [];
instance.element = null;
}
}
// 组件实例接口
interface ComponentInstance {
id: string;
componentId: string;
definition: ComponentDefinition;
props: any;
element: HTMLElement | null;
children: ComponentInstance[];
parent: ComponentInstance | null;
events: { [eventName: string]: Function };
state: any;
}
5.3.3 完整使用示例
// 完整的组件系统使用示例
class LowCodeComponentDemo {
private usageManager: ComponentUsageManager;
private container: HTMLElement;
constructor(container: HTMLElement) {
this.container = container;
}
async initialize(): Promise<void> {
// 初始化组件系统
const { registry, loader } = await initializeComponentSystem();
this.usageManager = new ComponentUsageManager(registry, loader);
// 创建演示界面
await this.createDemoInterface();
}
private async createDemoInterface(): Promise<void> {
// 创建主容器
const mainContainer = await this.usageManager.createComponentInstance(
'container-component',
{
direction: 'vertical',
gap: 20,
padding: '20px',
backgroundColor: '#f5f5f5'
},
this.container
);
// 创建标题
const title = await this.usageManager.createComponentInstance(
'text-component',
{
text: '低代码平台组件演示',
fontSize: '24px',
fontWeight: 'bold',
color: '#333',
textAlign: 'center'
},
mainContainer.element!
);
// 创建表单容器
const formContainer = await this.usageManager.createComponentInstance(
'container-component',
{
direction: 'vertical',
gap: 16,
padding: '20px',
backgroundColor: 'white',
border: '1px solid #e8e8e8',
borderRadius: '8px'
},
mainContainer.element!
);
// 创建输入框
const nameInput = await this.usageManager.createComponentInstance(
'input-component',
{
placeholder: '请输入您的姓名',
size: 'large',
prefix: 'icon-user'
},
formContainer.element!
);
// 创建按钮容器
const buttonContainer = await this.usageManager.createComponentInstance(
'container-component',
{
direction: 'horizontal',
gap: 12,
justify: 'center'
},
formContainer.element!
);
// 创建提交按钮
const submitButton = await this.usageManager.createComponentInstance(
'button-component',
{
text: '提交',
type: 'primary',
size: 'large'
},
buttonContainer.element!
);
// 创建重置按钮
const resetButton = await this.usageManager.createComponentInstance(
'button-component',
{
text: '重置',
type: 'secondary',
size: 'large'
},
buttonContainer.element!
);
// 绑定事件
this.bindEvents(nameInput, submitButton, resetButton);
}
private bindEvents(
nameInput: ComponentInstance,
submitButton: ComponentInstance,
resetButton: ComponentInstance
): void {
// 提交按钮事件
if (submitButton.element) {
submitButton.element.addEventListener('click', () => {
const input = nameInput.element?.querySelector('input') as HTMLInputElement;
const name = input?.value || '';
if (name.trim()) {
alert(`欢迎,${name}!`);
} else {
alert('请输入您的姓名');
}
});
}
// 重置按钮事件
if (resetButton.element) {
resetButton.element.addEventListener('click', () => {
const input = nameInput.element?.querySelector('input') as HTMLInputElement;
if (input) {
input.value = '';
}
});
}
}
}
// 启动演示
const startDemo = async () => {
const container = document.getElementById('app');
if (container) {
const demo = new LowCodeComponentDemo(container);
await demo.initialize();
}
};
// 页面加载完成后启动
document.addEventListener('DOMContentLoaded', startDemo);
5.4 小结
本章详细介绍了低代码平台的组件库系统,这是平台的核心功能模块之一。通过本章的学习,我们掌握了:
5.4.1 核心要点
组件库架构设计
- 组件定义标准化
- 组件注册与管理机制
- 组件加载与渲染系统
- 组件生命周期管理
组件实现技术
- TypeScript 类型定义
- 属性配置系统
- 事件处理机制
- 样式管理方案
- 渲染器模式
组件分类与功能
- 基础组件:文本、按钮、输入框
- 布局组件:容器、栅格系统
- 表单组件:各类输入控件
- 展示组件:图表、表格等
5.4.2 技术特色
- 高度可扩展:支持动态注册和加载外部组件
- 类型安全:完整的 TypeScript 类型定义
- 性能优化:组件懒加载和按需渲染
- 开发友好:清晰的组件开发规范
- 功能完整:涵盖属性、事件、样式、插槽等
5.4.3 最佳实践
组件设计原则
- 单一职责:每个组件专注特定功能
- 可复用性:通过属性配置实现灵活性
- 一致性:统一的设计语言和交互模式
性能优化策略
- 组件懒加载减少初始加载时间
- 虚拟滚动处理大量组件
- 样式隔离避免冲突
开发规范
- 完整的组件文档
- 标准化的属性命名
- 统一的事件处理模式
5.4.4 下一步学习
下一章我们将学习数据引擎与状态管理,了解如何: - 设计数据流架构 - 实现状态管理系统 - 处理数据绑定与同步 - 优化数据更新性能
组件库系统为低代码平台提供了丰富的UI构建能力,结合即将学习的数据引擎,将构成完整的应用开发基础设施。
try {
// 卸载旧版本
await this.unregisterLibrary(libraryId);
// 注册新版本
await this.registerLibrary(newLibrary);
// 触发更新事件
this.eventBus.emit('library:updated', {
oldLibrary: backup,
newLibrary: newLibrary
});
} catch (error) {
// 回滚到备份版本
await this.registerLibrary(backup);
throw error;
}
} catch (error) {
console.error(`Failed to update library "${library.name}":`, error);
throw error;
}
}
// 获取组件库统计信息 getLibraryStats(): LibraryStats { const libraries = Array.from(this.libraries.values());
return {
totalLibraries: libraries.length,
totalComponents: libraries.reduce((sum, lib) => sum + lib.components.length, 0),
librariesByType: this.groupLibrariesByType(libraries),
componentsByCategory: this.groupComponentsByCategory(libraries),
popularLibraries: this.getPopularLibraries(libraries)
};
}
private async validateLibrary(library: ComponentLibrary): Promise
// 验证版本格式
if (!this.versionManager.isValidVersion(library.version)) {
throw new Error(`Invalid version format: ${library.version}`);
}
// 检查是否已存在
if (this.libraries.has(library.id)) {
throw new Error(`Library "${library.id}" already exists`);
}
// 验证组件
for (const component of library.components) {
await this.validateComponent(component);
}
// 验证依赖
await this.validateDependencies(library.dependencies);
}
private async validateComponent(component: ComponentDefinition): Promise
// 检查组件是否已存在
if (this.componentRegistry.getComponent(component.type)) {
throw new Error(`Component type "${component.type}" already exists`);
}
// 验证属性定义
component.props.forEach(prop => {
if (!prop.name || !prop.type) {
throw new Error('Component property must have name and type');
}
});
}
private async validateDependencies(dependencies: LibraryDependency[]): Promise
if (dep.required && !library) {
throw new Error(`Required dependency "${dep.libraryId}" not found`);
}
if (library && !this.versionManager.isVersionCompatible(library.version, dep.version)) {
throw new Error(`Dependency version mismatch: ${dep.libraryId}@${dep.version}`);
}
}
}
private checkLibraryDependencies(libraryId: string): void { const dependentLibraries = Array.from(this.libraries.values()) .filter(lib => lib.dependencies.some(dep => dep.libraryId === libraryId && dep.required ) );
if (dependentLibraries.length > 0) {
const names = dependentLibraries.map(lib => lib.name).join(', ');
throw new Error(`Cannot unregister library. Required by: ${names}`);
}
}
private async fetchLibraryVersion(libraryId: string, version: string): Promise
private groupLibrariesByType(libraries: ComponentLibrary[]): Record
private groupComponentsByCategory(libraries: ComponentLibrary[]): Record
private getPopularLibraries(libraries: ComponentLibrary[]): ComponentLibrary[] { return libraries .sort((a, b) => (b.downloadCount || 0) - (a.downloadCount || 0)) .slice(0, 10); }
private async initializeBuiltinLibraries(): Promise
// 这里会添加内置组件
await this.registerLibrary(builtinLibrary);
} }
// 组件库接口定义 interface ComponentLibrary { id: string; name: string; version: string; description: string; author: string; type: ‘builtin’ | ‘official’ | ‘community’ | ‘private’; tags: string[]; components: ComponentDefinition[]; dependencies: LibraryDependency[]; assets: { css: string[]; js: string[]; }; downloadCount?: number; createdAt: Date; updatedAt: Date; }
interface LibraryDependency { libraryId: string; version: string; required: boolean; }
interface LibraryStats {
totalLibraries: number;
totalComponents: number;
librariesByType: Record
### 5.1.3 组件加载器
```typescript
// 组件加载器
class ComponentLoader {
private loadedAssets: Set<string> = new Set();
private loadingPromises: Map<string, Promise<void>> = new Map();
private eventBus: EventBus;
constructor(eventBus: EventBus) {
this.eventBus = eventBus;
}
// 加载组件库
async loadLibrary(library: ComponentLibrary): Promise<void> {
try {
// 加载依赖
await this.loadDependencies(library.dependencies);
// 加载CSS资源
await this.loadCSSAssets(library.assets.css);
// 加载JavaScript资源
await this.loadJSAssets(library.assets.js);
// 初始化组件
await this.initializeComponents(library.components);
console.log(`Library "${library.name}" loaded successfully`);
} catch (error) {
console.error(`Failed to load library "${library.name}":`, error);
throw error;
}
}
// 卸载组件库
async unloadLibrary(library: ComponentLibrary): Promise<void> {
try {
// 清理组件
await this.cleanupComponents(library.components);
// 卸载资源
await this.unloadAssets(library.assets);
console.log(`Library "${library.name}" unloaded successfully`);
} catch (error) {
console.error(`Failed to unload library "${library.name}":`, error);
throw error;
}
}
// 懒加载组件
async lazyLoadComponent(componentType: string): Promise<ComponentDefinition> {
const cacheKey = `component:${componentType}`;
// 检查是否已在加载中
if (this.loadingPromises.has(cacheKey)) {
await this.loadingPromises.get(cacheKey);
}
// 检查是否已加载
const component = this.getLoadedComponent(componentType);
if (component) {
return component;
}
// 开始加载
const loadingPromise = this.doLazyLoadComponent(componentType);
this.loadingPromises.set(cacheKey, loadingPromise);
try {
await loadingPromise;
return this.getLoadedComponent(componentType)!;
} finally {
this.loadingPromises.delete(cacheKey);
}
}
private async loadDependencies(dependencies: LibraryDependency[]): Promise<void> {
const requiredDeps = dependencies.filter(dep => dep.required);
for (const dep of requiredDeps) {
await this.loadDependency(dep);
}
}
private async loadDependency(dependency: LibraryDependency): Promise<void> {
// 这里应该加载依赖的组件库
// 为了示例,只是记录日志
console.log(`Loading dependency: ${dependency.libraryId}@${dependency.version}`);
}
private async loadCSSAssets(cssAssets: string[]): Promise<void> {
const loadPromises = cssAssets.map(asset => this.loadCSSAsset(asset));
await Promise.all(loadPromises);
}
private async loadCSSAsset(assetUrl: string): Promise<void> {
if (this.loadedAssets.has(assetUrl)) {
return;
}
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = assetUrl;
link.onload = () => {
this.loadedAssets.add(assetUrl);
resolve();
};
link.onerror = () => {
reject(new Error(`Failed to load CSS asset: ${assetUrl}`));
};
document.head.appendChild(link);
});
}
private async loadJSAssets(jsAssets: string[]): Promise<void> {
// 按顺序加载JavaScript资源
for (const asset of jsAssets) {
await this.loadJSAsset(asset);
}
}
private async loadJSAsset(assetUrl: string): Promise<void> {
if (this.loadedAssets.has(assetUrl)) {
return;
}
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = assetUrl;
script.async = false; // 保持加载顺序
script.onload = () => {
this.loadedAssets.add(assetUrl);
resolve();
};
script.onerror = () => {
reject(new Error(`Failed to load JS asset: ${assetUrl}`));
};
document.head.appendChild(script);
});
}
private async initializeComponents(components: ComponentDefinition[]): Promise<void> {
for (const component of components) {
await this.initializeComponent(component);
}
}
private async initializeComponent(component: ComponentDefinition): Promise<void> {
try {
// 验证组件
this.validateComponentDefinition(component);
// 预编译组件模板
if (component.template) {
component.compiledTemplate = await this.compileTemplate(component.template);
}
// 初始化组件样式
if (component.styles) {
await this.initializeComponentStyles(component);
}
// 触发组件初始化事件
this.eventBus.emit('component:initialized', component);
console.log(`Component "${component.type}" initialized`);
} catch (error) {
console.error(`Failed to initialize component "${component.type}":`, error);
throw error;
}
}
private async cleanupComponents(components: ComponentDefinition[]): Promise<void> {
for (const component of components) {
await this.cleanupComponent(component);
}
}
private async cleanupComponent(component: ComponentDefinition): Promise<void> {
try {
// 清理组件样式
if (component.styles) {
await this.cleanupComponentStyles(component);
}
// 清理编译缓存
delete component.compiledTemplate;
// 触发组件清理事件
this.eventBus.emit('component:cleaned', component);
console.log(`Component "${component.type}" cleaned up`);
} catch (error) {
console.error(`Failed to cleanup component "${component.type}":`, error);
}
}
private async unloadAssets(assets: { css: string[]; js: string[] }): Promise<void> {
// 卸载CSS资源
assets.css.forEach(assetUrl => {
const link = document.querySelector(`link[href="${assetUrl}"]`);
if (link) {
link.remove();
this.loadedAssets.delete(assetUrl);
}
});
// 卸载JavaScript资源
assets.js.forEach(assetUrl => {
const script = document.querySelector(`script[src="${assetUrl}"]`);
if (script) {
script.remove();
this.loadedAssets.delete(assetUrl);
}
});
}
private async doLazyLoadComponent(componentType: string): Promise<void> {
// 这里应该实现组件的懒加载逻辑
// 例如从远程服务器加载组件定义
console.log(`Lazy loading component: ${componentType}`);
}
private getLoadedComponent(componentType: string): ComponentDefinition | null {
// 这里应该从组件注册表获取已加载的组件
return null;
}
private validateComponentDefinition(component: ComponentDefinition): void {
if (!component.id || !component.type || !component.name) {
throw new Error('Component definition must have id, type, and name');
}
}
private async compileTemplate(template: string): Promise<Function> {
// 这里应该实现模板编译逻辑
// 例如将模板编译为渲染函数
return new Function('props', `return \`${template}\``);
}
private async initializeComponentStyles(component: ComponentDefinition): Promise<void> {
if (!component.styles) return;
const styleId = `component-style-${component.type}`;
let styleElement = document.getElementById(styleId) as HTMLStyleElement;
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = styleId;
document.head.appendChild(styleElement);
}
styleElement.textContent = component.styles;
}
private async cleanupComponentStyles(component: ComponentDefinition): Promise<void> {
const styleId = `component-style-${component.type}`;
const styleElement = document.getElementById(styleId);
if (styleElement) {
styleElement.remove();
}
}
}
5.2 内置组件实现
5.2.1 基础组件
// 文本组件
const TextComponent: ComponentDefinition = {
id: 'text-component',
name: '文本',
type: 'text',
category: 'basic',
icon: 'icon-text',
description: '用于显示文本内容的组件',
version: '1.0.0',
props: [
{
name: 'text',
type: 'textarea',
label: '文本内容',
defaultValue: '文本内容',
required: true,
description: '要显示的文本内容'
},
{
name: 'fontSize',
type: 'number',
label: '字体大小',
defaultValue: 14,
min: 8,
max: 72,
description: '文字的字体大小,单位为像素'
},
{
name: 'color',
type: 'color',
label: '文字颜色',
defaultValue: '#333333',
description: '文字的颜色'
},
{
name: 'fontWeight',
type: 'select',
label: '字体粗细',
defaultValue: 'normal',
options: [
{ label: '正常', value: 'normal' },
{ label: '粗体', value: 'bold' },
{ label: '细体', value: 'lighter' },
{ label: '特粗', value: 'bolder' }
],
description: '文字的粗细程度'
},
{
name: 'textAlign',
type: 'select',
label: '文字对齐',
defaultValue: 'left',
options: [
{ label: '左对齐', value: 'left' },
{ label: '居中', value: 'center' },
{ label: '右对齐', value: 'right' },
{ label: '两端对齐', value: 'justify' }
],
description: '文字的对齐方式'
},
{
name: 'lineHeight',
type: 'number',
label: '行高',
defaultValue: 1.5,
min: 1,
max: 3,
step: 0.1,
description: '文字的行高倍数'
}
],
events: [
{
name: 'click',
label: '点击事件',
params: [
{ name: 'event', type: 'MouseEvent', description: '鼠标事件对象' },
{ name: 'component', type: 'ComponentInstance', description: '组件实例' }
],
description: '当文本被点击时触发'
},
{
name: 'mouseenter',
label: '鼠标进入',
params: [
{ name: 'event', type: 'MouseEvent', description: '鼠标事件对象' }
],
description: '当鼠标进入文本区域时触发'
},
{
name: 'mouseleave',
label: '鼠标离开',
params: [
{ name: 'event', type: 'MouseEvent', description: '鼠标事件对象' }
],
description: '当鼠标离开文本区域时触发'
}
],
slots: [],
defaultProps: {
text: '文本内容',
fontSize: 14,
color: '#333333',
fontWeight: 'normal',
textAlign: 'left',
lineHeight: 1.5
},
template: `
<div
class="text-component"
style="
font-size: {{fontSize}}px;
color: {{color}};
font-weight: {{fontWeight}};
text-align: {{textAlign}};
line-height: {{lineHeight}};
"
@click="handleClick"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
{{text}}
</div>
`,
styles: `
.text-component {
display: inline-block;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
cursor: default;
transition: all 0.2s ease;
}
.text-component:hover {
opacity: 0.8;
}
.text-component.selectable {
user-select: text;
}
`,
preview: '/previews/text-component.png',
renderer: {
render: (props: any, events: any) => {
const element = document.createElement('div');
element.className = 'text-component';
element.textContent = props.text || '';
// 应用样式
Object.assign(element.style, {
fontSize: `${props.fontSize || 14}px`,
color: props.color || '#333333',
fontWeight: props.fontWeight || 'normal',
textAlign: props.textAlign || 'left',
lineHeight: props.lineHeight || 1.5
});
// 绑定事件
if (events.click) {
element.addEventListener('click', events.click);
}
if (events.mouseenter) {
element.addEventListener('mouseenter', events.mouseenter);
}
if (events.mouseleave) {
element.addEventListener('mouseleave', events.mouseleave);
}
return element;
},
update: (element: HTMLElement, props: any) => {
element.textContent = props.text || '';
Object.assign(element.style, {
fontSize: `${props.fontSize || 14}px`,
color: props.color || '#333333',
fontWeight: props.fontWeight || 'normal',
textAlign: props.textAlign || 'left',
lineHeight: props.lineHeight || 1.5
});
}
}
};
// 按钮组件
const ButtonComponent: ComponentDefinition = {
id: 'button-component',
name: '按钮',
type: 'button',
category: 'basic',
icon: 'icon-button',
description: '可点击的按钮组件',
version: '1.0.0',
props: [
{
name: 'text',
type: 'string',
label: '按钮文字',
defaultValue: '按钮',
required: true,
description: '按钮上显示的文字'
},
{
name: 'type',
type: 'select',
label: '按钮类型',
defaultValue: 'primary',
options: [
{ label: '主要按钮', value: 'primary' },
{ label: '次要按钮', value: 'secondary' },
{ label: '成功按钮', value: 'success' },
{ label: '警告按钮', value: 'warning' },
{ label: '危险按钮', value: 'danger' },
{ label: '信息按钮', value: 'info' },
{ label: '链接按钮', value: 'link' }
],
description: '按钮的视觉类型'
},
{
name: 'size',
type: 'select',
label: '按钮大小',
defaultValue: 'medium',
options: [
{ label: '小', value: 'small' },
{ label: '中', value: 'medium' },
{ label: '大', value: 'large' }
],
description: '按钮的大小'
},
{
name: 'disabled',
type: 'boolean',
label: '禁用状态',
defaultValue: false,
description: '是否禁用按钮'
},
{
name: 'loading',
type: 'boolean',
label: '加载状态',
defaultValue: false,
description: '是否显示加载状态'
},
{
name: 'icon',
type: 'string',
label: '图标',
defaultValue: '',
description: '按钮图标的类名'
},
{
name: 'iconPosition',
type: 'select',
label: '图标位置',
defaultValue: 'left',
options: [
{ label: '左侧', value: 'left' },
{ label: '右侧', value: 'right' }
],
description: '图标相对于文字的位置'
}
],
events: [
{
name: 'click',
label: '点击事件',
params: [
{ name: 'event', type: 'MouseEvent', description: '鼠标事件对象' },
{ name: 'component', type: 'ComponentInstance', description: '组件实例' }
],
description: '当按钮被点击时触发'
},
{
name: 'focus',
label: '获得焦点',
params: [
{ name: 'event', type: 'FocusEvent', description: '焦点事件对象' }
],
description: '当按钮获得焦点时触发'
},
{
name: 'blur',
label: '失去焦点',
params: [
{ name: 'event', type: 'FocusEvent', description: '焦点事件对象' }
],
description: '当按钮失去焦点时触发'
}
],
slots: [],
defaultProps: {
text: '按钮',
type: 'primary',
size: 'medium',
disabled: false,
loading: false,
icon: '',
iconPosition: 'left'
},
template: `
<button
class="btn btn-{{type}} btn-{{size}}"
:disabled="disabled || loading"
@click="handleClick"
@focus="handleFocus"
@blur="handleBlur"
>
<span v-if="loading" class="btn-loading">
<i class="icon-loading"></i>
</span>
<span v-else-if="icon && iconPosition === 'left'" class="btn-icon">
<i :class="icon"></i>
</span>
<span class="btn-text">{{text}}</span>
<span v-if="icon && iconPosition === 'right'" class="btn-icon">
<i :class="icon"></i>
</span>
</button>
`,
styles: `
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
font-weight: 500;
text-decoration: none;
transition: all 0.2s ease;
outline: none;
user-select: none;
}
.btn:focus {
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.btn:disabled {
cursor: not-allowed;
opacity: 0.6;
}
/* 按钮大小 */
.btn-small {
padding: 4px 8px;
font-size: 12px;
min-height: 24px;
}
.btn-medium {
padding: 8px 16px;
font-size: 14px;
min-height: 32px;
}
.btn-large {
padding: 12px 24px;
font-size: 16px;
min-height: 40px;
}
/* 按钮类型 */
.btn-primary {
background-color: #1890ff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #40a9ff;
}
.btn-secondary {
background-color: #f5f5f5;
color: #333;
border: 1px solid #d9d9d9;
}
.btn-secondary:hover:not(:disabled) {
background-color: #e6f7ff;
border-color: #1890ff;
color: #1890ff;
}
.btn-success {
background-color: #52c41a;
color: white;
}
.btn-success:hover:not(:disabled) {
background-color: #73d13d;
}
.btn-warning {
background-color: #faad14;
color: white;
}
.btn-warning:hover:not(:disabled) {
background-color: #ffc53d;
}
.btn-danger {
background-color: #ff4d4f;
color: white;
}
.btn-danger:hover:not(:disabled) {
background-color: #ff7875;
}
.btn-info {
background-color: #1890ff;
color: white;
}
.btn-info:hover:not(:disabled) {
background-color: #40a9ff;
}
.btn-link {
background-color: transparent;
color: #1890ff;
border: none;
padding: 0;
min-height: auto;
}
.btn-link:hover:not(:disabled) {
color: #40a9ff;
text-decoration: underline;
}
/* 加载状态 */
.btn-loading {
display: inline-flex;
align-items: center;
}
.icon-loading {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 图标 */
.btn-icon {
display: inline-flex;
align-items: center;
}
`,
preview: '/previews/button-component.png',
renderer: {
render: (props: any, events: any) => {
const button = document.createElement('button');
button.className = `btn btn-${props.type || 'primary'} btn-${props.size || 'medium'}`;
button.disabled = props.disabled || props.loading;
// 构建按钮内容
const content = [];
if (props.loading) {
content.push('<span class="btn-loading"><i class="icon-loading"></i></span>');
} else if (props.icon && props.iconPosition === 'left') {
content.push(`<span class="btn-icon"><i class="${props.icon}"></i></span>`);
}
content.push(`<span class="btn-text">${props.text || ''}</span>`);
if (props.icon && props.iconPosition === 'right') {
content.push(`<span class="btn-icon"><i class="${props.icon}"></i></span>`);
}
button.innerHTML = content.join('');
// 绑定事件
if (events.click) {
button.addEventListener('click', events.click);
}
if (events.focus) {
button.addEventListener('focus', events.focus);
}
if (events.blur) {
button.addEventListener('blur', events.blur);
}
return button;
},
update: (element: HTMLButtonElement, props: any) => {
element.className = `btn btn-${props.type || 'primary'} btn-${props.size || 'medium'}`;
element.disabled = props.disabled || props.loading;
// 更新按钮内容
const content = [];
if (props.loading) {
content.push('<span class="btn-loading"><i class="icon-loading"></i></span>');
} else if (props.icon && props.iconPosition === 'left') {
content.push(`<span class="btn-icon"><i class="${props.icon}"></i></span>`);
}
content.push(`<span class="btn-text">${props.text || ''}</span>`);
if (props.icon && props.iconPosition === 'right') {
content.push(`<span class="btn-icon"><i class="${props.icon}"></i></span>`);
}
element.innerHTML = content.join('');
}
}
};
// 输入框组件
const InputComponent: ComponentDefinition = {
id: 'input-component',
name: '输入框',
type: 'input',
category: 'form',
icon: 'icon-input',
description: '用于用户输入文本的组件',
version: '1.0.0',
props: [
{
name: 'value',
type: 'string',
label: '输入值',
defaultValue: '',
description: '输入框的值'
},
{
name: 'placeholder',
type: 'string',
label: '占位符',
defaultValue: '请输入内容',
description: '输入框的占位符文本'
},
{
name: 'type',
type: 'select',
label: '输入类型',
defaultValue: 'text',
options: [
{ label: '文本', value: 'text' },
{ label: '密码', value: 'password' },
{ label: '邮箱', value: 'email' },
{ label: '数字', value: 'number' },
{ label: '电话', value: 'tel' },
{ label: '网址', value: 'url' },
{ label: '搜索', value: 'search' }
],
description: '输入框的类型'
},
{
name: 'size',
type: 'select',
label: '输入框大小',
defaultValue: 'medium',
options: [
{ label: '小', value: 'small' },
{ label: '中', value: 'medium' },
{ label: '大', value: 'large' }
],
description: '输入框的大小'
},
{
name: 'disabled',
type: 'boolean',
label: '禁用状态',
defaultValue: false,
description: '是否禁用输入框'
},
{
name: 'readonly',
type: 'boolean',
label: '只读状态',
defaultValue: false,
description: '是否为只读状态'
},
{
name: 'required',
type: 'boolean',
label: '必填项',
defaultValue: false,
description: '是否为必填项'
},
{
name: 'maxLength',
type: 'number',
label: '最大长度',
defaultValue: 0,
min: 0,
description: '输入内容的最大长度,0表示无限制'
},
{
name: 'showClear',
type: 'boolean',
label: '显示清除按钮',
defaultValue: false,
description: '是否显示清除按钮'
},
{
name: 'prefix',
type: 'string',
label: '前缀图标',
defaultValue: '',
description: '输入框前缀图标的类名'
},
{
name: 'suffix',
type: 'string',
label: '后缀图标',
defaultValue: '',
description: '输入框后缀图标的类名'
}
],
events: [
{
name: 'input',
label: '输入事件',
params: [
{ name: 'value', type: 'string', description: '输入的值' },
{ name: 'event', type: 'InputEvent', description: '输入事件对象' }
],
description: '当输入内容时触发'
},
{
name: 'change',
label: '变化事件',
params: [
{ name: 'value', type: 'string', description: '输入的值' },
{ name: 'event', type: 'Event', description: '变化事件对象' }
],
description: '当输入内容变化并失去焦点时触发'
},
{
name: 'focus',
label: '获得焦点',
params: [
{ name: 'event', type: 'FocusEvent', description: '焦点事件对象' }
],
description: '当输入框获得焦点时触发'
},
{
name: 'blur',
label: '失去焦点',
params: [
{ name: 'event', type: 'FocusEvent', description: '焦点事件对象' }
],
description: '当输入框失去焦点时触发'
},
{
name: 'clear',
label: '清除事件',
params: [],
description: '当点击清除按钮时触发'
}
],
slots: [],
defaultProps: {
value: '',
placeholder: '请输入内容',
type: 'text',
size: 'medium',
disabled: false,
readonly: false,
required: false,
maxLength: 0,
showClear: false,
prefix: '',
suffix: ''
},
template: `
<div class="input-wrapper input-{{size}}" :class="{ 'input-disabled': disabled, 'input-focused': focused }">
<span v-if="prefix" class="input-prefix">
<i :class="prefix"></i>
</span>
<input
class="input-control"
:type="type"
:value="value"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:required="required"
:maxlength="maxLength > 0 ? maxLength : null"
@input="handleInput"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
>
<span v-if="showClear && value" class="input-clear" @click="handleClear">
<i class="icon-close"></i>
</span>
<span v-if="suffix" class="input-suffix">
<i :class="suffix"></i>
</span>
</div>
`,
styles: `
.input-wrapper {
display: inline-flex;
align-items: center;
border: 1px solid #d9d9d9;
border-radius: 4px;
background-color: white;
transition: all 0.2s ease;
position: relative;
}
.input-wrapper:hover:not(.input-disabled) {
border-color: #40a9ff;
}
.input-wrapper.input-focused {
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.input-wrapper.input-disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
.input-control {
flex: 1;
border: none;
outline: none;
background: transparent;
font-family: inherit;
font-size: inherit;
color: #333;
}
.input-control::placeholder {
color: #bfbfbf;
}
.input-control:disabled {
cursor: not-allowed;
color: #bfbfbf;
}
/* 输入框大小 */
.input-small {
padding: 4px 8px;
font-size: 12px;
min-height: 24px;
}
.input-medium {
padding: 8px 12px;
font-size: 14px;
min-height: 32px;
}
.input-large {
padding: 12px 16px;
font-size: 16px;
min-height: 40px;
}
/* 前缀和后缀 */
.input-prefix,
.input-suffix {
display: flex;
align-items: center;
color: #8c8c8c;
}
.input-prefix {
margin-right: 8px;
}
.input-suffix {
margin-left: 8px;
}
/* 清除按钮 */
.input-clear {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
margin-left: 4px;
cursor: pointer;
color: #bfbfbf;
border-radius: 50%;
transition: all 0.2s ease;
}
.input-clear:hover {
background-color: #f5f5f5;
color: #8c8c8c;
}
`,
preview: '/previews/input-component.png',
renderer: {
render: (props: any, events: any) => {
const wrapper = document.createElement('div');
wrapper.className = `input-wrapper input-${props.size || 'medium'}`;
if (props.disabled) {
wrapper.classList.add('input-disabled');
}
const content = [];
// 前缀图标
if (props.prefix) {
content.push(`<span class="input-prefix"><i class="${props.prefix}"></i></span>`);
}
// 输入框
const inputAttrs = [
`type="${props.type || 'text'}"`,
`value="${props.value || ''}"`,
`placeholder="${props.placeholder || ''}"`,
props.disabled ? 'disabled' : '',
props.readonly ? 'readonly' : '',
props.required ? 'required' : '',
props.maxLength > 0 ? `maxlength="${props.maxLength}"` : ''
].filter(Boolean).join(' ');
content.push(`<input class="input-control" ${inputAttrs}>`);
// 清除按钮
if (props.showClear && props.value) {
content.push('<span class="input-clear"><i class="icon-close"></i></span>');
}
// 后缀图标
if (props.suffix) {
content.push(`<span class="input-suffix"><i class="${props.suffix}"></i></span>`);
}
wrapper.innerHTML = content.join('');
// 绑定事件
const input = wrapper.querySelector('.input-control') as HTMLInputElement;
const clearBtn = wrapper.querySelector('.input-clear');
if (input) {
if (events.input) {
input.addEventListener('input', (e) => {
events.input((e.target as HTMLInputElement).value, e);
});
}
if (events.change) {
input.addEventListener('change', (e) => {
events.change((e.target as HTMLInputElement).value, e);
});
}
if (events.focus) {
input.addEventListener('focus', (e) => {
wrapper.classList.add('input-focused');
events.focus(e);
});
}
if (events.blur) {
input.addEventListener('blur', (e) => {
wrapper.classList.remove('input-focused');
events.blur(e);
});
}
}
if (clearBtn && events.clear) {
clearBtn.addEventListener('click', () => {
if (input) {
input.value = '';
input.dispatchEvent(new Event('input', { bubbles: true }));
}
events.clear();
});
}
return wrapper;
},
update: (element: HTMLElement, props: any) => {
const input = element.querySelector('.input-control') as HTMLInputElement;
if (input) {
input.value = props.value || '';
input.placeholder = props.placeholder || '';
input.disabled = props.disabled;
input.readOnly = props.readonly;
input.required = props.required;
if (props.maxLength > 0) {
input.maxLength = props.maxLength;
} else {
input.removeAttribute('maxlength');
}
}
// 更新样式类
element.className = `input-wrapper input-${props.size || 'medium'}`;
if (props.disabled) {
element.classList.add('input-disabled');
}
}
}
};
5.2.2 布局组件
// 容器组件
const ContainerComponent: ComponentDefinition = {
id: 'container-component',
name: '容器',
type: 'container',
category: 'layout',
icon: 'icon-container',
description: '用于包含其他组件的容器',
version: '1.0.0',
props: [
{
name: 'direction',
type: 'select',
label: '排列方向',
defaultValue: 'vertical',
options: [
{ label: '垂直', value: 'vertical' },
{ label: '水平', value: 'horizontal' }
],
description: '子组件的排列方向'
},
{
name: 'justify',
type: 'select',
label: '主轴对齐',
defaultValue: 'flex-start',
options: [
{ label: '起始对齐', value: 'flex-start' },
{ label: '居中对齐', value: 'center' },
{ label: '末尾对齐', value: 'flex-end' },
{ label: '两端对齐', value: 'space-between' },
{ label: '环绕对齐', value: 'space-around' },
{ label: '均匀分布', value: 'space-evenly' }
],
description: '主轴上的对齐方式'
},
{
name: 'align',
type: 'select',
label: '交叉轴对齐',
defaultValue: 'stretch',
options: [
{ label: '拉伸', value: 'stretch' },
{ label: '起始对齐', value: 'flex-start' },
{ label: '居中对齐', value: 'center' },
{ label: '末尾对齐', value: 'flex-end' },
{ label: '基线对齐', value: 'baseline' }
],
description: '交叉轴上的对齐方式'
},
{
name: 'gap',
type: 'number',
label: '间距',
defaultValue: 0,
min: 0,
description: '子组件之间的间距,单位为像素'
},
{
name: 'wrap',
type: 'boolean',
label: '允许换行',
defaultValue: false,
description: '是否允许子组件换行'
},
{
name: 'padding',
type: 'string',
label: '内边距',
defaultValue: '0',
description: '容器的内边距'
},
{
name: 'backgroundColor',
type: 'color',
label: '背景色',
defaultValue: 'transparent',
description: '容器的背景色'
},
{
name: 'border',
type: 'string',
label: '边框',
defaultValue: 'none',
description: '容器的边框样式'
},
{
name: 'borderRadius',
type: 'string',
label: '圆角',
defaultValue: '0',
description: '容器的圆角大小'
}
],
events: [
{
name: 'click',
label: '点击事件',
params: [
{ name: 'event', type: 'MouseEvent', description: '鼠标事件对象' }
],
description: '当容器被点击时触发'
}
],
slots: [
{
name: 'default',
label: '默认插槽',
description: '容器的主要内容区域'
}
],
defaultProps: {
direction: 'vertical',
justify: 'flex-start',
align: 'stretch',
gap: 0,
wrap: false,
padding: '0',
backgroundColor: 'transparent',
border: 'none',
borderRadius: '0'
},
template: `
<div
class="container-component"
:style="{
flexDirection: direction === 'horizontal' ? 'row' : 'column',
justifyContent: justify,
alignItems: align,
gap: gap + 'px',
flexWrap: wrap ? 'wrap' : 'nowrap',
padding: padding,
backgroundColor: backgroundColor,
border: border,
borderRadius: borderRadius
}"
@click="handleClick"
>
<slot name="default"></slot>
</div>
`,
styles: `
.container-component {
display: flex;
min-height: 50px;
min-width: 100px;
position: relative;
}
.container-component.container-empty {
border: 2px dashed #d9d9d9;
background-color: #fafafa;
}
.container-component.container-empty::after {
content: '拖拽组件到此处';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #bfbfbf;
font-size: 14px;
pointer-events: none;
}
.container-component.drop-target {
border: 2px dashed #1890ff;
background-color: rgba(24, 144, 255, 0.05);
}
`,
preview: '/previews/container-component.png',
renderer: {
render: (props: any, events: any) => {
const container = document.createElement('div');
container.className = 'container-component';
// 应用样式
Object.assign(container.style, {
flexDirection: props.direction === 'horizontal' ? 'row' : 'column',
justifyContent: props.justify || 'flex-start',
alignItems: props.align || 'stretch',
gap: `${props.gap || 0}px`,
flexWrap: props.wrap ? 'wrap' : 'nowrap',
padding: props.padding || '0',
backgroundColor: props.backgroundColor || 'transparent',
border: props.border || 'none',
borderRadius: props.borderRadius || '0'
});
// 绑定事件
if (events.click) {
container.addEventListener('click', events.click);
}
return container;
},
update: (element: HTMLElement, props: any) => {
Object.assign(element.style, {
flexDirection: props.direction === 'horizontal' ? 'row' : 'column',
justifyContent: props.justify || 'flex-start',
alignItems: props.align || 'stretch',
gap: `${props.gap || 0}px`,
flexWrap: props.wrap ? 'wrap' : 'nowrap',
padding: props.padding || '0',
backgroundColor: props.backgroundColor || 'transparent',
border: props.border || 'none',
borderRadius: props.borderRadius || '0'
});
}
}
};
// 栅格组件
const GridComponent: ComponentDefinition = {
id: 'grid-component',
name: '栅格',
type: 'grid',
category: 'layout',
icon: 'icon-grid',
description: '基于CSS Grid的栅格布局组件',
version: '1.0.0',
props: [
{
name: 'columns',
type: 'number',
label: '列数',
defaultValue: 12,
min: 1,
max: 24,
description: '栅格的列数'
},
{
name: 'rows',
type: 'number',
label: '行数',
defaultValue: 1,
min: 1,
description: '栅格的行数'
},
{
name: 'columnGap',
type: 'number',
label: '列间距',
defaultValue: 16,
min: 0,
description: '列之间的间距,单位为像素'
},
{
name: 'rowGap',
type: 'number',
label: '行间距',
defaultValue: 16,
min: 0,
description: '行之间的间距,单位为像素'
},
{
name: 'autoRows',
type: 'string',
label: '自动行高',
defaultValue: 'auto',
description: '自动生成行的高度'
},
{
name: 'justifyItems',
type: 'select',
label: '项目水平对齐',
defaultValue: 'stretch',
options: [
{ label: '拉伸', value: 'stretch' },
{ label: '起始对齐', value: 'start' },
{ label: '居中对齐', value: 'center' },
{ label: '末尾对齐', value: 'end' }
],
description: '栅格项目的水平对齐方式'
},
{
name: 'alignItems',
type: 'select',
label: '项目垂直对齐',
defaultValue: 'stretch',
options: [
{ label: '拉伸', value: 'stretch' },
{ label: '起始对齐', value: 'start' },
{ label: '居中对齐', value: 'center' },
{ label: '末尾对齐', value: 'end' }
],
description: '栅格项目的垂直对齐方式'
}
],
events: [
{
name: 'click',
label: '点击事件',
params: [
{ name: 'event', type: 'MouseEvent', description: '鼠标事件对象' }
],
description: '当栅格被点击时触发'
}
],
slots: [
{
name: 'default',
label: '默认插槽',
description: '栅格的内容区域'
}
],
defaultProps: {
columns: 12,
rows: 1,
columnGap: 16,
rowGap: 16,
autoRows: 'auto',
justifyItems: 'stretch',
alignItems: 'stretch'
},
template: `
<div
class="grid-component"
:style="{
gridTemplateColumns: 'repeat(' + columns + ', 1fr)',
gridTemplateRows: rows > 1 ? 'repeat(' + rows + ', 1fr)' : 'none',
gridAutoRows: autoRows,
columnGap: columnGap + 'px',
rowGap: rowGap + 'px',
justifyItems: justifyItems,
alignItems: alignItems
}"
@click="handleClick"
>
<slot name="default"></slot>
</div>
`,
styles: `
.grid-component {
display: grid;
min-height: 100px;
width: 100%;
}
.grid-component.grid-empty {
border: 2px dashed #d9d9d9;
background-color: #fafafa;
position: relative;
}
.grid-component.grid-empty::after {
content: '拖拽组件到栅格中';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #bfbfbf;
font-size: 14px;
pointer-events: none;
}
.grid-component.drop-target {
border: 2px dashed #1890ff;
background-color: rgba(24, 144, 255, 0.05);
}
`,
preview: '/previews/grid-component.png',
renderer: {
render: (props: any, events: any) => {
const grid = document.createElement('div');
grid.className = 'grid-component';
// 应用样式
Object.assign(grid.style, {
gridTemplateColumns: `repeat(${props.columns || 12}, 1fr)`,
gridTemplateRows: props.rows > 1 ? `repeat(${props.rows}, 1fr)` : 'none',
gridAutoRows: props.autoRows || 'auto',
columnGap: `${props.columnGap || 16}px`,
rowGap: `${props.rowGap || 16}px`,
justifyItems: props.justifyItems || 'stretch',
alignItems: props.alignItems || 'stretch'
});
// 绑定事件
if (events.click) {
grid.addEventListener('click', events.click);
}
return grid;
},
update: (element: HTMLElement, props: any) => {
Object.assign(element.style, {
gridTemplateColumns: `repeat(${props.columns || 12}, 1fr)`,
gridTemplateRows: props.rows > 1 ? `repeat(${props.rows}, 1fr)` : 'none',
gridAutoRows: props.autoRows || 'auto',
columnGap: `${props.columnGap || 16}px`,
rowGap: `${props.rowGap || 16}px`,
justifyItems: props.justifyItems || 'stretch',
alignItems: props.alignItems || 'stretch'
});
}
}
};