|
|
|
|
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. 分析组件关联关系并完善路径信息(第五层)
|
|
|
|
|
const { componentRelationships, enhancedApiMappings: finalApiMappings } = this.analyzeComponentRelationships(routes, optimizedApiMappings)
|
|
|
|
|
|
|
|
|
|
// 6. 生成最终映射文件
|
|
|
|
|
this.generateMappingFile(routes, finalApiMappings)
|
|
|
|
|
|
|
|
|
|
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方法的组件,并收集button触发器信息
|
|
|
|
|
const callingComponents = this.findComponentsCallingApiMethodWithTriggers(
|
|
|
|
|
routes,
|
|
|
|
|
moduleMapping.module,
|
|
|
|
|
moduleMapping.serviceName,
|
|
|
|
|
apiMapping.methodName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
console.log(`🔍 API方法 ${apiMapping.methodName} 的调用组件数量:`, callingComponents.length)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...apiMapping,
|
|
|
|
|
callingComponents: callingComponents
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...moduleMapping,
|
|
|
|
|
apiMappings: enhancedApiMappings
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
console.log('🔍 关联完成,输出API映射数量:', enhancedApiMappings.length)
|
|
|
|
|
return enhancedApiMappings
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 查找调用特定API方法的组件
|
|
|
|
|
findComponentsCallingApiMethod(routes, moduleName, serviceName, methodName) {
|
|
|
|
|
const callingComponents = []
|
|
|
|
|
|
|
|
|
|
// 1. 遍历所有路由,查找调用该API方法的页面组件
|
|
|
|
|
routes.forEach(route => {
|
|
|
|
|
if (route.module === moduleName) {
|
|
|
|
|
const componentAnalysis = this.analyzeComponentForServiceCalls(
|
|
|
|
|
route.component,
|
|
|
|
|
route.path,
|
|
|
|
|
moduleName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (componentAnalysis && componentAnalysis.serviceCalls) {
|
|
|
|
|
// 检查是否有调用该服务方法的调用
|
|
|
|
|
const hasServiceCall = componentAnalysis.serviceCalls.some(serviceCall =>
|
|
|
|
|
serviceCall.type === 'service' &&
|
|
|
|
|
serviceCall.service === serviceName &&
|
|
|
|
|
serviceCall.method === methodName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (hasServiceCall) {
|
|
|
|
|
callingComponents.push(route.component)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 2. 搜索模块的components目录,查找调用该API方法的组件
|
|
|
|
|
const componentsInModule = this.findComponentsInModule(moduleName)
|
|
|
|
|
componentsInModule.forEach(componentName => {
|
|
|
|
|
const componentAnalysis = this.analyzeComponentForServiceCalls(
|
|
|
|
|
componentName,
|
|
|
|
|
null, // components目录中的组件没有路由路径
|
|
|
|
|
moduleName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (componentAnalysis && componentAnalysis.serviceCalls) {
|
|
|
|
|
// 检查是否有调用该服务方法的调用
|
|
|
|
|
const hasServiceCall = componentAnalysis.serviceCalls.some(serviceCall =>
|
|
|
|
|
serviceCall.type === 'service' &&
|
|
|
|
|
serviceCall.service === serviceName &&
|
|
|
|
|
serviceCall.method === methodName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (hasServiceCall) {
|
|
|
|
|
callingComponents.push(componentName)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 去重
|
|
|
|
|
return [...new Set(callingComponents)]
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 查找调用特定API方法的组件,并收集button触发器信息
|
|
|
|
|
findComponentsCallingApiMethodWithTriggers(routes, moduleName, serviceName, methodName) {
|
|
|
|
|
const callingComponents = []
|
|
|
|
|
|
|
|
|
|
// 1. 遍历所有路由,查找调用该API方法的页面组件
|
|
|
|
|
routes.forEach(route => {
|
|
|
|
|
if (route.module === moduleName) {
|
|
|
|
|
const componentAnalysis = this.analyzeComponentForServiceCallsWithTriggers(
|
|
|
|
|
route.component,
|
|
|
|
|
route.path,
|
|
|
|
|
moduleName,
|
|
|
|
|
serviceName,
|
|
|
|
|
methodName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (componentAnalysis && componentAnalysis.hasServiceCall) {
|
|
|
|
|
callingComponents.push({
|
|
|
|
|
component: route.component,
|
|
|
|
|
path: route.path,
|
|
|
|
|
trigger_source: componentAnalysis.triggerSources
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 2. 搜索模块的components目录,查找调用该API方法的组件
|
|
|
|
|
const componentsInModule = this.findComponentsInModule(moduleName)
|
|
|
|
|
componentsInModule.forEach(componentName => {
|
|
|
|
|
const componentAnalysis = this.analyzeComponentForServiceCallsWithTriggers(
|
|
|
|
|
componentName,
|
|
|
|
|
null, // components目录中的组件没有路由路径
|
|
|
|
|
moduleName,
|
|
|
|
|
serviceName,
|
|
|
|
|
methodName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (componentAnalysis && componentAnalysis.hasServiceCall) {
|
|
|
|
|
callingComponents.push({
|
|
|
|
|
component: componentName,
|
|
|
|
|
path: null,
|
|
|
|
|
trigger_source: componentAnalysis.triggerSources
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return callingComponents
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找模块中的所有组件
|
|
|
|
|
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) {
|
|
|
|
|
|
|
|
|
|
const optimizedApiMappings = apiMappings.map(moduleMapping => {
|
|
|
|
|
// 暂时不过滤空的调用组件,保留所有API映射
|
|
|
|
|
const filteredApiMappings = moduleMapping.apiMappings.filter(apiMapping => {
|
|
|
|
|
return true // 保留所有API映射
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 为每个API映射的callingComponents添加路径信息
|
|
|
|
|
const enhancedApiMappings = filteredApiMappings.map(apiMapping => {
|
|
|
|
|
const enhancedCallingComponents = apiMapping.callingComponents.map(componentName => {
|
|
|
|
|
// 在routes中查找匹配的组件
|
|
|
|
|
const matchingRoute = routes.find(route => route.component === componentName)
|
|
|
|
|
|
|
|
|
|
if (matchingRoute) {
|
|
|
|
|
return {
|
|
|
|
|
component: componentName,
|
|
|
|
|
path: matchingRoute.path
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果找不到匹配的路由,只返回组件名
|
|
|
|
|
return {
|
|
|
|
|
component: componentName,
|
|
|
|
|
path: null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...apiMapping,
|
|
|
|
|
callingComponents: enhancedCallingComponents
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...moduleMapping,
|
|
|
|
|
apiMappings: enhancedApiMappings
|
|
|
|
|
}
|
|
|
|
|
}).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)
|
|
|
|
|
|
|
|
|
|
return optimizedApiMappings
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 第五步:分析组件关联关系并完善路径信息
|
|
|
|
|
analyzeComponentRelationships(routes, apiMappings) {
|
|
|
|
|
|
|
|
|
|
// 1. 首先收集组件关联关系
|
|
|
|
|
const componentRelationships = []
|
|
|
|
|
routes.forEach(route => {
|
|
|
|
|
if (route.component) {
|
|
|
|
|
const relationships = this.findComponentRelationships(route.component, route.path, route.module)
|
|
|
|
|
componentRelationships.push(...relationships)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 完善API映射中的路径信息
|
|
|
|
|
const enhancedApiMappings = this.enhanceApiMappingsWithPaths(routes, apiMappings, componentRelationships)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { componentRelationships, enhancedApiMappings }
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 完善API映射中的路径信息
|
|
|
|
|
enhanceApiMappingsWithPaths(routes, apiMappings, componentRelationships) {
|
|
|
|
|
|
|
|
|
|
let enhancedCount = 0
|
|
|
|
|
|
|
|
|
|
// 遍历每个模块的API映射
|
|
|
|
|
const enhancedApiMappings = apiMappings.map(moduleMapping => {
|
|
|
|
|
const enhancedModuleApiMappings = moduleMapping.apiMappings.map(apiMapping => {
|
|
|
|
|
const enhancedCallingComponents = apiMapping.callingComponents.map(callingComponent => {
|
|
|
|
|
// 如果path为空,尝试通过组件关系找到根组件路径
|
|
|
|
|
if (callingComponent.path === null) {
|
|
|
|
|
const rootPath = this.findRootComponentPath(
|
|
|
|
|
callingComponent.component,
|
|
|
|
|
componentRelationships,
|
|
|
|
|
routes
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (rootPath) {
|
|
|
|
|
enhancedCount++
|
|
|
|
|
return {
|
|
|
|
|
...callingComponent,
|
|
|
|
|
path: rootPath
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return callingComponent
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...apiMapping,
|
|
|
|
|
callingComponents: enhancedCallingComponents
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...moduleMapping,
|
|
|
|
|
apiMappings: enhancedModuleApiMappings
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return enhancedApiMappings
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 通过组件关系找到根组件路径
|
|
|
|
|
findRootComponentPath(targetComponent, componentRelationships, routes) {
|
|
|
|
|
// 递归查找组件关系链,直到找到根组件
|
|
|
|
|
const findParent = (componentName, visited = new Set()) => {
|
|
|
|
|
// 避免循环引用
|
|
|
|
|
if (visited.has(componentName)) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
visited.add(componentName)
|
|
|
|
|
|
|
|
|
|
// 查找该组件的父组件
|
|
|
|
|
const parentRelationship = componentRelationships.find(rel =>
|
|
|
|
|
rel.toComponent === componentName
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (parentRelationship) {
|
|
|
|
|
const parentComponent = parentRelationship.fromComponent
|
|
|
|
|
|
|
|
|
|
// 检查父组件是否为路由表中的根组件
|
|
|
|
|
const route = routes.find(route => route.component === parentComponent)
|
|
|
|
|
if (route) {
|
|
|
|
|
return route.path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果父组件不是根组件,继续向上查找
|
|
|
|
|
return findParent(parentComponent, visited)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return findParent(targetComponent)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 查找单个组件的关联关系
|
|
|
|
|
findComponentRelationships(componentName, componentPath, moduleName) {
|
|
|
|
|
const relationships = []
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 查找组件文件
|
|
|
|
|
const componentFile = this.findComponentFile(componentName)
|
|
|
|
|
if (!componentFile || !existsSync(componentFile)) {
|
|
|
|
|
return relationships
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const content = readFileSync(componentFile, 'utf-8')
|
|
|
|
|
|
|
|
|
|
if (componentFile.endsWith('.vue')) {
|
|
|
|
|
const { descriptor } = parse(content)
|
|
|
|
|
const scriptContent = descriptor.script?.content || ''
|
|
|
|
|
const templateContent = descriptor.template?.content || ''
|
|
|
|
|
|
|
|
|
|
// 分析模板中的组件引用
|
|
|
|
|
const templateRelationships = this.analyzeTemplateForComponents(templateContent, componentName)
|
|
|
|
|
relationships.push(...templateRelationships)
|
|
|
|
|
|
|
|
|
|
// 分析脚本中的组件引用
|
|
|
|
|
if (scriptContent) {
|
|
|
|
|
const scriptRelationships = this.analyzeScriptForComponents(scriptContent, componentName)
|
|
|
|
|
relationships.push(...scriptRelationships)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn(`⚠️ 分析组件关联关系失败: ${componentName}`, error.message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return relationships
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 分析模板中的组件引用
|
|
|
|
|
analyzeTemplateForComponents(templateContent, fromComponent) {
|
|
|
|
|
const relationships = []
|
|
|
|
|
|
|
|
|
|
// 查找组件标签(如 <UserRoleAssignment>)
|
|
|
|
|
const componentTagRegex = /<([A-Z][a-zA-Z0-9]*)/g
|
|
|
|
|
let match
|
|
|
|
|
|
|
|
|
|
while ((match = componentTagRegex.exec(templateContent)) !== null) {
|
|
|
|
|
const componentTag = match[1]
|
|
|
|
|
|
|
|
|
|
// 跳过HTML标签
|
|
|
|
|
if (this.isHtmlTag(componentTag)) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确定关联类型
|
|
|
|
|
let relationship = 'embed' // 默认是嵌入关系
|
|
|
|
|
|
|
|
|
|
// 检查是否是弹窗组件
|
|
|
|
|
if (this.isModalComponent(componentTag, templateContent, match.index)) {
|
|
|
|
|
relationship = 'modal'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
relationships.push({
|
|
|
|
|
fromComponent: fromComponent,
|
|
|
|
|
toComponent: componentTag,
|
|
|
|
|
relationship: relationship
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return relationships
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 分析脚本中的组件引用
|
|
|
|
|
analyzeScriptForComponents(scriptContent, fromComponent) {
|
|
|
|
|
const relationships = []
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const ast = parser.parse(scriptContent, {
|
|
|
|
|
sourceType: 'module',
|
|
|
|
|
plugins: ['jsx']
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const self = this
|
|
|
|
|
traverse(ast, {
|
|
|
|
|
// 查找import语句
|
|
|
|
|
ImportDeclaration(path) {
|
|
|
|
|
const source = path.node.source.value
|
|
|
|
|
if (source.startsWith('./') || source.startsWith('../')) {
|
|
|
|
|
// 相对路径导入,可能是组件
|
|
|
|
|
const importedNames = path.node.specifiers.map(spec => {
|
|
|
|
|
if (spec.type === 'ImportDefaultSpecifier') {
|
|
|
|
|
return spec.local.name
|
|
|
|
|
} else if (spec.type === 'ImportSpecifier') {
|
|
|
|
|
return spec.imported.name
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}).filter(Boolean)
|
|
|
|
|
|
|
|
|
|
importedNames.forEach(importedName => {
|
|
|
|
|
if (self.isComponentName(importedName)) {
|
|
|
|
|
relationships.push({
|
|
|
|
|
fromComponent: fromComponent,
|
|
|
|
|
toComponent: importedName,
|
|
|
|
|
relationship: 'import'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn(`⚠️ 分析脚本组件引用失败: ${fromComponent}`, error.message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return relationships
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断是否是HTML标签
|
|
|
|
|
isHtmlTag(tagName) {
|
|
|
|
|
const htmlTags = [
|
|
|
|
|
'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
|
|
|
'button', 'input', 'form', 'table', 'tr', 'td', 'th',
|
|
|
|
|
'ul', 'ol', 'li', 'a', 'img', 'select', 'option',
|
|
|
|
|
'textarea', 'label', 'fieldset', 'legend', 'section',
|
|
|
|
|
'article', 'header', 'footer', 'nav', 'aside', 'main'
|
|
|
|
|
]
|
|
|
|
|
return htmlTags.includes(tagName.toLowerCase())
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断是否是弹窗组件
|
|
|
|
|
isModalComponent(componentTag, templateContent, startIndex) {
|
|
|
|
|
// 查找组件标签周围的上下文
|
|
|
|
|
const beforeContext = templateContent.substring(Math.max(0, startIndex - 100), startIndex)
|
|
|
|
|
const afterContext = templateContent.substring(startIndex, Math.min(templateContent.length, startIndex + 200))
|
|
|
|
|
|
|
|
|
|
// 检查是否包含弹窗相关的关键词
|
|
|
|
|
const modalKeywords = ['modal', 'dialog', 'popup', 'overlay', 'v-if', 'v-show']
|
|
|
|
|
const context = (beforeContext + afterContext).toLowerCase()
|
|
|
|
|
|
|
|
|
|
return modalKeywords.some(keyword => context.includes(keyword))
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断是否是组件名称
|
|
|
|
|
isComponentName(name) {
|
|
|
|
|
// Vue组件通常以大写字母开头
|
|
|
|
|
return /^[A-Z]/.test(name) && name.length > 2
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 分析组件中的服务调用
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { routeMappingPlugin }
|