You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2426 lines
89 KiB

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({
apiMethodName: 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({
apiMethodName: functionName,
method: apiCall.method,
path: apiCall.path,
line: apiCall.line
})
})
}
}
},
// 查找对象方法定义
ObjectMethod(path) {
const apiMethodName = path.node.key?.name
if (apiMethodName) {
const apiCalls = self.extractApiCallsFromFunction(path)
apiCalls.forEach(apiCall => {
apiMappings.push({
apiMethodName: apiMethodName,
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 apiMethodName = path.node.key?.name
if (apiMethodName) {
const apiCalls = self.extractApiCallsFromFunction(path.get('value'))
apiCalls.forEach(apiCall => {
apiMappings.push({
apiMethodName: apiMethodName,
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.apiMethodName
)
console.log(`🔍 API方法 ${apiMapping.apiMethodName} 的触发器数量:`, triggerSources.length)
return {
...apiMapping,
triggerSources: triggerSources
}
})
return {
...moduleMapping,
apiMappings: enhancedApiMappings
}
})
console.log('🔍 关联完成,输出API映射数量:', enhancedApiMappings.length)
return enhancedApiMappings
},
// 查找API方法的触发器源 - 使用简化的方法
findTriggerSourcesForApiMethod(routes, moduleName, serviceName, apiMethodName) {
const triggerSources = []
console.log(`🔍 查找API方法 ${serviceName}.${apiMethodName} 的触发器源,模块: ${moduleName}`)
// 1. 遍历所有路由,查找调用该API方法的页面组件
routes.forEach(route => {
if (route.module === moduleName) {
console.log(`🔍 检查路由组件: ${route.component}`)
const triggerAnalysis = this.analyzeComponentForTriggerSourcesSimple(
route.component,
route.path,
moduleName,
serviceName,
apiMethodName
)
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,
apiMethodName
)
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}.${apiMethodName} 最终触发器数量:`, uniqueTriggerSources.length)
return uniqueTriggerSources
},
// 简化的组件触发器源分析方法
analyzeComponentForTriggerSourcesSimple(componentName, componentPath, moduleName, serviceName, apiMethodName) {
try {
let filePath = componentPath
console.log(`🔍 简化分析组件 ${componentName},查找服务 ${serviceName}.${apiMethodName}`)
// 如果是组件名,需要找到对应的文件
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')
// 使用AST分析方法
return this.analyzeComponentWithAST(content, componentName, serviceName, apiMethodName, filePath)
} catch (error) {
console.warn(` 简化分析组件触发器源失败: ${componentName}`, error.message)
return { triggerSources: [] }
}
},
// 生成唯一的按钮名称
generateUniqueButtonName(componentName, clickHandler) {
// 基于组件名生成简洁的名称
const baseName = componentName.toLowerCase()
// 添加随机尾缀确保唯一性
const randomSuffix = Math.random().toString(36).substring(2, 8)
return `${baseName}-${randomSuffix}`
},
// 为按钮添加name属性
addNameAttributeToButton(filePath, buttonHtml, generatedName) {
try {
const fs = require('fs')
const content = fs.readFileSync(filePath, 'utf-8')
// 检查按钮是否已经有name属性
if (buttonHtml.includes('name=')) {
console.log(` 按钮已有name属性,跳过: ${generatedName}`)
return
}
// 在button标签中添加name属性
const updatedButtonHtml = buttonHtml.replace(/<button([^>]*)>/, `<button$1 name="${generatedName}">`)
// 替换文件中的按钮HTML
const updatedContent = content.replace(buttonHtml, updatedButtonHtml)
// 写回文件
fs.writeFileSync(filePath, updatedContent, 'utf-8')
console.log(`✅ 已为按钮添加name属性: ${generatedName}`)
} catch (error) {
console.warn(` 添加name属性失败: ${error.message}`)
}
},
// 使用AST分析组件
analyzeComponentWithAST(content, componentName, serviceName, apiMethodName, filePath = null) {
const triggerSources = []
console.log(`🔍 AST分析组件 ${componentName},查找 ${serviceName}.${apiMethodName}`)
// 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}\\.${apiMethodName}\\s*\\(`, 'g')
if (!serviceCallPattern.test(content)) {
console.log(` 组件 ${componentName} 中没有找到 ${serviceName}.${apiMethodName} 调用`)
return { triggerSources: [] }
}
console.log(`✅ 组件 ${componentName} 中包含 ${serviceName}.${apiMethodName} 调用`)
// 3. 使用Babel AST分析找到调用该API的方法
const callingMethods = this.findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName)
console.log(`🔍 Babel找到调用 ${serviceName}.${apiMethodName} 的方法:`, callingMethods)
// 4. 分析每个调用方法的触发源
if (callingMethods.length > 0) {
const methodTriggers = this.analyzeMethodTriggerSources(content, componentName, callingMethods, filePath)
triggerSources.push(...methodTriggers)
}
// 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 }
},
// 使用Babel AST分析查找调用指定服务方法的方法
findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName) {
const callingMethods = []
console.log(`🔍 [Babel分析] 开始分析 ${serviceName}.${apiMethodName} 的调用`)
try {
// 解析script部分
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/)
if (!scriptMatch) {
console.log(` 没有找到script部分`)
return callingMethods
}
const scriptContent = scriptMatch[1]
console.log(`🔍 [Babel分析] 找到script内容,长度: ${scriptContent.length}`)
// 使用Babel解析JavaScript
const { parse } = require('@babel/parser')
const traverse = require('@babel/traverse').default
const ast = parse(scriptContent, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
})
console.log(`✅ [Babel分析] AST解析成功`)
// 遍历AST查找方法定义
const self = this
traverse(ast, {
// 查找const方法定义
VariableDeclarator(path) {
if (path.node.id && path.node.id.type === 'Identifier' &&
path.node.init && path.node.init.type === 'ArrowFunctionExpression') {
const componentMethodName = path.node.id.name
const methodPath = path.get('init')
// 检查该方法是否调用了指定的API
if (self.checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)) {
callingMethods.push(componentMethodName)
console.log(`✅ 找到调用 ${serviceName}.${apiMethodName} 的const方法: ${componentMethodName}`)
}
}
},
// 查找ObjectMethod定义
ObjectMethod(path) {
if (path.node.key && path.node.key.type === 'Identifier') {
const componentMethodName = path.node.key.name
const methodPath = path
// 检查该方法是否调用了指定的API
if (self.checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)) {
callingMethods.push(componentMethodName)
console.log(`✅ 找到调用 ${serviceName}.${apiMethodName} 的ObjectMethod: ${componentMethodName}`)
}
}
}
})
} catch (error) {
console.warn(` Babel分析失败: ${error.message}`)
}
return callingMethods
},
// 使用Babel检查方法是否调用了指定的service方法
checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName) {
let hasServiceCall = false
console.log(`🔍 [Babel检查] 检查方法是否调用 ${serviceName}.${apiMethodName}`)
const traverse = require('@babel/traverse').default
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.type === 'Identifier' &&
property.type === 'Identifier') {
console.log(`🔍 [Babel检查] 发现调用: ${object.name}.${property.name}`)
if (object.name === serviceName && property.name === apiMethodName) {
hasServiceCall = true
console.log(`✅ [Babel检查] 匹配到API调用: ${serviceName}.${apiMethodName}`)
}
}
}
}
}, methodPath.scope, methodPath)
console.log(`🔍 [Babel检查] 方法调用检查结果: ${hasServiceCall}`)
return hasServiceCall
},
// 分析方法的触发源
analyzeMethodTriggerSources(content, componentName, apiMethodNames, filePath = null) {
const triggerSources = []
console.log(`🔍 分析方法的触发源: ${apiMethodNames.join(', ')}`)
// 提取模板部分
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/)
if (!templateMatch) {
console.log(` 没有找到模板部分`)
return triggerSources
}
const templateContent = templateMatch[1]
// 使用正则表达式查找调用这些方法的按钮
apiMethodNames.forEach(apiMethodName => {
console.log(`🔍 查找调用方法 ${apiMethodName} 的按钮`)
// 1. 查找@click="apiMethodName"或@click="apiMethodName("
const clickPattern = new RegExp(`@click\\s*=\\s*["']${apiMethodName}\\s*\\([^)]*\\)["']`, 'g')
const clickMatches = templateContent.match(clickPattern)
if (clickMatches) {
console.log(` ✅ 找到 ${clickMatches.length} 个调用 ${apiMethodName} 的@click按钮`)
clickMatches.forEach((match, index) => {
// 查找包含这个@click的button标签
const buttonMatch = this.findButtonContainingClick(templateContent, match)
if (buttonMatch) {
const buttonName = this.extractButtonName(buttonMatch)
const triggerType = buttonName ? 'button' : 'button' // 有name就是button,没有name也是button但需要生成name
triggerSources.push({
component: componentName,
triggerName: buttonName || this.generateUniqueButtonName(componentName, apiMethodName),
triggerType: triggerType
})
console.log(` ✅ @click按钮 ${index + 1}: ${buttonName || '无name'} -> ${apiMethodName}`)
// 如果按钮没有name属性,添加name属性
if (!buttonName && filePath) {
this.addNameAttributeToButton(filePath, buttonMatch, this.generateUniqueButtonName(componentName, apiMethodName))
}
}
})
}
// 2. 查找@submit.prevent="apiMethodName"的表单提交按钮
const submitPattern = new RegExp(`@submit\\.prevent\\s*=\\s*["']${apiMethodName}["']`, 'g')
const submitMatches = templateContent.match(submitPattern)
if (submitMatches) {
console.log(` ✅ 找到 ${submitMatches.length} 个调用 ${apiMethodName} 的表单提交`)
submitMatches.forEach((match, index) => {
// 查找包含这个@submit.prevent的form标签
const formMatch = this.findFormContainingSubmit(templateContent, match)
if (formMatch) {
// 查找表单中的type="submit"按钮
const submitButtonMatch = this.findSubmitButtonInForm(formMatch)
if (submitButtonMatch) {
const buttonName = this.extractButtonName(submitButtonMatch)
const triggerType = buttonName ? 'button' : 'button'
triggerSources.push({
component: componentName,
triggerName: buttonName || this.generateUniqueButtonName(componentName, apiMethodName),
triggerType: triggerType
})
console.log(` ✅ 表单提交按钮 ${index + 1}: ${buttonName || '无name'} -> ${apiMethodName}`)
// 如果按钮没有name属性,添加name属性
if (!buttonName && filePath) {
this.addNameAttributeToButton(filePath, submitButtonMatch, this.generateUniqueButtonName(componentName, apiMethodName))
}
} else {
// 如果没有找到具体的提交按钮,记录为form类型
triggerSources.push({
component: componentName,
triggerName: null,
triggerType: 'form'
})
console.log(` ✅ 表单提交 ${index + 1}: form类型 -> ${apiMethodName}`)
}
}
})
}
// 3. 如果既没有@click也没有@submit,记录为页面级调用
if (!clickMatches && !submitMatches) {
console.log(` 没有找到调用 ${apiMethodName} 的按钮或表单`)
}
})
return triggerSources
},
// 查找包含指定@click的button标签
findButtonContainingClick(templateContent, clickMatch) {
const lines = templateContent.split('\n')
const clickIndex = templateContent.indexOf(clickMatch)
if (clickIndex === -1) return null
// 从@click位置往前查找最近的<button标签
const beforeClick = templateContent.substring(0, clickIndex)
const lastButtonIndex = beforeClick.lastIndexOf('<button')
if (lastButtonIndex === -1) return null
// 从<button开始往后查找对应的</button>
const afterButton = templateContent.substring(lastButtonIndex)
const buttonEndIndex = afterButton.indexOf('</button>')
if (buttonEndIndex === -1) return null
return afterButton.substring(0, buttonEndIndex + 9) // 包含</button>
},
// 从button标签中提取name属性
extractButtonName(buttonHtml) {
const nameMatch = buttonHtml.match(/name\s*=\s*["']([^"']+)["']/)
return nameMatch ? nameMatch[1] : null
},
// 查找包含指定@submit.prevent的form标签
findFormContainingSubmit(templateContent, submitMatch) {
const submitIndex = templateContent.indexOf(submitMatch)
if (submitIndex === -1) return null
// 从@submit.prevent位置往前查找最近的<form标签
const beforeSubmit = templateContent.substring(0, submitIndex)
const lastFormIndex = beforeSubmit.lastIndexOf('<form')
if (lastFormIndex === -1) return null
// 从<form开始往后查找对应的</form>
const afterForm = templateContent.substring(lastFormIndex)
const formEndIndex = afterForm.indexOf('</form>')
if (formEndIndex === -1) return null
return afterForm.substring(0, formEndIndex + 7) // 包含</form>
},
// 在表单中查找type="submit"的按钮
findSubmitButtonInForm(formHtml) {
const submitButtonPattern = /<button[^>]*type\s*=\s*["']submit["'][^>]*>[\s\S]*?<\/button>/g
const match = submitButtonPattern.exec(formHtml)
return match ? match[0] : null
},
// 在模板中查找触发器 - 简化版本
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, apiMethodName) {
try {
let filePath = componentPath
console.log(`🔍 分析组件 ${componentName},查找服务 ${serviceName}.${apiMethodName}`)
// 如果是组件名,需要找到对应的文件
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, apiMethodName)
} else {
return this.analyzeJavaScriptComponentForTriggerSources(content, componentName, moduleName, serviceName, apiMethodName)
}
} catch (error) {
console.warn(` 分析组件触发器源失败: ${componentName}`, error.message)
return { triggerSources: [] }
}
},
// 分析Vue组件中的触发器源
analyzeVueComponentForTriggerSources(content, componentName, moduleName, serviceName, apiMethodName) {
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, apiMethodName)
console.log(`🔍 在组件 ${componentName} 中找到调用 ${serviceName}.${apiMethodName} 的方法:`, callingMethods)
if (callingMethods.length === 0) {
return { triggerSources: [] }
}
// 解析template部分,找到调用这些方法的控件
const triggerSources = this.findTriggerSourcesInTemplate(templateContent, callingMethods, componentName)
return { triggerSources: triggerSources }
},
// 分析JavaScript组件中的触发器源
analyzeJavaScriptComponentForTriggerSources(content, componentName, moduleName, serviceName, apiMethodName) {
const ast = parser.parse(content, {
sourceType: 'module',
plugins: ['jsx']
})
const callingMethods = this.findMethodsCallingService(ast, serviceName, apiMethodName)
if (callingMethods.length === 0) {
return { triggerSources: [] }
}
// 对于JS组件,我们假设方法名就是触发器
const triggerSources = callingMethods.map(method => ({
component: componentName,
triggerName: method,
triggerType: 'method'
}))
return { triggerSources: triggerSources }
},
// 在模板中查找触发器源
findTriggerSourcesInTemplate(templateContent, apiMethodNames, componentName) {
const triggerSources = []
console.log(`🔍 在组件 ${componentName} 中查找触发器,方法名:`, apiMethodNames)
// 查找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是否在apiMethodNames中
if (apiMethodNames.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是否在apiMethodNames中
if (apiMethodNames.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是否在apiMethodNames中
if (apiMethodNames.includes(eventHandler)) {
triggerSources.push({
component: componentName,
triggerName: elementName,
triggerType: `${elementType}_${eventType}`
})
console.log(`✅ 匹配到控件触发器: ${elementName} -> ${eventHandler}`)
}
}
// 如果没有找到具体的控件,但有调用方法,则记录组件名
if (triggerSources.length === 0 && apiMethodNames.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.apiMethodName}`)
}
return hasTriggers
})
// 去重:移除重复的triggerSources
const deduplicatedApiMappings = filteredApiMappings.map(apiMapping => {
const uniqueTriggerSources = []
const seenTriggers = new Set()
apiMapping.triggerSources.forEach(trigger => {
const triggerKey = `${trigger.component}-${trigger.triggerName}-${trigger.triggerType}`
if (!seenTriggers.has(triggerKey)) {
seenTriggers.add(triggerKey)
uniqueTriggerSources.push(trigger)
} else {
console.log(`🗑 删除重复的触发器: ${triggerKey}`)
}
})
return {
...apiMapping,
triggerSources: uniqueTriggerSources
}
})
console.log(`🔍 模块 ${moduleMapping.module} 去重后API映射数量:`, deduplicatedApiMappings.length)
return {
...moduleMapping,
apiMappings: deduplicatedApiMappings
}
}).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, apiMethodName) {
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, apiMethodName)
} else {
return this.analyzeJavaScriptComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, apiMethodName)
}
} catch (error) {
console.warn(` 分析组件服务调用和触发器失败: ${componentName}`, error.message)
return { hasServiceCall: false, triggerSources: [] }
}
},
// 分析Vue组件中的服务调用和触发器
analyzeVueComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, apiMethodName) {
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, apiMethodName)
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, apiMethodName) {
const ast = parser.parse(content, {
sourceType: 'module',
plugins: ['jsx']
})
const callingMethods = this.findMethodsCallingService(ast, serviceName, apiMethodName)
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, apiMethodName) {
const callingMethods = []
const self = this
traverse(ast, {
ObjectMethod(path) {
const apiMethodName = path.node.key?.name
if (apiMethodName) {
const hasServiceCall = self.checkMethodCallsService(path, serviceName, apiMethodName)
if (hasServiceCall) {
callingMethods.push(apiMethodName)
}
}
},
ObjectProperty(path) {
if (path.node.value && (path.node.value.type === 'ArrowFunctionExpression' || path.node.value.type === 'FunctionExpression')) {
const apiMethodName = path.node.key?.name
if (apiMethodName) {
const hasServiceCall = self.checkMethodCallsService(path.get('value'), serviceName, apiMethodName)
if (hasServiceCall) {
callingMethods.push(apiMethodName)
}
}
}
}
})
return callingMethods
},
// 检查方法是否调用了指定的service方法
checkMethodCallsService(methodPath, serviceName, apiMethodName) {
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 === apiMethodName) {
hasServiceCall = true
}
}
}
}, methodPath.scope, methodPath)
return hasServiceCall
},
// 在template中查找调用指定方法的button
findButtonsCallingMethods(templateContent, apiMethodNames, 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是否在apiMethodNames中
if (apiMethodNames.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
}
},
// 使用AST分析查找调用指定服务方法的方法
findMethodsCallingServiceWithAST(content, serviceName, apiMethodName) {
const callingMethods = []
console.log(`🔍 [AST分析] 开始分析 ${serviceName}.${apiMethodName} 的调用`)
try {
// 解析script部分
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/)
if (!scriptMatch) {
console.log(` [AST分析] 没有找到script标签`)
return callingMethods
}
const scriptContent = scriptMatch[1]
console.log(`🔍 [AST分析] 找到script内容,长度: ${scriptContent.length}`)
// 使用Babel解析AST进行更准确的分析
const ast = parser.parse(scriptContent, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
})
console.log(`✅ [AST分析] AST解析成功`)
const self = this
traverse(ast, {
// 查找const方法定义
VariableDeclarator(path) {
if (path.node.id && path.node.id.type === 'Identifier' &&
path.node.init &&
(path.node.init.type === 'ArrowFunctionExpression' ||
path.node.init.type === 'FunctionExpression')) {
const componentMethodName = path.node.id.name
const methodPath = path.get('init')
// 检查该方法是否调用了指定的API
if (self.checkMethodCallsServiceAST(methodPath, serviceName, apiMethodName)) {
callingMethods.push(componentMethodName)
console.log(`✅ 找到调用 ${serviceName}.${apiMethodName} 的const方法: ${componentMethodName}`)
}
}
},
// 查找methods对象中的方法定义
ObjectMethod(path) {
if (path.node.key && path.node.key.type === 'Identifier') {
const componentMethodName = path.node.key.name
// 检查该方法是否调用了指定的API
if (self.checkMethodCallsServiceAST(path, serviceName, apiMethodName)) {
callingMethods.push(componentMethodName)
console.log(`✅ 找到调用 ${serviceName}.${apiMethodName} 的ObjectMethod: ${componentMethodName}`)
}
}
},
// 查找对象属性中的方法定义
ObjectProperty(path) {
if (path.node.key && path.node.key.type === 'Identifier' &&
path.node.value &&
(path.node.value.type === 'ArrowFunctionExpression' ||
path.node.value.type === 'FunctionExpression')) {
const componentMethodName = path.node.key.name
const methodPath = path.get('value')
// 检查该方法是否调用了指定的API
if (self.checkMethodCallsServiceAST(methodPath, serviceName, apiMethodName)) {
callingMethods.push(componentMethodName)
console.log(`✅ 找到调用 ${serviceName}.${apiMethodName} 的ObjectProperty: ${componentMethodName}`)
}
}
}
})
} catch (error) {
console.warn(` AST分析失败,回退到正则表达式: ${error.message}`)
console.warn(` 错误堆栈: ${error.stack}`)
// 回退到正则表达式方法
return this.findMethodsCallingServiceInTextRegex(content, serviceName, apiMethodName)
}
return callingMethods
},
// 使用AST分析模板中的事件绑定
findTriggersInTemplateWithAST(templateContent, componentName, apiMethodNames, filePath = null) {
const triggerSources = []
const processedButtons = new Set() // 用于跟踪已处理的按钮,避免重复关联
console.log(`🔍 AST分析模板中调用方法 ${apiMethodNames.join(', ')} 的触发器`)
try {
// 使用Vue模板编译器解析模板
const { parse } = require('@vue/compiler-sfc')
const { compileTemplate } = require('@vue/compiler-sfc')
// 创建一个完整的Vue单文件组件用于解析
const fullComponent = `<template>${templateContent}</template>`
const { descriptor } = parse(fullComponent)
if (!descriptor.template) {
console.log(` 没有找到模板内容`)
return triggerSources
}
console.log(`🔍 模板内容长度: ${templateContent.length}`)
console.log(`🔍 模板内容前100字符: ${templateContent.substring(0, 100)}`)
// 编译模板获取AST
const { ast } = compileTemplate({
source: templateContent,
filename: 'template.vue',
compilerOptions: {
hoistStatic: false,
cacheHandlers: false
}
})
console.log(`✅ 模板AST解析成功`)
console.log(`🔍 AST根节点类型: ${ast.type}, 标签: ${ast.tag}`)
console.log(`🔍 AST子节点数量: ${ast.children ? ast.children.length : 0}`)
// 遍历AST找到事件绑定
this.traverseTemplateAST(ast, (node) => {
console.log(`🔍 AST遍历节点:`, {
type: node.type,
tag: node.tag,
props: node.props ? node.props.map(p => ({ name: p.name, value: p.value?.content })) : [],
children: node.children ? node.children.length : 0
})
if (node.type === 1 && node.tag === 'button') { // 元素节点且是button标签
console.log(`🔍 AST找到button节点:`, {
tag: node.tag,
props: node.props ? node.props.map(p => ({ name: p.name, value: p.value?.content })) : []
})
this.processButtonNode(node, componentName, apiMethodNames, triggerSources, processedButtons, filePath)
}
})
} catch (error) {
console.warn(` 模板AST分析失败: ${error.message}`)
console.warn(` 错误堆栈: ${error.stack}`)
}
return triggerSources
},
// 遍历模板AST
traverseTemplateAST(node, callback) {
callback(node)
if (node.children) {
node.children.forEach(child => {
// 遍历所有类型的子节点
this.traverseTemplateAST(child, callback)
})
}
// 也遍历其他可能的子节点属性
if (node.branches) {
node.branches.forEach(branch => {
this.traverseTemplateAST(branch, callback)
})
}
if (node.alternate) {
this.traverseTemplateAST(node.alternate, callback)
}
if (node.consequent) {
this.traverseTemplateAST(node.consequent, callback)
}
},
// 处理按钮节点
processButtonNode(node, componentName, apiMethodNames, triggerSources, processedButtons, filePath) {
// 获取按钮的name属性
let buttonName = null
let clickHandler = null
// 查找属性
if (node.props) {
for (const prop of node.props) {
if (prop.name === 'name' && prop.value && prop.value.content) {
buttonName = prop.value.content
}
// Vue模板编译器会将@click转换为on属性,type为7表示指令
if (prop.type === 7 && prop.name === 'on' && prop.arg && prop.arg.content === 'click') {
// 从AST中提取click事件的处理函数
if (prop.exp && prop.exp.children) {
// 查找方法名(type为4的节点,包含_ctx.前缀)
const apiMethodName = prop.exp.children.find(child =>
child.type === 4 && child.content && child.content.includes('_ctx.')
)
if (apiMethodName) {
// 从_ctx.deleteRole中提取deleteRole
clickHandler = apiMethodName.content.replace('_ctx.', '')
// 检查是否有参数(查找'('字符)
const hasArgs = prop.exp.children.some(child => child === '(')
if (hasArgs) {
// 查找参数
const paramIndex = prop.exp.children.findIndex(child => child === '(')
if (paramIndex !== -1 && paramIndex + 1 < prop.exp.children.length) {
const param = prop.exp.children[paramIndex + 1]
if (param && param.type === 4 && param.content) {
const paramName = param.content.replace('_ctx.', '')
clickHandler += `(${paramName})`
} else {
clickHandler += '()'
}
} else {
clickHandler += '()'
}
}
}
}
}
// 也检查其他可能的事件属性名
if (prop.name === 'onClick' && prop.value && prop.value.content) {
clickHandler = prop.value.content
}
if (prop.name === 'onclick' && prop.value && prop.value.content) {
clickHandler = prop.value.content
}
}
}
if (!clickHandler) {
return // 没有点击事件,跳过
}
console.log(`🔍 AST找到按钮: ${buttonName || '无name'}, 点击处理器: ${clickHandler}`)
// 检查方法名是否匹配
const isMatch = apiMethodNames.some(apiMethodName => {
return clickHandler === apiMethodName || clickHandler.startsWith(apiMethodName + '(')
})
if (isMatch) {
if (buttonName) {
// 有name属性的按钮
const buttonKey = `${componentName}-${buttonName}`
if (!processedButtons.has(buttonKey)) {
processedButtons.add(buttonKey)
triggerSources.push({
component: componentName,
triggerName: buttonName,
triggerType: 'button'
})
console.log(`✅ AST匹配到按钮触发器: ${buttonName} -> ${clickHandler}`)
} else {
console.log(` AST跳过已处理的按钮: ${buttonName}`)
}
} else {
// 没有name属性的按钮
const buttonKey = `${componentName}-${clickHandler}`
if (!processedButtons.has(buttonKey)) {
processedButtons.add(buttonKey)
// 生成唯一的按钮名称
const generatedName = this.generateUniqueButtonName(componentName, clickHandler)
// 如果提供了文件路径,修改文件添加name属性
if (filePath) {
this.addNameAttributeToButton(filePath, `<button`, generatedName)
}
triggerSources.push({
component: componentName,
triggerName: generatedName,
triggerType: 'button'
})
console.log(`✅ AST匹配到无name按钮触发器: ${clickHandler} (已生成名称: ${generatedName})`)
} else {
console.log(` AST跳过已处理的无name按钮: ${clickHandler}`)
}
}
}
},
// 使用AST检查方法是否调用了指定的service方法(支持间接调用)
checkMethodCallsServiceAST(methodPath, serviceName, apiMethodName) {
let hasServiceCall = false
console.log(`🔍 [AST检查] 检查方法是否调用 ${serviceName}.${apiMethodName}`)
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.type === 'Identifier' &&
property.type === 'Identifier') {
console.log(`🔍 [AST检查] 发现调用: ${object.name}.${property.name}`)
if (object.name === serviceName && property.name === apiMethodName) {
hasServiceCall = true
console.log(`✅ [AST检查] 匹配到API调用: ${serviceName}.${apiMethodName}`)
}
}
}
}
}, methodPath.scope, methodPath)
console.log(`🔍 [AST检查] 方法调用检查结果: ${hasServiceCall}`)
return hasServiceCall
},
// 回退的正则表达式方法
findMethodsCallingServiceInTextRegex(content, serviceName, apiMethodName) {
const callingMethods = []
try {
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/)
if (!scriptMatch) {
return callingMethods
}
const scriptContent = scriptMatch[1]
// 简单的正则表达式匹配作为回退
const methodRegex = /(?:const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>\s*\{|(\w+)\s*\([^)]*\)\s*\{)[\s\S]*?\}/g
let match
while ((match = methodRegex.exec(scriptContent)) !== null) {
const apiMethodName = match[1] || match[2]
if (apiMethodName) {
const methodContent = match[0]
const apiCallPattern = new RegExp(`${serviceName}\\.${apiMethodName}\\s*\\(`, 'g')
if (apiCallPattern.test(methodContent)) {
callingMethods.push(apiMethodName)
console.log(`✅ 正则表达式找到调用 ${serviceName}.${apiMethodName} 的方法: ${apiMethodName}`)
}
}
}
} catch (error) {
console.warn(` 正则表达式查找方法调用失败: ${error.message}`)
}
return callingMethods
},
// 在模板中查找调用指定方法的触发器
findTriggersInTemplateForMethods(templateContent, componentName, apiMethodNames, filePath = null) {
const triggerSources = []
console.log(`🔍 在模板中查找调用方法 ${apiMethodNames.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}`)
// 检查方法名是否匹配(支持带参数的情况)
const isMatch = apiMethodNames.some(apiMethodName => {
return clickHandler === apiMethodName || clickHandler.startsWith(apiMethodName + '(')
})
if (isMatch) {
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}`)
// 检查方法名是否匹配(支持带参数的情况)
const isMatch = apiMethodNames.some(apiMethodName => {
return clickHandler === apiMethodName || clickHandler.startsWith(apiMethodName + '(')
})
if (isMatch) {
// 生成唯一的按钮名称
const generatedName = this.generateUniqueButtonName(componentName, clickHandler)
// 如果提供了文件路径,修改文件添加name属性
if (filePath) {
this.addNameAttributeToButton(filePath, match[0], generatedName)
}
triggerSources.push({
component: componentName,
triggerName: generatedName, // 使用生成的名称
triggerType: 'button'
})
console.log(`✅ 匹配到无name按钮触发器: ${clickHandler} (已生成名称: ${generatedName})`)
}
}
// 查找表单提交
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}`)
// 检查方法名是否匹配(支持带参数的情况)
const isMatch = apiMethodNames.some(apiMethodName => {
return submitHandler === apiMethodName || submitHandler.startsWith(apiMethodName + '(')
})
if (isMatch) {
// 查找该表单内的提交按钮
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 }