scan-vue-template-errors.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. /**
  2. * 扫描 list-search-actions 迁移导致的 Vue 模板结构问题
  3. */
  4. const fs = require('fs')
  5. const path = require('path')
  6. const roots = process.argv.slice(2).length
  7. ? process.argv.slice(2)
  8. : [
  9. path.join(__dirname, '../src/views'),
  10. path.join(__dirname, '../../saas-mgnui/src/views'),
  11. path.join(__dirname, '../../adminui/src/views')
  12. ]
  13. function walk(dir, files = []) {
  14. if (!fs.existsSync(dir)) return files
  15. for (const name of fs.readdirSync(dir)) {
  16. const full = path.join(dir, name)
  17. if (fs.statSync(full).isDirectory()) walk(full, files)
  18. else if (name.endsWith('.vue')) files.push(full)
  19. }
  20. return files
  21. }
  22. function scanTemplate(content) {
  23. const issues = []
  24. const tplMatch = content.match(/<template>([\s\S]*?)<\/template>/)
  25. if (!tplMatch) return issues
  26. const tpl = tplMatch[1]
  27. // 1. list-search-actions 内嵌套 el-form-item
  28. const actionsRe = /<el-form-item[^>]*\blist-search-actions\b[^>]*>([\s\S]*?)<\/el-form-item>/g
  29. let m
  30. while ((m = actionsRe.exec(tpl)) !== null) {
  31. if (/<el-form-item\b/.test(m[1])) {
  32. issues.push('nested-form-item-in-actions')
  33. }
  34. if (!/<\/el-form>\s*$/.test(m[1]) && /<\/el-form>/.test(m[1])) {
  35. issues.push('nested-form-in-actions')
  36. }
  37. }
  38. // 2. list-search-actions 后直接 </el-form> 但 actions 未闭合(只有开标签到 form 结束)
  39. if (
  40. /<el-form-item[^>]*list-search-actions[^>]*>\s*<el-form-item\b/.test(tpl) &&
  41. /<el-form-item[^>]*list-search-actions[^>]*>[\s\S]*?<\/el-form>/.test(tpl) &&
  42. !/<el-form-item[^>]*list-search-actions[^>]*>[\s\S]*?<\/el-form-item>\s*\n\s*<\/el-form>/.test(tpl)
  43. ) {
  44. issues.push('actions-not-closed-before-form-end')
  45. }
  46. // 3. 多余的 </el-form-item> 紧接 list-search-actions
  47. if (/>\s*\n\s*<\/el-form-item>\s*\n\s*<el-form-item[^>]*list-search-actions/.test(tpl)) {
  48. issues.push('stray-close-before-actions')
  49. }
  50. // 4. list-search-actions 内无按钮且含查询 label
  51. const actionsBlocks = tpl.match(/<el-form-item[^>]*list-search-actions[^>]*>[\s\S]*?<\/el-form-item>/g) || []
  52. for (const block of actionsBlocks) {
  53. if (
  54. /<el-form-item\b[^>]*label=/.test(block) &&
  55. !/handleQuery|resetQuery|right-toolbar/.test(block)
  56. ) {
  57. issues.push('query-fields-only-in-actions-block')
  58. }
  59. }
  60. // 5. 未闭合的 list-search-actions(开标签后遇到 </el-form> 而无 </el-form-item>)
  61. if (
  62. /<el-form-item[^>]*list-search-actions[^>]*>[\s\S]*?<\/el-form>/.test(tpl) &&
  63. !/<el-form-item[^>]*list-search-actions[^>]*>[\s\S]*?<\/el-form-item>[\s\S]*?<\/el-form>/.test(tpl)
  64. ) {
  65. issues.push('unclosed-list-search-actions')
  66. }
  67. // 6. el-form 内 tag 平衡(仅 queryForm 所在 form)
  68. const qIdx = tpl.indexOf('ref="queryForm"')
  69. if (qIdx >= 0) {
  70. const formStart = tpl.lastIndexOf('<el-form', qIdx)
  71. const formEnd = tpl.indexOf('</el-form>', qIdx)
  72. if (formStart >= 0 && formEnd >= 0) {
  73. const formBody = tpl.slice(formStart, formEnd + '</el-form>'.length)
  74. const openFi = (formBody.match(/<el-form-item\b/g) || []).length
  75. const closeFi = (formBody.match(/<\/el-form-item>/g) || []).length
  76. if (openFi !== closeFi) {
  77. issues.push(`form-item-balance:${openFi}open/${closeFi}close`)
  78. }
  79. }
  80. }
  81. return [...new Set(issues)]
  82. }
  83. const results = []
  84. for (const root of roots) {
  85. for (const file of walk(root)) {
  86. const content = fs.readFileSync(file, 'utf8')
  87. const issues = scanTemplate(content)
  88. if (issues.length) results.push({ file, issues })
  89. }
  90. }
  91. console.log('Total files with issues:', results.length)
  92. for (const r of results) {
  93. console.log(r.file.replace(/\\/g, '/'))
  94. console.log(' ', r.issues.join(', '))
  95. }