|
|
|
|
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() {
|
|
|
|
|
return {
|
|
|
|
|
name: 'route-mapping-phase1',
|
|
|
|
|
|
|
|
|
|
// 防重复生成机制
|
|
|
|
|
_lastGenerationTime: 0,
|
|
|
|
|
_generationInProgress: false,
|
|
|
|
|
_generationCooldown: 5000, // 5秒冷却时间
|
|
|
|
|
|
|
|
|
|
// Webpack插件接口
|
|
|
|
|
apply(compiler) {
|
|
|
|
|
const self = this
|
|
|
|
|
|
|
|
|
|
// 在编译开始前就生成映射文件
|
|
|
|
|
compiler.hooks.beforeCompile.tapAsync('RouteMappingPlugin', (params, callback) => {
|
|
|
|
|
if (self._shouldSkipGeneration()) {
|
|
|
|
|
console.log('⏭️ 跳过路由映射生成(冷却期内)')
|
|
|
|
|
callback()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.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) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果在冷却期内,跳过
|
|
|
|
|
if (now - this._lastGenerationTime < this._generationCooldown) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 检查是否需要重新生成文件
|
|
|
|
|
_shouldRegenerateFile() {
|
|
|
|
|
const outputPath = resolve(__dirname, '../src/renderer/modules/route-sync/direct-route-mappings.js')
|
|
|
|
|
|
|
|
|
|
// 如果文件不存在,需要生成
|
|
|
|
|
if (!existsSync(outputPath)) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查源文件是否比生成文件更新
|
|
|
|
|
const sourceFiles = this._getSourceFiles()
|
|
|
|
|
const outputStats = fs.statSync(outputPath)
|
|
|
|
|
|
|
|
|
|
for (const sourceFile of sourceFiles) {
|
|
|
|
|
if (existsSync(sourceFile)) {
|
|
|
|
|
const sourceStats = fs.statSync(sourceFile)
|
|
|
|
|
if (sourceStats.mtime > outputStats.mtime) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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. 分析页面组件(第一层)
|
|
|
|
|
const pageMappings = this.analyzePageComponents(routes)
|
|
|
|
|
|
|
|
|
|
// 3. 分析弹窗组件(第二层)
|
|
|
|
|
const modalMappings = this.analyzeModalComponents()
|
|
|
|
|
|
|
|
|
|
// 4. 生成映射文件
|
|
|
|
|
this.generateMappingFile(pageMappings, modalMappings)
|
|
|
|
|
|
|
|
|
|
console.log('✅ 第一阶段直接映射关系收集完成')
|
|
|
|
|
} else {
|
|
|
|
|
console.log('⏭️ 路由映射文件无需重新生成')
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('❌ 直接映射关系收集失败:', error)
|
|
|
|
|
throw error
|
|
|
|
|
} finally {
|
|
|
|
|
// 重置生成中标志并更新时间戳
|
|
|
|
|
this._generationInProgress = false
|
|
|
|
|
this._lastGenerationTime = 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)
|
|
|
|
|
routes.push(route)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return routes
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 分析页面组件(第一层)
|
|
|
|
|
analyzePageComponents(routes) {
|
|
|
|
|
const pageMappings = []
|
|
|
|
|
|
|
|
|
|
routes.forEach(route => {
|
|
|
|
|
if (route.component) {
|
|
|
|
|
const componentAnalysis = this.analyzeComponent(route.component, route.path)
|
|
|
|
|
if (componentAnalysis && componentAnalysis.apiCalls.length > 0) {
|
|
|
|
|
pageMappings.push({
|
|
|
|
|
route: route.path,
|
|
|
|
|
routeName: route.name,
|
|
|
|
|
component: route.component,
|
|
|
|
|
module: route.module,
|
|
|
|
|
layer: 'page',
|
|
|
|
|
apiCalls: componentAnalysis.apiCalls,
|
|
|
|
|
methods: componentAnalysis.methods
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return pageMappings
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 分析弹窗组件(第二层)
|
|
|
|
|
analyzeModalComponents() {
|
|
|
|
|
const modalMappings = []
|
|
|
|
|
const modalFiles = this.findModalFiles()
|
|
|
|
|
|
|
|
|
|
modalFiles.forEach(modalFile => {
|
|
|
|
|
const analysis = this.analyzeComponent(modalFile.name, modalFile.path)
|
|
|
|
|
if (analysis && analysis.apiCalls.length > 0) {
|
|
|
|
|
modalMappings.push({
|
|
|
|
|
component: modalFile.name,
|
|
|
|
|
path: modalFile.path,
|
|
|
|
|
module: modalFile.module,
|
|
|
|
|
layer: 'modal',
|
|
|
|
|
apiCalls: analysis.apiCalls,
|
|
|
|
|
methods: analysis.methods
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return modalMappings
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 分析单个组件
|
|
|
|
|
analyzeComponent(componentName, componentPath) {
|
|
|
|
|
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.analyzeVueComponent(content, componentName)
|
|
|
|
|
} else {
|
|
|
|
|
return this.analyzeJavaScriptComponent(content, componentName)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn(`⚠️ 分析组件失败: ${componentName}`, error.message)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 分析Vue组件
|
|
|
|
|
analyzeVueComponent(content, componentName) {
|
|
|
|
|
const { descriptor } = parse(content)
|
|
|
|
|
const scriptContent = descriptor.script?.content || ''
|
|
|
|
|
|
|
|
|
|
if (!scriptContent) {
|
|
|
|
|
return { apiCalls: [], methods: [] }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ast = parser.parse(scriptContent, {
|
|
|
|
|
sourceType: 'module',
|
|
|
|
|
plugins: ['jsx']
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return this.extractApiCallsFromAST(ast, componentName)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 分析JavaScript组件
|
|
|
|
|
analyzeJavaScriptComponent(content, componentName) {
|
|
|
|
|
const ast = parser.parse(content, {
|
|
|
|
|
sourceType: 'module',
|
|
|
|
|
plugins: ['jsx']
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return this.extractApiCallsFromAST(ast, componentName)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 从AST中提取API调用
|
|
|
|
|
extractApiCallsFromAST(ast, componentName) {
|
|
|
|
|
const apiCalls = []
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 查找API调用
|
|
|
|
|
CallExpression(path) {
|
|
|
|
|
const callInfo = self.extractApiCall(path)
|
|
|
|
|
if (callInfo) {
|
|
|
|
|
apiCalls.push(callInfo)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return { apiCalls, methods }
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 提取API调用信息
|
|
|
|
|
extractApiCall(path) {
|
|
|
|
|
const node = path.node
|
|
|
|
|
const self = this
|
|
|
|
|
|
|
|
|
|
// 检查是否是服务调用 (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 => self.extractArgumentValue(arg)),
|
|
|
|
|
line: node.loc?.start?.line || 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否是直接的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) {
|
|
|
|
|
if (object.name === 'api' || object.name === 'axios' || object.name === 'http') {
|
|
|
|
|
const method = property.name
|
|
|
|
|
const args = node.arguments
|
|
|
|
|
|
|
|
|
|
if (args.length > 0 && args[0].type === 'StringLiteral') {
|
|
|
|
|
return {
|
|
|
|
|
type: 'api',
|
|
|
|
|
method: method.toUpperCase(),
|
|
|
|
|
path: args[0].value,
|
|
|
|
|
arguments: args.slice(1).map(arg => self.extractArgumentValue(arg)),
|
|
|
|
|
line: node.loc?.start?.line || 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 从模块配置中读取模块名称
|
|
|
|
|
readModuleNamesFromConfig() {
|
|
|
|
|
try {
|
|
|
|
|
const configPath = resolve(__dirname, '../src/renderer/modules/config.js')
|
|
|
|
|
if (!existsSync(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 []
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 查找弹窗文件
|
|
|
|
|
findModalFiles() {
|
|
|
|
|
const modalFiles = []
|
|
|
|
|
const moduleDirs = this.readModuleNamesFromConfig()
|
|
|
|
|
|
|
|
|
|
moduleDirs.forEach(moduleName => {
|
|
|
|
|
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 => {
|
|
|
|
|
modalFiles.push({
|
|
|
|
|
name: file.replace('.vue', ''),
|
|
|
|
|
path: resolve(componentsPath, file),
|
|
|
|
|
module: moduleName
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return modalFiles
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 查找组件文件
|
|
|
|
|
findComponentFile(componentName) {
|
|
|
|
|
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(pageMappings, modalMappings) {
|
|
|
|
|
const outputPath = resolve(__dirname, '../src/renderer/modules/route-sync/direct-route-mappings.js')
|
|
|
|
|
|
|
|
|
|
// 检查文件内容是否真的需要更新
|
|
|
|
|
if (this._isFileContentSame(pageMappings, modalMappings, outputPath)) {
|
|
|
|
|
console.log('⏭️ 路由映射文件内容未变化,跳过写入')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mappingContent = `// 第一阶段:直接路由-API映射关系
|
|
|
|
|
// 此文件由 route-mapping-plugin 在构建时生成
|
|
|
|
|
// 收集页面和弹窗组件的直接API调用关系
|
|
|
|
|
// 请勿手动修改
|
|
|
|
|
|
|
|
|
|
export const directRouteMappings = {
|
|
|
|
|
// 第一层:页面组件的数据操作API
|
|
|
|
|
pageMappings: ${JSON.stringify(pageMappings, null, 2)},
|
|
|
|
|
|
|
|
|
|
// 第二层:弹窗组件的数据操作API
|
|
|
|
|
modalMappings: ${JSON.stringify(modalMappings, null, 2)},
|
|
|
|
|
|
|
|
|
|
// 分析信息
|
|
|
|
|
analysisInfo: {
|
|
|
|
|
phase: 'phase1',
|
|
|
|
|
description: '第一阶段:收集直接映射关系',
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
pageCount: ${pageMappings.length},
|
|
|
|
|
modalCount: ${modalMappings.length},
|
|
|
|
|
totalApiCalls: ${pageMappings.reduce((sum, p) => sum + p.apiCalls.length, 0) + modalMappings.reduce((sum, m) => sum + m.apiCalls.length, 0)}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按模块分组的映射
|
|
|
|
|
export const moduleMappings = ${JSON.stringify(this.groupMappingsByModule(pageMappings, modalMappings), null, 2)}
|
|
|
|
|
|
|
|
|
|
// 按API路径分组的映射
|
|
|
|
|
export const apiPathMappings = ${JSON.stringify(this.groupMappingsByApiPath(pageMappings, modalMappings), null, 2)}
|
|
|
|
|
|
|
|
|
|
export default directRouteMappings
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
fs.writeFileSync(outputPath, mappingContent, 'utf-8')
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 直接映射关系文件已生成: ${outputPath}`)
|
|
|
|
|
console.log(`📊 统计信息:`)
|
|
|
|
|
console.log(` - 页面组件: ${pageMappings.length} 个`)
|
|
|
|
|
console.log(` - 弹窗组件: ${modalMappings.length} 个`)
|
|
|
|
|
console.log(` - 总API调用: ${pageMappings.reduce((sum, p) => sum + p.apiCalls.length, 0) + modalMappings.reduce((sum, m) => sum + m.apiCalls.length, 0)} 个`)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 检查文件内容是否相同
|
|
|
|
|
_isFileContentSame(pageMappings, modalMappings, outputPath) {
|
|
|
|
|
if (!existsSync(outputPath)) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const currentContent = fs.readFileSync(outputPath, 'utf-8')
|
|
|
|
|
|
|
|
|
|
// 提取当前文件中的映射数据
|
|
|
|
|
const currentDataMatch = currentContent.match(/export const directRouteMappings = ({[\s\S]*?});/)
|
|
|
|
|
if (!currentDataMatch) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 解析当前数据
|
|
|
|
|
const currentData = JSON.parse(currentDataMatch[1])
|
|
|
|
|
|
|
|
|
|
// 比较数据
|
|
|
|
|
const newData = {
|
|
|
|
|
pageMappings,
|
|
|
|
|
modalMappings,
|
|
|
|
|
analysisInfo: {
|
|
|
|
|
phase: 'phase1',
|
|
|
|
|
description: '第一阶段:收集直接映射关系',
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
pageCount: pageMappings.length,
|
|
|
|
|
modalCount: modalMappings.length,
|
|
|
|
|
totalApiCalls: pageMappings.reduce((sum, p) => sum + p.apiCalls.length, 0) + modalMappings.reduce((sum, m) => sum + m.apiCalls.length, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 忽略时间戳差异进行比较
|
|
|
|
|
const currentDataForCompare = { ...currentData }
|
|
|
|
|
const newDataForCompare = { ...newData }
|
|
|
|
|
delete currentDataForCompare.analysisInfo.timestamp
|
|
|
|
|
delete newDataForCompare.analysisInfo.timestamp
|
|
|
|
|
|
|
|
|
|
return JSON.stringify(currentDataForCompare) === JSON.stringify(newDataForCompare)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('⚠️ 比较文件内容时出错:', error.message)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 按模块分组映射
|
|
|
|
|
groupMappingsByModule(pageMappings, modalMappings) {
|
|
|
|
|
const grouped = {}
|
|
|
|
|
|
|
|
|
|
// 分组页面映射
|
|
|
|
|
pageMappings.forEach(mapping => {
|
|
|
|
|
const module = mapping.module || 'unknown'
|
|
|
|
|
if (!grouped[module]) {
|
|
|
|
|
grouped[module] = { pages: [], modals: [] }
|
|
|
|
|
}
|
|
|
|
|
grouped[module].pages.push(mapping)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 分组弹窗映射
|
|
|
|
|
modalMappings.forEach(mapping => {
|
|
|
|
|
const module = mapping.module || 'unknown'
|
|
|
|
|
if (!grouped[module]) {
|
|
|
|
|
grouped[module] = { pages: [], modals: [] }
|
|
|
|
|
}
|
|
|
|
|
grouped[module].modals.push(mapping)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return grouped
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 按API路径分组映射
|
|
|
|
|
groupMappingsByApiPath(pageMappings, modalMappings) {
|
|
|
|
|
const grouped = {}
|
|
|
|
|
|
|
|
|
|
const addToGroup = (mapping, layer) => {
|
|
|
|
|
mapping.apiCalls.forEach(apiCall => {
|
|
|
|
|
let key = ''
|
|
|
|
|
if (apiCall.type === 'api') {
|
|
|
|
|
key = `${apiCall.method} ${apiCall.path}`
|
|
|
|
|
} else if (apiCall.type === 'service') {
|
|
|
|
|
key = `${apiCall.service}.${apiCall.method}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (key) {
|
|
|
|
|
if (!grouped[key]) {
|
|
|
|
|
grouped[key] = { pages: [], modals: [] }
|
|
|
|
|
}
|
|
|
|
|
grouped[key][layer].push({
|
|
|
|
|
component: mapping.component,
|
|
|
|
|
module: mapping.module,
|
|
|
|
|
route: mapping.route || mapping.path,
|
|
|
|
|
apiCall: apiCall
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pageMappings.forEach(mapping => addToGroup(mapping, 'pages'))
|
|
|
|
|
modalMappings.forEach(mapping => addToGroup(mapping, 'modals'))
|
|
|
|
|
|
|
|
|
|
return grouped
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 提取组件名称
|
|
|
|
|
extractComponentName(node) {
|
|
|
|
|
if (node.type === 'Identifier') {
|
|
|
|
|
return node.name
|
|
|
|
|
} else if (node.type === 'StringLiteral') {
|
|
|
|
|
return node.value
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 从路径提取模块名
|
|
|
|
|
extractModuleFromPath(path) {
|
|
|
|
|
if (path === '/' || path === '') return 'home'
|
|
|
|
|
|
|
|
|
|
const segments = path.split('/').filter(Boolean)
|
|
|
|
|
if (segments.length === 0) return 'home'
|
|
|
|
|
|
|
|
|
|
return segments[0]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { routeMappingPlugin }
|