diff --git a/gofaster/app/plugins/modules/call-chain-tracer.js b/gofaster/app/plugins/modules/call-chain-tracer.js new file mode 100644 index 0000000..d0d943e --- /dev/null +++ b/gofaster/app/plugins/modules/call-chain-tracer.js @@ -0,0 +1,537 @@ +const { readFileSync, existsSync } = require('fs') +const { resolve } = require('path') +const { parse } = require('@vue/compiler-dom') +const parser = require('@babel/parser') +const traverse = require('@babel/traverse').default + +/** + * 调用链追溯器 + * 专门用于追溯method类型的触发源到最终的可视控件 + */ +class CallChainTracer { + constructor() { + } + + /** + * 追溯所有method类型的触发源到可视控件 + * @param {Array} apiMappings - API映射数组 + * @returns {Array} 增强的API映射数组 + */ + traceMethodTriggersToVisualComponents(apiMappings) { + const enhancedApiMappings = apiMappings.map(module => { + const enhancedApiMappings = module.apiMappings.map(api => { + if (!api.triggerSources || api.triggerSources.length === 0) { + return api + } + + // 只处理triggerType为method的触发源 + const enhancedTriggerSources = api.triggerSources.map(trigger => { + if (trigger.triggerType === 'method') { + return this.traceMethodToVisualComponent( + trigger, + module.module, + module.serviceName, + api.apiMethodName + ) + } + return trigger + }) + + return { + ...api, + triggerSources: enhancedTriggerSources + } + }) + + return { + ...module, + apiMappings: enhancedApiMappings + } + }) + + return enhancedApiMappings + } + + /** + * 追溯单个method到可视控件 + * @param {Object} trigger - 触发源对象 + * @param {string} moduleName - 模块名称 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @returns {Object} 增强的触发源对象 + */ + traceMethodToVisualComponent(trigger, moduleName, serviceName, apiMethodName) { + try { + // 查找组件文件 + const componentPath = this.findComponentFile(trigger.component, moduleName) + if (!componentPath) { + return trigger + } + + const content = readFileSync(componentPath, 'utf-8') + + // 解析Vue组件 + const ast = this.parseVueComponent(content) + if (!ast) return trigger + + // 建立函数调用关系映射 + const functionCallMap = this.buildFunctionCallMap(ast) + + // 追溯method到可视控件 + const visualContext = this.traceToVisualComponent( + trigger.triggerName, + functionCallMap, + componentPath, + trigger.component + ) + + if (visualContext) { + return { + ...trigger, + triggerName: visualContext.triggerName, + triggerType: visualContext.triggerType + } + } + + return trigger + } catch (error) { + return trigger + } + } + + /** + * 查找组件文件 + * @param {string} componentName - 组件名称 + * @param {string} moduleName - 模块名称 + * @returns {string|null} 组件文件路径 + */ + findComponentFile(componentName, moduleName) { + // 优先在指定模块中查找 + if (moduleName) { + // 检查views目录 + const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/views/${componentName}.vue`) + if (existsSync(viewPath)) { + return viewPath + } + + // 检查components目录 + const componentPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/components/${componentName}.vue`) + if (existsSync(componentPath)) { + return componentPath + } + } + + return null + } + + /** + * 解析Vue组件内容 + * @param {string} content - 组件内容 + * @returns {Object|null} AST对象 + */ + parseVueComponent(content) { + try { + // 提取script部分 + const scriptMatch = content.match(/]*>([\s\S]*?)<\/script>/) + if (!scriptMatch) { + return null + } + + const scriptContent = scriptMatch[1] + + // 使用Babel解析JavaScript + const ast = parser.parse(scriptContent, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }) + + return ast + } catch (error) { + return null + } + } + + /** + * 建立函数调用关系映射 + * @param {Object} ast - AST对象 + * @returns {Map} 函数调用关系映射 + */ + buildFunctionCallMap(ast) { + const functionCallMap = new Map() + const self = this + + traverse(ast, { + CallExpression(path) { + const { node } = path + if (node.callee && node.callee.type === 'Identifier') { + const functionName = node.callee.name + const parentFunction = self.findParentFunction(path) + + if (parentFunction) { + if (!functionCallMap.has(functionName)) { + functionCallMap.set(functionName, { callers: [] }) + } + const funcInfo = functionCallMap.get(functionName) + if (!funcInfo.callers) funcInfo.callers = [] + funcInfo.callers.push(parentFunction) + } + } + } + }) + + return functionCallMap + } + + /** + * 查找父级函数 + * @param {Object} path - Babel路径对象 + * @returns {string|null} 父级函数名 + */ + findParentFunction(path) { + let currentPath = path.parentPath + while (currentPath) { + const { node } = currentPath + + if (node.type === 'FunctionDeclaration' && node.id) { + return node.id.name + } + + if (node.type === 'VariableDeclarator' && + node.id && node.id.type === 'Identifier' && + node.init && node.init.type === 'ArrowFunctionExpression') { + return node.id.name + } + + if (node.type === 'ObjectMethod' && node.key) { + return node.key.name + } + + currentPath = currentPath.parentPath + } + return null + } + + /** + * 追溯method到可视控件 + * @param {string} methodName - 方法名称 + * @param {Map} functionCallMap - 函数调用关系映射 + * @param {string} filePath - 组件文件路径 + * @param {string} componentName - 组件名称 + * @returns {Object|null} 可视控件上下文 + */ + traceToVisualComponent(methodName, functionCallMap, filePath, componentName) { + try { + // 读取组件文件内容 + const content = readFileSync(filePath, 'utf-8') + + // 分析模板中的事件绑定 + const visualContext = this.analyzeTemplateForMethod(content, methodName, componentName, filePath) + if (visualContext) { + return visualContext + } + + // 如果模板中没有找到,检查是否有其他方法调用了这个方法 + const callerContext = this.findMethodCaller(methodName, functionCallMap) + if (callerContext) { + // 检查调用者是否是生命周期钩子 + if (this.isLifecycleHook(callerContext)) { + return { + triggerName: '', + triggerType: 'page' + } + } + + // 继续递归追溯 + return this.traceToVisualComponent(callerContext, functionCallMap, filePath, componentName) + } + + } catch (error) { + // 静默处理错误 + } + + return null + } + + /** + * 分析模板中的方法调用 + * @param {string} content - 组件内容 + * @param {string} methodName - 方法名称 + * @param {string} componentName - 组件名称 + * @param {string} filePath - 组件文件路径 + * @returns {Object|null} 可视控件上下文 + */ + analyzeTemplateForMethod(content, methodName, componentName, filePath) { + try { + // 使用Vue编译器解析单文件组件 + const { descriptor } = parse(content) + + if (!descriptor.template) { + return null + } + + // 解析模板AST + const templateAst = descriptor.template.ast + if (!templateAst) { + return null + } + + // 遍历模板AST,查找事件绑定 + return this.findEventBindingInTemplate(templateAst, methodName, componentName, filePath) + + } catch (error) { + return null + } + } + + /** + * 在模板AST中查找事件绑定 + * @param {Object} node - AST节点 + * @param {string} methodName - 方法名称 + * @param {string} componentName - 组件名称 + * @param {string} filePath - 组件文件路径 + * @returns {Object|null} 可视组件上下文 + */ + findEventBindingInTemplate(node, methodName, componentName, filePath) { + if (!node) return null + + // 检查当前节点是否有事件绑定 + if (node.props) { + for (const prop of node.props) { + if (prop.type === 7) { // DIRECTIVE类型 + const eventName = prop.name + if (eventName === 'on' && prop.arg) { + const eventType = prop.arg.content + if (['click', 'submit', 'change', 'input'].includes(eventType)) { + // 检查事件处理函数 + if (prop.exp && this.isMethodCall(prop.exp, methodName)) { + const context = this.createVisualContext(node, eventType, componentName, methodName, filePath) + + // 如果找到的是form,需要查找其中的submit按钮 + if (context.triggerType === 'form') { + const submitButton = this.findSubmitButtonInForm(node, componentName, methodName, filePath) + if (submitButton) { + return submitButton + } + } + + return context + } + } + } + } + } + } + + // 递归检查子节点 + if (node.children) { + for (const child of node.children) { + if (child.type === 1) { // ELEMENT类型 + const result = this.findEventBindingInTemplate(child, methodName, componentName, filePath) + if (result) return result + } + } + } + + return null + } + + /** + * 在form中查找submit按钮 + * @param {Object} formNode - form AST节点 + * @param {string} componentName - 组件名称 + * @param {string} methodName - 方法名称 + * @param {string} filePath - 组件文件路径 + * @returns {Object|null} submit按钮上下文 + */ + findSubmitButtonInForm(formNode, componentName, methodName, filePath) { + if (!formNode || !formNode.children) return null + + // 递归查找submit按钮 + for (const child of formNode.children) { + if (child.type === 1) { // ELEMENT类型 + if (child.tag === 'button') { + // 检查是否是submit按钮 + const props = child.props || [] + const attributes = {} + props.forEach(prop => { + if (prop.type === 6) { // ATTRIBUTE + attributes[prop.name] = prop.value ? prop.value.content : '' + } + }) + + if (attributes.type === 'submit') { + let triggerName = attributes.name + if (!triggerName) { + // 生成唯一的name属性:组件名+唯一尾缀 + triggerName = `${componentName.toLowerCase()}-submit-${this.generateUniqueSuffix()}` + } + return { + triggerName: triggerName, + triggerType: 'button' + } + } + } + + // 递归查找子元素 + const result = this.findSubmitButtonInForm(child, componentName, methodName, filePath) + if (result) return result + } + } + + return null + } + + /** + * 检查表达式是否是方法调用 + * @param {Object} exp - 表达式节点 + * @param {string} methodName - 方法名称 + * @returns {boolean} 是否是方法调用 + */ + isMethodCall(exp, methodName) { + if (!exp) return false + + // 简单的方法调用:methodName + if (exp.type === 4 && exp.content === methodName) { // SIMPLE_EXPRESSION + return true + } + + // 带参数的方法调用:methodName(...) - 检查content是否以methodName开头 + if (exp.type === 4 && exp.content && exp.content.startsWith(methodName + '(')) { + return true + } + + // 带参数的方法调用:methodName(...) + if (exp.type === 8) { // COMPOUND_EXPRESSION + const children = exp.children + if (children && children.length >= 1) { + const firstChild = children[0] + if (firstChild.type === 4 && firstChild.content === methodName) { + return true + } + } + } + + // 检查AST中的方法调用 + if (exp.ast) { + return this.checkAstMethodCall(exp.ast, methodName) + } + + return false + } + + /** + * 检查AST中的方法调用 + * @param {Object} ast - AST节点 + * @param {string} methodName - 方法名称 + * @returns {boolean} 是否是方法调用 + */ + checkAstMethodCall(ast, methodName) { + if (!ast) return false + + // 简单标识符:methodName + if (ast.type === 'Identifier' && ast.name === methodName) { + return true + } + + // 方法调用:methodName(...) + if (ast.type === 'CallExpression') { + if (ast.callee && ast.callee.type === 'Identifier' && ast.callee.name === methodName) { + return true + } + } + + return false + } + + /** + * 创建可视组件上下文 + * @param {Object} node - AST节点 + * @param {string} eventType - 事件类型 + * @param {string} componentName - 组件名称 + * @param {string} methodName - 方法名称 + * @param {string} filePath - 组件文件路径 + * @returns {Object} 可视组件上下文 + */ + createVisualContext(node, eventType, componentName, methodName, filePath) { + const tag = node.tag + const props = node.props || [] + + // 提取元素属性 + const attributes = {} + props.forEach(prop => { + if (prop.type === 6) { // ATTRIBUTE + attributes[prop.name] = prop.value ? prop.value.content : '' + } + }) + + let triggerName = attributes.name || attributes.id || '' + let triggerType = 'button' // 默认类型 + + // 根据标签类型确定触发源类型 + if (tag === 'form') { + triggerType = 'form' + } else if (tag === 'button') { + triggerType = 'button' + } else if (tag === 'input') { + triggerType = 'input' + } else if (tag === 'select') { + triggerType = 'select' + } else if (tag === 'textarea') { + triggerType = 'textarea' + } else { + // 其他元素类型 + triggerType = 'element' + } + + // 如果没有名称,生成一个 + if (!triggerName) { + triggerName = `${componentName.toLowerCase()}-${tag}-${this.generateUniqueSuffix()}` + } + + return { + triggerName: triggerName, + triggerType: triggerType + } + } + + /** + * 查找方法的调用者 + * @param {string} methodName - 方法名称 + * @param {Map} functionCallMap - 函数调用关系映射 + * @returns {string|null} 调用者方法名 + */ + findMethodCaller(methodName, functionCallMap) { + const funcInfo = functionCallMap.get(methodName) + if (funcInfo && funcInfo.callers && funcInfo.callers.length > 0) { + // 返回第一个调用者 + return funcInfo.callers[0] + } + return null + } + + /** + * 检查方法名是否是生命周期钩子 + * @param {string} methodName - 方法名称 + * @returns {boolean} 是否是生命周期钩子 + */ + isLifecycleHook(methodName) { + const lifecycleHooks = [ + 'onMounted', 'onCreated', 'onBeforeMount', 'onBeforeCreate', + 'onUpdated', 'onBeforeUpdate', 'onUnmounted', 'onBeforeUnmount', + 'mounted', 'created', 'beforeMount', 'beforeCreate', + 'updated', 'beforeUpdate', 'unmounted', 'beforeUnmount', + 'setup' + ] + return lifecycleHooks.includes(methodName) + } + + /** + * 生成唯一后缀 + * @returns {string} 唯一后缀 + */ + generateUniqueSuffix() { + return Math.random().toString(36).substr(2, 6) + } +} + +module.exports = CallChainTracer diff --git a/gofaster/app/plugins/modules/data-cleaner.js b/gofaster/app/plugins/modules/data-cleaner.js new file mode 100644 index 0000000..a859b57 --- /dev/null +++ b/gofaster/app/plugins/modules/data-cleaner.js @@ -0,0 +1,168 @@ +const { readFileSync, writeFileSync } = require('fs') +const path = require('path') + +/** + * 数据清理器 + * 清理direct-route-mappings.js中的数据结构 + */ +class DataCleaner { + constructor() { + this.mappingsPath = path.join(__dirname, '../../src/renderer/modules/route-sync/direct-route-mappings.js') + } + + /** + * 清理数据结构 + */ + cleanData() { + try { + // 读取direct-route-mappings.js + const content = readFileSync(this.mappingsPath, 'utf-8') + + // 解析mappings内容 + const mappings = this.parseMappings(content) + + // 清理数据 + const cleanedMappings = this.cleanMappings(mappings) + + // 生成新的文件内容 + const newContent = this.generateNewContent(cleanedMappings) + + // 写回文件 + writeFileSync(this.mappingsPath, newContent, 'utf-8') + + // 返回清理后的数据,供后续处理 + return cleanedMappings + + } catch (error) { + console.error('❌ 清理数据结构失败:', error.message) + return null + } + } + + /** + * 解析mappings内容 + */ + parseMappings(content) { + // 提取export default部分,文件可能没有以分号结尾 + const mappingsMatch = content.match(/export default ([\s\S]*?)(?:;|$)/) + if (!mappingsMatch) { + throw new Error('无法解析direct-route-mappings.js') + } + + // 使用eval解析(在生产环境中应该使用更安全的方式) + const mappings = eval(`(${mappingsMatch[1]})`) + return mappings + } + + /** + * 清理mappings数据 + */ + cleanMappings(mappings) { + // 保持原有的结构,只清理apiMappings部分 + const cleanedMappings = { + routes: mappings.routes || [], + apiMappings: [] + } + + if (!mappings.apiMappings || mappings.apiMappings.length === 0) { + return cleanedMappings + } + + mappings.apiMappings.forEach(module => { + if (!module.apiMappings || module.apiMappings.length === 0) { + return + } + + const cleanedApiMappings = [] + + module.apiMappings.forEach(api => { + if (!api.triggerSources || api.triggerSources.length === 0) { + return + } + + // 1. 只保留triggerType为button的triggerSources + const buttonTriggerSources = api.triggerSources.filter(trigger => { + return trigger.triggerType === 'button' + }) + + // 2. 如果还有button类型的triggerSources,保留这个API + if (buttonTriggerSources.length > 0) { + const cleanedApi = { + ...api, + triggerSources: buttonTriggerSources + } + cleanedApiMappings.push(cleanedApi) + } + }) + + // 3. 如果还有apiMappings,保留这个模块 + if (cleanedApiMappings.length > 0) { + const cleanedModule = { + ...module, + apiMappings: cleanedApiMappings + } + cleanedMappings.apiMappings.push(cleanedModule) + } + }) + + return cleanedMappings + } + + /** + * 生成新的文件内容 + */ + generateNewContent(cleanedMappings) { + const header = `// 路由映射数据 - 构建时生成 +export default ${JSON.stringify(cleanedMappings, null, 2)};` + + return header + } + + /** + * 统计清理结果 + */ + getCleanupStats(originalMappings, cleanedMappings) { + const stats = { + original: { + modules: originalMappings.length, + apis: 0, + triggerSources: 0, + buttonTriggerSources: 0 + }, + cleaned: { + modules: cleanedMappings.length, + apis: 0, + triggerSources: 0 + } + } + + // 统计原始数据 + originalMappings.forEach(module => { + if (module.apiMappings) { + stats.original.apis += module.apiMappings.length + module.apiMappings.forEach(api => { + if (api.triggerSources) { + stats.original.triggerSources += api.triggerSources.length + stats.original.buttonTriggerSources += api.triggerSources.filter(t => t.triggerType === 'button').length + } + }) + } + }) + + // 统计清理后数据 + cleanedMappings.forEach(module => { + if (module.apiMappings) { + stats.cleaned.apis += module.apiMappings.length + module.apiMappings.forEach(api => { + if (api.triggerSources) { + stats.cleaned.triggerSources += api.triggerSources.length + } + }) + } + }) + + return stats + } +} + +module.exports = DataCleaner diff --git a/gofaster/app/plugins/modules/trigger-analyzer-simple.js b/gofaster/app/plugins/modules/trigger-analyzer-simple.js new file mode 100644 index 0000000..8df5cc5 --- /dev/null +++ b/gofaster/app/plugins/modules/trigger-analyzer-simple.js @@ -0,0 +1,453 @@ +const { readFileSync, existsSync, readdirSync } = require('fs') +const { resolve } = require('path') +const { parse } = require('@vue/compiler-dom') +const parser = require('@babel/parser') +const traverse = require('@babel/traverse').default + +/** + * 简化版Trigger分析器 + * 从原有trigger-analyzer中提取核心逻辑 + */ +class TriggerAnalyzerSimple { + constructor() { + } + + /** + * 为API映射查找触发源 + * 使用Babel插件追溯API调用控件,记录控件的类型和名称 + * @param {Array} apiMappings - API映射数组 + * @param {Array} routes - 路由数组 + * @returns {Array} 增强的API映射数组 + */ + findTriggerSourcesForApiMappings(apiMappings, routes) { + const enhancedApiMappings = apiMappings.map(module => { + const enhancedApiMappings = module.apiMappings.map(api => { + // 查找该API的触发源 + const triggerSources = this.findTriggerSourcesForApiMethod( + module.serviceName, + api.apiMethodName, + module.module + ) + + return { + ...api, + triggerSources: triggerSources + } + }) + + return { + ...module, + apiMappings: enhancedApiMappings + } + }) + + return enhancedApiMappings + } + + /** + * 查找API方法的触发源 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @param {string} moduleName - 模块名称 + * @returns {Array} 触发源数组 + */ + findTriggerSourcesForApiMethod(serviceName, apiMethodName, moduleName) { + const triggerSources = [] + + // 检查模块中的所有组件 + const componentsInModule = this.getModuleComponents(moduleName) + componentsInModule.forEach(componentName => { + const componentTriggerSources = this.analyzeComponentForApiCalls( + componentName, + serviceName, + apiMethodName, + moduleName + ) + triggerSources.push(...componentTriggerSources) + }) + + // 去重 + return this.deduplicateTriggerSources(triggerSources) + } + + + /** + * 获取模块中的所有组件 + * @param {string} moduleName - 模块名称 + * @returns {Array} 组件名称数组 + */ + getModuleComponents(moduleName) { + const components = [] + + try { + const modulePath = resolve(__dirname, '../../src/renderer/modules', moduleName) + if (existsSync(modulePath)) { + // 查找views目录 + const viewsPath = resolve(modulePath, 'views') + if (existsSync(viewsPath)) { + const viewFiles = readdirSync(viewsPath).filter(file => file.endsWith('.vue')) + viewFiles.forEach(file => { + components.push(file.replace('.vue', '')) + }) + } + + // 查找components目录 + const componentsPath = resolve(modulePath, 'components') + if (existsSync(componentsPath)) { + const componentFiles = readdirSync(componentsPath).filter(file => file.endsWith('.vue')) + componentFiles.forEach(file => { + components.push(file.replace('.vue', '')) + }) + } + } + } catch (error) { + console.warn(`获取模块组件时出错: ${error.message}`) + } + + return components + } + + /** + * 分析组件中的API调用 + * @param {string} componentName - 组件名称 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @param {string} moduleName - 模块名称 + * @returns {Array} 触发源数组 + */ + analyzeComponentForApiCalls(componentName, serviceName, apiMethodName, moduleName) { + const triggerSources = [] + + try { + // 查找组件文件,优先在指定模块中查找 + const componentPath = this.findComponentFile(componentName, moduleName) + if (!componentPath) { + return triggerSources + } + + // 检查组件的authType,如果是瀑布理财则跳过 + const authType = this.getComponentAuthType(componentName, moduleName) + if (authType === '瀑布理财') { + return triggerSources + } + + const content = readFileSync(componentPath, 'utf-8') + + // 使用Babel解析Vue组件 + const ast = this.parseVueComponent(content) + if (!ast) return triggerSources + + // 查找API调用和函数调用关系 + const apiCalls = this.findApiCallsWithContext(ast, serviceName, apiMethodName, componentPath, componentName) + + // 为每个API调用创建触发源 + apiCalls.forEach(call => { + triggerSources.push({ + component: componentName, + triggerName: call.triggerName || null, // 没有名称则为null + triggerType: call.triggerType || 'function' + }) + }) + + } catch (error) { + // 静默处理错误 + } + + return triggerSources + } + + /** + * 查找组件文件 + * @param {string} componentName - 组件名称 + * @param {string} moduleName - 模块名称(可选,如果提供则优先在该模块中查找) + * @returns {string|null} 组件文件路径 + */ + findComponentFile(componentName, moduleName = null) { + // 如果提供了模块名称,优先在该模块中查找 + if (moduleName) { + // 检查views目录 + const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/views/${componentName}.vue`) + if (existsSync(viewPath)) { + return viewPath + } + + // 检查components目录 + const componentPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/components/${componentName}.vue`) + if (existsSync(componentPath)) { + return componentPath + } + } + + // 如果没有找到或没有提供模块名称,则在所有模块中查找 + // 这里我们动态获取所有模块目录 + const modulesPath = resolve(__dirname, '../../src/renderer/modules') + if (existsSync(modulesPath)) { + const { readdirSync } = require('fs') + const moduleDirs = readdirSync(modulesPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) + + for (const moduleDir of moduleDirs) { + // 检查views目录 + const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleDir}/views/${componentName}.vue`) + if (existsSync(viewPath)) { + return viewPath + } + + // 检查components目录 + const componentPath = resolve(__dirname, `../../src/renderer/modules/${moduleDir}/components/${componentName}.vue`) + if (existsSync(componentPath)) { + return componentPath + } + } + } + + return null + } + + /** + * 从Vue组件文件中获取authType属性 + * @param {string} componentName - 组件名称 + * @param {string} moduleName - 模块名称(可选) + * @returns {string|null} authType值 + */ + getComponentAuthType(componentName, moduleName = null) { + try { + const componentPath = this.findComponentFile(componentName, moduleName) + if (componentPath) { + const content = readFileSync(componentPath, 'utf-8') + const authTypeMatch = content.match(/]*>([\s\S]*?)<\/script>/) + if (!scriptMatch) { + return null + } + + const scriptContent = scriptMatch[1] + + // 使用Babel解析JavaScript + const ast = parser.parse(scriptContent, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }) + + return ast + } catch (error) { + return null + } + } + + /** + * 查找API调用并分析上下文 + * @param {Object} ast - AST对象 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @param {string} filePath - 文件路径 + * @param {string} componentName - 组件名称 + * @returns {Array} API调用数组 + */ + findApiCallsWithContext(ast, serviceName, apiMethodName, filePath, componentName) { + const apiCalls = [] + const self = this + + // 第一遍:建立函数调用关系映射 + const functionCallMap = new Map() + traverse(ast, { + CallExpression(path) { + const { node } = path + if (node.callee && node.callee.type === 'Identifier') { + const functionName = node.callee.name + const parentFunction = self.findParentFunction(path) + + if (parentFunction) { + if (!functionCallMap.has(functionName)) { + functionCallMap.set(functionName, { callers: [] }) + } + const funcInfo = functionCallMap.get(functionName) + if (!funcInfo.callers) funcInfo.callers = [] + funcInfo.callers.push(parentFunction) + } + } + } + }) + + // 第二遍:查找API调用并分析调用链 + traverse(ast, { + CallExpression(path) { + const { node } = path + + // 查找 serviceName.apiMethodName() 调用 + if (node.callee.type === 'MemberExpression' && + node.callee.object.name === serviceName && + node.callee.property.name === apiMethodName) { + + // 查找调用上下文 + const context = self.findCallContext(path) + + apiCalls.push({ + triggerName: context.triggerName, + triggerType: context.triggerType + }) + } + } + }) + + return apiCalls + } + + /** + * 查找父级函数 + * @param {Object} path - Babel路径对象 + * @returns {string|null} 父级函数名 + */ + findParentFunction(path) { + let currentPath = path.parentPath + while (currentPath) { + const { node } = currentPath + + if (node.type === 'FunctionDeclaration' && node.id) { + return node.id.name + } + + if (node.type === 'VariableDeclarator' && + node.id && node.id.type === 'Identifier' && + node.init && node.init.type === 'ArrowFunctionExpression') { + return node.id.name + } + + if (node.type === 'ObjectMethod' && node.key) { + return node.key.name + } + + currentPath = currentPath.parentPath + } + return null + } + + /** + * 查找API调用的上下文 + * @param {Object} path - Babel路径对象 + * @returns {Object} 上下文信息 + */ + findCallContext(path) { + let currentPath = path + let triggerName = null + let triggerType = 'function' + + // 向上遍历AST,查找触发源 + while (currentPath) { + const { node } = currentPath + + // 检查是否在Vue 3 Composition API生命周期钩子中 + if (node.type === 'CallExpression' && + node.callee.type === 'Identifier' && + ['onMounted', 'onCreated', 'onBeforeMount', 'onBeforeCreate', 'onUpdated', 'onBeforeUpdate', 'onUnmounted', 'onBeforeUnmount'].includes(node.callee.name)) { + triggerName = node.callee.name + triggerType = 'lifecycle' + break + } + + // 检查是否在Vue 3 Composition API的setup方法中 + if (node.type === 'ObjectMethod' && node.key && node.key.name === 'setup') { + triggerName = 'setup' + triggerType = 'lifecycle' + break + } + + // 检查是否在方法定义中 + if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') { + if (node.id && node.id.name) { + triggerName = node.id.name + triggerType = 'function' + break + } + } + + // 检查是否在对象方法中(非setup) + if (node.type === 'ObjectMethod') { + if (node.key && node.key.name && node.key.name !== 'setup') { + triggerName = node.key.name + triggerType = 'method' + break + } + } + + // 检查是否在箭头函数中 + if (node.type === 'ArrowFunctionExpression') { + // 查找父级的属性名 + const parent = currentPath.parent + if (parent && parent.type === 'ObjectProperty' && parent.key) { + const methodName = parent.key.name || '' + if (methodName === 'setup') { + triggerName = 'setup' + triggerType = 'lifecycle' + } else { + triggerName = methodName + triggerType = 'method' + } + break + } + + // 检查是否在const声明的箭头函数中 + if (parent && parent.type === 'VariableDeclarator' && parent.id) { + const functionName = parent.id.name || '' + triggerName = functionName + triggerType = 'method' + break + } + } + + // 检查是否在Vue 2生命周期钩子中 + if (node.type === 'CallExpression' && + node.callee.type === 'MemberExpression' && + node.callee.object.name === 'this' && + ['mounted', 'created', 'beforeMount', 'beforeCreate'].includes(node.callee.property.name)) { + triggerName = node.callee.property.name + triggerType = 'lifecycle' + break + } + + currentPath = currentPath.parentPath + } + + return { triggerName, triggerType } + } + + /** + * 去重触发源 + * @param {Array} triggerSources - 触发源数组 + * @returns {Array} 去重后的触发源数组 + */ + deduplicateTriggerSources(triggerSources) { + const seen = new Set() + return triggerSources.filter(trigger => { + const key = `${trigger.component}-${trigger.triggerName}-${trigger.triggerType}` + if (seen.has(key)) { + return false + } + seen.add(key) + return true + }) + } + +} + +module.exports = TriggerAnalyzerSimple diff --git a/gofaster/app/plugins/route-mapping-plugin.js b/gofaster/app/plugins/route-mapping-plugin.js index 9584436..e39d02b 100644 --- a/gofaster/app/plugins/route-mapping-plugin.js +++ b/gofaster/app/plugins/route-mapping-plugin.js @@ -1,7 +1,8 @@ const { resolve } = require('path') const { existsSync, statSync } = require('fs') const ApiCollector = require('./modules/api-collector') -const TriggerAnalyzer = require('./modules/trigger-analyzer') +const TriggerAnalyzerSimple = require('./modules/trigger-analyzer-simple') +const CallChainTracer = require('./modules/call-chain-tracer') const FileGenerator = require('./modules/file-generator') const RouteAnalyzer = require('./modules/route-analyzer') @@ -33,7 +34,8 @@ function routeMappingPlugin() { // 初始化模块 _apiCollector: new ApiCollector(), - _triggerAnalyzer: new TriggerAnalyzer(), + _triggerAnalyzer: new TriggerAnalyzerSimple(), + _callChainTracer: new CallChainTracer(), _fileGenerator: new FileGenerator(), _routeAnalyzer: new RouteAnalyzer(), @@ -119,7 +121,7 @@ function routeMappingPlugin() { return true }, - // 收集直接映射关系 - 优化版本:简化逻辑 + // 收集直接映射关系 - 分步执行版本 collectDirectMappings() { try { // 设置生成中标志 @@ -130,19 +132,32 @@ function routeMappingPlugin() { return } - // 第三步第二小步:启用触发源关联功能 - // 1. 分析路由配置 + // ========== 第一步:收集route和api列表 ========== const routes = this._routeAnalyzer.analyzeRoutes() - - // 2. 收集API信息(第一层) const moduleDirs = this._routeAnalyzer.getModuleDirs() const apiMappings = this._apiCollector.collectApiMappings(moduleDirs) - // 3. 关联页面与API(第三层),传递路由信息用于过滤 + // 生成包含路由和API信息的文件(不包含triggerSources) + this._fileGenerator.generateRouteApiMappingFile(routes, apiMappings) + + // ========== 第二步:Trigger追溯 ========== const enhancedApiMappings = this._triggerAnalyzer.findTriggerSourcesForApiMappings(apiMappings, routes) - // 生成包含路由和API关联信息的文件 - this._fileGenerator.generateRouteApiMappingFile(routes, enhancedApiMappings) + // ========== 第二步半:调用链追溯 ========== + const tracedApiMappings = this._callChainTracer.traceMethodTriggersToVisualComponents(enhancedApiMappings) + + // 生成包含triggerSources的文件 + this._fileGenerator.generateRouteApiMappingFile(routes, tracedApiMappings) + + // ========== 第三步:删除非button(暂时注释) ========== + // const DataCleaner = require('./modules/data-cleaner.js') + // const dataCleaner = new DataCleaner() + // const cleanedMappings = dataCleaner.cleanData() + + // ========== 第五步:改写Vue(暂时注释) ========== + // if (cleanedMappings && cleanedMappings.apiMappings) { + // this._triggerAnalyzer.addButtonAttributesToCleanedData(cleanedMappings.apiMappings) + // } // 重置生成中标志并更新时间戳 this._generationInProgress = false @@ -153,16 +168,9 @@ function routeMappingPlugin() { this._generationWindowStart = this._lastGenerationTime } - // TODO: 后续步骤将在优化过程中逐步添加 - - // 4. 优化API映射 - // const optimizedApiMappings = this._fileGenerator.optimizeApiMappings(enhancedApiMappings) - - // 5. 生成最终映射文件 - // this._fileGenerator.generateMappingFile(routes, optimizedApiMappings) - } catch (error) { this._generationInProgress = false + console.error('❌ 生成direct-route-mappings.js时出错:', error) throw error } } diff --git a/gofaster/app/scripts/generate-route-mappings.js b/gofaster/app/scripts/generate-route-mappings.js index dcb3d56..d5f1565 100644 --- a/gofaster/app/scripts/generate-route-mappings.js +++ b/gofaster/app/scripts/generate-route-mappings.js @@ -13,28 +13,20 @@ if (isSilent) { // 独立运行路由映射生成 if (isCheckOnly) { - console.log('🔍 检查路由映射文件状态...') - const outputPath = resolve(__dirname, '../src/renderer/modules/route-sync/direct-route-mappings.js') if (existsSync(outputPath)) { - console.log('✅ 路由映射文件已存在,webpack插件将处理更新') process.exit(0) } else { - console.log('⚠️ 路由映射文件不存在,需要生成') - console.log('💡 请运行不带 --check-only 参数的脚本来生成文件') process.exit(1) } -} else { - console.log('🔧 独立生成路由映射文件...') } try { const plugin = routeMappingPlugin() plugin.collectDirectMappings() - console.log('✅ 路由映射文件生成完成') } catch (error) { - console.error('❌ 路由映射文件生成失败:', error) + console.error('路由映射文件生成失败:', error) process.exit(1) } diff --git a/gofaster/app/src/renderer/modules/core/directives/permission-control.js b/gofaster/app/src/renderer/modules/core/directives/permission-control.js new file mode 100644 index 0000000..d554023 --- /dev/null +++ b/gofaster/app/src/renderer/modules/core/directives/permission-control.js @@ -0,0 +1,160 @@ +/** + * 权限控制指令 + * 监听button的可用状态变化,进行权限校验 + */ + +// 权限状态存储 +const permissionStates = new Map() +const buttonStates = new Map() + +/** + * 设置用户权限 + * @param {Object} permissions - 权限对象 + */ +export function setUserPermissions(permissions) { + // 清除旧的权限状态 + permissionStates.clear() + + // 设置新的权限状态 + Object.entries(permissions).forEach(([permission, hasPermission]) => { + permissionStates.set(permission, hasPermission) + }) + + console.log('🔐 用户权限已更新:', permissions) + + // 重新校验所有button的权限状态 + revalidateAllButtons() +} + +/** + * 获取按钮权限 + * @param {string} buttonName - 按钮名称 + * @returns {string} 权限标识符 + */ +function getButtonPermission(buttonName) { + // 根据按钮名称生成权限标识符 + // 格式:模块名.组件名.按钮名 + const parts = buttonName.split('-') + if (parts.length >= 2) { + const module = parts[0] + const component = parts[1] + return `${module}.${component}.${buttonName}` + } + return buttonName +} + +/** + * 检查按钮权限 + * @param {string} buttonName - 按钮名称 + * @returns {boolean} 是否有权限 + */ +function hasButtonPermission(buttonName) { + const permission = getButtonPermission(buttonName) + return permissionStates.get(permission) || false +} + +/** + * 重新校验所有button的权限状态 + */ +function revalidateAllButtons() { + buttonStates.forEach((state, buttonName) => { + const hasPermission = hasButtonPermission(buttonName) + if (!hasPermission && !state.element.disabled) { + // 没有权限但按钮可用,强制禁用 + state.element.disabled = true + state.element.classList.add('permission-denied') + console.log(`🚫 按钮 ${buttonName} 权限被拒绝,已禁用`) + } else if (hasPermission && state.element.disabled && state.element.classList.contains('permission-denied')) { + // 有权限但被权限控制禁用,恢复原状态 + state.element.disabled = state.originalDisabled + state.element.classList.remove('permission-denied') + console.log(`✅ 按钮 ${buttonName} 权限已恢复`) + } + }) +} + +/** + * 权限控制指令 + */ +export const permissionControl = { + mounted(el, binding) { + const buttonName = el.getAttribute('name') + if (!buttonName) { + console.warn('权限控制指令需要button的name属性') + return + } + + // 保存按钮的原始状态 + const originalDisabled = el.disabled + buttonStates.set(buttonName, { + element: el, + originalDisabled: originalDisabled, + hasPermission: hasButtonPermission(buttonName) + }) + + // 初始权限检查 + if (!hasButtonPermission(buttonName)) { + el.disabled = true + el.classList.add('permission-denied') + console.log(`🚫 按钮 ${buttonName} 初始权限检查失败,已禁用`) + } + + // 监听disabled属性变化 + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'disabled') { + const hasPermission = hasButtonPermission(buttonName) + const isDisabled = el.disabled + + // 如果按钮被启用但没有权限,强制禁用 + if (!isDisabled && !hasPermission) { + el.disabled = true + el.classList.add('permission-denied') + console.log(`🚫 按钮 ${buttonName} 权限检查失败,已禁用`) + } else if (isDisabled && hasPermission && el.classList.contains('permission-denied')) { + // 如果有权限但被权限控制禁用,恢复原状态 + el.disabled = originalDisabled + el.classList.remove('permission-denied') + console.log(`✅ 按钮 ${buttonName} 权限检查通过,已恢复`) + } + } + }) + }) + + // 开始观察 + observer.observe(el, { + attributes: true, + attributeFilter: ['disabled'] + }) + + // 保存observer引用,用于清理 + el._permissionObserver = observer + }, + + unmounted(el) { + // 清理observer + if (el._permissionObserver) { + el._permissionObserver.disconnect() + delete el._permissionObserver + } + + // 清理按钮状态 + const buttonName = el.getAttribute('name') + if (buttonName) { + buttonStates.delete(buttonName) + } + } +} + +/** + * 全局权限控制对象 + */ +export const permissionControlGlobal = { + setUserPermissions, + hasButtonPermission, + revalidateAllButtons, + getButtonStates: () => Object.fromEntries(buttonStates) +} + +export default permissionControl + diff --git a/gofaster/app/src/renderer/modules/core/plugins/permission-plugin.js b/gofaster/app/src/renderer/modules/core/plugins/permission-plugin.js new file mode 100644 index 0000000..b99ad0e --- /dev/null +++ b/gofaster/app/src/renderer/modules/core/plugins/permission-plugin.js @@ -0,0 +1,20 @@ +/** + * 权限控制插件 + */ +import { permissionControl, permissionControlGlobal } from '../directives/permission-control.js' + +export default { + install(app, options = {}) { + // 注册权限控制指令 + app.directive('permission-control', permissionControl) + + // 将权限控制对象挂载到全局属性 + app.config.globalProperties.$permission = permissionControlGlobal + + // 提供权限控制方法 + app.provide('permission', permissionControlGlobal) + + console.log('🔐 权限控制插件已安装') + } +} + diff --git a/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js b/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js index 8a04ebf..28d1875 100644 --- a/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js +++ b/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js @@ -42,14 +42,14 @@ export default { "path": "/api/auth/roles", "triggerSources": [ { - "component": "UserRoleAssignment", + "component": "RoleManagement", "triggerName": "loadRoles", "triggerType": "method" }, { - "component": "RoleManagement", - "triggerName": "rolemanagement-7gjwbd", - "triggerType": "button" + "component": "UserRoleAssignment", + "triggerName": "loadRoles", + "triggerType": "method" } ] }, @@ -72,8 +72,8 @@ export default { "triggerSources": [ { "component": "RoleManagement", - "triggerName": "rolemanagement-7gjwbd", - "triggerType": "button" + "triggerName": "saveRole", + "triggerType": "method" } ] }, @@ -83,14 +83,14 @@ export default { "path": "/api/auth/roles/{id}", "triggerSources": [ { - "component": "PermissionManager", - "triggerName": "permissionmanager-ufeqez", - "triggerType": "button" + "component": "RoleManagement", + "triggerName": "saveRole", + "triggerType": "method" }, { - "component": "RoleManagement", - "triggerName": "rolemanagement-7gjwbd", - "triggerType": "button" + "component": "PermissionManager", + "triggerName": "savePermissions", + "triggerType": "method" } ] }, @@ -101,8 +101,8 @@ export default { "triggerSources": [ { "component": "RoleManagement", - "triggerName": "rolemanagement-gih20x", - "triggerType": "button" + "triggerName": "deleteRole", + "triggerType": "method" } ] }, @@ -130,8 +130,8 @@ export default { "triggerSources": [ { "component": "UserRoleAssignment", - "triggerName": "userroleassignment-snvkol", - "triggerType": "button" + "triggerName": "saveRoleAssignment", + "triggerType": "method" } ] }, @@ -154,8 +154,8 @@ export default { "triggerSources": [ { "component": "UserRoleAssignment", - "triggerName": "userroleassignment-snvkol", - "triggerType": "button" + "triggerName": "saveRoleAssignment", + "triggerType": "method" } ] }, @@ -166,8 +166,8 @@ export default { "triggerSources": [ { "component": "RolePermissionAssignment", - "triggerName": "rolepermissionassignment-106c2d", - "triggerType": "button" + "triggerName": "loadRolePermissions", + "triggerType": "method" } ] }, @@ -178,13 +178,13 @@ export default { "triggerSources": [ { "component": "RolePermissionAssignment", - "triggerName": "rolepermissionassignment-106c2d", - "triggerType": "button" + "triggerName": "assignPermission", + "triggerType": "method" }, { "component": "RolePermissionAssignment", - "triggerName": "rolepermissionassignment-u5nohd", - "triggerType": "button" + "triggerName": "assignSelectedPermissions", + "triggerType": "method" } ] }, @@ -195,13 +195,13 @@ export default { "triggerSources": [ { "component": "RolePermissionAssignment", - "triggerName": "rolepermissionassignment-nd91kw", - "triggerType": "button" + "triggerName": "removePermission", + "triggerType": "method" }, { "component": "RolePermissionAssignment", - "triggerName": "rolepermissionassignment-hve7dn", - "triggerType": "button" + "triggerName": "removeSelectedPermissions", + "triggerType": "method" } ] } @@ -218,11 +218,17 @@ export default { "triggerSources": [ { "component": "UserManagement", - "triggerName": "usermanagement-4rtqic", - "triggerType": "link" + "triggerName": "loadUsers", + "triggerType": "method" } ] }, + { + "apiMethodName": "getUser", + "method": "GET", + "path": "/api/auth/admin/users/{id}", + "triggerSources": [] + }, { "apiMethodName": "createUser", "method": "POST", @@ -230,8 +236,8 @@ export default { "triggerSources": [ { "component": "UserManagement", - "triggerName": "usermanagement-elrh6q", - "triggerType": "button" + "triggerName": "submitUser", + "triggerType": "method" } ] }, @@ -242,8 +248,8 @@ export default { "triggerSources": [ { "component": "UserManagement", - "triggerName": "usermanagement-elrh6q", - "triggerType": "button" + "triggerName": "submitUser", + "triggerType": "method" } ] }, @@ -254,11 +260,17 @@ export default { "triggerSources": [ { "component": "UserManagement", - "triggerName": "usermanagement-25s1yv", - "triggerType": "button" + "triggerName": "deleteUser", + "triggerType": "method" } ] }, + { + "apiMethodName": "getRoles", + "method": "GET", + "path": "/api/auth/admin/roles", + "triggerSources": [] + }, { "apiMethodName": "changePassword", "method": "POST", @@ -266,8 +278,8 @@ export default { "triggerSources": [ { "component": "PasswordChangeModal", - "triggerName": "passwordchangemodal-4ud6kv", - "triggerType": "button" + "triggerName": "handleSubmit", + "triggerType": "method" } ] }, @@ -290,11 +302,58 @@ export default { "triggerSources": [ { "component": "PasswordChangeModal", - "triggerName": "passwordchangemodal-ihl507", - "triggerType": "input" + "triggerName": "validatePassword", + "triggerType": "method" } ] }, + { + "apiMethodName": "checkPasswordStatus", + "method": "GET", + "path": "/api/auth/password-status", + "triggerSources": [] + }, + { + "apiMethodName": "getPermissions", + "method": "GET", + "path": "/api/permissions", + "triggerSources": [] + }, + { + "apiMethodName": "getCaptcha", + "method": "GET", + "path": "/api/auth/captcha", + "triggerSources": [ + { + "component": "LoginModal", + "triggerName": "refreshCaptcha", + "triggerType": "method" + }, + { + "component": "LoginModal", + "triggerName": "refreshCaptchaWithoutClearError", + "triggerType": "method" + } + ] + }, + { + "apiMethodName": "login", + "method": "POST", + "path": "/api/auth/login", + "triggerSources": [ + { + "component": "LoginModal", + "triggerName": "handleLogin", + "triggerType": "method" + } + ] + }, + { + "apiMethodName": "logout", + "method": "POST", + "path": "/api/auth/logout", + "triggerSources": [] + }, { "apiMethodName": "getCurrentUser", "method": "GET", @@ -302,8 +361,8 @@ export default { "triggerSources": [ { "component": "UserProfile", - "triggerName": "userprofile-sdddz1", - "triggerType": "button" + "triggerName": "loadUserProfile", + "triggerType": "method" } ] }, @@ -314,10 +373,16 @@ export default { "triggerSources": [ { "component": "PasswordChangeModal", - "triggerName": "passwordchangemodal-4ud6kv", - "triggerType": "button" + "triggerName": "handleSubmit", + "triggerType": "method" } ] + }, + { + "apiMethodName": "resetPassword", + "method": "POST", + "path": "/api/auth/reset-password", + "triggerSources": [] } ] } diff --git a/gofaster/app/vue-add-attr-loader.js b/gofaster/app/vue-add-attr-loader.js new file mode 100644 index 0000000..5b6bf82 --- /dev/null +++ b/gofaster/app/vue-add-attr-loader.js @@ -0,0 +1,135 @@ +// Vue Add Attribute Loader +// 用于在构建时为Vue文件的button标签添加name属性和权限控制属性 + +const { parse, compileTemplate } = require('@vue/compiler-sfc') + +module.exports = function(source) { + const callback = this.async() + + + try { + // 解析Vue文件 + const { descriptor } = parse(source, { filename: this.resourcePath }) + + + if (!descriptor.template || !descriptor.template.ast) { + // 如果没有template,直接返回原内容 + return callback(null, source) + } + + let modified = false + + // 遍历AST,找到button节点并添加属性 + const addAttributesToButton = (node) => { + if (node.type === 1 && node.tag === 'button') { + // 检查是否已经有name属性 + const hasNameAttr = node.props.some(prop => + prop.type === 6 && prop.name === 'name' + ) + + if (!hasNameAttr) { + // 生成唯一的name值 + const componentName = this.resourcePath.split('/').pop().replace('.vue', '').toLowerCase() + const uniqueSuffix = Math.random().toString(36).substr(2, 6) + const nameValue = `${componentName}-${uniqueSuffix}` + + // 添加name属性 + node.props.push({ + type: 6, + name: 'name', + value: { + type: 2, + content: nameValue, + loc: node.loc + }, + loc: node.loc + }) + + // 检查是否已经有v-permission-control指令 + const hasPermissionControl = node.props.some(prop => + prop.type === 7 && prop.name === 'v-permission-control' + ) + + if (!hasPermissionControl) { + // 添加v-permission-control指令(不添加:disabled,避免重复) + node.props.push({ + type: 7, + name: 'v-permission-control', + exp: { + type: 4, + content: 'true', + isStatic: false, + loc: node.loc + }, + loc: node.loc + }) + } + + modified = true + } + } + + // 递归处理子节点 + if (node.children) { + node.children.forEach(addAttributesToButton) + } + } + + // 开始遍历AST + addAttributesToButton(descriptor.template.ast) + + // 如果修改了AST,重新生成Vue文件 + if (modified) { + // 由于Vue编译器没有直接的AST到字符串转换,我们使用字符串替换的方式 + // 先找到template部分 + const templateMatch = source.match(/(]*>)([\s\S]*?)(<\/template>)/) + if (templateMatch) { + let templateContent = templateMatch[2] + + // 为每个button添加属性(使用字符串替换) + templateContent = templateContent.replace( + /]*?)(\s*\/?>)/g, + (match, attrs, closing) => { + // 检查是否已经有name属性 + if (attrs.includes('name=')) { + return match // 已经有name属性,不修改 + } + + // 生成唯一的name值 + const componentName = this.resourcePath.split('/').pop().replace('.vue', '').toLowerCase() + const uniqueSuffix = Math.random().toString(36).substr(2, 6) + const nameValue = `${componentName}-${uniqueSuffix}` + + // 检查是否已经有v-permission-control指令 + const hasPermissionControl = attrs.includes('v-permission-control') + + // 构建新的属性字符串 + let newAttrs = attrs + newAttrs += ` name="${nameValue}"` + if (!hasPermissionControl) { + newAttrs += ` v-permission-control` + } + + + return `