|
|
const { resolve } = require('path') |
|
|
const { readFileSync, existsSync, readdirSync } = require('fs') |
|
|
const fs = require('fs') |
|
|
const parser = require('@babel/parser') |
|
|
const traverse = require('@babel/traverse').default |
|
|
const { parse } = require('@vue/compiler-sfc') |
|
|
|
|
|
// 第一阶段路由映射插件 - 收集直接映射关系 |
|
|
function routeMappingPlugin() { |
|
|
// 检查是否在静默模式下运行 |
|
|
const isSilentMode = process.env.ROUTE_MAPPING_SILENT === 'true' || process.argv.includes('--silent') |
|
|
|
|
|
// 日志函数 |
|
|
const log = (message) => { |
|
|
if (!isSilentMode) { |
|
|
console.log(message) |
|
|
} |
|
|
} |
|
|
|
|
|
return { |
|
|
name: 'route-mapping-phase1', |
|
|
|
|
|
// 防重复生成机制 |
|
|
_lastGenerationTime: 0, |
|
|
_generationInProgress: false, |
|
|
_generationCooldown: 10000, // 10秒冷却时间,增加冷却时间 |
|
|
_maxGenerationsPerMinute: 3, // 每分钟最多生成3次 |
|
|
_generationCount: 0, |
|
|
_generationWindowStart: 0, |
|
|
|
|
|
// Webpack插件接口 |
|
|
apply(compiler) { |
|
|
const self = this |
|
|
|
|
|
// 在编译开始前就生成映射文件 |
|
|
compiler.hooks.beforeCompile.tapAsync('RouteMappingPlugin', (params, callback) => { |
|
|
if (self._shouldSkipGeneration()) { |
|
|
log('⏭️ 跳过路由映射生成(冷却期内)') |
|
|
callback() |
|
|
return |
|
|
} |
|
|
|
|
|
log('🔧 第一阶段:开始收集直接路由-API映射关系...') |
|
|
try { |
|
|
self.collectDirectMappings() |
|
|
callback() |
|
|
} catch (error) { |
|
|
console.error('❌ 路由映射插件执行失败:', error) |
|
|
callback(error) |
|
|
} |
|
|
}) |
|
|
|
|
|
// 备用钩子,确保在beforeRun时也执行 |
|
|
compiler.hooks.beforeRun.tapAsync('RouteMappingPlugin', (compilation, callback) => { |
|
|
if (self._shouldSkipGeneration()) { |
|
|
console.log('⏭️ 跳过路由映射生成(冷却期内)') |
|
|
callback() |
|
|
return |
|
|
} |
|
|
|
|
|
console.log('🔧 第一阶段:开始收集直接路由-API映射关系...') |
|
|
try { |
|
|
self.collectDirectMappings() |
|
|
callback() |
|
|
} catch (error) { |
|
|
console.error('❌ 路由映射插件执行失败:', error) |
|
|
callback(error) |
|
|
} |
|
|
}) |
|
|
}, |
|
|
|
|
|
// Vite插件接口 |
|
|
buildStart() { |
|
|
console.log('🔧 第一阶段:开始收集直接路由-API映射关系...') |
|
|
this.collectDirectMappings() |
|
|
}, |
|
|
|
|
|
// 在开发模式下也执行 |
|
|
configureServer(server) { |
|
|
if (this._shouldSkipGeneration()) { |
|
|
console.log('⏭️ 跳过路由映射生成(冷却期内)') |
|
|
return |
|
|
} |
|
|
console.log('🔧 开发模式下收集直接映射关系...') |
|
|
this.collectDirectMappings() |
|
|
}, |
|
|
|
|
|
// 检查是否应该跳过生成 |
|
|
_shouldSkipGeneration() { |
|
|
const now = Date.now() |
|
|
|
|
|
// 如果正在生成中,跳过 |
|
|
if (this._generationInProgress) { |
|
|
console.log('⏭️ 跳过生成:正在生成中') |
|
|
return true |
|
|
} |
|
|
|
|
|
// 如果在冷却期内,跳过 |
|
|
if (now - this._lastGenerationTime < this._generationCooldown) { |
|
|
const remainingTime = Math.ceil((this._generationCooldown - (now - this._lastGenerationTime)) / 1000) |
|
|
console.log(`⏭️ 跳过生成:冷却期内,还需等待 ${remainingTime} 秒`) |
|
|
return true |
|
|
} |
|
|
|
|
|
// 检查每分钟生成次数限制 |
|
|
if (now - this._generationWindowStart > 60000) { |
|
|
// 重置计数窗口 |
|
|
this._generationCount = 0 |
|
|
this._generationWindowStart = now |
|
|
} |
|
|
|
|
|
if (this._generationCount >= this._maxGenerationsPerMinute) { |
|
|
console.log(`⏭️ 跳过生成:已达到每分钟最大生成次数限制 (${this._maxGenerationsPerMinute} 次)`) |
|
|
return true |
|
|
} |
|
|
|
|
|
return false |
|
|
}, |
|
|
|
|
|
// 检查是否需要重新生成文件 |
|
|
_shouldRegenerateFile() { |
|
|
const outputPath = resolve(__dirname, '../src/renderer/modules/route-sync/direct-route-mappings.js') |
|
|
|
|
|
// 如果文件不存在,需要生成 |
|
|
if (!existsSync(outputPath)) { |
|
|
console.log('📄 映射文件不存在,需要生成') |
|
|
return true |
|
|
} |
|
|
|
|
|
const outputStats = fs.statSync(outputPath) |
|
|
const currentTime = Date.now() |
|
|
const fileModifyTime = outputStats.mtime.getTime() |
|
|
const timeDifference = currentTime - fileModifyTime |
|
|
const thirtyMinutes = 30 * 60 * 1000 // 30分钟的毫秒数 |
|
|
|
|
|
// 检查文件是否超过30分钟未更新 |
|
|
if (timeDifference > thirtyMinutes) { |
|
|
console.log(`⏰ 映射文件已存在 ${Math.round(timeDifference / (60 * 1000))} 分钟,超过30分钟限制,需要重新生成`) |
|
|
return true |
|
|
} |
|
|
|
|
|
// 检查源文件是否比生成文件更新 |
|
|
const sourceFiles = this._getSourceFiles() |
|
|
|
|
|
for (const sourceFile of sourceFiles) { |
|
|
if (existsSync(sourceFile)) { |
|
|
const sourceStats = fs.statSync(sourceFile) |
|
|
if (sourceStats.mtime > outputStats.mtime) { |
|
|
console.log(`📝 检测到源文件更新: ${sourceFile}`) |
|
|
return true |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
console.log(`✅ 映射文件无需重新生成 (文件年龄: ${Math.round(timeDifference / (60 * 1000))} 分钟)`) |
|
|
return false |
|
|
}, |
|
|
|
|
|
// 获取需要监控的源文件列表 |
|
|
_getSourceFiles() { |
|
|
const sourceFiles = [] |
|
|
|
|
|
// 路由配置文件 |
|
|
sourceFiles.push(resolve(__dirname, '../src/renderer/router/index.js')) |
|
|
|
|
|
// 模块配置文件 |
|
|
sourceFiles.push(resolve(__dirname, '../src/renderer/modules/config.js')) |
|
|
|
|
|
// 页面组件文件 |
|
|
const moduleDirs = this.readModuleNamesFromConfig() |
|
|
moduleDirs.forEach(moduleName => { |
|
|
// 页面组件 |
|
|
const viewsPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/views`) |
|
|
if (existsSync(viewsPath)) { |
|
|
const files = readdirSync(viewsPath).filter(f => f.endsWith('.vue')) |
|
|
files.forEach(file => { |
|
|
sourceFiles.push(resolve(viewsPath, file)) |
|
|
}) |
|
|
} |
|
|
|
|
|
// 弹窗组件 |
|
|
const componentsPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/components`) |
|
|
if (existsSync(componentsPath)) { |
|
|
const files = readdirSync(componentsPath).filter(f => |
|
|
f.endsWith('.vue') && (f.toLowerCase().includes('modal') || f.toLowerCase().includes('dialog')) |
|
|
) |
|
|
files.forEach(file => { |
|
|
sourceFiles.push(resolve(componentsPath, file)) |
|
|
}) |
|
|
} |
|
|
}) |
|
|
|
|
|
return sourceFiles |
|
|
}, |
|
|
|
|
|
// 收集直接映射关系 |
|
|
collectDirectMappings() { |
|
|
// 设置生成中标志 |
|
|
this._generationInProgress = true |
|
|
|
|
|
try { |
|
|
// 检查文件是否需要重新生成 |
|
|
if (this._shouldRegenerateFile()) { |
|
|
console.log('🔄 检测到需要重新生成路由映射文件') |
|
|
|
|
|
// 1. 分析路由配置 |
|
|
const routes = this.analyzeRoutes() |
|
|
|
|
|
// 2. 收集API信息(第一层) |
|
|
const apiMappings = this.collectApiMappings(routes) |
|
|
|
|
|
// 3. 关联页面与API(第三层) - 启用并调整逻辑 |
|
|
const enhancedApiMappings = this.associatePagesWithApi(routes, apiMappings) |
|
|
|
|
|
// 4. 清理和优化API映射(第四层) - 启用并调整逻辑 |
|
|
const optimizedApiMappings = this.optimizeApiMappings(routes, enhancedApiMappings) |
|
|
|
|
|
|
|
|
// 5. 生成最终映射文件 |
|
|
this.generateMappingFile(routes, optimizedApiMappings) |
|
|
|
|
|
console.log('✅ 第一阶段直接映射关系收集完成') |
|
|
} else { |
|
|
console.log('⏭️ 路由映射文件无需重新生成') |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('❌ 直接映射关系收集失败:', error) |
|
|
throw error |
|
|
} finally { |
|
|
// 重置生成中标志并更新时间戳 |
|
|
this._generationInProgress = false |
|
|
this._lastGenerationTime = Date.now() |
|
|
this._generationCount++ |
|
|
|
|
|
if (this._generationWindowStart === 0) { |
|
|
this._generationWindowStart = Date.now() |
|
|
} |
|
|
} |
|
|
}, |
|
|
|
|
|
// 分析路由配置 |
|
|
analyzeRoutes() { |
|
|
const routerPath = resolve(__dirname, '../src/renderer/router/index.js') |
|
|
if (!existsSync(routerPath)) { |
|
|
throw new Error('Router file not found') |
|
|
} |
|
|
|
|
|
const routerContent = readFileSync(routerPath, 'utf-8') |
|
|
const ast = parser.parse(routerContent, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
const routes = [] |
|
|
|
|
|
const self = this |
|
|
traverse(ast, { |
|
|
ObjectExpression(path) { |
|
|
const properties = path.node.properties |
|
|
let route = {} |
|
|
|
|
|
properties.forEach(prop => { |
|
|
if (prop.key && prop.key.name === 'path' && prop.value && prop.value.value) { |
|
|
route.path = prop.value.value |
|
|
} |
|
|
if (prop.key && prop.key.name === 'name' && prop.value && prop.value.value) { |
|
|
route.name = prop.value.value |
|
|
} |
|
|
if (prop.key && prop.key.name === 'component' && prop.value) { |
|
|
route.component = self.extractComponentName(prop.value) |
|
|
} |
|
|
}) |
|
|
|
|
|
if (route.path) { |
|
|
route.module = self.extractModuleFromPath(route.path) |
|
|
// 添加description属性 |
|
|
route.description = self.getRouteDescription(route.module, route.path) |
|
|
routes.push(route) |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
return routes |
|
|
}, |
|
|
|
|
|
|
|
|
// 分析单个服务文件的API映射 |
|
|
analyzeServiceFileForApiMappings(servicePath, serviceName, moduleName) { |
|
|
try { |
|
|
const content = readFileSync(servicePath, 'utf-8') |
|
|
const ast = parser.parse(content, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'], |
|
|
attachComments: true |
|
|
}) |
|
|
|
|
|
const apiMappings = [] |
|
|
const self = this |
|
|
|
|
|
traverse(ast, { |
|
|
// 查找函数定义 |
|
|
FunctionDeclaration(path) { |
|
|
const functionName = path.node.id?.name |
|
|
if (functionName) { |
|
|
// 分析函数内部的API调用 |
|
|
const apiCalls = self.extractApiCallsFromFunction(path) |
|
|
apiCalls.forEach(apiCall => { |
|
|
apiMappings.push({ |
|
|
methodName: functionName, |
|
|
method: apiCall.method, |
|
|
path: apiCall.path, |
|
|
line: apiCall.line |
|
|
}) |
|
|
}) |
|
|
} |
|
|
}, |
|
|
|
|
|
// 查找箭头函数和函数表达式 |
|
|
VariableDeclarator(path) { |
|
|
if (path.node.init && (path.node.init.type === 'ArrowFunctionExpression' || path.node.init.type === 'FunctionExpression')) { |
|
|
const functionName = path.node.id?.name |
|
|
if (functionName) { |
|
|
const functionPath = path.get('init') |
|
|
const apiCalls = self.extractApiCallsFromFunction(functionPath) |
|
|
apiCalls.forEach(apiCall => { |
|
|
apiMappings.push({ |
|
|
methodName: functionName, |
|
|
method: apiCall.method, |
|
|
path: apiCall.path, |
|
|
line: apiCall.line |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
}, |
|
|
|
|
|
// 查找对象方法定义 |
|
|
ObjectMethod(path) { |
|
|
const methodName = path.node.key?.name |
|
|
if (methodName) { |
|
|
const apiCalls = self.extractApiCallsFromFunction(path) |
|
|
apiCalls.forEach(apiCall => { |
|
|
apiMappings.push({ |
|
|
methodName: methodName, |
|
|
method: apiCall.method, |
|
|
path: apiCall.path, |
|
|
line: apiCall.line |
|
|
}) |
|
|
}) |
|
|
} |
|
|
}, |
|
|
|
|
|
// 查找对象属性中的函数 |
|
|
ObjectProperty(path) { |
|
|
if (path.node.value && (path.node.value.type === 'ArrowFunctionExpression' || path.node.value.type === 'FunctionExpression')) { |
|
|
const methodName = path.node.key?.name |
|
|
if (methodName) { |
|
|
const apiCalls = self.extractApiCallsFromFunction(path.get('value')) |
|
|
apiCalls.forEach(apiCall => { |
|
|
apiMappings.push({ |
|
|
methodName: methodName, |
|
|
method: apiCall.method, |
|
|
path: apiCall.path, |
|
|
line: apiCall.line |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
return apiMappings |
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 分析服务文件失败: ${servicePath}`, error.message) |
|
|
return [] |
|
|
} |
|
|
}, |
|
|
|
|
|
// 从函数中提取API调用 |
|
|
extractApiCallsFromFunction(functionPath) { |
|
|
const apiCalls = [] |
|
|
const self = this |
|
|
|
|
|
// 首先检查函数注释中是否有 @serverApiMethod 标记 |
|
|
const commentApiInfo = self.extractApiFromComment(functionPath) |
|
|
if (commentApiInfo) { |
|
|
apiCalls.push(commentApiInfo) |
|
|
return apiCalls |
|
|
} |
|
|
|
|
|
// 如果没有注释信息,则使用原来的AST遍历方式 |
|
|
if (functionPath.node) { |
|
|
traverse(functionPath.node, { |
|
|
CallExpression(path) { |
|
|
const callInfo = self.extractApiCall(path) |
|
|
if (callInfo && callInfo.path) { |
|
|
apiCalls.push(callInfo) |
|
|
} |
|
|
} |
|
|
}, functionPath.scope, functionPath) |
|
|
} |
|
|
|
|
|
return apiCalls |
|
|
}, |
|
|
|
|
|
// 从注释中提取API信息 |
|
|
extractApiFromComment(functionPath) { |
|
|
const node = functionPath.node |
|
|
|
|
|
// 首先检查 leadingComments |
|
|
if (node && node.leadingComments) { |
|
|
for (const comment of node.leadingComments) { |
|
|
const commentText = comment.value.trim() |
|
|
const match = commentText.match(/@serverApiMethod\s+(\w+)\s+(.+)/) |
|
|
if (match) { |
|
|
const method = match[1].toUpperCase() |
|
|
const path = match[2].trim() |
|
|
return { |
|
|
method, |
|
|
path, |
|
|
line: node.loc ? node.loc.start.line : 0, |
|
|
source: 'comment' |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} |
|
|
|
|
|
// 如果没有找到注释,尝试从函数名推断API信息 |
|
|
// 这是一个备用方案,用于测试 |
|
|
return null |
|
|
}, |
|
|
|
|
|
// 第二步(第一层):收集API信息 |
|
|
collectApiMappings(routes) { |
|
|
const apiMappings = [] |
|
|
const moduleDirs = this.readModuleNamesFromConfig() |
|
|
|
|
|
console.log('🔍 开始收集API映射,模块列表:', moduleDirs) |
|
|
|
|
|
moduleDirs.forEach(moduleName => { |
|
|
const servicesPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/services`) |
|
|
|
|
|
if (existsSync(servicesPath)) { |
|
|
const serviceFiles = readdirSync(servicesPath).filter(f => f.endsWith('.js')) |
|
|
console.log(`🔍 模块 ${moduleName} 的service文件:`, serviceFiles) |
|
|
|
|
|
serviceFiles.forEach(serviceFile => { |
|
|
const serviceName = serviceFile.replace('.js', '') |
|
|
const servicePath = resolve(servicesPath, serviceFile) |
|
|
const serviceApiMappings = this.analyzeServiceFileForApiMappings(servicePath, serviceName, moduleName) |
|
|
|
|
|
console.log(`🔍 服务 ${serviceName} 的API映射数量:`, serviceApiMappings.length) |
|
|
|
|
|
if (serviceApiMappings.length > 0) { |
|
|
apiMappings.push({ |
|
|
module: moduleName, |
|
|
serviceName: serviceName, |
|
|
servicePath: servicePath, |
|
|
apiMappings: serviceApiMappings |
|
|
}) |
|
|
} |
|
|
}) |
|
|
} else { |
|
|
console.warn(`⚠️ 模块 ${moduleName} 的services目录不存在: ${servicesPath}`) |
|
|
} |
|
|
}) |
|
|
|
|
|
console.log('🔍 最终收集到的API映射数量:', apiMappings.length) |
|
|
return apiMappings |
|
|
}, |
|
|
|
|
|
// 第三步:关联页面与API |
|
|
associatePagesWithApi(routes, apiMappings) { |
|
|
console.log('🔍 开始关联页面与API,输入API映射数量:', apiMappings.length) |
|
|
|
|
|
// 为每个API映射添加调用该API的页面信息和触发器信息 |
|
|
const enhancedApiMappings = apiMappings.map(moduleMapping => { |
|
|
console.log(`🔍 处理模块 ${moduleMapping.module},API映射数量:`, moduleMapping.apiMappings.length) |
|
|
|
|
|
const enhancedApiMappings = moduleMapping.apiMappings.map(apiMapping => { |
|
|
// 查找调用该API方法的组件,并收集triggerSources信息 |
|
|
const triggerSources = this.findTriggerSourcesForApiMethod( |
|
|
routes, |
|
|
moduleMapping.module, |
|
|
moduleMapping.serviceName, |
|
|
apiMapping.methodName |
|
|
) |
|
|
|
|
|
console.log(`🔍 API方法 ${apiMapping.methodName} 的触发器数量:`, triggerSources.length) |
|
|
|
|
|
return { |
|
|
...apiMapping, |
|
|
triggerSources: triggerSources |
|
|
} |
|
|
}) |
|
|
|
|
|
return { |
|
|
...moduleMapping, |
|
|
apiMappings: enhancedApiMappings |
|
|
} |
|
|
}) |
|
|
|
|
|
console.log('🔍 关联完成,输出API映射数量:', enhancedApiMappings.length) |
|
|
return enhancedApiMappings |
|
|
}, |
|
|
|
|
|
// 查找API方法的触发器源 - 使用简化的方法 |
|
|
findTriggerSourcesForApiMethod(routes, moduleName, serviceName, methodName) { |
|
|
const triggerSources = [] |
|
|
|
|
|
console.log(`🔍 查找API方法 ${serviceName}.${methodName} 的触发器源,模块: ${moduleName}`) |
|
|
|
|
|
// 1. 遍历所有路由,查找调用该API方法的页面组件 |
|
|
routes.forEach(route => { |
|
|
if (route.module === moduleName) { |
|
|
console.log(`🔍 检查路由组件: ${route.component}`) |
|
|
const triggerAnalysis = this.analyzeComponentForTriggerSourcesSimple( |
|
|
route.component, |
|
|
route.path, |
|
|
moduleName, |
|
|
serviceName, |
|
|
methodName |
|
|
) |
|
|
|
|
|
if (triggerAnalysis && triggerAnalysis.triggerSources.length > 0) { |
|
|
triggerSources.push(...triggerAnalysis.triggerSources) |
|
|
console.log(`✅ 在组件 ${route.component} 中找到 ${triggerAnalysis.triggerSources.length} 个触发器`) |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
// 2. 搜索模块的components目录,查找调用该API方法的组件 |
|
|
const componentsInModule = this.findComponentsInModule(moduleName) |
|
|
console.log(`🔍 模块 ${moduleName} 的组件:`, componentsInModule) |
|
|
|
|
|
componentsInModule.forEach(componentName => { |
|
|
console.log(`🔍 检查模块组件: ${componentName}`) |
|
|
const triggerAnalysis = this.analyzeComponentForTriggerSourcesSimple( |
|
|
componentName, |
|
|
null, // components目录中的组件没有路由路径 |
|
|
moduleName, |
|
|
serviceName, |
|
|
methodName |
|
|
) |
|
|
|
|
|
if (triggerAnalysis && triggerAnalysis.triggerSources.length > 0) { |
|
|
triggerSources.push(...triggerAnalysis.triggerSources) |
|
|
console.log(`✅ 在组件 ${componentName} 中找到 ${triggerAnalysis.triggerSources.length} 个触发器`) |
|
|
} |
|
|
}) |
|
|
|
|
|
// 去重 |
|
|
const uniqueTriggerSources = [] |
|
|
const seen = new Set() |
|
|
|
|
|
triggerSources.forEach(trigger => { |
|
|
const key = `${trigger.component}#${trigger.triggerName || 'default'}` |
|
|
if (!seen.has(key)) { |
|
|
seen.add(key) |
|
|
uniqueTriggerSources.push(trigger) |
|
|
} |
|
|
}) |
|
|
|
|
|
console.log(`🔍 API方法 ${serviceName}.${methodName} 最终触发器数量:`, uniqueTriggerSources.length) |
|
|
return uniqueTriggerSources |
|
|
}, |
|
|
|
|
|
// 简化的组件触发器源分析方法 |
|
|
analyzeComponentForTriggerSourcesSimple(componentName, componentPath, moduleName, serviceName, methodName) { |
|
|
try { |
|
|
let filePath = componentPath |
|
|
|
|
|
console.log(`🔍 简化分析组件 ${componentName},查找服务 ${serviceName}.${methodName}`) |
|
|
|
|
|
// 如果是组件名,需要找到对应的文件 |
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
filePath = this.findComponentFile(componentName) |
|
|
} |
|
|
|
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
console.warn(`⚠️ 组件文件未找到: ${componentName}`) |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
|
|
|
console.log(`🔍 找到组件文件: ${filePath}`) |
|
|
const content = readFileSync(filePath, 'utf-8') |
|
|
|
|
|
// 使用简化的文本搜索方法 |
|
|
return this.analyzeComponentWithTextSearch(content, componentName, serviceName, methodName) |
|
|
|
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 简化分析组件触发器源失败: ${componentName}`, error.message) |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
}, |
|
|
|
|
|
// 使用文本搜索分析组件 |
|
|
analyzeComponentWithTextSearch(content, componentName, serviceName, methodName) { |
|
|
const triggerSources = [] |
|
|
|
|
|
console.log(`🔍 文本搜索组件 ${componentName},查找 ${serviceName}.${methodName}`) |
|
|
|
|
|
// 1. 检查组件的authType属性,如果是public则跳过 |
|
|
// 支持两种格式:<template authType="public"> 和 authType: 'public' |
|
|
const authTypeMatch = content.match(/authType\s*[=:]\s*["']([^"']+)["']/) |
|
|
if (authTypeMatch) { |
|
|
const authType = authTypeMatch[1] |
|
|
console.log(`🔍 组件 ${componentName} 的authType: ${authType}`) |
|
|
if (authType === 'public') { |
|
|
console.log(`⏭️ 跳过公开组件 ${componentName},不记录triggerSource`) |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
} |
|
|
|
|
|
// 2. 检查是否包含服务调用 |
|
|
const serviceCallPattern = new RegExp(`${serviceName}\\.${methodName}\\s*\\(`, 'g') |
|
|
if (!serviceCallPattern.test(content)) { |
|
|
console.log(`⚠️ 组件 ${componentName} 中没有找到 ${serviceName}.${methodName} 调用`) |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
|
|
|
console.log(`✅ 组件 ${componentName} 中包含 ${serviceName}.${methodName} 调用`) |
|
|
|
|
|
// 3. 查找调用该API方法的具体方法名 |
|
|
const callingMethods = this.findMethodsCallingServiceInText(content, serviceName, methodName) |
|
|
console.log(`🔍 找到调用 ${serviceName}.${methodName} 的方法:`, callingMethods) |
|
|
|
|
|
// 4. 先查找Vue模板中调用这些方法的按钮和控件 |
|
|
if (content.includes('<template>') && callingMethods.length > 0) { |
|
|
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/) |
|
|
if (templateMatch) { |
|
|
const templateContent = templateMatch[1] |
|
|
const templateTriggers = this.findTriggersInTemplateForMethods(templateContent, componentName, callingMethods) |
|
|
triggerSources.push(...templateTriggers) |
|
|
} |
|
|
} |
|
|
|
|
|
// 5. 如果没有找到具体的按钮触发器,才记录页面级别的调用 |
|
|
if (triggerSources.length === 0) { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: null, |
|
|
triggerType: 'page' |
|
|
}) |
|
|
console.log(`✅ 没有找到按钮触发器,记录页面级触发器: ${componentName}`) |
|
|
} else { |
|
|
console.log(`✅ 找到 ${triggerSources.length} 个按钮触发器`) |
|
|
} |
|
|
|
|
|
console.log(`🔍 组件 ${componentName} 最终触发器数量:`, triggerSources.length) |
|
|
return { triggerSources: triggerSources } |
|
|
}, |
|
|
|
|
|
// 在模板中查找触发器 - 简化版本 |
|
|
findTriggersInTemplateSimple(templateContent, componentName) { |
|
|
const triggerSources = [] |
|
|
|
|
|
console.log(`🔍 在模板中查找触发器,组件: ${componentName}`) |
|
|
|
|
|
// 查找有name属性的button元素 |
|
|
const buttonWithNameRegex = /<button[^>]*name\s*=\s*["']([^"']+)["'][^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
let match |
|
|
|
|
|
while ((match = buttonWithNameRegex.exec(templateContent)) !== null) { |
|
|
const buttonName = match[1] |
|
|
const clickHandler = match[2] |
|
|
|
|
|
console.log(`🔍 找到按钮: ${buttonName}, 点击处理器: ${clickHandler}`) |
|
|
|
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: buttonName, |
|
|
triggerType: 'button' |
|
|
}) |
|
|
console.log(`✅ 匹配到按钮触发器: ${buttonName}`) |
|
|
} |
|
|
|
|
|
// 查找没有name属性的button元素 |
|
|
const buttonNoNameRegex = /<button[^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
|
|
|
while ((match = buttonNoNameRegex.exec(templateContent)) !== null) { |
|
|
const clickHandler = match[1] |
|
|
|
|
|
console.log(`🔍 找到无name按钮,点击处理器: ${clickHandler}`) |
|
|
|
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: null, |
|
|
triggerType: 'page' |
|
|
}) |
|
|
console.log(`✅ 匹配到无name按钮触发器: ${clickHandler}`) |
|
|
} |
|
|
|
|
|
// 查找表单提交 |
|
|
const formSubmitRegex = /<form[^>]*@submit\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
|
|
|
while ((match = formSubmitRegex.exec(templateContent)) !== null) { |
|
|
const submitHandler = match[1] |
|
|
|
|
|
console.log(`🔍 找到表单提交,处理器: ${submitHandler}`) |
|
|
|
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: 'form-submit', |
|
|
triggerType: 'form' |
|
|
}) |
|
|
console.log(`✅ 匹配到表单触发器: ${submitHandler}`) |
|
|
} |
|
|
|
|
|
return triggerSources |
|
|
}, |
|
|
|
|
|
// 分析组件中的触发器源 |
|
|
analyzeComponentForTriggerSources(componentName, componentPath, moduleName, serviceName, methodName) { |
|
|
try { |
|
|
let filePath = componentPath |
|
|
|
|
|
console.log(`🔍 分析组件 ${componentName},查找服务 ${serviceName}.${methodName}`) |
|
|
|
|
|
// 如果是组件名,需要找到对应的文件 |
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
filePath = this.findComponentFile(componentName) |
|
|
} |
|
|
|
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
console.warn(`⚠️ 组件文件未找到: ${componentName}`) |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
|
|
|
console.log(`🔍 找到组件文件: ${filePath}`) |
|
|
const content = readFileSync(filePath, 'utf-8') |
|
|
|
|
|
if (filePath.endsWith('.vue')) { |
|
|
return this.analyzeVueComponentForTriggerSources(content, componentName, moduleName, serviceName, methodName) |
|
|
} else { |
|
|
return this.analyzeJavaScriptComponentForTriggerSources(content, componentName, moduleName, serviceName, methodName) |
|
|
} |
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 分析组件触发器源失败: ${componentName}`, error.message) |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
}, |
|
|
|
|
|
// 分析Vue组件中的触发器源 |
|
|
analyzeVueComponentForTriggerSources(content, componentName, moduleName, serviceName, methodName) { |
|
|
const { descriptor } = parse(content) |
|
|
const scriptContent = descriptor.script?.content || '' |
|
|
const templateContent = descriptor.template?.content || '' |
|
|
|
|
|
console.log(`🔍 分析Vue组件 ${componentName},script长度: ${scriptContent.length}, template长度: ${templateContent.length}`) |
|
|
|
|
|
if (!scriptContent) { |
|
|
console.log(`⚠️ 组件 ${componentName} 没有script部分`) |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
|
|
|
// 解析script部分,找到调用指定service方法的方法 |
|
|
const ast = parser.parse(scriptContent, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
const callingMethods = this.findMethodsCallingService(ast, serviceName, methodName) |
|
|
console.log(`🔍 在组件 ${componentName} 中找到调用 ${serviceName}.${methodName} 的方法:`, callingMethods) |
|
|
|
|
|
if (callingMethods.length === 0) { |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
|
|
|
// 解析template部分,找到调用这些方法的控件 |
|
|
const triggerSources = this.findTriggerSourcesInTemplate(templateContent, callingMethods, componentName) |
|
|
|
|
|
return { triggerSources: triggerSources } |
|
|
}, |
|
|
|
|
|
// 分析JavaScript组件中的触发器源 |
|
|
analyzeJavaScriptComponentForTriggerSources(content, componentName, moduleName, serviceName, methodName) { |
|
|
const ast = parser.parse(content, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
const callingMethods = this.findMethodsCallingService(ast, serviceName, methodName) |
|
|
|
|
|
if (callingMethods.length === 0) { |
|
|
return { triggerSources: [] } |
|
|
} |
|
|
|
|
|
// 对于JS组件,我们假设方法名就是触发器 |
|
|
const triggerSources = callingMethods.map(method => ({ |
|
|
component: componentName, |
|
|
triggerName: method, |
|
|
triggerType: 'method' |
|
|
})) |
|
|
|
|
|
return { triggerSources: triggerSources } |
|
|
}, |
|
|
|
|
|
// 在模板中查找触发器源 |
|
|
findTriggerSourcesInTemplate(templateContent, methodNames, componentName) { |
|
|
const triggerSources = [] |
|
|
|
|
|
console.log(`🔍 在组件 ${componentName} 中查找触发器,方法名:`, methodNames) |
|
|
|
|
|
// 查找button元素 - 改进正则表达式 |
|
|
const buttonRegex = /<button[^>]*name\s*=\s*["']([^"']+)["'][^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
let match |
|
|
|
|
|
while ((match = buttonRegex.exec(templateContent)) !== null) { |
|
|
const buttonName = match[1] |
|
|
const clickHandler = match[2] |
|
|
|
|
|
console.log(`🔍 找到按钮: ${buttonName}, 点击处理器: ${clickHandler}`) |
|
|
|
|
|
// 检查clickHandler是否在methodNames中 |
|
|
if (methodNames.includes(clickHandler)) { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: buttonName, |
|
|
triggerType: 'button' |
|
|
}) |
|
|
console.log(`✅ 匹配到触发器: ${buttonName} -> ${clickHandler}`) |
|
|
} |
|
|
} |
|
|
|
|
|
// 查找没有name属性的button元素 |
|
|
const buttonNoNameRegex = /<button[^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
|
|
|
while ((match = buttonNoNameRegex.exec(templateContent)) !== null) { |
|
|
const clickHandler = match[1] |
|
|
|
|
|
console.log(`🔍 找到无name按钮,点击处理器: ${clickHandler}`) |
|
|
|
|
|
// 检查clickHandler是否在methodNames中 |
|
|
if (methodNames.includes(clickHandler)) { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: null, |
|
|
triggerType: 'button' |
|
|
}) |
|
|
console.log(`✅ 匹配到无name触发器: ${clickHandler}`) |
|
|
} |
|
|
} |
|
|
|
|
|
// 查找其他可能的响应式控件(如input、select等) |
|
|
const inputRegex = /<(input|select|textarea)[^>]*name\s*=\s*["']([^"']+)["'][^>]*@(change|input|blur)\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
|
|
|
while ((match = inputRegex.exec(templateContent)) !== null) { |
|
|
const elementType = match[1] |
|
|
const elementName = match[2] |
|
|
const eventType = match[3] |
|
|
const eventHandler = match[4] |
|
|
|
|
|
console.log(`🔍 找到控件: ${elementName}, 事件处理器: ${eventHandler}`) |
|
|
|
|
|
// 检查eventHandler是否在methodNames中 |
|
|
if (methodNames.includes(eventHandler)) { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: elementName, |
|
|
triggerType: `${elementType}_${eventType}` |
|
|
}) |
|
|
console.log(`✅ 匹配到控件触发器: ${elementName} -> ${eventHandler}`) |
|
|
} |
|
|
} |
|
|
|
|
|
// 如果没有找到具体的控件,但有调用方法,则记录组件名 |
|
|
if (triggerSources.length === 0 && methodNames.length > 0) { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: null, |
|
|
triggerType: 'component' |
|
|
}) |
|
|
console.log(`✅ 记录组件级触发器: ${componentName}`) |
|
|
} |
|
|
|
|
|
console.log(`🔍 组件 ${componentName} 最终触发器数量:`, triggerSources.length) |
|
|
return triggerSources |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找模块中的所有组件 |
|
|
findComponentsInModule(moduleName) { |
|
|
const components = [] |
|
|
|
|
|
try { |
|
|
// 查找views目录中的组件 |
|
|
const viewsPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/views`) |
|
|
if (existsSync(viewsPath)) { |
|
|
const viewFiles = readdirSync(viewsPath).filter(f => f.endsWith('.vue')) |
|
|
viewFiles.forEach(file => { |
|
|
components.push(file.replace('.vue', '')) |
|
|
}) |
|
|
} |
|
|
|
|
|
// 查找components目录中的组件 |
|
|
const componentsPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/components`) |
|
|
if (existsSync(componentsPath)) { |
|
|
const componentFiles = readdirSync(componentsPath).filter(f => f.endsWith('.vue')) |
|
|
componentFiles.forEach(file => { |
|
|
components.push(file.replace('.vue', '')) |
|
|
}) |
|
|
} |
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 查找模块组件失败: ${moduleName}`, error.message) |
|
|
} |
|
|
|
|
|
return components |
|
|
}, |
|
|
|
|
|
// 第四步:清理和优化API映射 |
|
|
optimizeApiMappings(routes, apiMappings) { |
|
|
console.log('🔍 开始清理和优化API映射,输入API映射数量:', apiMappings.length) |
|
|
|
|
|
const optimizedApiMappings = apiMappings.map(moduleMapping => { |
|
|
console.log(`🔍 处理模块 ${moduleMapping.module},原始API映射数量:`, moduleMapping.apiMappings.length) |
|
|
|
|
|
// 过滤掉triggerSources为空的API映射 |
|
|
const filteredApiMappings = moduleMapping.apiMappings.filter(apiMapping => { |
|
|
const hasTriggers = apiMapping.triggerSources && apiMapping.triggerSources.length > 0 |
|
|
if (!hasTriggers) { |
|
|
console.log(`🗑️ 删除无触发器的API映射: ${apiMapping.methodName}`) |
|
|
} |
|
|
return hasTriggers |
|
|
}) |
|
|
|
|
|
console.log(`🔍 模块 ${moduleMapping.module} 过滤后API映射数量:`, filteredApiMappings.length) |
|
|
|
|
|
return { |
|
|
...moduleMapping, |
|
|
apiMappings: filteredApiMappings |
|
|
} |
|
|
}).filter(moduleMapping => moduleMapping.apiMappings.length > 0) // 过滤掉没有API映射的模块 |
|
|
|
|
|
const totalRemoved = apiMappings.reduce((sum, module) => |
|
|
sum + module.apiMappings.length, 0) - optimizedApiMappings.reduce((sum, module) => |
|
|
sum + module.apiMappings.length, 0) |
|
|
|
|
|
console.log(`🔍 清理完成,删除了 ${totalRemoved} 个无触发器的API映射`) |
|
|
console.log(`🔍 输出API映射数量:`, optimizedApiMappings.length) |
|
|
|
|
|
return optimizedApiMappings |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 分析组件中的服务调用 |
|
|
analyzeComponentForServiceCalls(componentName, componentPath, moduleName) { |
|
|
try { |
|
|
let filePath = componentPath |
|
|
|
|
|
// 如果是组件名,需要找到对应的文件 |
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
filePath = this.findComponentFile(componentName) |
|
|
} |
|
|
|
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
console.warn(`⚠️ 组件文件未找到: ${componentName}`) |
|
|
return null |
|
|
} |
|
|
|
|
|
const content = readFileSync(filePath, 'utf-8') |
|
|
|
|
|
if (filePath.endsWith('.vue')) { |
|
|
return this.analyzeVueComponentForServiceCalls(content, componentName, moduleName) |
|
|
} else { |
|
|
return this.analyzeJavaScriptComponentForServiceCalls(content, componentName, moduleName) |
|
|
} |
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 分析组件失败: ${componentName}`, error.message) |
|
|
return null |
|
|
} |
|
|
}, |
|
|
|
|
|
// 分析组件中调用特定API方法的服务调用和触发器 |
|
|
analyzeComponentForServiceCallsWithTriggers(componentName, componentPath, moduleName, serviceName, methodName) { |
|
|
try { |
|
|
let filePath = componentPath |
|
|
|
|
|
// 如果是组件名,需要找到对应的文件 |
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
filePath = this.findComponentFile(componentName) |
|
|
} |
|
|
|
|
|
if (!filePath || !existsSync(filePath)) { |
|
|
console.warn(`⚠️ 组件文件未找到: ${componentName}`) |
|
|
return { hasServiceCall: false, triggerSources: [] } |
|
|
} |
|
|
|
|
|
const content = readFileSync(filePath, 'utf-8') |
|
|
|
|
|
if (filePath.endsWith('.vue')) { |
|
|
return this.analyzeVueComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) |
|
|
} else { |
|
|
return this.analyzeJavaScriptComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) |
|
|
} |
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 分析组件服务调用和触发器失败: ${componentName}`, error.message) |
|
|
return { hasServiceCall: false, triggerSources: [] } |
|
|
} |
|
|
}, |
|
|
|
|
|
// 分析Vue组件中的服务调用和触发器 |
|
|
analyzeVueComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) { |
|
|
const { descriptor } = parse(content) |
|
|
const scriptContent = descriptor.script?.content || '' |
|
|
const templateContent = descriptor.template?.content || '' |
|
|
|
|
|
if (!scriptContent) { |
|
|
return { hasServiceCall: false, triggerSources: [] } |
|
|
} |
|
|
|
|
|
// 解析script部分,找到调用指定service方法的方法 |
|
|
const ast = parser.parse(scriptContent, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
const callingMethods = this.findMethodsCallingService(ast, serviceName, methodName) |
|
|
|
|
|
if (callingMethods.length === 0) { |
|
|
return { hasServiceCall: false, triggerSources: [] } |
|
|
} |
|
|
|
|
|
// 解析template部分,找到调用这些方法的button |
|
|
const triggerSources = this.findButtonsCallingMethods(templateContent, callingMethods, componentName) |
|
|
|
|
|
return { |
|
|
hasServiceCall: true, |
|
|
triggerSources: triggerSources |
|
|
} |
|
|
}, |
|
|
|
|
|
// 分析JavaScript组件中的服务调用和触发器 |
|
|
analyzeJavaScriptComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) { |
|
|
const ast = parser.parse(content, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
const callingMethods = this.findMethodsCallingService(ast, serviceName, methodName) |
|
|
|
|
|
if (callingMethods.length === 0) { |
|
|
return { hasServiceCall: false, triggerSources: [] } |
|
|
} |
|
|
|
|
|
// 对于JS组件,我们假设方法名就是触发器 |
|
|
const triggerSources = callingMethods.map(method => `${componentName}#${method}`) |
|
|
|
|
|
return { |
|
|
hasServiceCall: true, |
|
|
triggerSources: triggerSources |
|
|
} |
|
|
}, |
|
|
|
|
|
|
|
|
// 查找调用指定service方法的方法 |
|
|
findMethodsCallingService(ast, serviceName, methodName) { |
|
|
const callingMethods = [] |
|
|
const self = this |
|
|
|
|
|
traverse(ast, { |
|
|
ObjectMethod(path) { |
|
|
const methodName = path.node.key?.name |
|
|
if (methodName) { |
|
|
const hasServiceCall = self.checkMethodCallsService(path, serviceName, methodName) |
|
|
if (hasServiceCall) { |
|
|
callingMethods.push(methodName) |
|
|
} |
|
|
} |
|
|
}, |
|
|
|
|
|
ObjectProperty(path) { |
|
|
if (path.node.value && (path.node.value.type === 'ArrowFunctionExpression' || path.node.value.type === 'FunctionExpression')) { |
|
|
const methodName = path.node.key?.name |
|
|
if (methodName) { |
|
|
const hasServiceCall = self.checkMethodCallsService(path.get('value'), serviceName, methodName) |
|
|
if (hasServiceCall) { |
|
|
callingMethods.push(methodName) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
return callingMethods |
|
|
}, |
|
|
|
|
|
// 检查方法是否调用了指定的service方法 |
|
|
checkMethodCallsService(methodPath, serviceName, methodName) { |
|
|
let hasServiceCall = false |
|
|
|
|
|
traverse(methodPath.node, { |
|
|
CallExpression(path) { |
|
|
const node = path.node |
|
|
if (node.callee && node.callee.type === 'MemberExpression') { |
|
|
const object = node.callee.object |
|
|
const property = node.callee.property |
|
|
|
|
|
if (object && property && |
|
|
object.name === serviceName && |
|
|
property.name === methodName) { |
|
|
hasServiceCall = true |
|
|
} |
|
|
} |
|
|
} |
|
|
}, methodPath.scope, methodPath) |
|
|
|
|
|
return hasServiceCall |
|
|
}, |
|
|
|
|
|
// 在template中查找调用指定方法的button |
|
|
findButtonsCallingMethods(templateContent, methodNames, componentName) { |
|
|
const triggerSources = [] |
|
|
|
|
|
// 使用正则表达式查找button元素 |
|
|
const buttonRegex = /<button[^>]*name\s*=\s*["']([^"']+)["'][^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
let match |
|
|
|
|
|
while ((match = buttonRegex.exec(templateContent)) !== null) { |
|
|
const buttonName = match[1] |
|
|
const clickHandler = match[2] |
|
|
|
|
|
// 检查clickHandler是否在methodNames中 |
|
|
if (methodNames.includes(clickHandler)) { |
|
|
triggerSources.push(`${componentName}#${buttonName}`) |
|
|
} |
|
|
} |
|
|
|
|
|
return triggerSources |
|
|
}, |
|
|
|
|
|
// 分析Vue组件中的服务调用 |
|
|
analyzeVueComponentForServiceCalls(content, componentName, moduleName) { |
|
|
const { descriptor } = parse(content) |
|
|
const scriptContent = descriptor.script?.content || '' |
|
|
|
|
|
if (!scriptContent) { |
|
|
return { serviceCalls: [], methods: [] } |
|
|
} |
|
|
|
|
|
const ast = parser.parse(scriptContent, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
return this.extractServiceCallsFromAST(ast, componentName, moduleName) |
|
|
}, |
|
|
|
|
|
// 分析JavaScript组件中的服务调用 |
|
|
analyzeJavaScriptComponentForServiceCalls(content, componentName, moduleName) { |
|
|
const ast = parser.parse(content, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
return this.extractServiceCallsFromAST(ast, componentName, moduleName) |
|
|
}, |
|
|
|
|
|
// 从AST中提取服务调用 |
|
|
extractServiceCallsFromAST(ast, componentName, moduleName) { |
|
|
const serviceCalls = [] |
|
|
const methods = [] |
|
|
|
|
|
const self = this |
|
|
traverse(ast, { |
|
|
// 查找方法定义 |
|
|
FunctionDeclaration(path) { |
|
|
if (path.node.id) { |
|
|
methods.push(path.node.id.name) |
|
|
} |
|
|
}, |
|
|
|
|
|
// 查找箭头函数 |
|
|
ArrowFunctionExpression(path) { |
|
|
if (path.parent && path.parent.type === 'VariableDeclarator' && path.parent.id) { |
|
|
methods.push(path.parent.id.name) |
|
|
} |
|
|
}, |
|
|
|
|
|
// 查找服务调用 |
|
|
CallExpression(path) { |
|
|
const callInfo = self.extractServiceCall(path) |
|
|
if (callInfo) { |
|
|
serviceCalls.push(callInfo) |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
return { serviceCalls, methods } |
|
|
}, |
|
|
|
|
|
// 提取服务调用信息 |
|
|
extractServiceCall(path) { |
|
|
const node = path.node |
|
|
|
|
|
// 检查是否是服务调用 (service.method()) |
|
|
if (node.callee && node.callee.type === 'MemberExpression') { |
|
|
const object = node.callee.object |
|
|
const property = node.callee.property |
|
|
|
|
|
if (object && property && object.name && property.name) { |
|
|
// 检查是否是服务调用 |
|
|
if (object.name.endsWith('Service') || object.name === 'api' || object.name === 'axios') { |
|
|
return { |
|
|
type: 'service', |
|
|
service: object.name, |
|
|
method: property.name, |
|
|
arguments: node.arguments.map(arg => this.extractArgumentValue(arg)), |
|
|
line: node.loc?.start?.line || 0 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return null |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 提取API调用信息 |
|
|
extractApiCall(path) { |
|
|
const node = path.node |
|
|
const self = this |
|
|
|
|
|
// 检查是否是直接的API调用 (api.get, api.post等) |
|
|
if (node.callee && node.callee.type === 'MemberExpression') { |
|
|
const object = node.callee.object |
|
|
const property = node.callee.property |
|
|
|
|
|
if (object && property) { |
|
|
// 支持多种API调用方式 |
|
|
const apiObjects = ['api', 'axios', 'http', 'request'] |
|
|
const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] |
|
|
|
|
|
// 检查对象名称(可能是Identifier或StringLiteral) |
|
|
let objectName = '' |
|
|
if (object.type === 'Identifier') { |
|
|
objectName = object.name |
|
|
} else if (object.type === 'StringLiteral') { |
|
|
objectName = object.value |
|
|
} |
|
|
|
|
|
// 检查属性名称 |
|
|
let propertyName = '' |
|
|
if (property.type === 'Identifier') { |
|
|
propertyName = property.name |
|
|
} else if (property.type === 'StringLiteral') { |
|
|
propertyName = property.value |
|
|
} |
|
|
|
|
|
if (apiObjects.includes(objectName) && httpMethods.includes(propertyName)) { |
|
|
const method = propertyName |
|
|
const args = node.arguments |
|
|
|
|
|
// 提取API路径 |
|
|
let apiPath = '' |
|
|
if (args.length > 0) { |
|
|
if (args[0].type === 'StringLiteral') { |
|
|
apiPath = args[0].value |
|
|
} else if (args[0].type === 'TemplateLiteral') { |
|
|
// 处理模板字符串 |
|
|
apiPath = self.extractTemplateLiteral(args[0]) |
|
|
} |
|
|
} |
|
|
|
|
|
if (apiPath) { |
|
|
return { |
|
|
method: method.toUpperCase(), |
|
|
path: apiPath, |
|
|
line: node.loc?.start?.line || 0 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// 检查是否是服务调用 (service.method()) |
|
|
if (node.callee && node.callee.type === 'MemberExpression') { |
|
|
const object = node.callee.object |
|
|
const property = node.callee.property |
|
|
|
|
|
if (object && property) { |
|
|
let objectName = '' |
|
|
let propertyName = '' |
|
|
|
|
|
if (object.type === 'Identifier') { |
|
|
objectName = object.name |
|
|
} else if (object.type === 'StringLiteral') { |
|
|
objectName = object.value |
|
|
} |
|
|
|
|
|
if (property.type === 'Identifier') { |
|
|
propertyName = property.name |
|
|
} else if (property.type === 'StringLiteral') { |
|
|
propertyName = property.value |
|
|
} |
|
|
|
|
|
// 检查是否是服务调用 |
|
|
if (objectName && propertyName && |
|
|
(objectName.endsWith('Service') || objectName === 'api' || objectName === 'axios')) { |
|
|
return { |
|
|
method: propertyName, |
|
|
service: objectName, |
|
|
line: node.loc?.start?.line || 0 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return null |
|
|
}, |
|
|
|
|
|
// 提取参数值 |
|
|
extractArgumentValue(node) { |
|
|
if (node.type === 'StringLiteral') { |
|
|
return node.value |
|
|
} else if (node.type === 'NumericLiteral') { |
|
|
return node.value |
|
|
} else if (node.type === 'Identifier') { |
|
|
return node.name |
|
|
} |
|
|
return null |
|
|
}, |
|
|
|
|
|
// 提取模板字符串内容 |
|
|
extractTemplateLiteral(templateLiteral) { |
|
|
if (templateLiteral.type !== 'TemplateLiteral') { |
|
|
return '' |
|
|
} |
|
|
|
|
|
let result = '' |
|
|
templateLiteral.quasis.forEach((quasi, index) => { |
|
|
result += quasi.value.raw |
|
|
if (index < templateLiteral.expressions.length) { |
|
|
// 对于表达式,我们使用实际的变量名作为占位符 |
|
|
const expression = templateLiteral.expressions[index] |
|
|
const variableName = this.extractArgumentValue(expression) |
|
|
|
|
|
if (variableName) { |
|
|
result += `{${variableName}}` |
|
|
} else { |
|
|
// 如果无法提取变量名,使用节点类型作为后备 |
|
|
result += `{${expression.type}}` |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
return result |
|
|
}, |
|
|
|
|
|
// 从模块配置中读取模块名称 |
|
|
readModuleNamesFromConfig() { |
|
|
try { |
|
|
const configPath = resolve(__dirname, '../src/renderer/modules/config.js') |
|
|
if (!existsSync(configPath)) { |
|
|
console.warn('⚠️ 模块配置文件不存在:', configPath) |
|
|
return [] |
|
|
} |
|
|
|
|
|
const code = readFileSync(configPath, 'utf-8') |
|
|
const ast = parser.parse(code, { sourceType: 'module' }) |
|
|
const modules = [] |
|
|
|
|
|
traverse(ast, { |
|
|
ExportNamedDeclaration(path) { |
|
|
const decl = path.node.declaration |
|
|
if (decl && decl.type === 'VariableDeclaration') { |
|
|
decl.declarations.forEach(d => { |
|
|
if ( |
|
|
d.id && d.id.name === 'MODULE_CONFIG' && |
|
|
d.init && d.init.type === 'ObjectExpression' |
|
|
) { |
|
|
d.init.properties.forEach(prop => { |
|
|
if (prop.type === 'ObjectProperty') { |
|
|
if (prop.key.type === 'Identifier') { |
|
|
modules.push(prop.key.name) |
|
|
} else if (prop.key.type === 'StringLiteral') { |
|
|
modules.push(prop.key.value) |
|
|
} |
|
|
} |
|
|
}) |
|
|
} |
|
|
}) |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
return modules |
|
|
} catch (e) { |
|
|
console.warn('⚠️ 读取 MODULE_CONFIG 失败,使用默认模块集合: ', e.message) |
|
|
return [] |
|
|
} |
|
|
}, |
|
|
|
|
|
|
|
|
// 查找组件文件 |
|
|
findComponentFile(componentName) { |
|
|
// 跳过MainLayout等特殊组件,或者返回正确的路径 |
|
|
if (componentName === 'MainLayout') { |
|
|
// MainLayout在core模块中 |
|
|
const mainLayoutPath = resolve(__dirname, '../src/renderer/modules/core/components/MainLayout.vue') |
|
|
if (existsSync(mainLayoutPath)) { |
|
|
return mainLayoutPath |
|
|
} |
|
|
return null |
|
|
} |
|
|
|
|
|
const moduleDirs = this.readModuleNamesFromConfig() |
|
|
|
|
|
for (const moduleName of moduleDirs) { |
|
|
// 查找views目录 |
|
|
const viewsPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/views`) |
|
|
if (existsSync(viewsPath)) { |
|
|
const files = readdirSync(viewsPath).filter(f => f.endsWith('.vue')) |
|
|
const matchingFile = files.find(f => f.replace('.vue', '') === componentName) |
|
|
if (matchingFile) { |
|
|
return resolve(viewsPath, matchingFile) |
|
|
} |
|
|
} |
|
|
|
|
|
// 查找components目录 |
|
|
const componentsPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/components`) |
|
|
if (existsSync(componentsPath)) { |
|
|
const files = readdirSync(componentsPath).filter(f => f.endsWith('.vue')) |
|
|
const matchingFile = files.find(f => f.replace('.vue', '') === componentName) |
|
|
if (matchingFile) { |
|
|
return resolve(componentsPath, matchingFile) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return null |
|
|
}, |
|
|
|
|
|
// 生成最终映射文件 |
|
|
generateMappingFile(routes, apiMappings) { |
|
|
const outputPath = resolve(__dirname, '../src/renderer/modules/route-sync/direct-route-mappings.js') |
|
|
|
|
|
console.log('🔍 生成映射文件,API映射数量:', apiMappings.length) |
|
|
console.log('🔍 API映射内容:', JSON.stringify(apiMappings, null, 2)) |
|
|
|
|
|
// 如果文件已存在,先删除文件内容(清空文件) |
|
|
if (existsSync(outputPath)) { |
|
|
console.log('🗑️ 检测到已存在的映射文件,正在清空文件内容...') |
|
|
fs.writeFileSync(outputPath, '', 'utf-8') |
|
|
} |
|
|
|
|
|
const testContent = `// 直接路由映射文件 |
|
|
// 此文件由 route-mapping-plugin 在构建时生成 |
|
|
// 包含路由配置分析结果和API收集结果 |
|
|
// 请勿手动修改 |
|
|
|
|
|
export const directRouteMappings = { |
|
|
// 路由配置分析结果 |
|
|
routes: ${JSON.stringify(routes, null, 2)}, |
|
|
|
|
|
// API收集结果(包含页面关联和路径完善) |
|
|
apiMappings: ${JSON.stringify(apiMappings, null, 2)} |
|
|
} |
|
|
|
|
|
export default directRouteMappings |
|
|
` |
|
|
|
|
|
fs.writeFileSync(outputPath, testContent, 'utf-8') |
|
|
|
|
|
console.log(`✅ 映射文件已生成: ${outputPath}`) |
|
|
console.log(`📊 统计信息:`) |
|
|
console.log(` - 路由数量: ${routes.length} 个`) |
|
|
console.log(` - 模块数量: ${apiMappings.length} 个`) |
|
|
console.log(` - 总API调用: ${apiMappings.reduce((sum, module) => sum + module.apiMappings.length, 0)} 个`) |
|
|
console.log(` - 总组件关联: ${apiMappings.reduce((sum, module) => |
|
|
sum + module.apiMappings.reduce((apiSum, api) => apiSum + (api.callingComponents ? api.callingComponents.length : 0), 0), 0)} 个`) |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 提取组件名称 |
|
|
extractComponentName(node) { |
|
|
if (node.type === 'Identifier') { |
|
|
return node.name |
|
|
} else if (node.type === 'StringLiteral') { |
|
|
return node.value |
|
|
} |
|
|
return null |
|
|
}, |
|
|
|
|
|
|
|
|
// 从路径提取模块名 |
|
|
extractModuleFromPath(path) { |
|
|
if (path === '/' || path === '') return 'home' |
|
|
|
|
|
const segments = path.split('/').filter(Boolean) |
|
|
if (segments.length === 0) return 'home' |
|
|
|
|
|
// 特殊处理:user-profile路径对应user-management模块 |
|
|
if (segments[0] === 'user-profile') { |
|
|
return 'user-management' |
|
|
} |
|
|
|
|
|
return segments[0] |
|
|
}, |
|
|
|
|
|
// 获取路由描述 |
|
|
getRouteDescription(module, path) { |
|
|
try { |
|
|
// 读取RouteConfig文件 |
|
|
const routeConfigPath = resolve(__dirname, '../src/renderer/modules/route-sync/RouteConfig.js') |
|
|
if (!existsSync(routeConfigPath)) { |
|
|
console.warn(`⚠️ RouteConfig文件不存在: ${routeConfigPath}`) |
|
|
return `${module}页面` |
|
|
} |
|
|
|
|
|
const routeConfigContent = readFileSync(routeConfigPath, 'utf-8') |
|
|
|
|
|
// 解析RouteConfig文件,提取descriptions对象 |
|
|
const descriptions = this.extractDescriptionsFromRouteConfig(routeConfigContent) |
|
|
|
|
|
// 查找对应的描述 |
|
|
if (descriptions && descriptions[module]) { |
|
|
return descriptions[module] |
|
|
} else { |
|
|
// 特殊处理:如果是user-profile路径,使用user-profile的描述 |
|
|
if (path === '/user-profile' && descriptions && descriptions['user-profile']) { |
|
|
return descriptions['user-profile'] |
|
|
} |
|
|
|
|
|
// 如果找不到对应的key,输出错误日志但不影响运行 |
|
|
console.error(`❌ RouteConfig.descriptions中缺少模块 "${module}" 的配置,路径: ${path}`) |
|
|
return `${module}页面` |
|
|
} |
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 获取路由描述失败: ${error.message}`) |
|
|
return `${module}页面` |
|
|
} |
|
|
}, |
|
|
|
|
|
// 从RouteConfig文件中提取descriptions对象 |
|
|
extractDescriptionsFromRouteConfig(content) { |
|
|
try { |
|
|
const ast = parser.parse(content, { |
|
|
sourceType: 'module', |
|
|
plugins: ['jsx'] |
|
|
}) |
|
|
|
|
|
let descriptions = null |
|
|
|
|
|
traverse(ast, { |
|
|
ObjectExpression(path) { |
|
|
// 查找descriptions属性 |
|
|
const properties = path.node.properties |
|
|
const descriptionsProp = properties.find(prop => |
|
|
prop.key && prop.key.name === 'descriptions' |
|
|
) |
|
|
|
|
|
if (descriptionsProp && descriptionsProp.value && descriptionsProp.value.type === 'ObjectExpression') { |
|
|
// 解析descriptions对象 |
|
|
descriptions = {} |
|
|
descriptionsProp.value.properties.forEach(prop => { |
|
|
if (prop.key && prop.key.type === 'StringLiteral' && prop.value && prop.value.type === 'StringLiteral') { |
|
|
descriptions[prop.key.value] = prop.value.value |
|
|
} |
|
|
}) |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
return descriptions |
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 解析RouteConfig失败: ${error.message}`) |
|
|
return null |
|
|
} |
|
|
}, |
|
|
|
|
|
// 在文本中查找调用指定service方法的方法名 |
|
|
findMethodsCallingServiceInText(content, serviceName, methodName) { |
|
|
const callingMethods = [] |
|
|
|
|
|
try { |
|
|
// 解析script部分 |
|
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/) |
|
|
if (!scriptMatch) { |
|
|
return callingMethods |
|
|
} |
|
|
|
|
|
const scriptContent = scriptMatch[1] |
|
|
|
|
|
// 使用更简单的方法:先找到所有const方法定义,然后检查每个方法 |
|
|
const constMethodMatches = scriptContent.match(/const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>\s*\{/g) |
|
|
if (constMethodMatches) { |
|
|
for (const methodMatch of constMethodMatches) { |
|
|
const methodNameMatch = methodMatch.match(/const\s+(\w+)\s*=/) |
|
|
if (methodNameMatch) { |
|
|
const componentMethodName = methodNameMatch[1] |
|
|
|
|
|
// 找到该方法的完整定义(从const开始到对应的}结束) |
|
|
const methodStart = scriptContent.indexOf(methodMatch) |
|
|
let braceCount = 0 |
|
|
let methodEnd = methodStart |
|
|
let inMethod = false |
|
|
|
|
|
for (let i = methodStart; i < scriptContent.length; i++) { |
|
|
const char = scriptContent[i] |
|
|
if (char === '{') { |
|
|
braceCount++ |
|
|
inMethod = true |
|
|
} else if (char === '}') { |
|
|
braceCount-- |
|
|
if (inMethod && braceCount === 0) { |
|
|
methodEnd = i + 1 |
|
|
break |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
const methodContent = scriptContent.substring(methodStart, methodEnd) |
|
|
|
|
|
// 检查该方法是否调用了指定的API |
|
|
const apiCallPattern = new RegExp(`${serviceName}\\.${methodName}\\s*\\(`, 'g') |
|
|
if (apiCallPattern.test(methodContent)) { |
|
|
callingMethods.push(componentMethodName) |
|
|
console.log(`✅ 找到调用 ${serviceName}.${methodName} 的const方法: ${componentMethodName}`) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// 查找Vue 2 Options API的方法定义:methods: { methodName() { ... } } |
|
|
const methodsObjectRegex = /methods\s*:\s*\{([\s\S]*?)\}/g |
|
|
while ((match = methodsObjectRegex.exec(scriptContent)) !== null) { |
|
|
const methodsContent = match[1] |
|
|
const methodDefRegex = /(\w+)\s*\([^)]*\)\s*\{[\s\S]*?\}/g |
|
|
let methodMatch |
|
|
|
|
|
while ((methodMatch = methodDefRegex.exec(methodsContent)) !== null) { |
|
|
const componentMethodName = methodMatch[1] |
|
|
const methodStart = methodMatch.index |
|
|
const methodEnd = methodStart + methodMatch[0].length |
|
|
const methodContent = methodsContent.substring(methodStart, methodEnd) |
|
|
|
|
|
const apiCallPattern = new RegExp(`${serviceName}\\.${methodName}\\s*\\(`, 'g') |
|
|
if (apiCallPattern.test(methodContent)) { |
|
|
callingMethods.push(componentMethodName) |
|
|
console.log(`✅ 找到调用 ${serviceName}.${methodName} 的methods方法: ${componentMethodName}`) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.warn(`⚠️ 查找方法调用失败: ${error.message}`) |
|
|
} |
|
|
|
|
|
return callingMethods |
|
|
}, |
|
|
|
|
|
// 在模板中查找调用指定方法的触发器 |
|
|
findTriggersInTemplateForMethods(templateContent, componentName, methodNames) { |
|
|
const triggerSources = [] |
|
|
|
|
|
console.log(`🔍 在模板中查找调用方法 ${methodNames.join(', ')} 的触发器`) |
|
|
|
|
|
// 查找有name属性的button元素 |
|
|
const buttonWithNameRegex = /<button[^>]*name\s*=\s*["']([^"']+)["'][^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
let match |
|
|
|
|
|
while ((match = buttonWithNameRegex.exec(templateContent)) !== null) { |
|
|
const buttonName = match[1] |
|
|
const clickHandler = match[2] |
|
|
|
|
|
console.log(`🔍 找到按钮: ${buttonName}, 点击处理器: ${clickHandler}`) |
|
|
|
|
|
// 检查clickHandler是否在methodNames中 |
|
|
if (methodNames.includes(clickHandler)) { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: buttonName, |
|
|
triggerType: 'button' |
|
|
}) |
|
|
console.log(`✅ 匹配到按钮触发器: ${buttonName} -> ${clickHandler}`) |
|
|
} |
|
|
} |
|
|
|
|
|
// 查找没有name属性的button元素 |
|
|
const buttonNoNameRegex = /<button[^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
|
|
|
while ((match = buttonNoNameRegex.exec(templateContent)) !== null) { |
|
|
const clickHandler = match[1] |
|
|
|
|
|
console.log(`🔍 找到无name按钮,点击处理器: ${clickHandler}`) |
|
|
|
|
|
// 检查clickHandler是否在methodNames中 |
|
|
if (methodNames.includes(clickHandler)) { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: "", // 设置为空字符串,提示用户需要补充name属性 |
|
|
triggerType: 'button' |
|
|
}) |
|
|
console.log(`✅ 匹配到无name按钮触发器: ${clickHandler} (需要补充name属性)`) |
|
|
} |
|
|
} |
|
|
|
|
|
// 查找表单提交 |
|
|
const formSubmitRegex = /<form[^>]*@submit(?:\.prevent)?\s*=\s*["']([^"']+)["'][^>]*>/g |
|
|
|
|
|
while ((match = formSubmitRegex.exec(templateContent)) !== null) { |
|
|
const submitHandler = match[1] |
|
|
const formStart = match.index |
|
|
const formEnd = formStart + match[0].length |
|
|
|
|
|
console.log(`🔍 找到表单提交,处理器: ${submitHandler}`) |
|
|
|
|
|
// 检查submitHandler是否在methodNames中 |
|
|
if (methodNames.includes(submitHandler)) { |
|
|
// 查找该表单内的提交按钮 |
|
|
const formContent = this.extractFormContent(templateContent, formStart) |
|
|
const submitButtons = this.findSubmitButtonsInForm(formContent) |
|
|
|
|
|
if (submitButtons.length > 0) { |
|
|
// 如果有提交按钮,关联到具体的按钮 |
|
|
submitButtons.forEach(button => { |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: button.name || 'form-submit', |
|
|
triggerType: 'button' |
|
|
}) |
|
|
console.log(`✅ 匹配到表单提交按钮触发器: ${button.name || 'form-submit'} -> ${submitHandler}`) |
|
|
}) |
|
|
} else { |
|
|
// 如果没有找到提交按钮,记录为表单提交 |
|
|
triggerSources.push({ |
|
|
component: componentName, |
|
|
triggerName: 'form-submit', |
|
|
triggerType: 'form' |
|
|
}) |
|
|
console.log(`✅ 匹配到表单触发器: ${submitHandler}`) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return triggerSources |
|
|
}, |
|
|
|
|
|
// 提取表单内容(从form标签开始到对应的</form>结束) |
|
|
extractFormContent(templateContent, formStart) { |
|
|
let braceCount = 0 |
|
|
let formEnd = formStart |
|
|
let inForm = false |
|
|
|
|
|
for (let i = formStart; i < templateContent.length; i++) { |
|
|
const char = templateContent[i] |
|
|
if (char === '<') { |
|
|
// 检查是否是form标签的开始或结束 |
|
|
const remaining = templateContent.substring(i) |
|
|
if (remaining.startsWith('<form')) { |
|
|
braceCount++ |
|
|
inForm = true |
|
|
} else if (remaining.startsWith('</form>')) { |
|
|
braceCount-- |
|
|
if (inForm && braceCount === 0) { |
|
|
formEnd = i + 7 // 7是'</form>'的长度 |
|
|
break |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return templateContent.substring(formStart, formEnd) |
|
|
}, |
|
|
|
|
|
// 在表单内容中查找提交按钮 |
|
|
findSubmitButtonsInForm(formContent) { |
|
|
const submitButtons = [] |
|
|
|
|
|
// 查找type="submit"的按钮 |
|
|
const submitButtonRegex = /<button[^>]*type\s*=\s*["']submit["'][^>]*>/g |
|
|
let match |
|
|
|
|
|
while ((match = submitButtonRegex.exec(formContent)) !== null) { |
|
|
const buttonTag = match[0] |
|
|
|
|
|
// 提取按钮的name属性 |
|
|
const nameMatch = buttonTag.match(/name\s*=\s*["']([^"']+)["']/) |
|
|
const buttonName = nameMatch ? nameMatch[1] : null |
|
|
|
|
|
submitButtons.push({ |
|
|
name: buttonName, |
|
|
tag: buttonTag |
|
|
}) |
|
|
|
|
|
console.log(`🔍 找到表单提交按钮: ${buttonName || 'unnamed'}`) |
|
|
} |
|
|
|
|
|
// 如果没有找到type="submit"的按钮,查找没有type属性或type="button"的按钮 |
|
|
// 这些按钮可能通过@click事件触发表单提交 |
|
|
if (submitButtons.length === 0) { |
|
|
const buttonRegex = /<button[^>]*>/g |
|
|
while ((match = buttonRegex.exec(formContent)) !== null) { |
|
|
const buttonTag = match[0] |
|
|
|
|
|
// 检查是否有@click事件 |
|
|
if (buttonTag.includes('@click')) { |
|
|
const nameMatch = buttonTag.match(/name\s*=\s*["']([^"']+)["']/) |
|
|
const buttonName = nameMatch ? nameMatch[1] : null |
|
|
|
|
|
submitButtons.push({ |
|
|
name: buttonName, |
|
|
tag: buttonTag |
|
|
}) |
|
|
|
|
|
console.log(`🔍 找到可能的表单提交按钮: ${buttonName || 'unnamed'}`) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return submitButtons |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
module.exports = { routeMappingPlugin }
|
|
|
|