bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
1,525 lines (1,144 loc) • 62.8 kB
text/typescript
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