9.1 页面生成器架构概览
9.1.1 页面生成器接口
// 页面生成器接口
interface PageGenerator {
// 页面管理
createPage(definition: PageDefinition): Promise<Page>;
updatePage(pageId: string, definition: Partial<PageDefinition>): Promise<Page>;
deletePage(pageId: string): Promise<void>;
getPage(pageId: string): Promise<Page | null>;
listPages(query?: PageQuery): Promise<Page[]>;
// 页面渲染
renderPage(pageId: string, context?: RenderContext): Promise<string>;
previewPage(definition: PageDefinition, context?: RenderContext): Promise<string>;
// 页面发布
publishPage(pageId: string, options?: PublishOptions): Promise<PublishResult>;
unpublishPage(pageId: string): Promise<void>;
// 页面模板
createTemplate(template: PageTemplate): Promise<PageTemplate>;
getTemplate(templateId: string): Promise<PageTemplate | null>;
listTemplates(): Promise<PageTemplate[]>;
// 事件处理
on(event: string, handler: Function): void;
off(event: string, handler: Function): void;
emit(event: string, data: any): void;
}
// 低代码页面生成器实现
class LowCodePageGenerator implements PageGenerator {
private pages: Map<string, Page> = new Map();
private templates: Map<string, PageTemplate> = new Map();
private renderer: PageRenderer;
private eventBus: EventBus;
private componentRegistry: ComponentRegistry;
private themeManager: ThemeManager;
constructor(options: PageGeneratorOptions) {
this.eventBus = options.eventBus || new EventBus();
this.componentRegistry = options.componentRegistry;
this.themeManager = options.themeManager || new DefaultThemeManager();
this.renderer = new PageRenderer({
componentRegistry: this.componentRegistry,
themeManager: this.themeManager,
eventBus: this.eventBus
});
this.initializeBuiltinTemplates();
}
async createPage(definition: PageDefinition): Promise<Page> {
// 验证页面定义
this.validatePageDefinition(definition);
// 创建页面实例
const page = new Page({
id: definition.id || this.generatePageId(),
title: definition.title,
description: definition.description,
layout: definition.layout,
components: definition.components || [],
styles: definition.styles || {},
scripts: definition.scripts || [],
meta: definition.meta || {},
settings: definition.settings || {},
createdAt: new Date(),
updatedAt: new Date()
});
// 存储页面
this.pages.set(page.id, page);
// 触发事件
this.emit('page:created', { page });
return page;
}
async updatePage(pageId: string, definition: Partial<PageDefinition>): Promise<Page> {
const page = this.pages.get(pageId);
if (!page) {
throw new Error(`Page ${pageId} not found`);
}
// 更新页面属性
if (definition.title !== undefined) page.title = definition.title;
if (definition.description !== undefined) page.description = definition.description;
if (definition.layout !== undefined) page.layout = definition.layout;
if (definition.components !== undefined) page.components = definition.components;
if (definition.styles !== undefined) page.styles = definition.styles;
if (definition.scripts !== undefined) page.scripts = definition.scripts;
if (definition.meta !== undefined) page.meta = definition.meta;
if (definition.settings !== undefined) page.settings = definition.settings;
page.updatedAt = new Date();
// 触发事件
this.emit('page:updated', { page, changes: definition });
return page;
}
async deletePage(pageId: string): Promise<void> {
const page = this.pages.get(pageId);
if (!page) {
throw new Error(`Page ${pageId} not found`);
}
// 删除页面
this.pages.delete(pageId);
// 触发事件
this.emit('page:deleted', { pageId, page });
}
async getPage(pageId: string): Promise<Page | null> {
return this.pages.get(pageId) || null;
}
async listPages(query?: PageQuery): Promise<Page[]> {
let pages = Array.from(this.pages.values());
if (query) {
// 应用查询过滤
if (query.title) {
pages = pages.filter(page =>
page.title.toLowerCase().includes(query.title!.toLowerCase())
);
}
if (query.status) {
pages = pages.filter(page => page.status === query.status);
}
if (query.tags && query.tags.length > 0) {
pages = pages.filter(page =>
query.tags!.some(tag => page.meta.tags?.includes(tag))
);
}
// 排序
if (query.orderBy) {
pages.sort((a, b) => {
const aValue = (a as any)[query.orderBy!];
const bValue = (b as any)[query.orderBy!];
if (query.orderDirection === 'desc') {
return bValue > aValue ? 1 : -1;
}
return aValue > bValue ? 1 : -1;
});
}
// 分页
if (query.offset !== undefined || query.limit !== undefined) {
const offset = query.offset || 0;
const limit = query.limit || 10;
pages = pages.slice(offset, offset + limit);
}
}
return pages;
}
async renderPage(pageId: string, context?: RenderContext): Promise<string> {
const page = this.pages.get(pageId);
if (!page) {
throw new Error(`Page ${pageId} not found`);
}
return this.renderer.render(page, context);
}
async previewPage(definition: PageDefinition, context?: RenderContext): Promise<string> {
// 创建临时页面实例
const tempPage = new Page({
id: 'preview',
title: definition.title,
description: definition.description,
layout: definition.layout,
components: definition.components || [],
styles: definition.styles || {},
scripts: definition.scripts || [],
meta: definition.meta || {},
settings: definition.settings || {},
createdAt: new Date(),
updatedAt: new Date()
});
return this.renderer.render(tempPage, context);
}
async publishPage(pageId: string, options?: PublishOptions): Promise<PublishResult> {
const page = this.pages.get(pageId);
if (!page) {
throw new Error(`Page ${pageId} not found`);
}
// 渲染页面
const html = await this.renderPage(pageId, options?.context);
// 发布页面
const publishResult: PublishResult = {
pageId,
url: this.generatePublishUrl(page, options),
html,
publishedAt: new Date(),
version: page.version || '1.0.0'
};
// 更新页面状态
page.status = PageStatus.PUBLISHED;
page.publishedAt = publishResult.publishedAt;
// 触发事件
this.emit('page:published', { page, result: publishResult });
return publishResult;
}
async unpublishPage(pageId: string): Promise<void> {
const page = this.pages.get(pageId);
if (!page) {
throw new Error(`Page ${pageId} not found`);
}
// 更新页面状态
page.status = PageStatus.DRAFT;
page.publishedAt = undefined;
// 触发事件
this.emit('page:unpublished', { page });
}
async createTemplate(template: PageTemplate): Promise<PageTemplate> {
// 验证模板
this.validateTemplate(template);
// 生成模板ID
if (!template.id) {
template.id = this.generateTemplateId();
}
// 存储模板
this.templates.set(template.id, template);
// 触发事件
this.emit('template:created', { template });
return template;
}
async getTemplate(templateId: string): Promise<PageTemplate | null> {
return this.templates.get(templateId) || null;
}
async listTemplates(): Promise<PageTemplate[]> {
return Array.from(this.templates.values());
}
on(event: string, handler: Function): void {
this.eventBus.on(event, handler);
}
off(event: string, handler: Function): void {
this.eventBus.off(event, handler);
}
emit(event: string, data: any): void {
this.eventBus.emit(event, data);
}
private validatePageDefinition(definition: PageDefinition): void {
if (!definition.title) {
throw new Error('Page title is required');
}
if (!definition.layout) {
throw new Error('Page layout is required');
}
// 验证组件
if (definition.components) {
for (const component of definition.components) {
if (!this.componentRegistry.hasComponent(component.type)) {
throw new Error(`Unknown component type: ${component.type}`);
}
}
}
}
private validateTemplate(template: PageTemplate): void {
if (!template.name) {
throw new Error('Template name is required');
}
if (!template.definition) {
throw new Error('Template definition is required');
}
}
private generatePageId(): string {
return `page_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private generateTemplateId(): string {
return `template_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private generatePublishUrl(page: Page, options?: PublishOptions): string {
const baseUrl = options?.baseUrl || 'https://example.com';
const path = options?.path || page.id;
return `${baseUrl}/${path}`;
}
private initializeBuiltinTemplates(): void {
// 初始化内置模板
const builtinTemplates: PageTemplate[] = [
{
id: 'blank',
name: '空白页面',
description: '空白页面模板',
category: 'basic',
thumbnail: '/templates/blank.png',
definition: {
title: '新页面',
layout: {
type: 'container',
properties: {
maxWidth: '1200px',
padding: '20px'
}
},
components: [],
styles: {},
scripts: [],
meta: {},
settings: {}
}
},
{
id: 'dashboard',
name: '仪表板',
description: '数据仪表板模板',
category: 'dashboard',
thumbnail: '/templates/dashboard.png',
definition: {
title: '数据仪表板',
layout: {
type: 'grid',
properties: {
columns: 12,
gap: '16px'
}
},
components: [
{
id: 'header',
type: 'header',
properties: {
title: '数据仪表板',
subtitle: '实时数据监控'
},
layout: {
grid: { col: 12, row: 1 }
}
},
{
id: 'chart1',
type: 'chart',
properties: {
type: 'line',
title: '趋势图',
dataSource: 'api://charts/trend'
},
layout: {
grid: { col: 6, row: 2 }
}
},
{
id: 'chart2',
type: 'chart',
properties: {
type: 'pie',
title: '分布图',
dataSource: 'api://charts/distribution'
},
layout: {
grid: { col: 6, row: 2 }
}
}
],
styles: {
body: {
backgroundColor: '#f5f5f5'
}
},
scripts: [],
meta: {
tags: ['dashboard', 'charts']
},
settings: {
responsive: true,
autoRefresh: true,
refreshInterval: 30000
}
}
}
];
for (const template of builtinTemplates) {
this.templates.set(template.id, template);
}
}
}
9.1.2 核心数据结构
// 页面定义
interface PageDefinition {
id?: string;
title: string;
description?: string;
layout: LayoutDefinition;
components?: ComponentInstance[];
styles?: StyleDefinition;
scripts?: ScriptDefinition[];
meta?: PageMeta;
settings?: PageSettings;
}
// 页面类
class Page {
id: string;
title: string;
description?: string;
layout: LayoutDefinition;
components: ComponentInstance[];
styles: StyleDefinition;
scripts: ScriptDefinition[];
meta: PageMeta;
settings: PageSettings;
status: PageStatus;
version?: string;
createdAt: Date;
updatedAt: Date;
publishedAt?: Date;
constructor(options: PageOptions) {
this.id = options.id;
this.title = options.title;
this.description = options.description;
this.layout = options.layout;
this.components = options.components || [];
this.styles = options.styles || {};
this.scripts = options.scripts || [];
this.meta = options.meta || {};
this.settings = options.settings || {};
this.status = options.status || PageStatus.DRAFT;
this.version = options.version;
this.createdAt = options.createdAt || new Date();
this.updatedAt = options.updatedAt || new Date();
this.publishedAt = options.publishedAt;
}
// 添加组件
addComponent(component: ComponentInstance, index?: number): void {
if (index !== undefined) {
this.components.splice(index, 0, component);
} else {
this.components.push(component);
}
this.updatedAt = new Date();
}
// 移除组件
removeComponent(componentId: string): boolean {
const index = this.components.findIndex(c => c.id === componentId);
if (index !== -1) {
this.components.splice(index, 1);
this.updatedAt = new Date();
return true;
}
return false;
}
// 更新组件
updateComponent(componentId: string, updates: Partial<ComponentInstance>): boolean {
const component = this.components.find(c => c.id === componentId);
if (component) {
Object.assign(component, updates);
this.updatedAt = new Date();
return true;
}
return false;
}
// 获取组件
getComponent(componentId: string): ComponentInstance | undefined {
return this.components.find(c => c.id === componentId);
}
// 移动组件
moveComponent(componentId: string, newIndex: number): boolean {
const currentIndex = this.components.findIndex(c => c.id === componentId);
if (currentIndex !== -1 && newIndex >= 0 && newIndex < this.components.length) {
const [component] = this.components.splice(currentIndex, 1);
this.components.splice(newIndex, 0, component);
this.updatedAt = new Date();
return true;
}
return false;
}
// 克隆页面
clone(newId?: string): Page {
return new Page({
id: newId || `${this.id}_copy`,
title: `${this.title} (副本)`,
description: this.description,
layout: JSON.parse(JSON.stringify(this.layout)),
components: JSON.parse(JSON.stringify(this.components)),
styles: JSON.parse(JSON.stringify(this.styles)),
scripts: JSON.parse(JSON.stringify(this.scripts)),
meta: JSON.parse(JSON.stringify(this.meta)),
settings: JSON.parse(JSON.stringify(this.settings)),
status: PageStatus.DRAFT,
createdAt: new Date(),
updatedAt: new Date()
});
}
// 导出配置
export(): PageDefinition {
return {
id: this.id,
title: this.title,
description: this.description,
layout: this.layout,
components: this.components,
styles: this.styles,
scripts: this.scripts,
meta: this.meta,
settings: this.settings
};
}
}
// 页面状态
enum PageStatus {
DRAFT = 'draft',
PUBLISHED = 'published',
ARCHIVED = 'archived'
}
// 页面选项
interface PageOptions {
id: string;
title: string;
description?: string;
layout: LayoutDefinition;
components?: ComponentInstance[];
styles?: StyleDefinition;
scripts?: ScriptDefinition[];
meta?: PageMeta;
settings?: PageSettings;
status?: PageStatus;
version?: string;
createdAt?: Date;
updatedAt?: Date;
publishedAt?: Date;
}
// 页面元数据
interface PageMeta {
author?: string;
tags?: string[];
category?: string;
keywords?: string[];
description?: string;
thumbnail?: string;
[key: string]: any;
}
// 页面设置
interface PageSettings {
responsive?: boolean;
theme?: string;
language?: string;
seo?: SEOSettings;
performance?: PerformanceSettings;
security?: SecuritySettings;
[key: string]: any;
}
// SEO设置
interface SEOSettings {
title?: string;
description?: string;
keywords?: string[];
ogTitle?: string;
ogDescription?: string;
ogImage?: string;
canonical?: string;
}
// 性能设置
interface PerformanceSettings {
lazyLoad?: boolean;
preload?: string[];
minify?: boolean;
compress?: boolean;
cache?: boolean;
}
// 安全设置
interface SecuritySettings {
csp?: string;
xssProtection?: boolean;
frameOptions?: string;
contentTypeOptions?: boolean;
}
// 页面查询
interface PageQuery {
title?: string;
status?: PageStatus;
tags?: string[];
author?: string;
category?: string;
createdAfter?: Date;
createdBefore?: Date;
orderBy?: string;
orderDirection?: 'asc' | 'desc';
offset?: number;
limit?: number;
}
// 页面模板
interface PageTemplate {
id: string;
name: string;
description?: string;
category?: string;
thumbnail?: string;
definition: PageDefinition;
variables?: TemplateVariable[];
createdAt?: Date;
updatedAt?: Date;
}
// 模板变量
interface TemplateVariable {
name: string;
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
label: string;
description?: string;
defaultValue?: any;
required?: boolean;
options?: any[];
}
// 渲染上下文
interface RenderContext {
variables?: Record<string, any>;
user?: any;
request?: any;
theme?: string;
locale?: string;
device?: 'desktop' | 'tablet' | 'mobile';
[key: string]: any;
}
// 发布选项
interface PublishOptions {
baseUrl?: string;
path?: string;
context?: RenderContext;
minify?: boolean;
compress?: boolean;
}
// 发布结果
interface PublishResult {
pageId: string;
url: string;
html: string;
publishedAt: Date;
version: string;
}
// 页面生成器选项
interface PageGeneratorOptions {
eventBus?: EventBus;
componentRegistry: ComponentRegistry;
themeManager?: ThemeManager;
storage?: PageStorage;
cache?: PageCache;
}
9.2 布局系统
9.2.1 布局定义
// 布局定义
interface LayoutDefinition {
type: LayoutType;
properties?: LayoutProperties;
children?: LayoutDefinition[];
responsive?: ResponsiveLayout;
}
// 布局类型
enum LayoutType {
CONTAINER = 'container',
GRID = 'grid',
FLEX = 'flex',
ABSOLUTE = 'absolute',
TABS = 'tabs',
ACCORDION = 'accordion',
SPLIT = 'split'
}
// 布局属性
interface LayoutProperties {
// 容器属性
maxWidth?: string;
minWidth?: string;
padding?: string;
margin?: string;
// 网格属性
columns?: number;
rows?: number;
gap?: string;
columnGap?: string;
rowGap?: string;
// Flex属性
direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
wrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
justify?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly';
align?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
// 绝对定位属性
position?: 'relative' | 'absolute' | 'fixed' | 'sticky';
top?: string;
right?: string;
bottom?: string;
left?: string;
zIndex?: number;
// 标签页属性
tabPosition?: 'top' | 'bottom' | 'left' | 'right';
tabType?: 'line' | 'card' | 'editable-card';
// 手风琴属性
accordion?: boolean;
collapsible?: boolean;
// 分割面板属性
split?: 'horizontal' | 'vertical';
resizerStyle?: any;
// 通用样式
className?: string;
style?: CSSProperties;
[key: string]: any;
}
// 响应式布局
interface ResponsiveLayout {
xs?: Partial<LayoutProperties>; // < 576px
sm?: Partial<LayoutProperties>; // >= 576px
md?: Partial<LayoutProperties>; // >= 768px
lg?: Partial<LayoutProperties>; // >= 992px
xl?: Partial<LayoutProperties>; // >= 1200px
xxl?: Partial<LayoutProperties>; // >= 1600px
}
// 布局引擎
class LayoutEngine {
private layouts: Map<LayoutType, LayoutRenderer> = new Map();
constructor() {
this.registerBuiltinLayouts();
}
// 注册布局渲染器
registerLayout(type: LayoutType, renderer: LayoutRenderer): void {
this.layouts.set(type, renderer);
}
// 渲染布局
renderLayout(definition: LayoutDefinition, components: ComponentInstance[], context: RenderContext): string {
const renderer = this.layouts.get(definition.type);
if (!renderer) {
throw new Error(`Unknown layout type: ${definition.type}`);
}
return renderer.render(definition, components, context);
}
// 获取响应式属性
getResponsiveProperties(definition: LayoutDefinition, device: string): LayoutProperties {
const baseProperties = definition.properties || {};
const responsiveProperties = definition.responsive?.[device as keyof ResponsiveLayout] || {};
return { ...baseProperties, ...responsiveProperties };
}
private registerBuiltinLayouts(): void {
// 容器布局
this.registerLayout(LayoutType.CONTAINER, new ContainerLayoutRenderer());
// 网格布局
this.registerLayout(LayoutType.GRID, new GridLayoutRenderer());
// Flex布局
this.registerLayout(LayoutType.FLEX, new FlexLayoutRenderer());
// 绝对定位布局
this.registerLayout(LayoutType.ABSOLUTE, new AbsoluteLayoutRenderer());
// 标签页布局
this.registerLayout(LayoutType.TABS, new TabsLayoutRenderer());
// 手风琴布局
this.registerLayout(LayoutType.ACCORDION, new AccordionLayoutRenderer());
// 分割面板布局
this.registerLayout(LayoutType.SPLIT, new SplitLayoutRenderer());
}
}
// 布局渲染器接口
interface LayoutRenderer {
render(definition: LayoutDefinition, components: ComponentInstance[], context: RenderContext): string;
}
// 容器布局渲染器
class ContainerLayoutRenderer implements LayoutRenderer {
render(definition: LayoutDefinition, components: ComponentInstance[], context: RenderContext): string {
const properties = definition.properties || {};
const style = this.buildContainerStyle(properties);
const className = this.buildClassName(properties);
const componentHtml = components.map(component =>
this.renderComponent(component, context)
).join('');
return `
<div class="${className}" style="${style}">
${componentHtml}
</div>
`;
}
private buildContainerStyle(properties: LayoutProperties): string {
const styles: string[] = [];
if (properties.maxWidth) styles.push(`max-width: ${properties.maxWidth}`);
if (properties.minWidth) styles.push(`min-width: ${properties.minWidth}`);
if (properties.padding) styles.push(`padding: ${properties.padding}`);
if (properties.margin) styles.push(`margin: ${properties.margin}`);
// 合并自定义样式
if (properties.style) {
const customStyles = Object.entries(properties.style)
.map(([key, value]) => `${this.camelToKebab(key)}: ${value}`)
.join('; ');
styles.push(customStyles);
}
return styles.join('; ');
}
private buildClassName(properties: LayoutProperties): string {
const classes = ['layout-container'];
if (properties.className) {
classes.push(properties.className);
}
return classes.join(' ');
}
private renderComponent(component: ComponentInstance, context: RenderContext): string {
// 这里应该调用组件渲染器
// 简化实现
return `<div data-component="${component.type}" data-id="${component.id}"></div>`;
}
private camelToKebab(str: string): string {
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
}
}
// 网格布局渲染器
class GridLayoutRenderer implements LayoutRenderer {
render(definition: LayoutDefinition, components: ComponentInstance[], context: RenderContext): string {
const properties = definition.properties || {};
const style = this.buildGridStyle(properties);
const className = this.buildClassName(properties);
const componentHtml = components.map(component =>
this.renderGridItem(component, context)
).join('');
return `
<div class="${className}" style="${style}">
${componentHtml}
</div>
`;
}
private buildGridStyle(properties: LayoutProperties): string {
const styles = ['display: grid'];
if (properties.columns) {
styles.push(`grid-template-columns: repeat(${properties.columns}, 1fr)`);
}
if (properties.rows) {
styles.push(`grid-template-rows: repeat(${properties.rows}, auto)`);
}
if (properties.gap) {
styles.push(`gap: ${properties.gap}`);
} else {
if (properties.columnGap) styles.push(`column-gap: ${properties.columnGap}`);
if (properties.rowGap) styles.push(`row-gap: ${properties.rowGap}`);
}
if (properties.padding) styles.push(`padding: ${properties.padding}`);
if (properties.margin) styles.push(`margin: ${properties.margin}`);
return styles.join('; ');
}
private buildClassName(properties: LayoutProperties): string {
const classes = ['layout-grid'];
if (properties.className) {
classes.push(properties.className);
}
return classes.join(' ');
}
private renderGridItem(component: ComponentInstance, context: RenderContext): string {
const gridStyle = this.buildGridItemStyle(component.layout?.grid);
return `
<div class="grid-item" style="${gridStyle}" data-component="${component.type}" data-id="${component.id}">
<!-- 组件内容 -->
</div>
`;
}
private buildGridItemStyle(grid?: GridItemLayout): string {
if (!grid) return '';
const styles: string[] = [];
if (grid.col !== undefined) {
if (typeof grid.col === 'number') {
styles.push(`grid-column: span ${grid.col}`);
} else {
styles.push(`grid-column: ${grid.col}`);
}
}
if (grid.row !== undefined) {
if (typeof grid.row === 'number') {
styles.push(`grid-row: span ${grid.row}`);
} else {
styles.push(`grid-row: ${grid.row}`);
}
}
if (grid.area) {
styles.push(`grid-area: ${grid.area}`);
}
return styles.join('; ');
}
}
// 网格项布局
interface GridItemLayout {
col?: number | string;
row?: number | string;
area?: string;
}
// Flex布局渲染器
class FlexLayoutRenderer implements LayoutRenderer {
render(definition: LayoutDefinition, components: ComponentInstance[], context: RenderContext): string {
const properties = definition.properties || {};
const style = this.buildFlexStyle(properties);
const className = this.buildClassName(properties);
const componentHtml = components.map(component =>
this.renderFlexItem(component, context)
).join('');
return `
<div class="${className}" style="${style}">
${componentHtml}
</div>
`;
}
private buildFlexStyle(properties: LayoutProperties): string {
const styles = ['display: flex'];
if (properties.direction) {
styles.push(`flex-direction: ${properties.direction}`);
}
if (properties.wrap) {
styles.push(`flex-wrap: ${properties.wrap}`);
}
if (properties.justify) {
styles.push(`justify-content: ${properties.justify}`);
}
if (properties.align) {
styles.push(`align-items: ${properties.align}`);
}
if (properties.gap) {
styles.push(`gap: ${properties.gap}`);
}
if (properties.padding) styles.push(`padding: ${properties.padding}`);
if (properties.margin) styles.push(`margin: ${properties.margin}`);
return styles.join('; ');
}
private buildClassName(properties: LayoutProperties): string {
const classes = ['layout-flex'];
if (properties.className) {
classes.push(properties.className);
}
return classes.join(' ');
}
private renderFlexItem(component: ComponentInstance, context: RenderContext): string {
const flexStyle = this.buildFlexItemStyle(component.layout?.flex);
return `
<div class="flex-item" style="${flexStyle}" data-component="${component.type}" data-id="${component.id}">
<!-- 组件内容 -->
</div>
`;
}
private buildFlexItemStyle(flex?: FlexItemLayout): string {
if (!flex) return '';
const styles: string[] = [];
if (flex.grow !== undefined) {
styles.push(`flex-grow: ${flex.grow}`);
}
if (flex.shrink !== undefined) {
styles.push(`flex-shrink: ${flex.shrink}`);
}
if (flex.basis !== undefined) {
styles.push(`flex-basis: ${flex.basis}`);
}
if (flex.align !== undefined) {
styles.push(`align-self: ${flex.align}`);
}
if (flex.order !== undefined) {
styles.push(`order: ${flex.order}`);
}
return styles.join('; ');
}
}
// Flex项布局
interface FlexItemLayout {
grow?: number;
shrink?: number;
basis?: string;
align?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch';
order?: number;
}
9.3 页面渲染器
9.3.1 页面渲染器实现
// 页面渲染器
class PageRenderer {
private componentRegistry: ComponentRegistry;
private layoutEngine: LayoutEngine;
private themeManager: ThemeManager;
private eventBus: EventBus;
constructor(options: PageRendererOptions) {
this.componentRegistry = options.componentRegistry;
this.themeManager = options.themeManager;
this.eventBus = options.eventBus;
this.layoutEngine = new LayoutEngine();
}
async render(page: Page, context?: RenderContext): Promise<string> {
const renderContext = this.buildRenderContext(page, context);
// 渲染页面结构
const html = this.renderPageStructure(page, renderContext);
// 触发渲染事件
this.eventBus.emit('page:rendered', { page, context: renderContext, html });
return html;
}
private buildRenderContext(page: Page, context?: RenderContext): RenderContext {
return {
variables: {},
theme: page.settings.theme || 'default',
locale: page.settings.language || 'zh-CN',
device: 'desktop',
...context
};
}
private renderPageStructure(page: Page, context: RenderContext): string {
const head = this.renderHead(page, context);
const body = this.renderBody(page, context);
const scripts = this.renderScripts(page, context);
return `
<!DOCTYPE html>
<html lang="${context.locale}">
<head>
${head}
</head>
<body>
${body}
${scripts}
</body>
</html>
`.trim();
}
private renderHead(page: Page, context: RenderContext): string {
const meta = this.renderMeta(page);
const styles = this.renderStyles(page, context);
const preloadScripts = this.renderPreloadScripts(page);
return `
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${page.title}</title>
${meta}
${styles}
${preloadScripts}
`.trim();
}
private renderMeta(page: Page): string {
const seo = page.settings.seo || {};
const meta: string[] = [];
if (seo.description || page.description) {
meta.push(`<meta name="description" content="${seo.description || page.description}">`);
}
if (seo.keywords) {
meta.push(`<meta name="keywords" content="${seo.keywords.join(', ')}">`);
}
if (seo.ogTitle) {
meta.push(`<meta property="og:title" content="${seo.ogTitle}">`);
}
if (seo.ogDescription) {
meta.push(`<meta property="og:description" content="${seo.ogDescription}">`);
}
if (seo.ogImage) {
meta.push(`<meta property="og:image" content="${seo.ogImage}">`);
}
if (seo.canonical) {
meta.push(`<link rel="canonical" href="${seo.canonical}">`);
}
// 安全头
const security = page.settings.security || {};
if (security.csp) {
meta.push(`<meta http-equiv="Content-Security-Policy" content="${security.csp}">`);
}
if (security.xssProtection) {
meta.push(`<meta http-equiv="X-XSS-Protection" content="1; mode=block">`);
}
if (security.contentTypeOptions) {
meta.push(`<meta http-equiv="X-Content-Type-Options" content="nosniff">`);
}
return meta.join('\n ');
}
private renderStyles(page: Page, context: RenderContext): string {
const styles: string[] = [];
// 主题样式
const themeStyles = this.themeManager.getThemeStyles(context.theme);
if (themeStyles) {
styles.push(`<style data-theme="${context.theme}">${themeStyles}</style>`);
}
// 页面样式
if (page.styles && Object.keys(page.styles).length > 0) {
const pageStyles = this.buildPageStyles(page.styles);
styles.push(`<style data-page="${page.id}">${pageStyles}</style>`);
}
// 组件样式
const componentStyles = this.buildComponentStyles(page.components);
if (componentStyles) {
styles.push(`<style data-components>${componentStyles}</style>`);
}
// 响应式样式
const responsiveStyles = this.buildResponsiveStyles(page, context);
if (responsiveStyles) {
styles.push(`<style data-responsive>${responsiveStyles}</style>`);
}
return styles.join('\n ');
}
private renderPreloadScripts(page: Page): string {
const performance = page.settings.performance || {};
const preloads: string[] = [];
if (performance.preload) {
for (const resource of performance.preload) {
preloads.push(`<link rel="preload" href="${resource}" as="script">`);
}
}
return preloads.join('\n ');
}
private renderBody(page: Page, context: RenderContext): string {
// 渲染布局和组件
const content = this.layoutEngine.renderLayout(page.layout, page.components, context);
return `
<div id="app" data-page="${page.id}">
${content}
</div>
`.trim();
}
private renderScripts(page: Page, context: RenderContext): string {
const scripts: string[] = [];
// 运行时脚本
scripts.push(this.renderRuntimeScript(page, context));
// 页面脚本
for (const script of page.scripts) {
scripts.push(this.renderScript(script, context));
}
// 组件脚本
const componentScripts = this.buildComponentScripts(page.components, context);
if (componentScripts) {
scripts.push(componentScripts);
}
return scripts.join('\n ');
}
private renderRuntimeScript(page: Page, context: RenderContext): string {
const runtime = {
pageId: page.id,
context,
components: page.components.map(c => ({
id: c.id,
type: c.type,
properties: c.properties
}))
};
return `
<script>
window.__LOWCODE_RUNTIME__ = ${JSON.stringify(runtime)};
// 初始化运行时
(function() {
const runtime = window.__LOWCODE_RUNTIME__;
// 组件初始化
runtime.components.forEach(component => {
const element = document.querySelector('[data-id="' + component.id + '"]');
if (element) {
element.setAttribute('data-initialized', 'true');
// 触发组件初始化事件
element.dispatchEvent(new CustomEvent('component:init', {
detail: component
}));
}
});
// 页面初始化完成
document.dispatchEvent(new CustomEvent('page:ready', {
detail: { pageId: runtime.pageId }
}));
})();
</script>
`.trim();
}
private renderScript(script: ScriptDefinition, context: RenderContext): string {
if (script.type === 'inline') {
return `<script>${script.content}</script>`;
} else if (script.type === 'external') {
const attrs = script.async ? ' async' : '';
const defer = script.defer ? ' defer' : '';
return `<script src="${script.src}"${attrs}${defer}></script>`;
}
return '';
}
private buildPageStyles(styles: StyleDefinition): string {
const css: string[] = [];
for (const [selector, rules] of Object.entries(styles)) {
const ruleStrings = Object.entries(rules)
.map(([property, value]) => ` ${this.camelToKebab(property)}: ${value};`)
.join('\n');
css.push(`${selector} {\n${ruleStrings}\n}`);
}
return css.join('\n\n');
}
private buildComponentStyles(components: ComponentInstance[]): string {
const styles: string[] = [];
for (const component of components) {
const componentDef = this.componentRegistry.getComponent(component.type);
if (componentDef?.styles) {
styles.push(componentDef.styles);
}
// 组件实例样式
if (component.styles) {
const instanceStyles = this.buildInstanceStyles(component);
styles.push(instanceStyles);
}
}
return styles.join('\n\n');
}
private buildInstanceStyles(component: ComponentInstance): string {
if (!component.styles) return '';
const selector = `[data-id="${component.id}"]`;
const rules = Object.entries(component.styles)
.map(([property, value]) => ` ${this.camelToKebab(property)}: ${value};`)
.join('\n');
return `${selector} {\n${rules}\n}`;
}
private buildResponsiveStyles(page: Page, context: RenderContext): string {
if (!page.layout.responsive) return '';
const breakpoints = {
xs: '(max-width: 575.98px)',
sm: '(min-width: 576px) and (max-width: 767.98px)',
md: '(min-width: 768px) and (max-width: 991.98px)',
lg: '(min-width: 992px) and (max-width: 1199.98px)',
xl: '(min-width: 1200px) and (max-width: 1599.98px)',
xxl: '(min-width: 1600px)'
};
const mediaQueries: string[] = [];
for (const [breakpoint, query] of Object.entries(breakpoints)) {
const responsiveProps = page.layout.responsive[breakpoint as keyof ResponsiveLayout];
if (responsiveProps) {
const styles = Object.entries(responsiveProps)
.map(([property, value]) => ` ${this.camelToKebab(property)}: ${value};`)
.join('\n');
mediaQueries.push(`@media ${query} {\n .layout-container {\n${styles}\n }\n}`);
}
}
return mediaQueries.join('\n\n');
}
private buildComponentScripts(components: ComponentInstance[], context: RenderContext): string {
const scripts: string[] = [];
for (const component of components) {
const componentDef = this.componentRegistry.getComponent(component.type);
if (componentDef?.script) {
scripts.push(componentDef.script);
}
}
return scripts.length > 0 ? `<script>\n${scripts.join('\n\n')}\n</script>` : '';
}
private camelToKebab(str: string): string {
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
}
}
// 页面渲染器选项
interface PageRendererOptions {
componentRegistry: ComponentRegistry;
themeManager: ThemeManager;
eventBus: EventBus;
}
9.4 样式系统
9.4.1 样式定义
// 样式定义
interface StyleDefinition {
[selector: string]: CSSProperties;
}
// CSS属性
interface CSSProperties {
// 布局
display?: string;
position?: string;
top?: string;
right?: string;
bottom?: string;
left?: string;
zIndex?: number;
// 盒模型
width?: string;
height?: string;
minWidth?: string;
minHeight?: string;
maxWidth?: string;
maxHeight?: string;
margin?: string;
marginTop?: string;
marginRight?: string;
marginBottom?: string;
marginLeft?: string;
padding?: string;
paddingTop?: string;
paddingRight?: string;
paddingBottom?: string;
paddingLeft?: string;
// 边框
border?: string;
borderTop?: string;
borderRight?: string;
borderBottom?: string;
borderLeft?: string;
borderRadius?: string;
borderColor?: string;
borderStyle?: string;
borderWidth?: string;
// 背景
background?: string;
backgroundColor?: string;
backgroundImage?: string;
backgroundSize?: string;
backgroundPosition?: string;
backgroundRepeat?: string;
// 文字
color?: string;
fontSize?: string;
fontFamily?: string;
fontWeight?: string;
fontStyle?: string;
lineHeight?: string;
textAlign?: string;
textDecoration?: string;
textTransform?: string;
letterSpacing?: string;
wordSpacing?: string;
// Flex
flexDirection?: string;
flexWrap?: string;
justifyContent?: string;
alignItems?: string;
alignContent?: string;
flex?: string;
flexGrow?: number;
flexShrink?: number;
flexBasis?: string;
alignSelf?: string;
order?: number;
// Grid
gridTemplateColumns?: string;
gridTemplateRows?: string;
gridTemplateAreas?: string;
gridColumn?: string;
gridRow?: string;
gridArea?: string;
gap?: string;
columnGap?: string;
rowGap?: string;
// 变换
transform?: string;
transformOrigin?: string;
transition?: string;
animation?: string;
// 其他
opacity?: number;
visibility?: string;
overflow?: string;
overflowX?: string;
overflowY?: string;
cursor?: string;
userSelect?: string;
pointerEvents?: string;
[property: string]: any;
}
// 主题管理器
interface ThemeManager {
getTheme(name: string): Theme | null;
setTheme(name: string, theme: Theme): void;
getThemeStyles(name: string): string;
listThemes(): string[];
}
// 主题定义
interface Theme {
name: string;
displayName: string;
description?: string;
colors: ThemeColors;
typography: ThemeTypography;
spacing: ThemeSpacing;
breakpoints: ThemeBreakpoints;
shadows: ThemeShadows;
borders: ThemeBorders;
variables?: Record<string, any>;
}
// 主题颜色
interface ThemeColors {
primary: string;
secondary: string;
success: string;
warning: string;
error: string;
info: string;
background: string;
surface: string;
text: {
primary: string;
secondary: string;
disabled: string;
};
border: string;
divider: string;
[key: string]: any;
}
// 主题字体
interface ThemeTypography {
fontFamily: {
primary: string;
secondary: string;
monospace: string;
};
fontSize: {
xs: string;
sm: string;
base: string;
lg: string;
xl: string;
'2xl': string;
'3xl': string;
'4xl': string;
};
fontWeight: {
light: number;
normal: number;
medium: number;
semibold: number;
bold: number;
};
lineHeight: {
tight: number;
normal: number;
relaxed: number;
};
}
// 主题间距
interface ThemeSpacing {
xs: string;
sm: string;
md: string;
lg: string;
xl: string;
'2xl': string;
'3xl': string;
'4xl': string;
}
// 主题断点
interface ThemeBreakpoints {
xs: string;
sm: string;
md: string;
lg: string;
xl: string;
xxl: string;
}
// 主题阴影
interface ThemeShadows {
sm: string;
md: string;
lg: string;
xl: string;
'2xl': string;
}
// 主题边框
interface ThemeBorders {
width: {
thin: string;
medium: string;
thick: string;
};
radius: {
none: string;
sm: string;
md: string;
lg: string;
xl: string;
full: string;
};
}
// 默认主题管理器
class DefaultThemeManager implements ThemeManager {
private themes: Map<string, Theme> = new Map();
constructor() {
this.initializeDefaultThemes();
}
getTheme(name: string): Theme | null {
return this.themes.get(name) || null;
}
setTheme(name: string, theme: Theme): void {
this.themes.set(name, theme);
}
getThemeStyles(name: string): string {
const theme = this.getTheme(name);
if (!theme) return '';
return this.generateThemeCSS(theme);
}
listThemes(): string[] {
return Array.from(this.themes.keys());
}
private generateThemeCSS(theme: Theme): string {
const css: string[] = [];
// CSS变量定义
const variables: string[] = [];
// 颜色变量
variables.push(`--color-primary: ${theme.colors.primary};`);
variables.push(`--color-secondary: ${theme.colors.secondary};`);
variables.push(`--color-success: ${theme.colors.success};`);
variables.push(`--color-warning: ${theme.colors.warning};`);
variables.push(`--color-error: ${theme.colors.error};`);
variables.push(`--color-info: ${theme.colors.info};`);
variables.push(`--color-background: ${theme.colors.background};`);
variables.push(`--color-surface: ${theme.colors.surface};`);
variables.push(`--color-text-primary: ${theme.colors.text.primary};`);
variables.push(`--color-text-secondary: ${theme.colors.text.secondary};`);
variables.push(`--color-text-disabled: ${theme.colors.text.disabled};`);
variables.push(`--color-border: ${theme.colors.border};`);
variables.push(`--color-divider: ${theme.colors.divider};`);
// 字体变量
variables.push(`--font-family-primary: ${theme.typography.fontFamily.primary};`);
variables.push(`--font-family-secondary: ${theme.typography.fontFamily.secondary};`);
variables.push(`--font-family-monospace: ${theme.typography.fontFamily.monospace};`);
// 字体大小变量
Object.entries(theme.typography.fontSize).forEach(([key, value]) => {
variables.push(`--font-size-${key}: ${value};`);
});
// 字体粗细变量
Object.entries(theme.typography.fontWeight).forEach(([key, value]) => {
variables.push(`--font-weight-${key}: ${value};`);
});
// 间距变量
Object.entries(theme.spacing).forEach(([key, value]) => {
variables.push(`--spacing-${key}: ${value};`);
});
// 阴影变量
Object.entries(theme.shadows).forEach(([key, value]) => {
variables.push(`--shadow-${key}: ${value};`);
});
// 边框变量
Object.entries(theme.borders.width).forEach(([key, value]) => {
variables.push(`--border-width-${key}: ${value};`);
});
Object.entries(theme.borders.radius).forEach(([key, value]) => {
variables.push(`--border-radius-${key}: ${value};`);
});
// 自定义变量
if (theme.variables) {
Object.entries(theme.variables).forEach(([key, value]) => {
variables.push(`--${key}: ${value};`);
});
}
// 根元素样式
css.push(`:root {\n ${variables.join('\n ')}\n}`);
// 基础样式
css.push(`
body {
font-family: var(--font-family-primary);
font-size: var(--font-size-base);
line-height: var(--line-height-normal, 1.5);
color: var(--color-text-primary);
background-color: var(--color-background);
margin: 0;
padding: 0;
}
* {
box-sizing: border-box;
}
a {
color: var(--color-primary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button {
font-family: inherit;
font-size: inherit;
}
input, textarea, select {
font-family: inherit;
font-size: inherit;
}
`);
return css.join('\n');
}
private initializeDefaultThemes(): void {
// 默认主题
const defaultTheme: Theme = {
name: 'default',
displayName: '默认主题',
description: '系统默认主题',
colors: {
primary: '#1976d2',
secondary: '#dc004e',
success: '#388e3c',
warning: '#f57c00',
error: '#d32f2f',
info: '#0288d1',
background: '#ffffff',
surface: '#f5f5f5',
text: {
primary: '#212121',
secondary: '#757575',
disabled: '#bdbdbd'
},
border: '#e0e0e0',
divider: '#e0e0e0'
},
typography: {
fontFamily: {
primary: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
secondary: 'Georgia, "Times New Roman", Times, serif',
monospace: '"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace'
},
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem'
},
fontWeight: {
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700
},
lineHeight: {
tight: 1.25,
normal: 1.5,
relaxed: 1.75
}
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
'2xl': '3rem',
'3xl': '4rem',
'4xl': '6rem'
},
breakpoints: {
xs: '0px',
sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
xxl: '1600px'
},
shadows: {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)'
},
borders: {
width: {
thin: '1px',
medium: '2px',
thick: '4px'
},
radius: {
none: '0',
sm: '0.125rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
full: '9999px'
}
}
};
this.setTheme('default', defaultTheme);
// 暗色主题
const darkTheme: Theme = {
...defaultTheme,
name: 'dark',
displayName: '暗色主题',
description: '暗色模式主题',
colors: {
...defaultTheme.colors,
background: '#121212',
surface: '#1e1e1e',
text: {
primary: '#ffffff',
secondary: '#b3b3b3',
disabled: '#666666'
},
border: '#333333',
divider: '#333333'
}
};
this.setTheme('dark', darkTheme);
}
}
9.5 脚本系统
9.5.1 脚本定义
// 脚本定义
interface ScriptDefinition {
id?: string;
type: 'inline' | 'external';
content?: string;
src?: string;
async?: boolean;
defer?: boolean;
module?: boolean;
dependencies?: string[];
position?: 'head' | 'body';
condition?: string;
}
// 脚本管理器
class ScriptManager {
private scripts: Map<string, ScriptDefinition> = new Map();
private loadedScripts: Set<string> = new Set();
private eventBus: EventBus;
constructor(eventBus: EventBus) {
this.eventBus = eventBus;
}
// 注册脚本
registerScript(script: ScriptDefinition): void {
const id = script.id || this.generateScriptId();
script.id = id;
this.scripts.set(id, script);
}
// 加载脚本
async loadScript(scriptId: string): Promise<void> {
if (this.loadedScripts.has(scriptId)) {
return; // 已加载
}
const script = this.scripts.get(scriptId);
if (!script) {
throw new Error(`Script ${scriptId} not found`);
}
// 检查依赖
if (script.dependencies) {
for (const dep of script.dependencies) {
await this.loadScript(dep);
}
}
// 加载脚本
if (script.type === 'external') {
await this.loadExternalScript(script);
} else {
this.executeInlineScript(script);
}
this.loadedScripts.add(scriptId);
this.eventBus.emit('script:loaded', { scriptId, script });
}
// 卸载脚本
unloadScript(scriptId: string): void {
const script = this.scripts.get(scriptId);
if (!script) return;
if (script.type === 'external' && script.src) {
const element = document.querySelector(`script[src="${script.src}"]`);
if (element) {
element.remove();
}
}
this.loadedScripts.delete(scriptId);
this.eventBus.emit('script:unloaded', { scriptId, script });
}
// 执行脚本
executeScript(content: string, context?: any): any {
try {
// 创建执行上下文
const func = new Function('context', content);
return func(context);
} catch (error) {
this.eventBus.emit('script:error', { content, error });
throw error;
}
}
// 检查脚本是否已加载
isScriptLoaded(scriptId: string): boolean {
return this.loadedScripts.has(scriptId);
}
// 获取已加载的脚本
getLoadedScripts(): string[] {
return Array.from(this.loadedScripts);
}
private async loadExternalScript(script: ScriptDefinition): Promise<void> {
return new Promise((resolve, reject) => {
const element = document.createElement('script');
element.src = script.src!;
if (script.async) element.async = true;
if (script.defer) element.defer = true;
if (script.module) element.type = 'module';
element.onload = () => resolve();
element.onerror = () => reject(new Error(`Failed to load script: ${script.src}`));
const target = script.position === 'head' ? document.head : document.body;
target.appendChild(element);
});
}
private executeInlineScript(script: ScriptDefinition): void {
if (!script.content) return;
try {
// 检查条件
if (script.condition && !this.evaluateCondition(script.condition)) {
return;
}
// 执行脚本
if (script.module) {
// 模块脚本
const blob = new Blob([script.content], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
import(url).finally(() => URL.revokeObjectURL(url));
} else {
// 普通脚本
const func = new Function(script.content);
func();
}
} catch (error) {
this.eventBus.emit('script:error', { script, error });
throw error;
}
}
private evaluateCondition(condition: string): boolean {
try {
const func = new Function('return ' + condition);
return Boolean(func());
} catch {
return false;
}
}
private generateScriptId(): string {
return `script_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
9.6 组件系统
9.6.1 组件注册器
// 组件注册器
interface ComponentRegistry {
registerComponent(type: string, definition: ComponentDefinition): void;
getComponent(type: string): ComponentDefinition | null;
listComponents(): string[];
unregisterComponent(type: string): void;
}
// 组件定义
interface ComponentDefinition {
type: string;
name: string;
description?: string;
category: string;
icon?: string;
properties: ComponentPropertyDefinition[];
events?: ComponentEventDefinition[];
methods?: ComponentMethodDefinition[];
styles?: string;
script?: string;
template: string;
defaultProps?: Record<string, any>;
validation?: ComponentValidation;
}
// 组件属性定义
interface ComponentPropertyDefinition {
name: string;
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'function';
label: string;
description?: string;
defaultValue?: any;
required?: boolean;
options?: Array<{ label: string; value: any }>;
validation?: PropertyValidation;
group?: string;
editor?: PropertyEditor;
}
// 组件事件定义
interface ComponentEventDefinition {
name: string;
description?: string;
parameters?: EventParameter[];
}
// 组件方法定义
interface ComponentMethodDefinition {
name: string;
description?: string;
parameters?: MethodParameter[];
returnType?: string;
}
// 组件验证
interface ComponentValidation {
rules?: ValidationRule[];
custom?: (component: ComponentInstance) => ValidationResult;
}
// 属性验证
interface PropertyValidation {
min?: number;
max?: number;
pattern?: string;
custom?: (value: any) => boolean;
}
// 属性编辑器
interface PropertyEditor {
type: 'input' | 'textarea' | 'select' | 'checkbox' | 'radio' | 'color' | 'date' | 'file' | 'custom';
options?: any;
component?: string;
}
// 事件参数
interface EventParameter {
name: string;
type: string;
description?: string;
}
// 方法参数
interface MethodParameter {
name: string;
type: string;
description?: string;
required?: boolean;
defaultValue?: any;
}
// 默认组件注册器
class DefaultComponentRegistry implements ComponentRegistry {
private components: Map<string, ComponentDefinition> = new Map();
private eventBus: EventBus;
constructor(eventBus: EventBus) {
this.eventBus = eventBus;
this.initializeBuiltinComponents();
}
registerComponent(type: string, definition: ComponentDefinition): void {
this.components.set(type, definition);
this.eventBus.emit('component:registered', { type, definition });
}
getComponent(type: string): ComponentDefinition | null {
return this.components.get(type) || null;
}
listComponents(): string[] {
return Array.from(this.components.keys());
}
unregisterComponent(type: string): void {
const definition = this.components.get(type);
if (definition) {
this.components.delete(type);
this.eventBus.emit('component:unregistered', { type, definition });
}
}
private initializeBuiltinComponents(): void {
// 文本组件
this.registerComponent('text', {
type: 'text',
name: '文本',
description: '显示文本内容',
category: 'basic',
icon: 'text',
properties: [
{
name: 'content',
type: 'string',
label: '文本内容',
description: '要显示的文本内容',
defaultValue: '文本内容',
required: true,
editor: { type: 'textarea' }
},
{
name: 'tag',
type: 'string',
label: 'HTML标签',
description: '文本的HTML标签',
defaultValue: 'p',
options: [
{ label: '段落 (p)', value: 'p' },
{ label: '标题1 (h1)', value: 'h1' },
{ label: '标题2 (h2)', value: 'h2' },
{ label: '标题3 (h3)', value: 'h3' },
{ label: '行内 (span)', value: 'span' }
],
editor: { type: 'select' }
}
],
template: '<{{tag}} class="text-component">{{content}}</{{tag}}>',
styles: `
.text-component {
margin: 0;
padding: 8px;
}
`
});
// 按钮组件
this.registerComponent('button', {
type: 'button',
name: '按钮',
description: '可点击的按钮',
category: 'basic',
icon: 'button',
properties: [
{
name: 'text',
type: 'string',
label: '按钮文本',
description: '按钮显示的文本',
defaultValue: '按钮',
required: true
},
{
name: 'type',
type: 'string',
label: '按钮类型',
description: '按钮的样式类型',
defaultValue: 'primary',
options: [
{ label: '主要', value: 'primary' },
{ label: '次要', value: 'secondary' },
{ label: '成功', value: 'success' },
{ label: '警告', value: 'warning' },
{ label: '危险', value: 'danger' }
],
editor: { type: 'select' }
},
{
name: 'disabled',
type: 'boolean',
label: '禁用状态',
description: '是否禁用按钮',
defaultValue: false,
editor: { type: 'checkbox' }
}
],
events: [
{
name: 'click',
description: '按钮点击事件',
parameters: [
{ name: 'event', type: 'MouseEvent', description: '鼠标事件对象' }
]
}
],
template: '<button class="btn btn-{{type}}" {{#if disabled}}disabled{{/if}} onclick="handleClick(event)">{{text}}</button>',
styles: `
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: var(--color-primary);
color: white;
}
.btn-secondary {
background-color: var(--color-secondary);
color: white;
}
.btn-success {
background-color: var(--color-success);
color: white;
}
.btn-warning {
background-color: var(--color-warning);
color: white;
}
.btn-danger {
background-color: var(--color-error);
color: white;
}
`,
script: `
function handleClick(event) {
const component = event.target.closest('[data-component="button"]');
if (component) {
component.dispatchEvent(new CustomEvent('component:click', {
detail: { event, component }
}));
}
}
`
});
// 输入框组件
this.registerComponent('input', {
type: 'input',
name: '输入框',
description: '文本输入框',
category: 'form',
icon: 'input',
properties: [
{
name: 'placeholder',
type: 'string',
label: '占位符',
description: '输入框的占位符文本',
defaultValue: '请输入内容'
},
{
name: 'value',
type: 'string',
label: '默认值',
description: '输入框的默认值',
defaultValue: ''
},
{
name: 'type',
type: 'string',
label: '输入类型',
description: '输入框的类型',
defaultValue: 'text',
options: [
{ label: '文本', value: 'text' },
{ label: '密码', value: 'password' },
{ label: '邮箱', value: 'email' },
{ label: '数字', value: 'number' },
{ label: '电话', value: 'tel' },
{ label: 'URL', value: 'url' }
],
editor: { type: 'select' }
},
{
name: 'required',
type: 'boolean',
label: '必填',
description: '是否为必填字段',
defaultValue: false,
editor: { type: 'checkbox' }
},
{
name: 'disabled',
type: 'boolean',
label: '禁用',
description: '是否禁用输入框',
defaultValue: false,
editor: { type: 'checkbox' }
}
],
events: [
{
name: 'input',
description: '输入事件',
parameters: [
{ name: 'event', type: 'InputEvent', description: '输入事件对象' },
{ name: 'value', type: 'string', description: '输入值' }
]
},
{
name: 'change',
description: '值变化事件',
parameters: [
{ name: 'event', type: 'Event', description: '事件对象' },
{ name: 'value', type: 'string', description: '新值' }
]
}
],
template: '<input class="input-component" type="{{type}}" placeholder="{{placeholder}}" value="{{value}}" {{#if required}}required{{/if}} {{#if disabled}}disabled{{/if}} oninput="handleInput(event)" onchange="handleChange(event)">',
styles: `
.input-component {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--color-border);
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s;
}
.input-component:focus {
outline: none;
border-color: var(--color-primary);
}
.input-component:disabled {
background-color: var(--color-surface);
cursor: not-allowed;
}
`,
script: `
function handleInput(event) {
const component = event.target.closest('[data-component="input"]');
if (component) {
component.dispatchEvent(new CustomEvent('component:input', {
detail: { event, value: event.target.value, component }
}));
}
}
function handleChange(event) {
const component = event.target.closest('[data-component="input"]');
if (component) {
component.dispatchEvent(new CustomEvent('component:change', {
detail: { event, value: event.target.value, component }
}));
}
}
`
});
// 图片组件
this.registerComponent('image', {
type: 'image',
name: '图片',
description: '显示图片',
category: 'media',
icon: 'image',
properties: [
{
name: 'src',
type: 'string',
label: '图片地址',
description: '图片的URL地址',
defaultValue: 'https://via.placeholder.com/300x200',
required: true,
editor: { type: 'input' }
},
{
name: 'alt',
type: 'string',
label: '替代文本',
description: '图片的替代文本',
defaultValue: '图片',
editor: { type: 'input' }
},
{
name: 'width',
type: 'string',
label: '宽度',
description: '图片宽度',
defaultValue: 'auto',
editor: { type: 'input' }
},
{
name: 'height',
type: 'string',
label: '高度',
description: '图片高度',
defaultValue: 'auto',
editor: { type: 'input' }
}
],
events: [
{
name: 'load',
description: '图片加载完成事件',
parameters: [
{ name: 'event', type: 'Event', description: '加载事件对象' }
]
},
{
name: 'error',
description: '图片加载错误事件',
parameters: [
{ name: 'event', type: 'Event', description: '错误事件对象' }
]
}
],
template: '<img class="image-component" src="{{src}}" alt="{{alt}}" style="width: {{width}}; height: {{height}};" onload="handleLoad(event)" onerror="handleError(event)">',
styles: `
.image-component {
max-width: 100%;
height: auto;
display: block;
}
`,
script: `
function handleLoad(event) {
const component = event.target.closest('[data-component="image"]');
if (component) {
component.dispatchEvent(new CustomEvent('component:load', {
detail: { event, component }
}));
}
}
function handleError(event) {
const component = event.target.closest('[data-component="image"]');
if (component) {
component.dispatchEvent(new CustomEvent('component:error', {
detail: { event, component }
}));
}
}
`
});
}
}
9.7 页面生成器使用示例
9.7.1 完整使用示例
// 页面生成器使用示例
class LowCodePageGeneratorDemo {
private pageGenerator: LowCodePageGenerator;
private eventBus: EventBus;
constructor() {
this.eventBus = new EventBus();
this.setupEventListeners();
// 初始化页面生成器
this.pageGenerator = new LowCodePageGenerator({
eventBus: this.eventBus,
templateEngine: new DefaultTemplateEngine(),
themeManager: new DefaultThemeManager(),
componentRegistry: new DefaultComponentRegistry(this.eventBus)
});
}
async runDemo(): Promise<void> {
console.log('=== 低代码页面生成器演示 ===');
try {
// 1. 创建产品展示页面
await this.createProductPage();
// 2. 创建用户注册页面
await this.createRegistrationPage();
// 3. 创建仪表板页面
await this.createDashboardPage();
// 4. 页面管理操作
await this.demonstratePageManagement();
// 5. 模板管理
await this.demonstrateTemplateManagement();
} catch (error) {
console.error('演示过程中发生错误:', error);
}
}
private async createProductPage(): Promise<void> {
console.log('\n--- 创建产品展示页面 ---');
// 创建页面定义
const pageDefinition: PageDefinition = {
title: '产品展示',
description: '展示公司产品的页面',
layout: {
type: 'grid',
properties: {
columns: '1fr 2fr 1fr',
rows: 'auto 1fr auto',
gap: '20px',
padding: '20px'
},
responsive: {
md: {
columns: '1fr',
rows: 'auto auto auto auto auto'
}
}
},
components: [
{
id: 'header',
type: 'text',
properties: {
content: '我们的产品',
tag: 'h1'
},
layout: {
grid: { area: '1 / 1 / 2 / 4' }
},
styles: {
textAlign: 'center',
color: 'var(--color-primary)',
marginBottom: '20px'
}
},
{
id: 'product-image',
type: 'image',
properties: {
src: 'https://via.placeholder.com/400x300',
alt: '产品图片',
width: '100%'
},
layout: {
grid: { area: '2 / 1 / 3 / 2' }
}
},
{
id: 'product-description',
type: 'text',
properties: {
content: '这是一款革命性的产品,具有出色的性能和用户体验。它采用最新的技术,为用户提供前所未有的便利。',
tag: 'p'
},
layout: {
grid: { area: '2 / 2 / 3 / 3' }
},
styles: {
fontSize: '16px',
lineHeight: '1.6',
padding: '20px'
}
},
{
id: 'product-features',
type: 'text',
properties: {
content: '<ul><li>高性能处理器</li><li>超长续航</li><li>精美设计</li><li>智能功能</li></ul>',
tag: 'div'
},
layout: {
grid: { area: '2 / 3 / 3 / 4' }
},
styles: {
padding: '20px'
}
},
{
id: 'buy-button',
type: 'button',
properties: {
text: '立即购买',
type: 'primary'
},
layout: {
grid: { area: '3 / 2 / 4 / 3' }
},
styles: {
justifySelf: 'center',
padding: '12px 24px',
fontSize: '16px'
}
}
],
styles: {
'body': {
fontFamily: 'Arial, sans-serif',
margin: '0',
padding: '0'
},
'.product-page': {
minHeight: '100vh',
backgroundColor: '#f5f5f5'
}
},
scripts: [
{
type: 'inline',
content: `
document.addEventListener('component:click', function(event) {
if (event.detail.component.dataset.id === 'buy-button') {
alert('感谢您的购买!');
}
});
`
}
],
settings: {
theme: 'default',
language: 'zh-CN',
seo: {
description: '查看我们最新的产品,了解其特性和优势',
keywords: ['产品', '购买', '科技'],
ogTitle: '产品展示 - 我们的公司',
ogDescription: '发现我们的革命性产品'
},
performance: {
preload: []
},
security: {
csp: "default-src 'self' 'unsafe-inline' https:"
}
}
};
// 创建页面
const page = await this.pageGenerator.createPage(pageDefinition);
console.log('产品页面创建成功:', page.id);
// 渲染页面
const html = await this.pageGenerator.renderPage(page.id);
console.log('页面HTML长度:', html.length);
// 发布页面
const publishResult = await this.pageGenerator.publishPage(page.id, {
environment: 'production',
version: '1.0.0'
});
console.log('页面发布成功:', publishResult.url);
}
private async createRegistrationPage(): Promise<void> {
console.log('\n--- 创建用户注册页面 ---');
const pageDefinition: PageDefinition = {
title: '用户注册',
description: '新用户注册页面',
layout: {
type: 'flex',
properties: {
direction: 'column',
align: 'center',
justify: 'center',
minHeight: '100vh',
padding: '20px'
}
},
components: [
{
id: 'form-title',
type: 'text',
properties: {
content: '用户注册',
tag: 'h2'
},
styles: {
marginBottom: '30px',
color: 'var(--color-primary)'
}
},
{
id: 'username-input',
type: 'input',
properties: {
placeholder: '请输入用户名',
type: 'text',
required: true
},
styles: {
marginBottom: '15px',
width: '300px'
}
},
{
id: 'email-input',
type: 'input',
properties: {
placeholder: '请输入邮箱',
type: 'email',
required: true
},
styles: {
marginBottom: '15px',
width: '300px'
}
},
{
id: 'password-input',
type: 'input',
properties: {
placeholder: '请输入密码',
type: 'password',
required: true
},
styles: {
marginBottom: '20px',
width: '300px'
}
},
{
id: 'register-button',
type: 'button',
properties: {
text: '注册',
type: 'primary'
},
styles: {
width: '300px',
padding: '12px'
}
}
],
scripts: [
{
type: 'inline',
content: `
document.addEventListener('component:click', function(event) {
if (event.detail.component.dataset.id === 'register-button') {
const username = document.querySelector('[data-id="username-input"] input').value;
const email = document.querySelector('[data-id="email-input"] input').value;
const password = document.querySelector('[data-id="password-input"] input').value;
if (username && email && password) {
alert('注册成功!欢迎 ' + username);
} else {
alert('请填写所有必填字段');
}
}
});
`
}
],
settings: {
theme: 'default',
language: 'zh-CN'
}
};
const page = await this.pageGenerator.createPage(pageDefinition);
console.log('注册页面创建成功:', page.id);
const html = await this.pageGenerator.renderPage(page.id);
console.log('页面HTML长度:', html.length);
}
private async createDashboardPage(): Promise<void> {
console.log('\n--- 创建仪表板页面 ---');
// 使用内置模板
const page = await this.pageGenerator.createPageFromTemplate('dashboard', {
title: '管理仪表板',
description: '系统管理仪表板',
variables: {
userName: '管理员',
totalUsers: '1,234',
totalOrders: '5,678',
revenue: '¥123,456'
}
});
console.log('仪表板页面创建成功:', page.id);
// 添加自定义组件
await this.pageGenerator.updatePage(page.id, {
components: [
...page.components,
{
id: 'chart-container',
type: 'text',
properties: {
content: '<div id="chart" style="width: 100%; height: 300px; background: #f0f0f0; display: flex; align-items: center; justify-content: center;">图表占位符</div>',
tag: 'div'
},
styles: {
marginTop: '20px',
padding: '20px',
backgroundColor: 'white',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}
}
]
});
console.log('仪表板页面更新完成');
}
private async demonstratePageManagement(): Promise<void> {
console.log('\n--- 页面管理演示 ---');
// 查询页面
const pages = await this.pageGenerator.queryPages({
status: 'published',
limit: 10
});
console.log('已发布页面数量:', pages.length);
// 获取页面详情
if (pages.length > 0) {
const pageDetail = await this.pageGenerator.getPage(pages[0].id);
console.log('页面详情:', {
id: pageDetail.id,
title: pageDetail.title,
status: pageDetail.status,
componentCount: pageDetail.components.length
});
// 复制页面
const copiedPage = await this.pageGenerator.duplicatePage(pages[0].id, {
title: pageDetail.title + ' (副本)'
});
console.log('页面复制成功:', copiedPage.id);
// 删除副本
await this.pageGenerator.deletePage(copiedPage.id);
console.log('副本页面已删除');
}
}
private async demonstrateTemplateManagement(): Promise<void> {
console.log('\n--- 模板管理演示 ---');
// 创建自定义模板
const template: PageTemplate = {
id: 'blog-post',
name: '博客文章',
description: '博客文章页面模板',
category: 'content',
variables: [
{
name: 'title',
type: 'string',
label: '文章标题',
required: true
},
{
name: 'author',
type: 'string',
label: '作者',
required: true
},
{
name: 'content',
type: 'string',
label: '文章内容',
required: true
},
{
name: 'publishDate',
type: 'string',
label: '发布日期',
required: true
}
],
definition: {
title: '{{title}}',
description: '{{author}}的博客文章',
layout: {
type: 'container',
properties: {
maxWidth: '800px',
margin: '0 auto',
padding: '40px 20px'
}
},
components: [
{
id: 'article-title',
type: 'text',
properties: {
content: '{{title}}',
tag: 'h1'
},
styles: {
marginBottom: '10px'
}
},
{
id: 'article-meta',
type: 'text',
properties: {
content: '作者:{{author}} | 发布时间:{{publishDate}}',
tag: 'p'
},
styles: {
color: 'var(--color-text-secondary)',
marginBottom: '30px',
fontSize: '14px'
}
},
{
id: 'article-content',
type: 'text',
properties: {
content: '{{content}}',
tag: 'div'
},
styles: {
lineHeight: '1.8',
fontSize: '16px'
}
}
],
settings: {
theme: 'default',
language: 'zh-CN'
}
}
};
// 注册模板
await this.pageGenerator.registerTemplate(template);
console.log('博客模板注册成功');
// 使用模板创建页面
const blogPage = await this.pageGenerator.createPageFromTemplate('blog-post', {
title: '低代码平台的未来',
variables: {
title: '低代码平台的未来',
author: '技术专家',
content: '低代码平台正在改变软件开发的方式,让更多人能够参与到应用程序的创建中来...',
publishDate: '2024-01-15'
}
});
console.log('博客页面创建成功:', blogPage.id);
// 列出所有模板
const templates = await this.pageGenerator.listTemplates();
console.log('可用模板:', templates.map(t => t.name));
}
private setupEventListeners(): void {
// 页面事件监听
this.eventBus.on('page:created', (event) => {
console.log('页面创建事件:', event.page.title);
});
this.eventBus.on('page:updated', (event) => {
console.log('页面更新事件:', event.page.title);
});
this.eventBus.on('page:published', (event) => {
console.log('页面发布事件:', event.page.title, '-> URL:', event.result.url);
});
this.eventBus.on('page:deleted', (event) => {
console.log('页面删除事件:', event.pageId);
});
// 组件事件监听
this.eventBus.on('component:registered', (event) => {
console.log('组件注册事件:', event.type);
});
// 模板事件监听
this.eventBus.on('template:registered', (event) => {
console.log('模板注册事件:', event.template.name);
});
// 渲染事件监听
this.eventBus.on('page:rendered', (event) => {
console.log('页面渲染完成:', event.page.title);
});
}
}
// 运行演示
const demo = new LowCodePageGeneratorDemo();
demo.runDemo().then(() => {
console.log('\n=== 演示完成 ===');
}).catch(error => {
console.error('演示失败:', error);
});
9.8 小结
本章详细介绍了低代码平台的页面生成器与动态页面系统,涵盖了以下核心要点:
9.8.1 架构设计
- 页面生成器架构:设计了完整的页面生成器接口和实现,支持页面的创建、更新、渲染、发布和管理
- 布局系统:实现了多种布局类型(容器、网格、Flex),支持响应式设计
- 组件系统:建立了组件注册机制,支持自定义组件和内置组件
- 模板系统:提供了页面模板功能,支持变量替换和模板复用
9.8.2 核心功能
- 页面管理:完整的页面生命周期管理,包括创建、编辑、发布、删除
- 动态渲染:支持服务端渲染和客户端渲染,生成完整的HTML页面
- 样式系统:集成主题管理,支持CSS变量和响应式样式
- 脚本系统:支持内联脚本和外部脚本,提供依赖管理
9.8.3 技术特色
- 类型安全:使用TypeScript提供完整的类型定义和检查
- 事件驱动:基于事件总线的松耦合架构设计
- 插件化:支持自定义组件、布局和模板的扩展
- 性能优化:支持脚本预加载、样式优化和响应式设计
- SEO友好:支持元数据管理、结构化数据和搜索引擎优化
9.8.4 最佳实践
- 组件设计:遵循单一职责原则,保持组件的简洁和可复用性
- 布局规划:合理使用网格和Flex布局,确保响应式设计
- 性能考虑:优化资源加载,减少不必要的重渲染
- 安全防护:实施内容安全策略,防范XSS攻击
- 用户体验:提供直观的页面编辑界面和实时预览功能
9.8.5 扩展方向
- 可视化编辑器:开发拖拽式页面编辑器
- 动画系统:集成CSS动画和JavaScript动画
- 国际化支持:多语言内容管理和本地化
- 版本控制:页面版本管理和回滚功能
- 协作功能:多人协作编辑和权限管理
通过本章的学习,你已经掌握了如何构建一个功能完整的页面生成器系统。下一章我们将学习权限管理与安全控制,了解如何为低代码平台添加完善的安全机制。 “`