UNPKG

bytefun

Version:

一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台

1,525 lines (1,144 loc) 62.8 kB
import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import { ProjectInfo, ProjectManager } from './projectManager'; import { captureHtmlScreenshot, ScreenshotCapture } from './screenshotCapture'; import { HtmlZeroWHFixer } from './htmlZeroWHFixer'; import * as crypto from 'crypto'; import { WorkspaceUtils } from './workspaceUtils'; import { getMultiPlatformCodeData } from './utils'; export class EditorProvider { private static editorWebviewPanel: vscode.WebviewPanel | undefined; private readonly context: vscode.ExtensionContext; private readonly projectManager: ProjectManager; // 是否为调试模式:true为本地开发模式,false为生产模式 // 发布时请将此值改为false private readonly isDebugMode: boolean = true; constructor(context: vscode.ExtensionContext) { this.context = context; this.projectManager = ProjectManager.getInstance(); } // 避免触发文件监听器的文件更新方法 private updateFileWithoutTriggeringWatcher(filePath: string, content: string): void { try { // 写入文件 fs.writeFileSync(filePath, content, 'utf8'); // 计算文件hash并更新全局记录,避免触发监听器 const currentHash = crypto.createHash('md5').update(content).digest('hex'); const now = Date.now(); // 通知extension.ts更新文件记录 if ((global as any).updateFileProcessRecord) { (global as any).updateFileProcessRecord(filePath, currentHash, now); } } catch (error) { console.error('❌ [ByteFun Editor] 更新文件失败:', error); } } public openOrFocusEditor(context: vscode.ExtensionContext) { if (EditorProvider.editorWebviewPanel) { // 检查webview面板是否已被销毁 try { // 如果已经存在ByteFun tab,就切换到它 EditorProvider.editorWebviewPanel.reveal(vscode.ViewColumn.One); } catch (error) { EditorProvider.editorWebviewPanel = undefined; // 递归调用以创建新面板 this.openOrFocusEditor(context); return; } } else { // 创建新的ByteFun tab EditorProvider.editorWebviewPanel = vscode.window.createWebviewPanel( 'bytefunEditor', 'ByteFun工程管理', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [this.context.extensionUri] } ); // 设置webview内容 EditorProvider.editorWebviewPanel.webview.html = this.getEditorWebviewContent(EditorProvider.editorWebviewPanel.webview); // 监听tab关闭事件 EditorProvider.editorWebviewPanel.onDidDispose(() => { EditorProvider.editorWebviewPanel = undefined; }); // 监听来自编辑器webview的消息 EditorProvider.editorWebviewPanel.webview.onDidReceiveMessage( message => { switch (message.command || message.type) { case 'refresh': break; case 'ready': break; case 'openInVsCode': // 处理在工作台点击项目的打开项目按钮后,新开窗口打开项目 const projectData = message.data || message; this.handleOpenInVsCode(projectData); return; case 'webviewInitDone': this.handleWebviewInitDone(); return; case 'returnProjectPageList': this.handleReturnProjectPageList(message.data); break; case 'updateApi': this.handleUpdateApi(message.data || message); break; case 'tempHtmlCodeTest': this.handleTempHtmlCodeTest(message.data || message); break; case 'mcpToEditor': this.handleMcpToEditor(message.data || message); break; case 'codeZipDone': this.handleCodeZipDone(message.data || message); break; case 'recordMissingColorVariables': this.handleRecordMissingColorVariables(message.data || message); break; case 'synCodeToEditorDone': this.handleSynCodeToEditorDone(message.data || message); break; // case 'backendZipDone': // // this.handleBackendZipDone(message.data || message); // break; default: break; } } ); } } // 处理webview编辑器初始化完成消息 private async handleWebviewInitDone(): Promise<void> { try { // 获取当前工作区根目录 const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { return; } // 读取.bytefun/project.json文件 const projectJsonPath = path.join(workspaceRoot, '.bytefun', 'project.json'); try { // 检查文件是否存在 if (!fs.existsSync(projectJsonPath)) { return; } // 读取并解析JSON文件 const projectJsonContent = fs.readFileSync(projectJsonPath, 'utf8'); const projectConfig = JSON.parse(projectJsonContent); // 提取projectID const projectID = projectConfig.projectID; const projectName = projectConfig.projectName; const treeProjectId = projectConfig.treeProjectId; if (!projectID || !projectName) { return; } // 发送getProjectPageList消息到webview const messageData = { projectID: projectID, projectName: projectName, treeProjectId: treeProjectId }; this.sendMessageToWebview({ type: 'getProjectPageList', data: messageData }); } catch (fileError) { console.error('❌ [ByteFun WebviewProvider] 读取或解析project.json失败:', fileError); } } catch (error) { console.error('❌ [ByteFun WebviewProvider] 处理用户登录失败:', error); } } private async handleReturnProjectPageList(projectData: any): Promise<void> { // 检查是否是同步完成 if (projectData.syncPageEnName) { // 通知侧边栏同步完成 const bytefunSidebarProvider = (global as any).bytefunSidebarProvider; if (bytefunSidebarProvider) { if (projectData) { bytefunSidebarProvider.handleForwardedMessage('syncComplete', { syncPageEnName: projectData.syncPageEnName // 传递页面英文名 }); } } } // 发送给侧边栏 const bytefunSidebarProvider = (global as any).bytefunSidebarProvider; if (bytefunSidebarProvider) { bytefunSidebarProvider.handleForwardedMessage('initProjectPageList', projectData); } // 解析构造pageListData并更新project.json await this.updateProjectPageList(projectData); } // 智能合并pageList,保留现有的subPageList字段 private mergePageListWithSubPages(existingPageList: any[], newPageList: any[]): any[] { try { const mergedList: any[] = []; const processedExistingPages = new Set<string>(); // 记录已处理的现有页面 // 第一步:遍历新的页面列表 for (const newPage of newPageList) { // 尝试在现有列表中找到匹配的项(优先使用nodeId,其次resourceId,最后enName) let existingPage = null; let matchKey = ''; if (newPage.nodeId && newPage.nodeId > 0) { existingPage = existingPageList.find(page => page.nodeId === newPage.nodeId); if (existingPage) { matchKey = `nodeId:${newPage.nodeId}`; } } if (!existingPage && newPage.resourceId && newPage.resourceId > 0) { existingPage = existingPageList.find(page => page.resourceId === newPage.resourceId); if (existingPage) { matchKey = `resourceId:${newPage.resourceId}`; } } if (!existingPage && newPage.enName) { existingPage = existingPageList.find(page => page.enName === newPage.enName); if (existingPage) { matchKey = `enName:${newPage.enName}`; } } if (existingPage) { // 记录已处理的现有页面 processedExistingPages.add(matchKey); if (existingPage.subPageList) { // 找到匹配项且有subPageList,保留subPageList,更新其他字段 mergedList.push({ ...newPage, // 使用新的字段值 subPageList: existingPage.subPageList // 保留原有的subPageList }); } else { // 找到匹配项但没有subPageList,直接使用新项 mergedList.push(newPage); } } else { // 没找到匹配项,直接使用新项 mergedList.push(newPage); } } // 第二步:遍历现有页面列表,找出未被处理且有subPageList的页面 for (const existingPage of existingPageList) { if (!existingPage.subPageList) { continue; // 只关注有subPageList的页面 } let wasProcessed = false; // 检查是否已被处理(通过各种匹配键) if (existingPage.nodeId && existingPage.nodeId > 0) { if (processedExistingPages.has(`nodeId:${existingPage.nodeId}`)) { wasProcessed = true; } } if (!wasProcessed && existingPage.resourceId && existingPage.resourceId > 0) { if (processedExistingPages.has(`resourceId:${existingPage.resourceId}`)) { wasProcessed = true; } } if (!wasProcessed && existingPage.enName) { if (processedExistingPages.has(`enName:${existingPage.enName}`)) { wasProcessed = true; } } if (!wasProcessed) { // 这是一个在新列表中不存在但有subPageList的现有页面,需要保留 mergedList.push(existingPage); } } return mergedList; } catch (error) { console.error('❌ [ByteFun WebviewProvider] 智能合并pageList失败:', error); // 如果合并失败,返回新的列表 return newPageList; } } // 更新项目页面列表 pageList 到 project.json private async updateProjectPageList(projectData: any): Promise<void> { try { // 获取当前工作区根目录 const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { return; } // 获取project.json文件路径 const projectJsonPath = path.join(workspaceRoot, '.bytefun', 'project.json'); // 检查文件是否存在 if (!fs.existsSync(projectJsonPath)) { return; } // 读取并解析当前的project.json文件 const projectJsonContent = fs.readFileSync(projectJsonPath, 'utf8'); const projectConfig = JSON.parse(projectJsonContent); // 从projectData解析页面列表数据 let pageListData: any[] = []; // 尝试从不同的字段解析页面列表 if (projectData.projectPageList) { // 如果projectPageList是字符串,尝试解析为JSON if (typeof projectData.projectPageList === 'string') { try { pageListData = JSON.parse(projectData.projectPageList); } catch (parseError) { console.error('❌ [ByteFun WebviewProvider] 解析projectPageList字符串失败:', parseError); pageListData = []; } } else if (Array.isArray(projectData.projectPageList)) { // 如果是数组,直接使用 pageListData = projectData.projectPageList; } else { pageListData = []; } } else if (projectData.pageList && Array.isArray(projectData.pageList)) { // 如果有pageList字段,使用它 pageListData = projectData.pageList; } else { } // 递归提取文件节点的函数(只提取 resourceId > 0 的文件节点) const extractFileNodes = (nodes: any[]): any[] => { const fileNodes: any[] = []; for (const node of nodes) { // 如果是文件节点(resourceId > 0),添加到结果中 if (node.resourceId && node.resourceId > 0) { fileNodes.push({ enName: node.enName || node.name, nodeId: node.nodeId || 0, resourceId: node.resourceId || 0, type: node.type || 2 }); } // 如果有子节点,递归处理 if (node.children && Array.isArray(node.children) && node.children.length > 0) { fileNodes.push(...extractFileNodes(node.children)); } } return fileNodes; }; // 构造适合project.json的pageList格式(只包含文件节点) const formattedPageList = extractFileNodes(pageListData); // 保存原有的pageList用于变化检测 const originalPageList = JSON.parse(JSON.stringify(projectConfig.pageList || [])); // 智能合并pageList,保留现有的subPageList字段 const mergedPageList = this.mergePageListWithSubPages(projectConfig.pageList || [], formattedPageList); // 检测pageList变化并处理新增页面 this.detectAndHandlePageListChanges(projectConfig, originalPageList, mergedPageList); // 更新项目配置中的pageList字段 projectConfig.pageList = mergedPageList; // 写回到project.json文件 const updatedProjectJsonContent = JSON.stringify(projectConfig, null, 2); fs.writeFileSync(projectJsonPath, updatedProjectJsonContent, 'utf8'); } catch (error) { console.error('❌ [ByteFun WebviewProvider] 更新项目页面列表失败:', error); } } // 检测pageList变化并处理新增页面 private detectAndHandlePageListChanges(projectConfig: any, originalPageList: any[], mergedPageList: any[]): void { try { // 检测新增页面 const newPageList: any[] = []; // 遍历合并后的pageList,找出新增页面 for (const mergedPage of mergedPageList) { // 尝试在原有列表中查找匹配的页面(通过多种方式匹配) const existingPage = this.findExistingPage(originalPageList, mergedPage); if (!existingPage) { // 没有找到匹配的页面,说明是新增页面 newPageList.push(mergedPage); } } if (newPageList.length === 0) { return; } // 查找有subPageList字段的页面并检测匹配 const newSubPageMap: any[] = []; // 遍历所有页面(使用合并后的列表),查找有subPageList的页面 for (const page of mergedPageList) { if (page.subPageList && Array.isArray(page.subPageList)) { const newSubPageList: any[] = []; // 遍历subPageList中的字符串 let findOne = false for (const subPageName of page.subPageList) { if (typeof subPageName === 'string') { // 检查这个subPageName是否匹配新增页面的enName const matchedNewPage = newPageList.find(newPage => newPage.enName === subPageName); let findSubPage = false if (matchedNewPage) { // 找到匹配,将新增页面记录到newSubPageList(去重) if (!newSubPageList.some(existingPage => existingPage.enName === matchedNewPage.enName)) { newSubPageList.push(matchedNewPage.resourceId); findSubPage = true findOne = true } } if (!findSubPage) { newSubPageList.push(-1) } } } if (findOne) { newSubPageMap.push({ page: page, subPageList: newSubPageList }) } } } // 输出结果摘要 if (newPageList.length > 0 || newSubPageMap.length > 0) { for (const subPageMap of newSubPageMap) { if (subPageMap.subPageList.length > 0) { const subListData = { "project_res_id": projectConfig.projectID, "project_page_res_id": subPageMap.page.resourceId, "subpage_res_ids": subPageMap.subPageList } this.sendMessageToWebview({ type: 'fillSubpageInEditJson', data: subListData }); } } } } catch (error) { console.error('❌ [ByteFun WebviewProvider] 检测pageList变化失败:', error); } } // 在原有页面列表中查找匹配的页面(支持多种匹配方式) private findExistingPage(originalPageList: any[], targetPage: any): any | null { try { // 优先通过nodeId匹配 if (targetPage.nodeId && targetPage.nodeId > 0) { const matchedPage = originalPageList.find(page => page.nodeId === targetPage.nodeId); if (matchedPage) { return matchedPage; } } // 其次通过resourceId匹配 if (targetPage.resourceId && targetPage.resourceId > 0) { const matchedPage = originalPageList.find(page => page.resourceId === targetPage.resourceId); if (matchedPage) { return matchedPage; } } // 最后通过enName匹配 if (targetPage.enName) { const matchedPage = originalPageList.find(page => page.enName === targetPage.enName); if (matchedPage) { return matchedPage; } } return null; } catch (error) { console.error('❌ [ByteFun WebviewProvider] 查找现有页面失败:', error); return null; } } // 处理API更新请求 private async handleUpdateApi(data: any): Promise<void> { try { // 提取apiCode,现在应该是string[]类型 const apiCodeArray = data.apiCodeArray; if (!apiCodeArray || !Array.isArray(apiCodeArray)) { console.error('❌ [ByteFun Editor] apiCode为空或格式错误,应该是string[]类型'); return; } // 获取当前工作区根目录 const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { console.error('❌ [ByteFun Editor] 未找到工作区'); return; } const backendApiDir = path.join(workspaceRoot, 'src', 'backendApi'); // 确保目录存在 if (!fs.existsSync(backendApiDir)) { fs.mkdirSync(backendApiDir, { recursive: true }); } // 处理每个API接口代码 let successCount = 0; let failCount = 0; for (let i = 0; i < apiCodeArray.length; i++) { const apiCodeString = apiCodeArray[i]; try { // 从接口代码中提取接口名和文件名 const interfaceName = this.extractInterfaceName(apiCodeString); if (!interfaceName) { console.error(`❌ [ByteFun Editor] 无法从第${i + 1}个API代码中提取接口名`); failCount++; continue; } // 生成文件名 const fileName = this.generateFileName(interfaceName); const filePath = path.join(backendApiDir, `${fileName}.ts`); // 写入API代码到文件 fs.writeFileSync(filePath, apiCodeString, 'utf8'); successCount++; } catch (fileError) { console.error(`❌ [ByteFun Editor] 创建第${i + 1}个API文件失败:`, fileError); failCount++; } } } catch (error) { console.error('❌ [ByteFun Editor] 更新API文件失败:', error); } } // 处理临时HTML代码测试请求 private async handleTempHtmlCodeTest(data: any): Promise<void> { try { // 提取数据 const { html, projectID, treeProjectId, fileName, pageName } = data; if (!html || !fileName) { console.error('❌ [ByteFun Editor] html或fileName为空'); return; } // 获取当前工作区根目录 const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { console.error('❌ [ByteFun Editor] 未找到工作区'); return; } const srcDir = path.join(workspaceRoot, 'src'); // 处理 fileName,如果有 .html 后缀则去掉 let folderName = fileName; if (folderName.endsWith('.html')) { folderName = folderName.slice(0, -5); // 去掉 '.html' 后缀 } // 在 src 目录下搜索对应的页面文件夹 const pageDir = this.findPageDirectory(srcDir, folderName); if (!pageDir) { console.error(`❌ [ByteFun Editor] 在 src 目录下未找到名为 "${folderName}" 的文件夹`); return; } // 创建 tempHtmlCode.html 文件路径 const tempHtmlFilePath = path.join(pageDir, 'tempHtmlCode.html'); // 写入HTML代码到文件 fs.writeFileSync(tempHtmlFilePath, html, 'utf8'); } catch (error) { console.error('❌ [ByteFun Editor] 创建临时HTML文件失败:', error); } } // 处理来自MCP的消息 private async handleMcpToEditor(data: any): Promise<void> { try { // 提取消息内容 const { msg, roleType, dataList, actType } = data; if (!msg || !roleType) { console.error('❌ [ByteFun Editor] MCP消息缺少必要字段'); return; } // 检查是否是代码开发完成的消息 if (roleType === 'frontEndDev' && actType === 'code_development_completed') { // 从dataList中获取文件路径信息 if (dataList && dataList.length > 0) { // 从第一个文件路径中提取页面名称 const filePath = dataList[0]; // 从文件路径中提取页面名称 const pageName = this.extractPageNameFromPath(filePath); if (pageName) { // 构建PageData对象 const pageData = { name: pageName, enName: pageName, type: 2, // 页面类型 id: `page_${Date.now()}`, // 临时ID }; // 调用侧边栏的handleSyncPage方法 const sidebarProvider = (global as any).bytefunSidebarProvider; if (sidebarProvider) { sidebarProvider.handleSyncPage(pageData); } else { console.error('❌ [ByteFun Editor] 侧边栏提供者不可用'); } } else { console.error('❌ [ByteFun Editor] 无法从文件路径中提取页面名称'); } } else { console.error('❌ [ByteFun Editor] 代码开发完成消息缺少文件路径信息'); } } // 处理消息中的 <a> 标签,添加 url 属性 let processedMsg = msg; if (dataList && Array.isArray(dataList) && dataList.length > 0) { // 使用正则表达式匹配所有的 <a> 标签 let aTagIndex = 0; processedMsg = msg.replace(/<a\s*>([^<]+)<\/a>/g, (match: string, linkText: string) => { if (aTagIndex < dataList.length) { const url = dataList[aTagIndex]; aTagIndex++; // 为 <a> 标签添加 url 属性 return `<a url='${url}'>${linkText}</a>`; } // 如果 dataList 中的 URL 用完了,保持原样 return match; }); } // 转发消息给侧边栏,让侧边栏将消息添加到对应角色的聊天列表中 const bytefunSidebarProvider = (global as any).bytefunSidebarProvider; if (bytefunSidebarProvider) { bytefunSidebarProvider.handleForwardedMessage('mcpMessage', { roleType: roleType, message: processedMsg }); } else { console.error('❌ [ByteFun Editor] 侧边栏提供者不可用'); } } catch (error) { console.error('❌ [ByteFun Editor] 处理MCP消息失败:', error); } } // 在 src 目录下遍历搜索匹配的文件夹名(不包含 .html 后缀) private findPageDirectory(searchDir: string, targetFolderName: string): string | null { try { if (!fs.existsSync(searchDir)) { return null; } const items = fs.readdirSync(searchDir, { withFileTypes: true }); // 先列出所有文件夹,便于调试 const folders = items.filter(item => item.isDirectory()).map(item => item.name); // 遍历每个项目 for (const item of items) { // 只处理文件夹 if (item.isDirectory()) { // 检查文件夹名是否完全匹配 if (item.name === targetFolderName) { const matchedPath = path.join(searchDir, item.name); return matchedPath; } // 递归搜索子文件夹 const subDirPath = path.join(searchDir, item.name); const result = this.findPageDirectory(subDirPath, targetFolderName); if (result) { return result; } } } return null; } catch (error) { console.error(`❌ [ByteFun Editor] 遍历文件夹时发生错误:`, error); return null; } } // 从API代码中提取接口名 private extractInterfaceName(apiCode: string): string | null { try { // 使用正则表达式匹配 export interface 接口名 const interfaceMatch = apiCode.match(/export\s+interface\s+(\w+)/); if (interfaceMatch && interfaceMatch[1]) { return interfaceMatch[1]; } console.error('❌ [ByteFun Editor] 无法从API代码中提取接口名:', apiCode.substring(0, 100) + '...'); return null; } catch (error) { console.error('❌ [ByteFun Editor] 提取接口名时发生错误:', error); return null; } } // 从接口名生成文件名 private generateFileName(interfaceName: string): string { try { // 移除Response后缀 let fileName = interfaceName; if (fileName.endsWith('Response')) { fileName = fileName.slice(0, -8); // 移除'Response' } // 将PascalCase转换为camelCase fileName = fileName.charAt(0).toLowerCase() + fileName.slice(1); return fileName; } catch (error) { console.error('❌ [ByteFun Editor] 生成文件名时发生错误:', error); // 如果转换失败,返回原接口名的小写版本 return interfaceName.toLowerCase(); } } // 处理项目打开请求 private async handleOpenInVsCode(projectData: any): Promise<void> { try { // 提取项目信息 const projectInfo: ProjectInfo = { uid: projectData.uid, projectID: projectData.projectID, projectName: projectData.projectName || projectData.name || 'Unknown Project', enName: projectData.enName || projectData.projectName || projectData.name, treeProjectId: projectData.treeProjectId, projectPageList: projectData.projectPageList }; // 使用项目管理器打开项目 await this.projectManager.openInVsCode(projectInfo); } catch (error) { console.error('❌ [ByteFun WebviewProvider] 处理项目打开失败:', error); // 显示错误通知 vscode.window.showErrorMessage( `打开项目失败: ${error}`, '查看详情' ).then(selection => { if (selection === '查看详情') { console.error('项目打开错误详情:', error); } }); } } // 处理代码ZIP完成消息 private async handleCodeZipDone(data: any): Promise<void> { try { // 验证数据格式 if (!data || !Array.isArray(data)) { console.error('❌ [ByteFun Editor] 无效的代码ZIP数据格式:', data); vscode.window.showErrorMessage('代码ZIP数据格式错误'); return; } // 验证每个ZIP项的必要字段 for (const item of data) { if (!item.platform || !item.url || !item.name) { console.error('❌ [ByteFun Editor] ZIP项缺少必要字段:', item); vscode.window.showErrorMessage(`代码ZIP项缺少必要字段: ${JSON.stringify(item)}`); return; } } // 使用ProjectManager处理代码ZIP下载 await this.projectManager.handleCodeZipDownload(data); } catch (error) { console.error('❌ [ByteFun Editor] 处理代码ZIP失败:', error); vscode.window.showErrorMessage(`处理代码ZIP失败: ${error}`); } } // 处理后端ZIP完成消息 private async handleBackendZipDone(data: any): Promise<void> { try { // 使用ProjectManager处理后端ZIP下载 await this.projectManager.handleBackendZipDownload(data); } catch (error) { console.error('❌ [ByteFun Editor] 处理后端ZIP失败:', error); vscode.window.showErrorMessage(`处理后端ZIP失败: ${error}`); } } // 处理代码同步完成消息 private async handleSynCodeToEditorDone(data: any): Promise<void> { try { // 验证数据格式 if (!data || !data.pageNameEn) { console.error('❌ [ByteFun Editor] 代码同步完成消息数据格式错误:', data); return; } const pageNameEn = data.pageNameEn; // 转发消息给侧边栏 const bytefunSidebarProvider = (global as any).bytefunSidebarProvider; if (bytefunSidebarProvider) { bytefunSidebarProvider.handleForwardedMessage('synCodeToEditorDone', { pageNameEn: pageNameEn }); } else { console.error('❌ [ByteFun Editor] 侧边栏提供者不可用'); } } catch (error) { console.error('❌ [ByteFun Editor] 处理代码同步完成消息失败:', error); } } // 处理缺失颜色变量记录消息 private async handleRecordMissingColorVariables(data: any): Promise<void> { try { // 验证数据格式 if (!data || !data.pageFileName || !data.missingVariables) { console.error('❌ [ByteFun Editor] 缺失颜色变量数据格式错误:', data); vscode.window.showErrorMessage('缺失颜色变量数据格式错误'); return; } const { pageFileName, missingVariables } = data; // 验证 missingVariables 是数组 if (!Array.isArray(missingVariables)) { console.error('❌ [ByteFun Editor] missingVariables 必须是数组类型:', missingVariables); vscode.window.showErrorMessage('missingVariables 必须是数组类型'); return; } // 获取当前工作区根目录 const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { console.error('❌ [ByteFun Editor] 未找到工作区'); return; } const bugDir = path.join(workspaceRoot, 'doc', 'bug'); const uiBugFilePath = path.join(bugDir, 'uibug.json'); // 确保目录存在 if (!fs.existsSync(bugDir)) { fs.mkdirSync(bugDir, { recursive: true }); } // 读取或创建 uibug.json 文件 let bugData: any[] = []; if (fs.existsSync(uiBugFilePath)) { try { const bugContent = fs.readFileSync(uiBugFilePath, 'utf8'); bugData = JSON.parse(bugContent); } catch (parseError) { console.error('❌ [ByteFun Editor] 解析bug文件失败,将创建新文件:', parseError); bugData = []; } } else { } // 验证并确保数据是数组 if (!Array.isArray(bugData)) { bugData = []; } // 查找是否已存在该页面的记录 let pageRecord = bugData.find((record: any) => record.page === pageFileName); if (!pageRecord) { // 创建新的页面记录 pageRecord = { page: pageFileName, bugList: [] }; bugData.push(pageRecord); } else { } // 验证并确保 bugList 存在且是数组 if (!pageRecord.bugList || !Array.isArray(pageRecord.bugList)) { pageRecord.bugList = []; } // 查找是否已存在缺失颜色变量的bug记录 const bugTypeEN = 'missing-color-variable-definition'; let colorVariableBug = pageRecord.bugList.find((bug: any) => bug.bugTypeEN === bugTypeEN); if (!colorVariableBug) { // 创建新的缺失颜色变量bug记录 colorVariableBug = { bugTypeCN: '缺失颜色变量定义', bugTypeEN: bugTypeEN, missingColorVariableList: [] }; pageRecord.bugList.push(colorVariableBug); } else { } // 验证并确保 missingColorVariableList 存在且是数组 if (!colorVariableBug.missingColorVariableList || !Array.isArray(colorVariableBug.missingColorVariableList)) { colorVariableBug.missingColorVariableList = []; } // 添加新的缺失变量(去重) let addedCount = 0; for (const variable of missingVariables) { if (typeof variable === 'string' && !colorVariableBug.missingColorVariableList.includes(variable)) { colorVariableBug.missingColorVariableList.push(variable); addedCount++; } else if (colorVariableBug.missingColorVariableList.includes(variable)) { } } // 写回文件 const updatedBugContent = JSON.stringify(bugData, null, 4); fs.writeFileSync(uiBugFilePath, updatedBugContent, 'utf8'); } catch (error) { console.error('❌ [ByteFun Editor] 处理缺失颜色变量记录失败:', error); vscode.window.showErrorMessage(`记录缺失颜色变量失败: ${error}`); } } // 确保ByteFun工程管理tab已打开并激活,然后执行回调 public async ensureWebviewActiveAndExecute(callback: () => void): Promise<void> { // 检查是否已经有面板打开 if (EditorProvider.editorWebviewPanel) { // 检查面板是否可见 if (EditorProvider.editorWebviewPanel.visible) { callback(); return; } else { // 激活现有面板 EditorProvider.editorWebviewPanel.reveal(vscode.ViewColumn.One); // 等待面板激活后执行回调 setTimeout(() => { callback(); }, 100); return; } } else { // 直接调用openOrFocusEditor来创建tab this.openOrFocusEditor({} as vscode.ExtensionContext); // 等待webview创建并加载完成后执行回调 setTimeout(() => { callback(); }, 500); // 给webview更多时间加载 } } // 发送消息到webview public sendMessageToWebview(data: any) { if (EditorProvider.editorWebviewPanel) { EditorProvider.editorWebviewPanel.webview.postMessage(data); return true; } else { return false; } } public static async dispose() { try { // 清理webview面板 if (EditorProvider.editorWebviewPanel) { try { EditorProvider.editorWebviewPanel.dispose(); } catch (error) { console.error('❌ [ByteFun Editor] 清理webview面板失败:', error); } EditorProvider.editorWebviewPanel = undefined; } // 清理截图模块 try { await ScreenshotCapture.dispose(); } catch (error) { console.error('❌ [ByteFun Editor] 清理截图模块失败:', error); } } catch (error) { console.error('❌ [ByteFun Editor] 清理EditorProvider资源时发生错误:', error); } } private getEditorWebviewContent(webview: vscode.Webview): string { // 根据环境配置选择不同的内容 if (this.isDebugMode) { return this.getLocalEditorWebviewContent(webview); } else { return this.getProductionEditorWebviewContent(webview); } } private getLocalEditorWebviewContent(webview: vscode.Webview): string { // 获取webview的script nonce,用于CSP const nonce = this.getNonce(); // 获取主要脚本内容的URI const scriptUri = webview.asWebviewUri( vscode.Uri.joinPath(this.context.extensionUri, 'media', 'editor-script.js') ); return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline' ${webview.cspSource}; script-src 'nonce-${nonce}' ${webview.cspSource}; img-src ${webview.cspSource} data: https:; frame-src https: http://localhost:8080 http://124.221.33.105:65534/; connect-src http://localhost:8080 http://124.221.33.105:65534/ https:;"> <title>ByteFun Editor</title> <style> body { margin: 0; padding: 0; height: 100vh; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } iframe { width: 100%; height: 100vh; border: none; } .loading { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: var(--vscode-editor-background); color: var(--vscode-editor-foreground); } </style> </head> <body> <div class="loading" id="loading">正在加载ByteFun工程管理(本地服务器)...</div> <iframe src="http://localhost:8080/" frameborder="0" id="mainFrame"></iframe> <script nonce="${nonce}" src="${scriptUri}"></script> </body> </html>`; } private getProductionEditorWebviewContent(webview: vscode.Webview): string { // 获取webview的script nonce,用于CSP const nonce = this.getNonce(); // 获取主要脚本内容的URI const scriptUri = webview.asWebviewUri( vscode.Uri.joinPath(this.context.extensionUri, 'media', 'editor-script.js') ); return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline' ${webview.cspSource}; script-src 'nonce-${nonce}' ${webview.cspSource}; img-src ${webview.cspSource} data: https:; frame-src https: http://124.221.33.105:65534; connect-src http://124.221.33.105:65534 https:;"> <title>ByteFun Editor</title> <style> body { margin: 0; padding: 0; height: 100vh; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } iframe { width: 100%; height: 100vh; border: none; } .loading { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: var(--vscode-editor-background); color: var(--vscode-editor-foreground); } </style> </head> <body> <div class="loading" id="loading">正在加载ByteFun工程管理...</div> <iframe src="http://localhost:8080/" frameborder="0" id="mainFrame"></iframe> <script nonce="${nonce}" src="${scriptUri}"></script> </body> </html>`; } // 生成nonce用于CSP private getNonce() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < 32; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } // 处理UI设计文件创建(从文件监听器触发) public handleUIDesignFileCreated(designFilePath: string, changedPage?: any, syncPageEnName?: string): void { try { // 获取文件名 let fileName = path.basename(designFilePath); const fileNameLower = fileName.toLowerCase(); // 排除 index.html 和 tempHtmlCode.html 文件 if (fileNameLower === 'index.html' || fileNameLower === 'temphtmlcode.html') { return; } // 获取工作区根目录 const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { return; } let fullPath: string; // 判断是否是绝对路径 if (path.isAbsolute(designFilePath)) { fullPath = designFilePath; } else { fullPath = path.join(workspaceRoot, designFilePath); } this.oneUIDesignDone(designFilePath, changedPage, syncPageEnName); } catch (error) { console.error('❌ [ByteFun Editor] 处理UI设计文件失败:', error); } } // UI设计完成处理方法 private async oneUIDesignDone(designFilePath: string, changedPage?: any, syncPageEnName?: string): Promise<void> { try { // 如果有changedPage参数,使用其中的pageNameEN作为fileName let fileName = changedPage?.pageNameEN || path.basename(designFilePath); if (fileName.endsWith('.html')) { fileName = fileName.replace('.html', ''); } // 获取工作区根目录 const workspaceRoot = WorkspaceUtils.getProjectRootPath(); if (!workspaceRoot) { return; } const fullDesignFilePath = path.isAbsolute(designFilePath) ? designFilePath : path.join(workspaceRoot, designFilePath); // 读取项目配置获取projectID const projectJsonPath = path.join(workspaceRoot, '.bytefun', 'project.json'); let projectID = ''; let treeProjectId: number | undefined = undefined; let projectConfig try { if (fs.existsSync(projectJsonPath)) { const projectJsonContent = fs.readFileSync(projectJsonPath, 'utf8'); projectConfig = JSON.parse(projectJsonContent); projectID = projectConfig.projectID || ''; treeProjectId = projectConfig.treeProjectId; } else { } } catch (error) { console.error('❌ [ByteFun Editor] 读取项目配置失败:', error); } // 读取设计文件内容 let designFileContent = ''; // 如果有changedPage参数,使用其中的pageNameCN作为pageName,否则默认使用文件名 let pageName = changedPage?.pageNameCN || path.basename(designFilePath, '.html'); const pageNameEN = changedPage?.pageNameEN || path.basename(designFilePath, '.html'); try { if (fs.existsSync(fullDesignFilePath)) { designFileContent = fs.readFileSync(fullDesignFilePath, 'utf8'); // 如果没有changedPage参数,从HTML内容中提取title标签的内容作为页面名称 if (!changedPage) { const titleMatch = designFileContent.match(/<title[^>]*>(.*?)<\/title>/i); if (titleMatch && titleMatch[1]) { const extractedTitle = titleMatch[1].trim(); if (extractedTitle) { pageName = extractedTitle; } } } const sizeFixedContent = this.forceContainerSize(designFileContent); // 检查是否有尺寸修改 if (sizeFixedContent !== designFileContent) { this.updateFileWithoutTriggeringWatcher(fullDesignFilePath, sizeFixedContent); designFileContent = sizeFixedContent; // 更新后续使用的内容 } } else { } } catch (error) { console.error('❌ [ByteFun Editor] 读取设计文件内容失败:', error); } // 读取UI设计进度文件并获取模块名 let moduleName = ''; try { const uiDesignProgressPath = path.join(workspaceRoot, 'doc', 'UI设计进度.json'); if (fs.existsSync(uiDesignProgressPath)) { const uiDesignProgressContent = fs.readFileSync(uiDesignProgressPath, 'utf8'); const uiDesignProgress = JSON.parse(uiDesignProgressContent); // 通过pageNameEN查找对应的moduleNameCN if (uiDesignProgress.modules && Array.isArray(uiDesignProgress.modules)) { for (const module of uiDesignProgress.modules) { if (module.pages && Array.isArray(module.pages)) { for (const page of module.pages) { if (page.pageNameEN === pageNameEN) { moduleName = module.moduleNameCN; break; } } } if (moduleName) break; } } if (!moduleName) { } } else { } } catch (error) { console.error('❌ [ByteFun Editor] 读取UI设计进度文件失败:', error); } // 🆕 添加HTML页面截图功能 let screenshotPath: string | null = null; let screenshotRelativePath: string | null = null; let screenshotDuration: number = 0; try { const screenshotResult = await captureHtmlScreenshot(fullDesignFilePath, workspaceRoot, { width: 393, height: 852, deviceScaleFactor: 2, waitTime: 3000, blockResources: false, // 不拦截资源,让图片和图标正常加载 waitForSelector: 'body', // 等待body元素加载完成 elementSelector: '.page-container' // 只截取 page-container 元素 }); if (screenshotResult.success && screenshotResult.screenshotPath) { screenshotPath = screenshotResult.screenshotPath; screenshotRelativePath = screenshotResult.relativePath || null; screenshotDuration = screenshotResult.duration; } else { } } catch (error) { console.error('❌ [ByteFun Editor] 截图过程出错:', error); // 不让截图错误影响主流程 } // 从project.json中获取pageId let pageID: number | null = null; let pageType: number | null = null; let resourceId: number | null = null; const subPageList: number[] = []; try { if (fs.existsSync(projectJsonPath)) { const projectJsonContent = fs.readFileSync(projectJsonPath, 'utf8'); const projectConfig = JSON.parse(projectJsonContent); if (projectConfig.pageList && Array.isArray(projectConfig.pageList)) { // 通过enName匹配找到对应的nodeId const matchedPage = projectConfig.pageList.find((page: any) => page.enName === pageNameEN); if (matchedPage && matchedPage.nodeId) { pageID = matchedPage.nodeId; pageType = matchedPage.type; resourceId = matchedPage.resourceId; if (matchedPage.subPageList && Array.isArray(matchedPage.subPageList)) { for (const subPageEnName of matchedPage.subPageList) { const subPage = project