Browse Source

部分完成

master
hejl 24 hours ago
parent
commit
4893edd35b
  1. 537
      gofaster/app/plugins/modules/call-chain-tracer.js
  2. 168
      gofaster/app/plugins/modules/data-cleaner.js
  3. 453
      gofaster/app/plugins/modules/trigger-analyzer-simple.js
  4. 44
      gofaster/app/plugins/route-mapping-plugin.js
  5. 10
      gofaster/app/scripts/generate-route-mappings.js
  6. 160
      gofaster/app/src/renderer/modules/core/directives/permission-control.js
  7. 20
      gofaster/app/src/renderer/modules/core/plugins/permission-plugin.js
  8. 153
      gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js
  9. 135
      gofaster/app/vue-add-attr-loader.js
  10. 8
      gofaster/app/vue.config.js

537
gofaster/app/plugins/modules/call-chain-tracer.js

@ -0,0 +1,537 @@ @@ -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(/<script[^>]*>([\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

168
gofaster/app/plugins/modules/data-cleaner.js

@ -0,0 +1,168 @@ @@ -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

453
gofaster/app/plugins/modules/trigger-analyzer-simple.js

@ -0,0 +1,453 @@ @@ -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(/<template\s+authType\s*=\s*["']([^"']+)["']/)
if (authTypeMatch) {
return authTypeMatch[1]
}
}
} catch (error) {
// 静默处理错误
}
return null
}
/**
* 解析Vue组件内容
* @param {string} content - 组件内容
* @returns {Object|null} AST对象
*/
parseVueComponent(content) {
try {
// 提取script部分
const scriptMatch = content.match(/<script[^>]*>([\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

44
gofaster/app/plugins/route-mapping-plugin.js

@ -1,7 +1,8 @@ @@ -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() { @@ -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() { @@ -119,7 +121,7 @@ function routeMappingPlugin() {
return true
},
// 收集直接映射关系 - 优化版本:简化逻辑
// 收集直接映射关系 - 分步执行版本
collectDirectMappings() {
try {
// 设置生成中标志
@ -130,19 +132,32 @@ function routeMappingPlugin() { @@ -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() { @@ -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
}
}

10
gofaster/app/scripts/generate-route-mappings.js

@ -13,28 +13,20 @@ if (isSilent) { @@ -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)
}

160
gofaster/app/src/renderer/modules/core/directives/permission-control.js

@ -0,0 +1,160 @@ @@ -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

20
gofaster/app/src/renderer/modules/core/plugins/permission-plugin.js

@ -0,0 +1,20 @@ @@ -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('🔐 权限控制插件已安装')
}
}

153
gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js

@ -42,14 +42,14 @@ export default { @@ -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 { @@ -72,8 +72,8 @@ export default {
"triggerSources": [
{
"component": "RoleManagement",
"triggerName": "rolemanagement-7gjwbd",
"triggerType": "button"
"triggerName": "saveRole",
"triggerType": "method"
}
]
},
@ -83,14 +83,14 @@ export default { @@ -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 { @@ -101,8 +101,8 @@ export default {
"triggerSources": [
{
"component": "RoleManagement",
"triggerName": "rolemanagement-gih20x",
"triggerType": "button"
"triggerName": "deleteRole",
"triggerType": "method"
}
]
},
@ -130,8 +130,8 @@ export default { @@ -130,8 +130,8 @@ export default {
"triggerSources": [
{
"component": "UserRoleAssignment",
"triggerName": "userroleassignment-snvkol",
"triggerType": "button"
"triggerName": "saveRoleAssignment",
"triggerType": "method"
}
]
},
@ -154,8 +154,8 @@ export default { @@ -154,8 +154,8 @@ export default {
"triggerSources": [
{
"component": "UserRoleAssignment",
"triggerName": "userroleassignment-snvkol",
"triggerType": "button"
"triggerName": "saveRoleAssignment",
"triggerType": "method"
}
]
},
@ -166,8 +166,8 @@ export default { @@ -166,8 +166,8 @@ export default {
"triggerSources": [
{
"component": "RolePermissionAssignment",
"triggerName": "rolepermissionassignment-106c2d",
"triggerType": "button"
"triggerName": "loadRolePermissions",
"triggerType": "method"
}
]
},
@ -178,13 +178,13 @@ export default { @@ -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 { @@ -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 { @@ -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 { @@ -230,8 +236,8 @@ export default {
"triggerSources": [
{
"component": "UserManagement",
"triggerName": "usermanagement-elrh6q",
"triggerType": "button"
"triggerName": "submitUser",
"triggerType": "method"
}
]
},
@ -242,8 +248,8 @@ export default { @@ -242,8 +248,8 @@ export default {
"triggerSources": [
{
"component": "UserManagement",
"triggerName": "usermanagement-elrh6q",
"triggerType": "button"
"triggerName": "submitUser",
"triggerType": "method"
}
]
},
@ -254,11 +260,17 @@ export default { @@ -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 { @@ -266,8 +278,8 @@ export default {
"triggerSources": [
{
"component": "PasswordChangeModal",
"triggerName": "passwordchangemodal-4ud6kv",
"triggerType": "button"
"triggerName": "handleSubmit",
"triggerType": "method"
}
]
},
@ -290,11 +302,58 @@ export default { @@ -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 { @@ -302,8 +361,8 @@ export default {
"triggerSources": [
{
"component": "UserProfile",
"triggerName": "userprofile-sdddz1",
"triggerType": "button"
"triggerName": "loadUserProfile",
"triggerType": "method"
}
]
},
@ -314,10 +373,16 @@ export default { @@ -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": []
}
]
}

135
gofaster/app/vue-add-attr-loader.js

@ -0,0 +1,135 @@ @@ -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(/(<template[^>]*>)([\s\S]*?)(<\/template>)/)
if (templateMatch) {
let templateContent = templateMatch[2]
// 为每个button添加属性(使用字符串替换)
templateContent = templateContent.replace(
/<button([^>]*?)(\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 `<button${newAttrs}${closing}`
}
)
// 替换原文件中的template部分
const newSource = source.replace(
templateMatch[0],
`${templateMatch[1]}${templateContent}${templateMatch[3]}`
)
return callback(null, newSource)
}
}
// 如果没有修改,返回原内容
callback(null, source)
} catch (error) {
console.warn(`[Vue Add Attr Loader] 处理文件时出错:`, error.message)
// 出错时返回原内容
callback(null, source)
}
}

8
gofaster/app/vue.config.js

@ -182,6 +182,14 @@ module.exports = defineConfig({ @@ -182,6 +182,14 @@ module.exports = defineConfig({
// 添加路由映射插件
config.plugin('route-mapping').use(routeMappingPlugin())
// 添加Vue文件属性添加loader - 在编译时动态添加属性
config.module
.rule('vue-add-attr')
.test(/\.vue$/)
.use('vue-add-attr-loader')
.loader(require.resolve('./vue-add-attr-loader'))
.before('vue-loader')
// 强制禁用所有sourcemap,避免eval
config.devtool(false)

Loading…
Cancel
Save