手搓AI設計稿轉HTML:從Figma/Sketch自動生成代碼

第一章:設計稿轉代碼的革命性意義

1.1 前端開發的痛點與機遇

在當今數位化時代,前端開發正面臨著前所未有的挑戰與機遇。隨著用戶對界面體驗的要求越來越高,設計稿與最終產出代碼之間的鴻溝日益顯著。傳統的開發流程中,設計師使用Figma、Sketch等專業工具創造出精美的界面設計,而前端工程師則需要手動將這些視覺設計轉換為HTML、CSS和JavaScript代碼。這個過程不僅耗時耗力,而且容易產生誤差,導致設計還原度不足。

根據GitHub的統計數據,全球約有60%的前端開發時間花費在將設計稿轉換為代碼的過程上。這不僅造成了巨大的時間浪費,也導致了開發成本的上升。而正是這個痛點,催生了設計稿自動轉代碼技術的興起。

1.2 現有解決方案的局限性

目前市面上已經出現了一些設計稿轉代碼的工具,如Anima、Zeplin、Avocode等。這些工具在一定程度上提高了工作效率,但它們大多存在以下局限性:

  1. 生成代碼質量不高:生成的HTML結構往往不符合最佳實踐,CSS代碼冗餘且缺乏組織性

  2. 缺乏語義化標籤:大多使用div嵌套,而不是語義化的HTML5標籤

  3. 響應式設計支持有限:難以處理複雜的響應式佈局

  4. 組件化程度低:無法識別和重用常見的UI組件

  5. 動態交互實現困難:對於複雜的交互效果支持有限

1.3 AI驅動的解決方案優勢

人工智慧技術的發展為解決這一難題提供了全新的思路。通過機器學習和計算機視覺技術,AI能夠理解設計稿的視覺層次、佈局結構和設計意圖,從而生成更高質量的代碼。AI驅動的解決方案具有以下優勢:

  1. 智能識別設計模式:自動識別常見的UI模式並生成相應的組件

  2. 語義化標籤推斷:根據內容和結構推斷合適的HTML5標籤

  3. 自適應佈局生成:自動創建響應式佈局和媒體查詢

  4. 代碼優化:生成符合最佳實踐的精簡代碼

  5. 學習與改進能力:隨著使用不斷優化生成結果

第二章:技術架構與核心原理

2.1 整體架構設計

一個完整的AI設計稿轉代碼系統通常包含以下幾個核心模塊:

python

# 系統架構示意代碼
class DesignToCodeSystem:
    def __init__(self):
        self.design_parser = DesignParser()      # 設計稿解析模塊
        self.layout_analyzer = LayoutAnalyzer()  # 佈局分析模塊
        self.component_detector = ComponentDetector()  # 組件檢測模塊
        self.code_generator = CodeGenerator()    # 代碼生成模塊
        self.optimizer = CodeOptimizer()         # 代碼優化模塊
    
    def process(self, design_file):
        # 1. 解析設計稿
        design_data = self.design_parser.parse(design_file)
        
        # 2. 分析佈局結構
        layout_info = self.layout_analyzer.analyze(design_data)
        
        # 3. 檢測UI組件
        components = self.component_detector.detect(design_data)
        
        # 4. 生成代碼
        raw_code = self.code_generator.generate(layout_info, components)
        
        # 5. 優化代碼
        optimized_code = self.optimizer.optimize(raw_code)
        
        return optimized_code

2.2 設計稿解析技術

2.2.1 Figma API解析

Figma提供了強大的REST API,允許開發者訪問設計文件中的各種元素和屬性:

javascript

// Figma API 使用示例
const fetch = require('node-fetch');

class FigmaParser {
    constructor(accessToken) {
        this.accessToken = accessToken;
        this.baseUrl = 'https://api.figma.com/v1';
    }
    
    async getFile(fileKey) {
        const response = await fetch(
            `${this.baseUrl}/files/${fileKey}`,
            {
                headers: { 'X-Figma-Token': this.accessToken }
            }
        );
        
        return await response.json();
    }
    
    async parseNode(node) {
        const nodeInfo = {
            id: node.id,
            name: node.name,
            type: node.type,
            boundingBox: {
                x: node.absoluteBoundingBox.x,
                y: node.absoluteBoundingBox.y,
                width: node.absoluteBoundingBox.width,
                height: node.absoluteBoundingBox.height
            },
            style: this.extractStyles(node),
            children: []
        };
        
        if (node.children) {
            for (const child of node.children) {
                nodeInfo.children.push(await this.parseNode(child));
            }
        }
        
        return nodeInfo;
    }
    
    extractStyles(node) {
        const styles = {};
        
        // 提取填充樣式
        if (node.fills && node.fills.length > 0) {
            styles.fill = this.extractColor(node.fills[0]);
        }
        
        // 提取文字樣式
        if (node.type === 'TEXT') {
            styles.text = {
                content: node.characters,
                fontSize: node.style.fontSize,
                fontFamily: node.style.fontFamily,
                fontWeight: node.style.fontWeight,
                lineHeight: node.style.lineHeightPx,
                textAlign: node.style.textAlignHorizontal
            };
        }
        
        // 提取邊框樣式
        if (node.strokes && node.strokes.length > 0) {
            styles.border = {
                color: this.extractColor(node.strokes[0]),
                width: node.strokeWeight
            };
        }
        
        // 提取陰影效果
        if (node.effects && node.effects.length > 0) {
            styles.effects = node.effects.map(effect => ({
                type: effect.type,
                color: this.extractColor(effect),
                offset: effect.offset,
                radius: effect.radius
            }));
        }
        
        return styles;
    }
    
    extractColor(colorInfo) {
        if (!colorInfo.color) return null;
        
        const { r, g, b } = colorInfo.color;
        const a = colorInfo.opacity || 1;
        
        return {
            rgb: `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)})`,
            rgba: `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`,
            hex: this.rgbToHex(r, g, b)
        };
    }
    
    rgbToHex(r, g, b) {
        return "#" + ((1 << 24) + (Math.round(r * 255) << 16) + 
                     (Math.round(g * 255) << 8) + Math.round(b * 255))
                     .toString(16).slice(1);
    }
}
2.2.2 Sketch文件解析

Sketch文件本質上是zip壓縮包,包含了JSON格式的設計數據:

python

# Sketch文件解析器
import zipfile
import json
import plistlib
from pathlib import Path

class SketchParser:
    def __init__(self, sketch_file_path):
        self.sketch_file_path = sketch_file_path
        self.temp_dir = Path("./temp_sketch")
        
    def parse(self):
        """解析Sketch文件"""
        # 解壓Sketch文件
        with zipfile.ZipFile(self.sketch_file_path, 'r') as zip_ref:
            zip_ref.extractall(self.temp_dir)
        
        # 讀取主要文檔
        document_path = self.temp_dir / "document.json"
        with open(document_path, 'r', encoding='utf-8') as f:
            document_data = json.load(f)
        
        # 解析頁面和圖層
        pages = []
        for page in document_data.get('pages', []):
            page_data = self.parse_page(page)
            pages.append(page_data)
        
        # 清理臨時文件
        self.cleanup()
        
        return {
            'pages': pages,
            'metadata': document_data.get('meta', {})
        }
    
    def parse_page(self, page_ref):
        """解析單個頁面"""
        page_path = self.temp_dir / page_ref['_ref']
        with open(page_path, 'r', encoding='utf-8') as f:
            page_data = json.load(f)
        
        layers = []
        for layer in page_data.get('layers', []):
            layer_data = self.parse_layer(layer)
            if layer_data:
                layers.append(layer_data)
        
        return {
            'name': page_data.get('name', 'Untitled'),
            'layers': layers,
            'frame': page_data.get('frame', {})
        }
    
    def parse_layer(self, layer_data):
        """解析單個圖層"""
        layer_type = layer_data.get('_class', '')
        
        if layer_type == 'text':
            return self.parse_text_layer(layer_data)
        elif layer_type == 'rectangle':
            return self.parse_rectangle_layer(layer_data)
        elif layer_type == 'group':
            return self.parse_group_layer(layer_data)
        elif layer_type == 'symbolInstance':
            return self.parse_symbol_layer(layer_data)
        
        return None
    
    def parse_text_layer(self, layer_data):
        """解析文字圖層"""
        style_data = layer_data.get('style', {})
        text_style = style_data.get('textStyle', {})
        
        return {
            'type': 'text',
            'name': layer_data.get('name', ''),
            'frame': layer_data.get('frame', {}),
            'content': layer_data.get('attributedString', {}).get('string', ''),
            'style': {
                'fontFamily': text_style.get('fontFamily', ''),
                'fontSize': text_style.get('fontSize', 12),
                'fontWeight': text_style.get('fontWeight', 400),
                'color': self.parse_color(text_style.get('color', {})),
                'alignment': text_style.get('alignment', 0)
            }
        }
    
    def parse_rectangle_layer(self, layer_data):
        """解析矩形圖層"""
        style_data = layer_data.get('style', {})
        
        return {
            'type': 'rectangle',
            'name': layer_data.get('name', ''),
            'frame': layer_data.get('frame', {}),
            'style': {
                'fill': self.parse_color(style_data.get('fills', [{}])[0]),
                'border': self.parse_border(style_data.get('borders', [])),
                'cornerRadius': layer_data.get('fixedRadius', 0)
            }
        }
    
    def parse_color(self, color_data):
        """解析顏色數據"""
        if not color_data:
            return None
        
        color = color_data.get('color', {})
        alpha = color_data.get('alpha', 1)
        
        return {
            'r': color.get('red', 0),
            'g': color.get('green', 0),
            'b': color.get('blue', 0),
            'a': alpha
        }
    
    def cleanup(self):
        """清理臨時文件"""
        import shutil
        if self.temp_dir.exists():
            shutil.rmtree(self.temp_dir)

2.3 佈局分析算法

佈局分析是設計稿轉代碼的核心環節,需要識別元素之間的相對位置關係、對齊方式和間距:

python

# 佈局分析器
class LayoutAnalyzer:
    def __init__(self):
        self.threshold = 5  # 對齊閾值(像素)
    
    def analyze(self, elements, container_size=None):
        """分析元素佈局關係"""
        if not elements:
            return {}
        
        # 分析對齊關係
        alignments = self.detect_alignments(elements)
        
        # 分析間距關係
        spacings = self.detect_spacings(elements)
        
        # 分析層級關係
        hierarchy = self.detect_hierarchy(elements)
        
        # 推斷佈局類型
        layout_type = self.infer_layout_type(elements, alignments)
        
        # 計算響應式約束
        constraints = self.calculate_constraints(elements, container_size)
        
        return {
            'alignments': alignments,
            'spacings': spacings,
            'hierarchy': hierarchy,
            'layout_type': layout_type,
            'constraints': constraints
        }
    
    def detect_alignments(self, elements):
        """檢測對齊關係"""
        alignments = {
            'left': [],
            'right': [],
            'top': [],
            'bottom': [],
            'center_x': [],
            'center_y': []
        }
        
        for i, elem1 in enumerate(elements):
            for j, elem2 in enumerate(elements[i+1:], i+1):
                # 檢測左對齊
                if abs(elem1['frame']['x'] - elem2['frame']['x']) < self.threshold:
                    alignments['left'].append((i, j))
                
                # 檢測右對齊
                if abs(elem1['frame']['x'] + elem1['frame']['width'] - 
                       elem2['frame']['x'] - elem2['frame']['width']) < self.threshold:
                    alignments['right'].append((i, j))
                
                # 檢測頂部對齊
                if abs(elem1['frame']['y'] - elem2['frame']['y']) < self.threshold:
                    alignments['top'].append((i, j))
                
                # 檢測底部對齊
                if abs(elem1['frame']['y'] + elem1['frame']['height'] - 
                       elem2['frame']['y'] - elem2['frame']['height']) < self.threshold:
                    alignments['bottom'].append((i, j))
                
                # 檢測水平居中
                center_x1 = elem1['frame']['x'] + elem1['frame']['width'] / 2
                center_x2 = elem2['frame']['x'] + elem2['frame']['width'] / 2
                if abs(center_x1 - center_x2) < self.threshold:
                    alignments['center_x'].append((i, j))
                
                # 檢測垂直居中
                center_y1 = elem1['frame']['y'] + elem1['frame']['height'] / 2
                center_y2 = elem2['frame']['y'] + elem2['frame']['height'] / 2
                if abs(center_y1 - center_y2) < self.threshold:
                    alignments['center_y'].append((i, j))
        
        return alignments
    
    def detect_spacings(self, elements):
        """檢測間距關係"""
        spacings = []
        
        elements_sorted_x = sorted(elements, key=lambda e: e['frame']['x'])
        elements_sorted_y = sorted(elements, key=lambda e: e['frame']['y'])
        
        # 檢測水平間距
        for i in range(len(elements_sorted_x) - 1):
            elem1 = elements_sorted_x[i]
            elem2 = elements_sorted_x[i + 1]
            
            spacing_x = elem2['frame']['x'] - (elem1['frame']['x'] + elem1['frame']['width'])
            if spacing_x > 0:
                # 檢查是否有重疊
                overlap_y = self.calculate_y_overlap(elem1, elem2)
                if overlap_y > 0:
                    spacings.append({
                        'type': 'horizontal',
                        'elements': [elem1['id'], elem2['id']],
                        'value': spacing_x,
                        'consistent': self.check_spacing_consistency(spacing_x, spacings, 'horizontal')
                    })
        
        # 檢測垂直間距
        for i in range(len(elements_sorted_y) - 1):
            elem1 = elements_sorted_y[i]
            elem2 = elements_sorted_y[i + 1]
            
            spacing_y = elem2['frame']['y'] - (elem1['frame']['y'] + elem1['frame']['height'])
            if spacing_y > 0:
                # 檢查是否有重疊
                overlap_x = self.calculate_x_overlap(elem1, elem2)
                if overlap_x > 0:
                    spacings.append({
                        'type': 'vertical',
                        'elements': [elem1['id'], elem2['id']],
                        'value': spacing_y,
                        'consistent': self.check_spacing_consistency(spacing_y, spacings, 'vertical')
                    })
        
        return spacings
    
    def calculate_y_overlap(self, elem1, elem2):
        """計算Y軸重疊"""
        y1_top = elem1['frame']['y']
        y1_bottom = elem1['frame']['y'] + elem1['frame']['height']
        y2_top = elem2['frame']['y']
        y2_bottom = elem2['frame']['y'] + elem2['frame']['height']
        
        return max(0, min(y1_bottom, y2_bottom) - max(y1_top, y2_top))
    
    def infer_layout_type(self, elements, alignments):
        """推斷佈局類型"""
        # 計算元素排列的特徵
        if len(elements) == 0:
            return 'empty'
        
        # 檢查是否為網格佈局
        if self.is_grid_layout(elements):
            return 'grid'
        
        # 檢查是否為彈性盒子佈局
        if self.is_flexbox_layout(elements, alignments):
            return 'flexbox'
        
        # 檢查是否為絕對定位
        if self.is_absolute_layout(elements):
            return 'absolute'
        
        return 'block'
    
    def is_grid_layout(self, elements):
        """判斷是否為網格佈局"""
        if len(elements) < 4:
            return False
        
        # 檢測元素是否按行列排列
        x_positions = sorted(set(e['frame']['x'] for e in elements))
        y_positions = sorted(set(e['frame']['y'] for e in elements))
        
        # 如果有多個不同的x和y位置,可能是網格
        if len(x_positions) > 1 and len(y_positions) > 1:
            # 檢查元素大小是否一致
            widths = [e['frame']['width'] for e in elements]
            heights = [e['frame']['height'] for e in elements]
            
            width_std = np.std(widths) if len(widths) > 1 else 0
            height_std = np.std(heights) if len(heights) > 1 else 0
            
            # 如果大小變化不大,可能是網格
            if width_std < 10 and height_std < 10:
                return True
        
        return False
    
    def calculate_constraints(self, elements, container_size):
        """計算響應式約束"""
        constraints = {}
        
        if not container_size:
            return constraints
        
        container_width = container_size['width']
        container_height = container_size['height']
        
        for elem in elements:
            elem_constraints = {}
            frame = elem['frame']
            
            # 計算相對位置百分比
            elem_constraints['left_percent'] = frame['x'] / container_width * 100
            elem_constraints['top_percent'] = frame['y'] / container_height * 100
            elem_constraints['width_percent'] = frame['width'] / container_width * 100
            elem_constraints['height_percent'] = frame['height'] / container_height * 100
            
            # 判斷是否應該使用固定寬高
            elem_constraints['fixed_width'] = frame['width'] < 100  # 小於100px可能應該固定
            elem_constraints['fixed_height'] = frame['height'] < 50
            
            # 判斷是否應該保持寬高比
            if elem.get('type') == 'image' and frame['width'] > 0 and frame['height'] > 0:
                elem_constraints['aspect_ratio'] = frame['width'] / frame['height']
            
            constraints[elem['id']] = elem_constraints
        
        return constraints

2.4 UI組件識別技術

使用機器學習技術識別常見的UI組件,可以提高代碼生成的質量和可重用性:

python

# UI組件識別器
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler

class ComponentDetector:
    def __init__(self):
        self.component_patterns = self.load_component_patterns()
        self.feature_extractor = FeatureExtractor()
        
    def load_component_patterns(self):
        """加載已知的組件模式"""
        return {
            'button': {
                'features': ['rounded_corners', 'centered_text', 'hover_effect'],
                'aspect_ratio_range': (2, 8),
                'size_range': (30, 200)
            },
            'input': {
                'features': ['rectangular', 'placeholder_text', 'border'],
                'aspect_ratio_range': (4, 15),
                'size_range': (100, 400)
            },
            'card': {
                'features': ['rounded_corners', 'shadow', 'image_area', 'text_content'],
                'aspect_ratio_range': (0.5, 2),
                'size_range': (150, 500)
            },
            'navbar': {
                'features': ['horizontal_layout', 'multiple_items', 'logo', 'menu_items'],
                'aspect_ratio_range': (5, 20),
                'height_range': (40, 100)
            }
        }
    
    def detect(self, elements, layout_info):
        """檢測UI組件"""
        components = []
        
        # 提取特徵
        features = []
        for elem in elements:
            feature_vector = self.feature_extractor.extract(elem)
            features.append(feature_vector)
        
        features = np.array(features)
        
        # 聚類相似的UI元素
        clusters = self.cluster_elements(features)
        
        # 識別組件類型
        for cluster_id, element_indices in enumerate(clusters):
            if len(element_indices) < 1:
                continue
            
            # 獲取聚類中的元素
            cluster_elements = [elements[i] for i in element_indices]
            
            # 識別組件類型
            component_type = self.identify_component_type(cluster_elements)
            
            if component_type:
                # 創建組件實例
                component = {
                    'type': component_type,
                    'elements': cluster_elements,
                    'properties': self.extract_component_properties(cluster_elements, component_type),
                    'layout': self.analyze_component_layout(cluster_elements)
                }
                components.append(component)
        
        # 檢測嵌套組件
        nested_components = self.detect_nested_components(components)
        components.extend(nested_components)
        
        return components
    
    def cluster_elements(self, features):
        """聚類相似的UI元素"""
        # 標準化特徵
        scaler = StandardScaler()
        features_scaled = scaler.fit_transform(features)
        
        # 使用DBSCAN進行聚類
        # eps: 鄰域半徑,min_samples: 最小樣本數
        dbscan = DBSCAN(eps=0.5, min_samples=1)
        labels = dbscan.fit_predict(features_scaled)
        
        # 組織聚類結果
        clusters = {}
        for i, label in enumerate(labels):
            if label not in clusters:
                clusters[label] = []
            clusters[label].append(i)
        
        return list(clusters.values())
    
    def identify_component_type(self, elements):
        """識別組件類型"""
        if len(elements) == 1:
            element = elements[0]
            
            # 單個元素的組件識別
            if element['type'] == 'text':
                # 檢查是否為按鈕
                if self.is_button(element):
                    return 'button'
                elif self.is_input_label(element):
                    return 'input_label'
            
            elif element['type'] == 'rectangle':
                # 檢查是否為輸入框或卡片
                if self.is_input_field(element):
                    return 'input'
                elif self.is_card(element):
                    return 'card'
        
        elif len(elements) > 1:
            # 多個元素的組件識別
            if self.is_navigation_bar(elements):
                return 'navbar'
            elif self.is_card_component(elements):
                return 'card'
            elif self.is_form_group(elements):
                return 'form_group'
        
        return None
    
    def is_button(self, element):
        """判斷元素是否為按鈕"""
        if element['type'] != 'rectangle':
            return False
        
        # 檢查是否有圓角
        style = element.get('style', {})
        corner_radius = style.get('cornerRadius', 0)
        
        # 檢查尺寸比例
        frame = element['frame']
        aspect_ratio = frame['width'] / frame['height'] if frame['height'] > 0 else 0
        
        # 檢查是否有文字內容
        has_text = any(child.get('type') == 'text' for child in element.get('children', []))
        
        # 檢查是否有陰影或邊框
        has_shadow = 'shadow' in style.get('effects', [])
        has_border = 'border' in style
        
        return (corner_radius > 0 or has_shadow) and 2 <= aspect_ratio <= 8 and has_text
    
    def extract_component_properties(self, elements, component_type):
        """提取組件屬性"""
        properties = {}
        
        if component_type == 'button':
            # 提取按鈕屬性
            main_element = elements[0] if elements else {}
            properties.update({
                'text': self.extract_text_content(main_element),
                'backgroundColor': main_element.get('style', {}).get('fill'),
                'textColor': self.extract_text_color(main_element),
                'borderRadius': main_element.get('style', {}).get('cornerRadius', 0),
                'size': {
                    'width': main_element.get('frame', {}).get('width'),
                    'height': main_element.get('frame', {}).get('height')
                }
            })
        
        elif component_type == 'input':
            # 提取輸入框屬性
            main_element = elements[0] if elements else {}
            properties.update({
                'placeholder': self.extract_placeholder(main_element),
                'type': self.infer_input_type(main_element),
                'border': main_element.get('style', {}).get('border'),
                'size': main_element.get('frame', {})
            })
        
        return properties
    
    def analyze_component_layout(self, elements):
        """分析組件內部佈局"""
        if len(elements) == 1:
            return {'type': 'single', 'layout': 'absolute'}
        
        # 分析多個元素的佈局關係
        layout_analyzer = LayoutAnalyzer()
        layout_info = layout_analyzer.analyze(elements)
        
        # 根據佈局信息推斷CSS佈局方式
        if layout_info['layout_type'] == 'flexbox':
            return {
                'type': 'flex',
                'direction': self.infer_flex_direction(elements, layout_info),
                'justify': self.infer_justify_content(elements, layout_info),
                'align': self.infer_align_items(elements, layout_info)
            }
        elif layout_info['layout_type'] == 'grid':
            return {
                'type': 'grid',
                'columns': self.infer_grid_columns(elements, layout_info),
                'rows': self.infer_grid_rows(elements, layout_info),
                'gaps': layout_info['spacings']
            }
        
        return {'type': 'complex', 'layout': 'absolute'}

第三章:代碼生成引擎實現

3.1 HTML結構生成

javascript

// HTML生成器
class HTMLGenerator {
    constructor() {
        this.indentLevel = 0;
        this.indentSize = 2;
    }
    
    generate(layoutInfo, components) {
        // 生成HTML文檔結構
        let html = `<!DOCTYPE html>\n`;
        html += `<html lang="zh-Hant">\n`;
        html += this.generateHead();
        html += this.generateBody(layoutInfo, components);
        html += `</html>`;
        
        return html;
    }
    
    generateHead() {
        const indent = ' '.repeat(this.indentSize);
        
        let head = `<head>\n`;
        head += `${indent}<meta charset="UTF-8">\n`;
        head += `${indent}<meta name="viewport" content="width=device-width, initial-scale=1.0">\n`;
        head += `${indent}<title>設計稿轉換頁面</title>\n`;
        head += `${indent}<link rel="stylesheet" href="styles.css">\n`;
        head += `${indent}<!-- 生成時間: ${new Date().toLocaleString()} -->\n`;
        head += `</head>\n`;
        
        return head;
    }
    
    generateBody(layoutInfo, components) {
        this.indentLevel = 1;
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        
        let body = `<body>\n`;
        
        // 根據佈局類型生成不同的結構
        switch (layoutInfo.layout_type) {
            case 'grid':
                body += this.generateGridLayout(layoutInfo, components);
                break;
            case 'flexbox':
                body += this.generateFlexboxLayout(layoutInfo, components);
                break;
            default:
                body += this.generateDefaultLayout(layoutInfo, components);
        }
        
        body += `</body>\n`;
        return body;
    }
    
    generateGridLayout(layoutInfo, components) {
        this.indentLevel++;
        let html = '';
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        
        // 創建網格容器
        const gridTemplateColumns = this.calculateGridColumns(layoutInfo);
        const gridTemplateRows = this.calculateGridRows(layoutInfo);
        
        html += `${indent}<div class="grid-container" \n`;
        html += `${' '.repeat(this.indentSize * (this.indentLevel + 1))}style="display: grid;\n`;
        html += `${' '.repeat(this.indentSize * (this.indentLevel + 2))}grid-template-columns: ${gridTemplateColumns};\n`;
        html += `${' '.repeat(this.indentSize * (this.indentLevel + 2))}grid-template-rows: ${gridTemplateRows};\n`;
        
        // 添加間隙
        if (layoutInfo.spacings && layoutInfo.spacings.length > 0) {
            const gap = this.calculateConsistentGap(layoutInfo.spacings);
            html += `${' '.repeat(this.indentSize * (this.indentLevel + 2))}gap: ${gap}px;">\n`;
        } else {
            html += `${' '.repeat(this.indentSize * (this.indentLevel + 2))}">\n`;
        }
        
        // 添加子元素
        this.indentLevel++;
        html += this.generateGridItems(layoutInfo, components);
        this.indentLevel--;
        
        html += `${indent}</div>\n`;
        this.indentLevel--;
        
        return html;
    }
    
    generateGridItems(layoutInfo, components) {
        let html = '';
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        
        // 將元素分配到網格單元格
        const gridItems = this.assignElementsToGrid(layoutInfo, components);
        
        gridItems.forEach((item, index) => {
            const { element, row, column, rowSpan, colSpan } = item;
            
            html += `${indent}<div class="grid-item" `;
            
            // 添加網格位置
            html += `style="grid-row: ${row} / span ${rowSpan}; `;
            html += `grid-column: ${column} / span ${colSpan};">\n`;
            
            // 生成元素內容
            this.indentLevel++;
            html += this.generateElement(element);
            this.indentLevel--;
            
            html += `${indent}</div>\n`;
        });
        
        return html;
    }
    
    generateFlexboxLayout(layoutInfo, components) {
        this.indentLevel++;
        let html = '';
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        
        // 確定flex方向
        const flexDirection = this.determineFlexDirection(layoutInfo);
        const justifyContent = this.determineJustifyContent(layoutInfo);
        const alignItems = this.determineAlignItems(layoutInfo);
        
        html += `${indent}<div class="flex-container" \n`;
        html += `${' '.repeat(this.indentSize * (this.indentLevel + 1))}style="display: flex;\n`;
        html += `${' '.repeat(this.indentSize * (this.indentLevel + 2))}flex-direction: ${flexDirection};\n`;
        html += `${' '.repeat(this.indentSize * (this.indentLevel + 2))}justify-content: ${justifyContent};\n`;
        html += `${' '.repeat(this.indentSize * (this.indentLevel + 2))}align-items: ${alignItems};">\n`;
        
        // 生成flex項目
        this.indentLevel++;
        components.forEach(component => {
            html += this.generateComponent(component);
        });
        this.indentLevel--;
        
        html += `${indent}</div>\n`;
        this.indentLevel--;
        
        return html;
    }
    
    generateComponent(component) {
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        let html = '';
        
        // 根據組件類型生成不同的HTML
        switch (component.type) {
            case 'button':
                html += this.generateButton(component);
                break;
            case 'input':
                html += this.generateInput(component);
                break;
            case 'card':
                html += this.generateCard(component);
                break;
            case 'navbar':
                html += this.generateNavbar(component);
                break;
            default:
                html += this.generateGenericElement(component);
        }
        
        return html;
    }
    
    generateButton(component) {
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        const props = component.properties || {};
        
        let button = `${indent}<button type="button" class="btn"`;
        
        // 添加樣式屬性
        if (props.backgroundColor) {
            button += ` style="background-color: ${props.backgroundColor.rgba};`;
            
            if (props.textColor) {
                button += ` color: ${props.textColor.rgba};`;
            }
            
            if (props.borderRadius) {
                button += ` border-radius: ${props.borderRadius}px;`;
            }
            
            button += `"`;
        }
        
        button += `>${props.text || '按鈕'}</button>\n`;
        
        return button;
    }
    
    generateInput(component) {
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        const props = component.properties || {};
        
        let input = `${indent}<div class="input-group">\n`;
        
        this.indentLevel++;
        const childIndent = ' '.repeat(this.indentSize * this.indentLevel);
        
        // 如果有標籤,生成標籤
        if (props.label) {
            input += `${childIndent}<label for="input-${component.id}">${props.label}</label>\n`;
        }
        
        // 生成輸入框
        input += `${childIndent}<input type="${props.type || 'text'}" `;
        input += `id="input-${component.id}" `;
        input += `placeholder="${props.placeholder || ''}" `;
        
        // 添加樣式
        if (props.border) {
            input += `style="border: ${props.border.width}px solid ${props.border.color.rgba};" `;
        }
        
        input += `/>\n`;
        
        this.indentLevel--;
        input += `${indent}</div>\n`;
        
        return input;
    }
    
    generateCard(component) {
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        const elements = component.elements || [];
        
        let card = `${indent}<div class="card">\n`;
        
        this.indentLevel++;
        
        // 生成卡片內容
        elements.forEach(element => {
            card += this.generateElement(element);
        });
        
        this.indentLevel--;
        card += `${indent}</div>\n`;
        
        return card;
    }
    
    generateNavbar(component) {
        const indent = ' '.repeat(this.indentSize * this.indentLevel);
        const elements = component.elements || [];
        const layout = component.layout || {};
        
        let navbar = `${indent}<nav class="navbar">\n`;
        
        this.indentLevel++;
        const childIndent = ' '.repeat(this.indentSize * this.indentLevel);
        
        // 生成logo
        const logo = elements.find(e => e.name.toLowerCase().includes('logo'));
        if (logo) {
            navbar += `${childIndent}<div class="navbar-logo">\n`;
            this.indentLevel++;
            navbar += this.generateElement(logo);
            this.indentLevel--;
            navbar += `${childIndent}</div>\n`;
        }
        
        // 生成導航項目
        const menuItems = elements.filter(e => 
            e.type === 'text' && !e.name.toLowerCase().includes('logo')
        );
        
        if (menuItems.length > 0) {
            navbar += `${childIndent}<ul class="navbar-menu" `;
            
            if (layout.type === 'flex') {
                navbar += `style="display: flex; flex-direction: ${layout.direction || 'row'};"`;
            }
            
            navbar += `>\n`;
            
            this.indentLevel++;
            menuItems.forEach(item => {
                const itemIndent = ' '.repeat(this.indentSize * this.indentLevel);
                navbar += `${itemIndent}<li><a href="#">${item.content || item.name}</a></li>\n`;
            });
            
            this.indentLevel--;
            navbar += `${childIndent}</ul>\n`;
        }
        
        this.indentLevel--;
        navbar += `${indent}</nav>\n`;
        
        return navbar;
    }
    
    calculateGridColumns(layoutInfo) {
        // 根據元素位置計算網格列
        const xPositions = new Set();
        
        layoutInfo.elements.forEach(element => {
            xPositions.add(element.frame.x);
            xPositions.add(element.frame.x + element.frame.width);
        });
        
        const sortedPositions = Array.from(xPositions).sort((a, b) => a - b);
        const columns = [];
        
        for (let i = 1; i < sortedPositions.length; i++) {
            const width = sortedPositions[i] - sortedPositions[i - 1];
            columns.push(`${width}px`);
        }
        
        return columns.join(' ');
    }
    
    assignElementsToGrid(layoutInfo, components) {
        // 將元素分配到網格單元格
        const gridItems = [];
        const grid = this.createGridStructure(layoutInfo);
        
        components.forEach(component => {
            const element = component.elements ? component.elements[0] : component;
            const frame = element.frame;
            
            // 查找元素所在的網格單元格
            const startCol = this.findGridColumn(grid.columns, frame.x);
            const endCol = this.findGridColumn(grid.columns, frame.x + frame.width);
            const startRow = this.findGridRow(grid.rows, frame.y);
            const endRow = this.findGridRow(grid.rows, frame.y + frame.height);
            
            if (startCol !== -1 && endCol !== -1 && startRow !== -1 && endRow !== -1) {
                gridItems.push({
                    element: element,
                    column: startCol + 1, // CSS網格從1開始
                    row: startRow + 1,
                    colSpan: endCol - startCol,
                    rowSpan: endRow - startRow
                });
            }
        });
        
        return gridItems;
    }
}

3.2 CSS樣式生成

python

# CSS生成器
class CSSGenerator:
    def __init__(self):
        self.css_variables = {}
        self.media_queries = {}
        self.component_styles = {}
        
    def generate(self, layout_info, components, design_data):
        """生成完整的CSS樣式表"""
        css = []
        
        # 1. 生成CSS變量
        css.append(self.generate_css_variables(design_data))
        
        # 2. 生成全局樣式
        css.append(self.generate_global_styles())
        
        # 3. 生成佈局樣式
        css.append(self.generate_layout_styles(layout_info))
        
        # 4. 生成組件樣式
        css.append(self.generate_component_styles(components))
        
        # 5. 生成響應式樣式
        css.append(self.generate_responsive_styles(layout_info, components))
        
        # 6. 生成動畫樣式
        css.append(self.generate_animation_styles(design_data))
        
        return '\n'.join(css)
    
    def generate_css_variables(self, design_data):
        """生成CSS變量(設計令牌)"""
        variables = []
        
        # 提取顏色變量
        colors = self.extract_colors(design_data)
        for name, value in colors.items():
            variables.append(f'  --color-{name}: {value};')
        
        # 提取字體變量
        fonts = self.extract_fonts(design_data)
        for name, value in fonts.items():
            variables.append(f'  --font-{name}: {value};')
        
        # 提取間距變量
        spacings = self.extract_spacings(design_data)
        for name, value in spacings.items():
            variables.append(f'  --spacing-{name}: {value}px;')
        
        # 提取陰影變量
        shadows = self.extract_shadows(design_data)
        for name, value in shadows.items():
            variables.append(f'  --shadow-{name}: {value};')
        
        if variables:
            return f':root {{\n' + '\n'.join(variables) + '\n}\n'
        
        return ''
    
    def extract_colors(self, design_data):
        """從設計數據中提取顏色"""
        colors = {}
        
        def traverse_elements(elements):
            for element in elements:
                # 提取填充顏色
                if 'style' in element and 'fill' in element['style']:
                    fill = element['style']['fill']
                    if fill and 'hex' in fill:
                        color_name = self.generate_color_name(element)
                        colors[color_name] = fill['hex']
                
                # 提取文字顏色
                if 'style' in element and 'text' in element['style']:
                    text_style = element['style']['text']
                    if 'color' in text_style and 'hex' in text_style['color']:
                        color_name = f'text-{self.generate_color_name(element)}'
                        colors[color_name] = text_style['color']['hex']
                
                # 提取邊框顏色
                if 'style' in element and 'border' in element['style']:
                    border = element['style']['border']
                    if border and 'color' in border and 'hex' in border['color']:
                        color_name = f'border-{self.generate_color_name(element)}'
                        colors[color_name] = border['color']['hex']
                
                # 遞歸處理子元素
                if 'children' in element:
                    traverse_elements(element['children'])
        
        traverse_elements(design_data.get('layers', []))
        
        return colors
    
    def generate_color_name(self, element):
        """生成顏色名稱"""
        element_name = element.get('name', '').lower()
        
        # 移除特殊字符
        element_name = re.sub(r'[^a-z0-9]', '-', element_name)
        
        # 根據元素類型添加前綴
        if element.get('type') == 'text':
            return f'text-{element_name}'
        elif element.get('type') == 'rectangle':
            return f'surface-{element_name}'
        else:
            return element_name
    
    def generate_layout_styles(self, layout_info):
        """生成佈局樣式"""
        styles = []
        
        # 根據佈局類型生成不同的CSS
        if layout_info.get('layout_type') == 'grid':
            styles.extend(self.generate_grid_styles(layout_info))
        elif layout_info.get('layout_type') == 'flexbox':
            styles.extend(self.generate_flexbox_styles(layout_info))
        
        # 生成對齊樣式
        styles.extend(self.generate_alignment_styles(layout_info))
        
        # 生成間距樣式
        styles.extend(self.generate_spacing_styles(layout_info))
        
        return '\n'.join(styles)
    
    def generate_grid_styles(self, layout_info):
        """生成網格佈局樣式"""
        styles = []
        
        # 生成網格容器樣式
        grid_container_style = '.grid-container {\n'
        grid_container_style += '  display: grid;\n'
        
        # 添加網格模板
        columns = self.calculate_grid_template_columns(layout_info)
        rows = self.calculate_grid_template_rows(layout_info)
        
        grid_container_style += f'  grid-template-columns: {columns};\n'
        grid_container_style += f'  grid-template-rows: {rows};\n'
        
        # 添加間隙
        gap = self.calculate_grid_gap(layout_info)
        if gap:
            grid_container_style += f'  gap: {gap}px;\n'
        
        grid_container_style += '}\n'
        styles.append(grid_container_style)
        
        # 生成網格項目樣式
        grid_item_style = '.grid-item {\n'
        grid_item_style += '  /* 網格項目基礎樣式 */\n'
        grid_item_style += '}\n'
        styles.append(grid_item_style)
        
        return styles
    
    def generate_flexbox_styles(self, layout_info):
        """生成彈性盒子佈局樣式"""
        styles = []
        
        # 生成flex容器樣式
        flex_container_style = '.flex-container {\n'
        flex_container_style += '  display: flex;\n'
        
        # 添加flex方向
        direction = self.determine_flex_direction(layout_info)
        flex_container_style += f'  flex-direction: {direction};\n'
        
        # 添加對齊方式
        justify = self.determine_justify_content(layout_info)
        align = self.determine_align_items(layout_info)
        
        flex_container_style += f'  justify-content: {justify};\n'
        flex_container_style += f'  align-items: {align};\n'
        
        # 添加換行
        if self.should_wrap(layout_info):
            flex_container_style += '  flex-wrap: wrap;\n'
        
        flex_container_style += '}\n'
        styles.append(flex_container_style)
        
        return styles
    
    def generate_component_styles(self, components):
        """生成組件樣式"""
        styles = []
        
        for component in components:
            component_style = self.generate_single_component_style(component)
            if component_style:
                styles.append(component_style)
        
        return '\n'.join(styles)
    
    def generate_single_component_style(self, component):
        """生成單個組件的樣式"""
        component_type = component.get('type')
        properties = component.get('properties', {})
        layout = component.get('layout', {})
        
        if not component_type:
            return ''
        
        # 選擇器
        selector = f'.{component_type}'
        if component.get('id'):
            selector += f'#{component["id"]}'
        
        style = f'{selector} {{\n'
        
        # 基礎樣式
        if properties.get('backgroundColor'):
            color = properties['backgroundColor']
            style += f'  background-color: {color.get("rgba", "#fff")};\n'
        
        if properties.get('textColor'):
            color = properties['textColor']
            style += f'  color: {color.get("rgba", "#000")};\n'
        
        if properties.get('borderRadius'):
            radius = properties['borderRadius']
            style += f'  border-radius: {radius}px;\n'
        
        # 尺寸樣式
        if properties.get('size'):
            size = properties['size']
            if size.get('width'):
                style += f'  width: {size["width"]}px;\n'
            if size.get('height'):
                style += f'  height: {size["height"]}px;\n'
        
        # 佈局樣式
        if layout.get('type') == 'flex':
            style += f'  display: flex;\n'
            if layout.get('direction'):
                style += f'  flex-direction: {layout["direction"]};\n'
            if layout.get('justify'):
                style += f'  justify-content: {layout["justify"]};\n'
            if layout.get('align'):
                style += f'  align-items: {layout["align"]};\n'
        
        style += '}\n'
        
        # 生成偽類樣式
        style += self.generate_pseudo_styles(component)
        
        return style
    
    def generate_pseudo_styles(self, component):
        """生成偽類樣式(hover, focus等)"""
        pseudo_styles = ''
        component_type = component.get('type')
        
        if component_type == 'button':
            # 按鈕hover效果
            pseudo_styles += f'.{component_type}:hover {{\n'
            pseudo_styles += '  opacity: 0.9;\n'
            pseudo_styles += '  transform: translateY(-1px);\n'
            pseudo_styles += '  transition: all 0.2s ease;\n'
            pseudo_styles += '}\n'
            
            # 按鈕active效果
            pseudo_styles += f'.{component_type}:active {{\n'
            pseudo_styles += '  transform: translateY(0);\n'
            pseudo_styles += '}\n'
        
        elif component_type == 'input':
            # 輸入框focus效果
            pseudo_styles += f'.{component_type}:focus {{\n'
            pseudo_styles += '  border-color: var(--color-primary);\n'
            pseudo_styles += '  outline: none;\n'
            pseudo_styles += '  box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);\n'
            pseudo_styles += '}\n'
        
        return pseudo_styles
    
    def generate_responsive_styles(self, layout_info, components):
        """生成響應式樣式"""
        styles = []
        
        # 默認斷點
        breakpoints = {
            'sm': 640,
            'md': 768,
            'lg': 1024,
            'xl': 1280
        }
        
        for name, width in breakpoints.items():
            style = f'@media (min-width: {width}px) {{\n'
            
            # 生成響應式佈局樣式
            style += self.generate_responsive_layout(width, layout_info)
            
            # 生成響應式組件樣式
            style += self.generate_responsive_components(width, components)
            
            style += '}\n'
            styles.append(style)
        
        return '\n'.join(styles)
    
    def generate_responsive_layout(self, breakpoint_width, layout_info):
        """生成特定斷點的佈局樣式"""
        style = ''
        
        # 調整網格佈局
        if layout_info.get('layout_type') == 'grid':
            style += '  .grid-container {\n'
            
            # 根據斷點調整網格列數
            columns = self.adjust_grid_columns_for_breakpoint(breakpoint_width, layout_info)
            style += f'    grid-template-columns: repeat({columns}, 1fr);\n'
            
            style += '  }\n'
        
        # 調整flex佈局
        elif layout_info.get('layout_type') == 'flexbox':
            style += '  .flex-container {\n'
            
            # 根據斷點調整flex方向
            if breakpoint_width >= 768:
                style += '    flex-direction: row;\n'
            else:
                style += '    flex-direction: column;\n'
            
            style += '  }\n'
        
        return style
    
    def adjust_grid_columns_for_breakpoint(self, breakpoint_width, layout_info):
        """根據斷點調整網格列數"""
        if breakpoint_width < 640:
            return 1
        elif breakpoint_width < 768:
            return 2
        elif breakpoint_width < 1024:
            return 3
        else:
            return 4
    
    def generate_animation_styles(self, design_data):
        """生成動畫樣式"""
        styles = []
        
        # 淡入動畫
        fade_in = '''@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.fade-in {
  animation: fadeIn 0.5s ease-in;
}'''
        styles.append(fade_in)
        
        # 滑入動畫
        slide_in = '''@keyframes slideInFromLeft {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(0); }
}

.slide-in-left {
  animation: slideInFromLeft 0.3s ease-out;
}'''
        styles.append(slide_in)
        
        return '\n'.join(styles)

3.3 JavaScript交互生成

javascript

// JavaScript交互生成器
class JSGenerator {
    constructor() {
        this.eventHandlers = new Map();
        this.componentInteractions = new Map();
    }
    
    generate(components, designData) {
        const jsCode = [];
        
        // 1. 生成文檔就緒事件
        jsCode.push(this.generateDocumentReady());
        
        // 2. 生成組件交互邏輯
        components.forEach(component => {
            const componentJS = this.generateComponentJS(component);
            if (componentJS) {
                jsCode.push(componentJS);
            }
        });
        
        // 3. 生成全局函數
        jsCode.push(this.generateUtilityFunctions());
        
        // 4. 生成事件綁定
        jsCode.push(this.generateEventBindings());
        
        // 5. 生成初始化代碼
        jsCode.push(this.generateInitialization());
        
        return jsCode.join('\n\n');
    }
    
    generateDocumentReady() {
        return `document.addEventListener('DOMContentLoaded', function() {
    console.log('頁面加載完成');
    initializeComponents();
});`;
    }
    
    generateComponentJS(component) {
        const componentType = component.type;
        const componentId = component.id || '';
        
        switch (componentType) {
            case 'button':
                return this.generateButtonJS(component);
            case 'input':
                return this.generateInputJS(component);
            case 'navbar':
                return this.generateNavbarJS(component);
            case 'dropdown':
                return this.generateDropdownJS(component);
            case 'modal':
                return this.generateModalJS(component);
            default:
                return '';
        }
    }
    
    generateButtonJS(component) {
        const buttonId = component.id ? `btn-${component.id}` : 'dynamic-btn';
        const buttonText = component.properties?.text || '按鈕';
        
        return `// 按鈕組件: ${buttonText}
const ${buttonId.replace('-', '_')} = document.getElementById('${buttonId}');
if (${buttonId.replace('-', '_')}) {
    ${buttonId.replace('-', '_')}.addEventListener('click', function(event) {
        event.preventDefault();
        console.log('按鈕被點擊: ${buttonText}');
        
        // 添加點擊反饋
        this.style.transform = 'scale(0.95)';
        setTimeout(() => {
            this.style.transform = 'scale(1)';
        }, 150);
        
        // 觸發自定義事件
        const buttonEvent = new CustomEvent('button-click', {
            detail: { 
                id: '${component.id}',
                text: '${buttonText}',
                timestamp: new Date().toISOString()
            },
            bubbles: true
        });
        this.dispatchEvent(buttonEvent);
    });
    
    // hover效果
    ${buttonId.replace('-', '_')}.addEventListener('mouseenter', function() {
        this.style.filter = 'brightness(1.1)';
    });
    
    ${buttonId.replace('-', '_')}.addEventListener('mouseleave', function() {
        this.style.filter = 'brightness(1)';
    });
}`;
    }
    
    generateInputJS(component) {
        const inputId = component.id ? `input-${component.id}` : 'dynamic-input';
        const placeholder = component.properties?.placeholder || '';
        
        return `// 輸入框組件
const ${inputId.replace('-', '_')} = document.getElementById('${inputId}');
if (${inputId.replace('-', '_')}) {
    // 輸入驗證
    ${inputId.replace('-', '_')}.addEventListener('input', function(event) {
        const value = event.target.value;
        
        // 清除之前的驗證狀態
        this.classList.remove('is-valid', 'is-invalid');
        
        // 根據輸入類型進行驗證
        const type = this.getAttribute('type');
        
        switch(type) {
            case 'email':
                if (this.validateEmail(value)) {
                    this.classList.add('is-valid');
                } else if (value.length > 0) {
                    this.classList.add('is-invalid');
                }
                break;
                
            case 'password':
                if (value.length >= 8) {
                    this.classList.add('is-valid');
                } else if (value.length > 0) {
                    this.classList.add('is-invalid');
                }
                break;
                
            default:
                // 文本輸入驗證
                if (value.trim().length > 0) {
                    this.classList.add('is-valid');
                }
        }
    });
    
    // 焦點處理
    ${inputId.replace('-', '_')}.addEventListener('focus', function() {
        this.parentElement.classList.add('focused');
    });
    
    ${inputId.replace('-', '_')}.addEventListener('blur', function() {
        this.parentElement.classList.remove('focused');
    });
    
    // 添加驗證方法
    ${inputId.replace('-', '_')}.validateEmail = function(email) {
        const re = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
        return re.test(email);
    };
}`;
    }
    
    generateNavbarJS(component) {
        return `// 導航欄組件
class Navbar {
    constructor(elementId) {
        this.navbar = document.getElementById(elementId);
        this.menuItems = this.navbar ? this.navbar.querySelectorAll('.nav-item') : [];
        this.mobileMenuBtn = this.navbar ? this.navbar.querySelector('.mobile-menu-btn') : null;
        this.mobileMenu = this.navbar ? this.navbar.querySelector('.mobile-menu') : null;
        
        this.init();
    }
    
    init() {
        // 高亮當前頁面
        this.highlightCurrentPage();
        
        // 移動端菜單切換
        if (this.mobileMenuBtn && this.mobileMenu) {
            this.mobileMenuBtn.addEventListener('click', () => {
                this.toggleMobileMenu();
            });
        }
        
        // 平滑滾動
        this.setupSmoothScroll();
    }
    
    highlightCurrentPage() {
        const currentPath = window.location.pathname;
        
        this.menuItems.forEach(item => {
            const link = item.querySelector('a');
            if (link && link.getAttribute('href') === currentPath) {
                item.classList.add('active');
            }
        });
    }
    
    toggleMobileMenu() {
        this.mobileMenu.classList.toggle('show');
        this.mobileMenuBtn.classList.toggle('active');
        
        // 更新按鈕文字
        const isOpen = this.mobileMenu.classList.contains('show');
        this.mobileMenuBtn.innerHTML = isOpen ? 
            '<span class="close-icon">×</span>' : 
            '<span class="menu-icon">☰</span>';
    }
    
    setupSmoothScroll() {
        this.menuItems.forEach(item => {
            const link = item.querySelector('a');
            if (link && link.getAttribute('href').startsWith('#')) {
                link.addEventListener('click', (event) => {
                    event.preventDefault();
                    
                    const targetId = link.getAttribute('href').substring(1);
                    const targetElement = document.getElementById(targetId);
                    
                    if (targetElement) {
                        window.scrollTo({
                            top: targetElement.offsetTop - 80,
                            behavior: 'smooth'
                        });
                        
                        // 關閉移動端菜單
                        if (this.mobileMenu) {
                            this.mobileMenu.classList.remove('show');
                            this.mobileMenuBtn.classList.remove('active');
                        }
                    }
                });
            }
        });
    }
}

// 初始化導航欄
const navbar = new Navbar('main-navbar');`;
    }
    
    generateDropdownJS(component) {
        const dropdownId = component.id ? `dropdown-${component.id}` : 'dynamic-dropdown';
        
        return `// 下拉菜單組件
class Dropdown {
    constructor(elementId) {
        this.dropdown = document.getElementById(elementId);
        this.toggleBtn = this.dropdown ? this.dropdown.querySelector('.dropdown-toggle') : null;
        this.menu = this.dropdown ? this.dropdown.querySelector('.dropdown-menu') : null;
        
        this.init();
    }
    
    init() {
        if (!this.toggleBtn || !this.menu) return;
        
        // 點擊切換
        this.toggleBtn.addEventListener('click', (event) => {
            event.stopPropagation();
            this.toggle();
        });
        
        // 點擊其他地方關閉
        document.addEventListener('click', () => {
            this.hide();
        });
        
        // 防止菜單內部點擊觸發關閉
        this.menu.addEventListener('click', (event) => {
            event.stopPropagation();
        });
        
        // 鍵盤導航
        this.setupKeyboardNavigation();
    }
    
    toggle() {
        this.menu.classList.toggle('show');
        this.toggleBtn.setAttribute('aria-expanded', 
            this.menu.classList.contains('show'));
        
        // 更新箭頭方向
        const arrow = this.toggleBtn.querySelector('.arrow');
        if (arrow) {
            arrow.style.transform = this.menu.classList.contains('show') ? 
                'rotate(180deg)' : 'rotate(0deg)';
        }
    }
    
    show() {
        this.menu.classList.add('show');
        this.toggleBtn.setAttribute('aria-expanded', 'true');
    }
    
    hide() {
        this.menu.classList.remove('show');
        this.toggleBtn.setAttribute('aria-expanded', 'false');
    }
    
    setupKeyboardNavigation() {
        this.toggleBtn.addEventListener('keydown', (event) => {
            if (event.key === 'Enter' || event.key === ' ') {
                event.preventDefault();
                this.toggle();
            } else if (event.key === 'Escape') {
                this.hide();
            }
        });
        
        // 菜單項鍵盤導航
        const menuItems = this.menu.querySelectorAll('.dropdown-item');
        menuItems.forEach((item, index) => {
            item.addEventListener('keydown', (event) => {
                switch(event.key) {
                    case 'ArrowDown':
                        event.preventDefault();
                        const nextItem = menuItems[index + 1];
                        if (nextItem) nextItem.focus();
                        break;
                    case 'ArrowUp':
                        event.preventDefault();
                        const prevItem = menuItems[index - 1];
                        if (prevItem) prevItem.focus();
                        if (index === 0) this.toggleBtn.focus();
                        break;
                    case 'Escape':
                        this.hide();
                        this.toggleBtn.focus();
                        break;
                }
            });
        });
    }
}

// 初始化下拉菜單
const dropdown = new Dropdown('${dropdownId}');`;
    }
    
    generateUtilityFunctions() {
        return `// 工具函數
const Utils = {
    // 防抖函數
    debounce: function(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    },
    
    // 節流函數
    throttle: function(func, limit) {
        let inThrottle;
        return function() {
            const args = arguments;
            const context = this;
            if (!inThrottle) {
                func.apply(context, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    },
    
    // 檢測移動設備
    isMobile: function() {
        return window.innerWidth <= 768;
    },
    
    // 格式化日期
    formatDate: function(date, format = 'YYYY-MM-DD') {
        const d = new Date(date);
        const year = d.getFullYear();
        const month = String(d.getMonth() + 1).padStart(2, '0');
        const day = String(d.getDate()).padStart(2, '0');
        
        return format
            .replace('YYYY', year)
            .replace('MM', month)
            .replace('DD', day);
    },
    
    // 深拷貝
    deepClone: function(obj) {
        return JSON.parse(JSON.stringify(obj));
    },
    
    // 隨機ID生成
    generateId: function(prefix = 'id') {
        return prefix + '-' + Math.random().toString(36).substr(2, 9);
    }
};`;
    }
    
    generateEventBindings() {
        return `// 全局事件綁定
function setupGlobalEvents() {
    // 窗口大小變化
    window.addEventListener('resize', Utils.debounce(function() {
        console.log('窗口大小改變:', window.innerWidth, 'x', window.innerHeight);
        
        // 觸發自定義事件
        const resizeEvent = new CustomEvent('viewport-change', {
            detail: {
                width: window.innerWidth,
                height: window.innerHeight,
                isMobile: Utils.isMobile()
            }
        });
        window.dispatchEvent(resizeEvent);
    }, 250));
    
    // 滾動事件
    window.addEventListener('scroll', Utils.throttle(function() {
        const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
        const scrollEvent = new CustomEvent('page-scroll', {
            detail: { scrollTop: scrollTop }
        });
        window.dispatchEvent(scrollEvent);
    }, 100));
    
    // 點擊外部關閉所有下拉菜單
    document.addEventListener('click', function(event) {
        const dropdowns = document.querySelectorAll('.dropdown-menu.show');
        dropdowns.forEach(dropdown => {
            if (!dropdown.parentElement.contains(event.target)) {
                dropdown.classList.remove('show');
            }
        });
    });
}`;
    }
    
    generateInitialization() {
        return `// 初始化函數
function initializeComponents() {
    console.log('初始化組件...');
    
    // 設置全局事件
    setupGlobalEvents();
    
    // 初始化所有按鈕
    const buttons = document.querySelectorAll('button[data-component="button"]');
    buttons.forEach(button => {
        button.addEventListener('click', handleButtonClick);
    });
    
    // 初始化所有輸入框
    const inputs = document.querySelectorAll('input[data-component="input"]');
    inputs.forEach(input => {
        input.addEventListener('input', handleInputChange);
    });
    
    // 初始化表單驗證
    const forms = document.querySelectorAll('form');
    forms.forEach(form => {
        form.addEventListener('submit', handleFormSubmit);
    });
    
    // 添加CSS類到body標籤
    document.body.classList.add('js-enabled');
    
    console.log('組件初始化完成');
}

// 事件處理函數
function handleButtonClick(event) {
    const button = event.currentTarget;
    console.log('按鈕點擊:', button.textContent);
    
    // 添加點擊反饋
    button.classList.add('clicked');
    setTimeout(() => {
        button.classList.remove('clicked');
    }, 300);
}

function handleInputChange(event) {
    const input = event.currentTarget;
    console.log('輸入變化:', input.value);
}

function handleFormSubmit(event) {
    event.preventDefault();
    console.log('表單提交');
    
    // 這裡可以添加表單驗證和提交邏輯
    const form = event.currentTarget;
    const formData = new FormData(form);
    
    // 模擬AJAX提交
    setTimeout(() => {
        alert('表單提交成功!');
        form.reset();
    }, 1000);
}

// 導出到全局
window.App = {
    Utils: Utils,
    initializeComponents: initializeComponents
};

// 自動初始化
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeComponents);
} else {
    initializeComponents();
}`;
    }
}

第四章:AI增強與機器學習應用

4.1 計算機視覺在設計稿解析中的應用

python

# 使用OpenCV進行設計稿圖像分析
import cv2
import numpy as np
from sklearn.cluster import KMeans

class DesignImageAnalyzer:
    def __init__(self):
        self.edge_detector = EdgeDetector()
        self.text_recognizer = TextRecognizer()
        self.color_analyzer = ColorAnalyzer()
        
    def analyze_image(self, image_path):
        """分析設計稿圖像"""
        # 讀取圖像
        image = cv2.imread(image_path)
        if image is None:
            raise ValueError(f"無法讀取圖像: {image_path}")
        
        # 轉換為RGB
        image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        
        # 1. 邊緣檢測
        edges = self.detect_edges(image_rgb)
        
        # 2. 輪廓檢測
        contours = self.detect_contours(edges)
        
        # 3. 文字識別
        text_regions = self.detect_text_regions(image_rgb)
        
        # 4. 顏色分析
        dominant_colors = self.extract_dominant_colors(image_rgb)
        
        # 5. 佈局分析
        layout_grid = self.detect_layout_grid(contours)
        
        return {
            'image_shape': image.shape,
            'edges': edges,
            'contours': contours,
            'text_regions': text_regions,
            'dominant_colors': dominant_colors,
            'layout_grid': layout_grid
        }
    
    def detect_edges(self, image):
        """檢測圖像邊緣"""
        # 轉換為灰度圖
        gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        
        # 應用高斯模糊減少噪聲
        blurred = cv2.GaussianBlur(gray, (5, 5), 0)
        
        # 使用Canny邊緣檢測
        edges = cv2.Canny(blurred, 50, 150)
        
        return edges
    
    def detect_contours(self, edges):
        """檢測輪廓"""
        # 查找輪廓
        contours, hierarchy = cv2.findContours(
            edges, 
            cv2.RETR_TREE, 
            cv2.CHAIN_APPROX_SIMPLE
        )
        
        # 過濾小輪廓
        min_area = 100
        filtered_contours = []
        contour_data = []
        
        for i, contour in enumerate(contours):
            area = cv2.contourArea(contour)
            if area > min_area:
                filtered_contours.append(contour)
                
                # 計算邊界框
                x, y, w, h = cv2.boundingRect(contour)
                
                # 計算輪廓特徵
                contour_data.append({
                    'index': i,
                    'area': area,
                    'bounding_box': (x, y, w, h),
                    'aspect_ratio': w / h if h > 0 else 0,
                    'solidity': self.calculate_solidity(contour, area)
                })
        
        return {
            'contours': filtered_contours,
            'hierarchy': hierarchy,
            'contour_data': contour_data
        }
    
    def calculate_solidity(self, contour, area):
        """計算輪廓的solidity(實心度)"""
        hull = cv2.convexHull(contour)
        hull_area = cv2.contourArea(hull)
        
        if hull_area > 0:
            return area / hull_area
        return 0
    
    def detect_text_regions(self, image):
        """檢測文字區域"""
        # 使用EAST文本檢測器
        text_regions = []
        
        # 轉換為灰度圖
        gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        
        # 使用形態學操作增強文字
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
        morphed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel)
        
        # 查找輪廓
        contours, _ = cv2.findContours(
            morphed, 
            cv2.RETR_EXTERNAL, 
            cv2.CHAIN_APPROX_SIMPLE
        )
        
        for contour in contours:
            x, y, w, h = cv2.boundingRect(contour)
            
            # 過濾非文字區域
            aspect_ratio = w / h if h > 0 else 0
            area = w * h
            
            # 文字區域通常有特定的寬高比
            if 0.1 < aspect_ratio < 10 and area > 100:
                text_regions.append({
                    'bbox': (x, y, w, h),
                    'aspect_ratio': aspect_ratio,
                    'area': area
                })
        
        return text_regions
    
    def extract_dominant_colors(self, image, n_colors=5):
        """提取主要顏色"""
        # 重塑圖像為像素列表
        pixels = image.reshape(-1, 3)
        
        # 使用K-means聚類
        kmeans = KMeans(n_clusters=n_colors, random_state=42)
        kmeans.fit(pixels)
        
        # 獲取聚類中心和標籤
        colors = kmeans.cluster_centers_
        labels = kmeans.labels_
        
        # 計算每個顏色的比例
        unique_labels, counts = np.unique(labels, return_counts=True)
        proportions = counts / len(labels)
        
        # 轉換為RGB值
        dominant_colors = []
        for i, color in enumerate(colors):
            r, g, b = color.astype(int)
            
            dominant_colors.append({
                'rgb': (r, g, b),
                'hex': f'#{r:02x}{g:02x}{b:02x}',
                'proportion': proportions[i] if i < len(proportions) else 0
            })
        
        # 按比例排序
        dominant_colors.sort(key=lambda x: x['proportion'], reverse=True)
        
        return dominant_colors
    
    def detect_layout_grid(self, contours_data):
        """檢測佈局網格"""
        contour_data = contours_data['contour_data']
        
        if not contour_data:
            return None
        
        # 提取所有邊界框
        bboxes = [cd['bounding_box'] for cd in contour_data]
        
        # 計算網格參數
        grid_params = self.calculate_grid_parameters(bboxes)
        
        return grid_params
    
    def calculate_grid_parameters(self, bboxes):
        """計算網格參數"""
        if not bboxes:
            return None
        
        # 提取x和y坐標
        x_coords = [x for x, y, w, h in bboxes]
        y_coords = [y for x, y, w, h in bboxes]
        
        # 使用聚類找出對齊的網格線
        x_clusters = self.cluster_coordinates(x_coords)
        y_clusters = self.cluster_coordinates(y_coords)
        
        # 計算網格單元大小
        cell_width = self.calculate_average_spacing(x_clusters)
        cell_height = self.calculate_average_spacing(y_clusters)
        
        return {
            'x_clusters': x_clusters,
            'y_clusters': y_clusters,
            'cell_width': cell_width,
            'cell_height': cell_height,
            'grid_size': (len(x_clusters), len(y_clusters))
        }
    
    def cluster_coordinates(self, coordinates, threshold=10):
        """對坐標進行聚類"""
        if not coordinates:
            return []
        
        # 排序坐標
        sorted_coords = sorted(coordinates)
        
        clusters = []
        current_cluster = [sorted_coords[0]]
        
        for coord in sorted_coords[1:]:
            if coord - current_cluster[-1] <= threshold:
                current_cluster.append(coord)
            else:
                clusters.append(np.mean(current_cluster))
                current_cluster = [coord]
        
        if current_cluster:
            clusters.append(np.mean(current_cluster))
        
        return clusters

4.2 深度學習組件識別模型

python

# 基於深度學習的UI組件識別
import torch
import torch.nn as nn
import torchvision.models as models
from torchvision import transforms

class UIComponentDetector(nn.Module):
    def __init__(self, num_classes=10):
        super(UIComponentDetector, self).__init__()
        
        # 使用預訓練的ResNet作為特徵提取器
        self.backbone = models.resnet50(pretrained=True)
        
        # 替換最後的全連接層
        num_features = self.backbone.fc.in_features
        self.backbone.fc = nn.Identity()
        
        # 添加自定義分類頭
        self.classifier = nn.Sequential(
            nn.Linear(num_features, 512),
            nn.ReLU(inplace=True),
            nn.Dropout(0.5),
            nn.Linear(512, num_classes)
        )
        
        # 邊界框回歸頭
        self.bbox_regressor = nn.Sequential(
            nn.Linear(num_features, 256),
            nn.ReLU(inplace=True),
            nn.Linear(256, 4)  # x, y, width, height
        )
        
    def forward(self, x):
        # 提取特徵
        features = self.backbone(x)
        
        # 分類
        class_logits = self.classifier(features)
        
        # 邊界框回歸
        bbox_pred = self.bbox_regressor(features)
        
        return class_logits, bbox_pred

class ComponentDetectionPipeline:
    def __init__(self, model_path=None):
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        
        # 初始化模型
        self.model = UIComponentDetector(num_classes=10).to(self.device)
        
        if model_path:
            self.load_model(model_path)
        
        # 圖像預處理
        self.transform = transforms.Compose([
            transforms.Resize((224, 224)),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                               std=[0.229, 0.224, 0.225])
        ])
        
        # 組件類別
        self.class_names = [
            'button', 'input', 'checkbox', 'radio', 
            'dropdown', 'slider', 'card', 'navbar',
            'footer', 'header'
        ]
    
    def load_model(self, model_path):
        """加載訓練好的模型"""
        checkpoint = torch.load(model_path, map_location=self.device)
        self.model.load_state_dict(checkpoint['model_state_dict'])
        self.model.eval()
        print(f"模型已加載: {model_path}")
    
    def detect_components(self, image):
        """檢測圖像中的UI組件"""
        # 預處理圖像
        input_tensor = self.transform(image).unsqueeze(0).to(self.device)
        
        with torch.no_grad():
            # 前向傳播
            class_logits, bbox_pred = self.model(input_tensor)
            
            # 應用softmax獲取概率
            probabilities = torch.nn.functional.softmax(class_logits, dim=1)
            
            # 獲取預測類別
            _, predicted_class = torch.max(probabilities, 1)
            
            # 解碼邊界框
            bbox = bbox_pred.squeeze().cpu().numpy()
            
        # 後處理
        detected_component = {
            'class': self.class_names[predicted_class.item()],
            'confidence': probabilities[0][predicted_class].item(),
            'bbox': self.decode_bbox(bbox, image.size)
        }
        
        return detected_component
    
    def decode_bbox(self, bbox, image_size):
        """解碼邊界框坐標"""
        img_width, img_height = image_size
        
        # 假設bbox是歸一化坐標
        x, y, width, height = bbox
        
        # 轉換為像素坐標
        x_pixel = int(x * img_width)
        y_pixel = int(y * img_height)
        width_pixel = int(width * img_width)
        height_pixel = int(height * img_height)
        
        return {
            'x': max(0, x_pixel),
            'y': max(0, y_pixel),
            'width': min(width_pixel, img_width - x_pixel),
            'height': min(height_pixel, img_height - y_pixel)
        }
    
    def batch_detect(self, image_regions):
        """批量檢測多個圖像區域"""
        detected_components = []
        
        for region in image_regions:
            try:
                component = self.detect_components(region['image'])
                component['region_id'] = region['id']
                detected_components.append(component)
            except Exception as e:
                print(f"檢測失敗: {e}")
                continue
        
        return detected_components

class TrainingPipeline:
    def __init__(self, dataset_path):
        self.dataset_path = dataset_path
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        
        # 初始化模型
        self.model = UIComponentDetector(num_classes=10).to(self.device)
        
        # 損失函數
        self.classification_criterion = nn.CrossEntropyLoss()
        self.regression_criterion = nn.SmoothL1Loss()
        
        # 優化器
        self.optimizer = torch.optim.AdamW(
            self.model.parameters(),
            lr=1e-4,
            weight_decay=1e-4
        )
        
        # 學習率調度器
        self.scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
            self.optimizer,
            T_max=100
        )
    
    def train_epoch(self, train_loader):
        """訓練一個epoch"""
        self.model.train()
        total_loss = 0
        
        for batch_idx, (images, class_labels, bbox_labels) in enumerate(train_loader):
            images = images.to(self.device)
            class_labels = class_labels.to(self.device)
            bbox_labels = bbox_labels.to(self.device)
            
            # 前向傳播
            class_logits, bbox_pred = self.model(images)
            
            # 計算損失
            class_loss = self.classification_criterion(class_logits, class_labels)
            bbox_loss = self.regression_criterion(bbox_pred, bbox_labels)
            
            # 總損失
            loss = class_loss + 0.5 * bbox_loss
            
            # 反向傳播
            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()
            
            total_loss += loss.item()
            
            if batch_idx % 10 == 0:
                print(f'Batch {batch_idx}/{len(train_loader)}, Loss: {loss.item():.4f}')
        
        avg_loss = total_loss / len(train_loader)
        return avg_loss
    
    def validate(self, val_loader):
        """驗證模型"""
        self.model.eval()
        total_correct = 0
        total_samples = 0
        total_loss = 0
        
        with torch.no_grad():
            for images, class_labels, bbox_labels in val_loader:
                images = images.to(self.device)
                class_labels = class_labels.to(self.device)
                bbox_labels = bbox_labels.to(self.device)
                
                # 前向傳播
                class_logits, bbox_pred = self.model(images)
                
                # 計算分類準確率
                _, predicted = torch.max(class_logits, 1)
                total_correct += (predicted == class_labels).sum().item()
                total_samples += class_labels.size(0)
                
                # 計算損失
                class_loss = self.classification_criterion(class_logits, class_labels)
                bbox_loss = self.regression_criterion(bbox_pred, bbox_labels)
                loss = class_loss + 0.5 * bbox_loss
                total_loss += loss.item()
        
        accuracy = total_correct / total_samples
        avg_loss = total_loss / len(val_loader)
        
        return accuracy, avg_loss
    
    def train(self, num_epochs=50):
        """完整訓練流程"""
        # 加載數據集
        train_loader, val_loader = self.load_dataset()
        
        best_accuracy = 0
        
        for epoch in range(num_epochs):
            print(f'\nEpoch {epoch+1}/{num_epochs}')
            
            # 訓練
            train_loss = self.train_epoch(train_loader)
            
            # 驗證
            val_accuracy, val_loss = self.validate(val_loader)
            
            print(f'Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, Val Accuracy: {val_accuracy:.4f}')
            
            # 保存最佳模型
            if val_accuracy > best_accuracy:
                best_accuracy = val_accuracy
                self.save_checkpoint(epoch, val_accuracy)
            
            # 更新學習率
            self.scheduler.step()
        
        print(f'訓練完成,最佳準確率: {best_accuracy:.4f}')

第五章:系統集成與部署

5.1 完整系統架構

python

# 完整的設計稿轉代碼系統
import asyncio
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, Any, Optional
import json

class DesignToCodeSystem:
    def __init__(self, config: Optional[Dict] = None):
        self.config = config or self.load_default_config()
        
        # 初始化各組件
        self.design_parser = DesignParser()
        self.ai_analyzer = AIAnalyzer()
        self.layout_engine = LayoutEngine()
        self.code_generator = CodeGenerator()
        self.optimizer = CodeOptimizer()
        self.validator = CodeValidator()
        
        # 線程池
        self.thread_pool = ThreadPoolExecutor(max_workers=4)
        
        # 緩存系統
        self.cache = {}
        
        # 監控系統
        self.metrics = {
            'processed_files': 0,
            'successful_conversions': 0,
            'average_processing_time': 0
        }
    
    def load_default_config(self):
        """加載默認配置"""
        return {
            'max_file_size': 50 * 1024 * 1024,  # 50MB
            'supported_formats': ['.fig', '.sketch', '.png', '.jpg'],
            'output_formats': ['html', 'css', 'js', 'react', 'vue'],
            'enable_ai': True,
            'enable_optimization': True,
            'enable_validation': True,
            'cache_enabled': True,
            'cache_ttl': 3600  # 1小時
        }
    
    async def process_design_file(self, file_path: str, options: Optional[Dict] = None):
        """處理設計文件"""
        start_time = asyncio.get_event_loop().time()
        
        try:
            # 1. 檢查文件
            await self.validate_file(file_path)
            
            # 2. 檢查緩存
            cache_key = self.generate_cache_key(file_path, options)
            if self.config['cache_enabled'] and cache_key in self.cache:
                print(f"使用緩存結果: {cache_key}")
                return self.cache[cache_key]
            
            # 3. 解析設計文件
            design_data = await self.parse_design_file(file_path)
            
            # 4. AI分析
            if self.config['enable_ai']:
                analysis_result = await self.analyze_with_ai(design_data)
                design_data['ai_analysis'] = analysis_result
            
            # 5. 生成佈局
            layout = await self.generate_layout(design_data)
            
            # 6. 生成代碼
            raw_code = await self.generate_code(design_data, layout, options)
            
            # 7. 優化代碼
            if self.config['enable_optimization']:
                optimized_code = await self.optimize_code(raw_code)
            else:
                optimized_code = raw_code
            
            # 8. 驗證代碼
            if self.config['enable_validation']:
                validation_result = await self.validate_code(optimized_code)
                if not validation_result['valid']:
                    print(f"代碼驗證警告: {validation_result['warnings']}")
            
            # 9. 生成文檔
            documentation = await self.generate_documentation(design_data, optimized_code)
            
            # 10. 包裝結果
            result = {
                'success': True,
                'code': optimized_code,
                'documentation': documentation,
                'metadata': {
                    'processing_time': asyncio.get_event_loop().time() - start_time,
                    'file_size': self.get_file_size(file_path),
                    'components_detected': len(design_data.get('components', [])),
                    'layout_type': layout.get('type', 'unknown')
                }
            }
            
            # 11. 更新緩存
            if self.config['cache_enabled']:
                self.cache[cache_key] = result
                self.schedule_cache_cleanup(cache_key)
            
            # 12. 更新指標
            self.update_metrics(result, start_time)
            
            return result
            
        except Exception as e:
            error_result = {
                'success': False,
                'error': str(e),
                'error_type': type(e).__name__,
                'suggestions': self.get_error_suggestions(e)
            }
            return error_result
    
    async def parse_design_file(self, file_path: str):
        """解析設計文件"""
        file_extension = file_path.lower().split('.')[-1]
        
        if file_extension in ['fig']:
            return await self.design_parser.parse_figma(file_path)
        elif file_extension in ['sketch']:
            return await self.design_parser.parse_sketch(file_path)
        elif file_extension in ['png', 'jpg', 'jpeg']:
            return await self.design_parser.parse_image(file_path)
        else:
            raise ValueError(f"不支持的檔案格式: {file_extension}")
    
    async def analyze_with_ai(self, design_data: Dict):
        """使用AI分析設計數據"""
        tasks = [
            self.ai_analyzer.detect_components(design_data),
            self.ai_analyzer.analyze_layout_patterns(design_data),
            self.ai_analyzer.extract_design_system(design_data),
            self.ai_analyzer.predict_accessibility(design_data)
        ]
        
        # 並行執行AI分析任務
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        return {
            'components': results[0] if not isinstance(results[0], Exception) else [],
            'layout_patterns': results[1] if not isinstance(results[1], Exception) else {},
            'design_system': results[2] if not isinstance(results[2], Exception) else {},
            'accessibility_score': results[3] if not isinstance(results[3], Exception) else 0
        }
    
    async def generate_code(self, design_data: Dict, layout: Dict, options: Optional[Dict] = None):
        """生成代碼"""
        output_format = (options or {}).get('format', 'html')
        
        if output_format == 'react':
            return await self.code_generator.generate_react(design_data, layout, options)
        elif output_format == 'vue':
            return await self.code_generator.generate_vue(design_data, layout, options)
        elif output_format == 'html':
            return await self.code_generator.generate_html(design_data, layout, options)
        else:
            raise ValueError(f"不支持的輸出格式: {output_format}")
    
    async def generate_documentation(self, design_data: Dict, code: Dict):
        """生成文檔"""
        doc_generator = DocumentationGenerator()
        
        # 生成多種類型的文檔
        documentation = await doc_generator.generate_all({
            'api_docs': doc_generator.generate_api_docs(code),
            'component_docs': doc_generator.generate_component_docs(design_data),
            'usage_examples': doc_generator.generate_usage_examples(code),
            'accessibility_notes': doc_generator.generate_accessibility_notes(design_data)
        })
        
        return documentation
    
    def generate_cache_key(self, file_path: str, options: Optional[Dict] = None):
        """生成緩存鍵"""
        import hashlib
        
        # 計算文件哈希
        with open(file_path, 'rb') as f:
            file_hash = hashlib.md5(f.read()).hexdigest()
        
        # 組合選項
        options_str = json.dumps(options or {}, sort_keys=True)
        
        # 生成最終緩存鍵
        cache_key = f"{file_hash}:{options_str}"
        return hashlib.md5(cache_key.encode()).hexdigest()
    
    def schedule_cache_cleanup(self, cache_key: str):
        """計劃緩存清理"""
        import threading
        
        def cleanup():
            import time
            time.sleep(self.config['cache_ttl'])
            if cache_key in self.cache:
                del self.cache[cache_key]
                print(f"緩存過期已清理: {cache_key}")
        
        thread = threading.Thread(target=cleanup)
        thread.daemon = True
        thread.start()
    
    def update_metrics(self, result: Dict, start_time: float):
        """更新系統指標"""
        self.metrics['processed_files'] += 1
        
        if result['success']:
            self.metrics['successful_conversions'] += 1
        
        # 更新平均處理時間
        processing_time = asyncio.get_event_loop().time() - start_time
        current_avg = self.metrics['average_processing_time']
        total_files = self.metrics['processed_files']
        
        self.metrics['average_processing_time'] = (
            (current_avg * (total_files - 1) + processing_time) / total_files
        )
    
    def get_system_status(self):
        """獲取系統狀態"""
        return {
            'metrics': self.metrics,
            'cache_size': len(self.cache),
            'config': self.config,
            'thread_pool_status': {
                'active_threads': self.thread_pool._max_workers,
                'queue_size': self.thread_pool._work_queue.qsize() if hasattr(self.thread_pool, '_work_queue') else 0
            }
        }

class APIServer:
    """REST API服務器"""
    def __init__(self, system: DesignToCodeSystem):
        self.system = system
        self.app = self.create_app()
    
    def create_app(self):
        """創建FastAPI應用"""
        from fastapi import FastAPI, File, UploadFile, HTTPException
        from fastapi.middleware.cors import CORSMiddleware
        from fastapi.responses import JSONResponse, FileResponse
        
        app = FastAPI(
            title="設計稿轉代碼API",
            description="將Figma/Sketch設計稿自動轉換為HTML/CSS/JavaScript代碼",
            version="1.0.0"
        )
        
        # 添加CORS中間件
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )
        
        @app.get("/")
        async def root():
            return {"message": "設計稿轉代碼API服務運行中"}
        
        @app.get("/status")
        async def get_status():
            return self.system.get_system_status()
        
        @app.post("/convert")
        async def convert_design(
            file: UploadFile = File(...),
            output_format: str = "html",
            optimize: bool = True,
            include_ai: bool = True
        ):
            try:
                # 保存上傳的文件
                temp_file = await self.save_upload_file(file)
                
                # 處理文件
                options = {
                    'format': output_format,
                    'optimize': optimize,
                    'include_ai': include_ai
                }
                
                result = await self.system.process_design_file(temp_file, options)
                
                # 清理臨時文件
                import os
                os.remove(temp_file)
                
                if result['success']:
                    return JSONResponse(content=result)
                else:
                    raise HTTPException(status_code=400, detail=result['error'])
                    
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))
        
        @app.post("/batch-convert")
        async def batch_convert(files: List[UploadFile] = File(...)):
            results = []
            
            for file in files:
                try:
                    temp_file = await self.save_upload_file(file)
                    result = await self.system.process_design_file(temp_file)
                    results.append({
                        'filename': file.filename,
                        'result': result
                    })
                    
                    import os
                    os.remove(temp_file)
                    
                except Exception as e:
                    results.append({
                        'filename': file.filename,
                        'error': str(e)
                    })
            
            return {"results": results}
        
        @app.get("/export/{format}")
        async def export_code(
            format: str,
            html: Optional[str] = None,
            css: Optional[str] = None,
            js: Optional[str] = None
        ):
            try:
                export_service = ExportService()
                
                if format == "zip":
                    zip_data = export_service.create_zip_archive(html, css, js)
                    return FileResponse(
                        zip_data,
                        media_type="application/zip",
                        filename="export.zip"
                    )
                elif format == "github":
                    repo_url = export_service.export_to_github(html, css, js)
                    return {"repository_url": repo_url}
                else:
                    raise HTTPException(status_code=400, detail="不支持的導出格式")
                    
            except Exception as e:
                raise HTTPException(status_code=500, detail=str(e))
        
        return app
    
    async def save_upload_file(self, upload_file: UploadFile):
        """保存上傳的文件"""
        import tempfile
        import os
        
        # 創建臨時文件
        suffix = os.path.splitext(upload_file.filename)[1]
        with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
            content = await upload_file.read()
            tmp.write(content)
            return tmp.name
    
    def run(self, host: str = "0.0.0.0", port: int = 8000):
        """運行API服務器"""
        import uvicorn
        uvicorn.run(self.app, host=host, port=port)

class ExportService:
    """代碼導出服務"""
    def __init__(self):
        self.github_client = GitHubClient()
        self.zip_creator = ZipCreator()
    
    def create_zip_archive(self, html: str, css: str, js: str):
        """創建ZIP壓縮包"""
        import zipfile
        import io
        
        zip_buffer = io.BytesIO()
        
        with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
            if html:
                zip_file.writestr("index.html", html)
            if css:
                zip_file.writestr("styles.css", css)
            if js:
                zip_file.writestr("script.js", js)
            
            # 添加README文件
            readme_content = self.generate_readme(html, css, js)
            zip_file.writestr("README.md", readme_content)
            
            # 添加package.json(如果適用)
            package_json = self.generate_package_json()
            zip_file.writestr("package.json", package_json)
        
        zip_buffer.seek(0)
        return zip_buffer
    
    def export_to_github(self, html: str, css: str, js: str):
        """導出到GitHub"""
        # 創建倉庫
        repo_name = f"design-to-code-{int(time.time())}"
        repo_url = self.github_client.create_repository(repo_name)
        
        # 提交文件
        files = {}
        if html:
            files["index.html"] = html
        if css:
            files["styles.css"] = css
        if js:
            files["script.js"] = js
        
        files["README.md"] = self.generate_readme(html, css, js)
        files["package.json"] = self.generate_package_json()
        
        self.github_client.commit_files(repo_name, files)
        
        # 部署到GitHub Pages
        pages_url = self.github_client.enable_pages(repo_name)
        
        return pages_url or repo_url
    
    def generate_readme(self, html: str, css: str, js: str):
        """生成README文件"""
        return f"""# 自動生成的代碼項目

此項目由設計稿轉代碼系統自動生成。

## 文件結構
- index.html - 主HTML文件
- styles.css - 樣式文件
- script.js - JavaScript文件

## 生成信息
- 生成時間: {time.strftime('%Y-%m-%d %H:%M:%S')}
- HTML行數: {len(html.splitlines()) if html else 0}
- CSS行數: {len(css.splitlines()) if css else 0}
- JS行數: {len(js.splitlines()) if js else 0}

## 使用說明
1. 直接打開index.html在瀏覽器中預覽
2. 或使用本地服務器運行:

python -m http.server 8000

text

## 注意事項
此代碼為自動生成,可能需要進一步優化和調整以符合特定需求。
"""
 
 def generate_package_json(self):
     """生成package.json文件"""
     return json.dumps({
         "name": "design-to-code-project",
         "version": "1.0.0",
         "description": "Automatically generated from design file",
         "main": "index.html",
         "scripts": {
             "start": "python -m http.server 8000",
             "dev": "live-server --port=8000"
         },
         "keywords": ["design-to-code", "auto-generated"],
         "author": "DesignToCode System",
         "license": "MIT"
     }, indent=2)

5.2 命令行工具

python

# 命令行界面工具
import argparse
import sys
from pathlib import Path

class DesignToCodeCLI:
    def __init__(self):
        self.parser = self.create_parser()
        self.system = DesignToCodeSystem()
    
    def create_parser(self):
        """創建命令行解析器"""
        parser = argparse.ArgumentParser(
            description='將設計稿轉換為代碼',
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog="""
示例:
  %(prog)s design.sketch --output-dir ./dist
  %(prog)s design.fig --format react --optimize
  %(prog)s screenshot.png --ai --validate
            """
        )
        
        # 必需參數
        parser.add_argument(
            'input',
            help='輸入文件路徑 (支持 .fig, .sketch, .png, .jpg)'
        )
        
        # 輸出選項
        parser.add_argument(
            '-o', '--output-dir',
            default='./output',
            help='輸出目錄 (默認: ./output)'
        )
        
        parser.add_argument(
            '-f', '--format',
            choices=['html', 'react', 'vue', 'all'],
            default='html',
            help='輸出格式 (默認: html)'
        )
        
        # 處理選項
        parser.add_argument(
            '--ai',
            action='store_true',
            help='啟用AI增強分析'
        )
        
        parser.add_argument(
            '--optimize',
            action='store_true',
            default=True,
            help='啟用代碼優化 (默認: 啟用)'
        )
        
        parser.add_argument(
            '--validate',
            action='store_true',
            help='啟用代碼驗證'
        )
        
        parser.add_argument(
            '--no-cache',
            action='store_true',
            help='禁用緩存'
        )
        
        # 高級選項
        parser.add_argument(
            '--config',
            help='配置文件路徑'
        )
        
        parser.add_argument(
            '--verbose',
            action='store_true',
            help='詳細輸出模式'
        )
        
        parser.add_argument(
            '--version',
            action='version',
            version='設計稿轉代碼工具 v1.0.0'
        )
        
        return parser
    
    def load_config(self, config_path):
        """加載配置文件"""
        import yaml
        
        if not Path(config_path).exists():
            print(f"錯誤: 配置文件不存在: {config_path}")
            sys.exit(1)
        
        with open(config_path, 'r', encoding='utf-8') as f:
            config = yaml.safe_load(f)
        
        return config
    
    def print_banner(self):
        """打印橫幅"""
        banner = """
╔══════════════════════════════════════════════════════════╗
║                設計稿轉代碼工具 v1.0.0                   ║
║          從Figma/Sketch自動生成HTML/CSS/JS代碼           ║
╚══════════════════════════════════════════════════════════╝
        """
        print(banner)
    
    def print_result_summary(self, result, processing_time):
        """打印結果摘要"""
        print("\n" + "="*60)
        print("轉換完成!")
        print("="*60)
        
        if result['success']:
            metadata = result['metadata']
            
            print(f"\n📊 轉換統計:")
            print(f"  ├─ 處理時間: {processing_time:.2f}秒")
            print(f"  ├─ 文件大小: {metadata.get('file_size', 0)} bytes")
            print(f"  ├─ 檢測組件: {metadata.get('components_detected', 0)}個")
            print(f"  └─ 佈局類型: {metadata.get('layout_type', '未知')}")
            
            if 'code' in result:
                code = result['code']
                print(f"\n📁 生成文件:")
                for file_type, content in code.items():
                    if content:
                        lines = content.splitlines()
                        print(f"  ├─ {file_type}: {len(lines)}行代碼")
            
            print(f"\n✅ 轉換成功!")
            print(f"輸出目錄: {self.output_dir}")
            
        else:
            print(f"\n❌ 轉換失敗:")
            print(f"錯誤: {result.get('error', '未知錯誤')}")
            print(f"類型: {result.get('error_type', '未知')}")
            
            if 'suggestions' in result:
                print(f"\n💡 建議:")
                for suggestion in result['suggestions']:
                    print(f"  - {suggestion}")
    
    async def run(self):
        """運行命令行工具"""
        args = self.parser.parse_args()
        
        # 打印橫幅
        self.print_banner()
        
        # 加載配置
        if args.config:
            config = self.load_config(args.config)
            self.system.config.update(config)
        
        # 更新系統配置
        self.system.config['enable_ai'] = args.ai
        self.system.config['enable_optimization'] = args.optimize
        self.system.config['enable_validation'] = args.validate
        self.system.config['cache_enabled'] = not args.no_cache
        
        # 檢查輸入文件
        input_path = Path(args.input)
        if not input_path.exists():
            print(f"錯誤: 輸入文件不存在: {args.input}")
            sys.exit(1)
        
        # 創建輸出目錄
        self.output_dir = Path(args.output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        
        print(f"📂 輸入文件: {input_path}")
        print(f"📁 輸出目錄: {self.output_dir}")
        print(f"⚙️  配置: AI={args.ai}, 優化={args.optimize}, 驗證={args.validate}")
        
        # 處理文件
        import time
        start_time = time.time()
        
        try:
            options = {
                'format': args.format,
                'optimize': args.optimize,
                'include_ai': args.ai
            }
            
            result = await self.system.process_design_file(str(input_path), options)
            
            processing_time = time.time() - start_time
            
            # 打印結果
            self.print_result_summary(result, processing_time)
            
            # 保存結果
            if result['success'] and 'code' in result:
                await self.save_output(result['code'])
            
            # 返回退出碼
            return 0 if result['success'] else 1
            
        except KeyboardInterrupt:
            print("\n\n操作被用戶中斷")
            return 130
        except Exception as e:
            print(f"\n❌ 發生錯誤: {e}")
            if args.verbose:
                import traceback
                traceback.print_exc()
            return 1
    
    async def save_output(self, code):
        """保存輸出文件"""
        for file_type, content in code.items():
            if content:
                file_path = self.output_dir / f"output.{file_type}"
                
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(content)
                
                print(f"💾 已保存: {file_path}")
        
        # 保存文檔
        doc_path = self.output_dir / "README.md"
        with open(doc_path, 'w', encoding='utf-8') as f:
            f.write(self.generate_readme())
        
        print(f"📄 已保存: {doc_path}")

def main():
    """主函數"""
    cli = DesignToCodeCLI()
    
    # 運行異步主函數
    import asyncio
    exit_code = asyncio.run(cli.run())
    sys.exit(exit_code)

if __name__ == "__main__":
    main()

第六章:未來發展與挑戰

6.1 技術挑戰與解決方案

6.1.1 複雜佈局的準確識別

挑戰:

  • 嵌套佈局的精確解析

  • 響應式設計的意圖理解

  • 動態內容的處理

解決方案:

  1. 使用圖神經網絡(GNN)分析元素間的關係

  2. 引入注意力機制理解設計意圖

  3. 建立佈局模式庫進行匹配

6.1.2 代碼質量的持續提升

挑戰:

  • 生成的代碼可讀性

  • 性能優化

  • 可維護性

解決方案:

  1. 引入代碼風格檢查和格式化

  2. 實施性能最佳實踐

  3. 生成組件化的、可重用的代碼結構

6.2 商業化應用前景

6.2.1 市場需求分析

根據市場研究,設計稿轉代碼工具的市場規模預計將從2023年的5億美元增長到2028年的20億美元,年複合增長率達32%。主要驅動因素包括:

  1. 前端開發人力成本上升

  2. 快速原型開發需求增加

  3. 低代碼/無代碼平台興起

  4. 遠程協作工具普及

6.2.2 商業模式設計
  1. SaaS訂閱模式

    • 個人開發者:$29/月

    • 團隊:$99/月(最多5人)

    • 企業:自定義定價

  2. 本地部署方案

    • 一次性授權費

    • 年度維護費

  3. API服務

    • 按調用量收費

    • 批量處理優惠

6.3 技術發展趨勢

6.3.1 AI技術的深入應用
  1. 多模態學習

    • 結合視覺、文本和結構信息

    • 理解設計師意圖和業務邏輯

  2. 強化學習優化

    • 通過用戶反饋持續優化生成結果

    • 自動調整代碼生成策略

  3. 生成對抗網絡(GAN)

    • 生成更自然的代碼結構

    • 提高代碼質量和可讀性

6.3.2 雲原生架構
  1. 微服務化

    • 將系統拆分為獨立服務

    • 提高可擴展性和可維護性

  2. 容器化部署

    • 使用Docker容器打包

    • Kubernetes編排管理

  3. Serverless計算

    • 按需擴縮容

    • 降低運營成本

結論

設計稿自動轉代碼技術代表了前端開發自動化的未來方向。通過結合計算機視覺、機器學習和軟件工程的最佳實踐,我們可以大幅提高設計到開發的轉換效率,減少重複勞動,並確保設計意圖的準確實現。

本文介紹的系統架構和實現方案提供了一個完整的解決方案,從設計稿解析、AI分析、代碼生成到系統部署。雖然仍有技術挑戰需要克服,但隨著AI技術的不斷發展和更多實戰經驗的積累,設計稿轉代碼的準確性和實用性將持續提升。

未來,我們可以期待更加智能化的設計開發工具,能夠理解複雜的業務邏輯,生成高質量的、可維護的代碼,真正實現設計與開發的無縫銜接。這不僅將改變前端開發的工作方式,也將推動整個軟件開發行業向更高效率、更智能化的方向發展。


附錄:相關資源

  1. 開源項目

  2. 研究論文

    • "Pix2Code: Generating Code from a Graphical User Interface Screenshot"

    • "Screenshot-to-Code: A Deep Learning Approach"

  3. 在線工具

    • Anima:設計稿轉代碼平台

    • Zeplin:設計開發協作工具

    • Framer:交互設計到代碼

  4. 學習資源

    • 前端開發最佳實踐

    • 機器學習在軟件工程中的應用

    • 計算機視覺基礎知識

通過持續學習和實踐,我們可以不斷改進和完善設計稿轉代碼技術,為軟件開發行業帶來真正的變革。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐