Files
MES_Manage_View_V20/work/system-manual/audit-manual-actions.js

159 lines
5.7 KiB
JavaScript

const fs = require('fs')
const path = require('path')
const { modules } = require('./capture-full-manual-pages')
const root = path.resolve(__dirname, '..', '..')
const outputPath = path.resolve(__dirname, 'manual-action-audit.json')
function unique(values) {
return [...new Set(values.map(value => String(value || '').trim()).filter(Boolean))]
}
function cleanText(value) {
return String(value || '')
.replace(/<[^>]+>/g, ' ')
.replace(/\{\{[\s\S]*?\}\}/g, ' ')
.replace(/&nbsp;/g, ' ')
.replace(/\s+/g, ' ')
.trim()
}
function literalAttribute(attributes, name) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const match = attributes.match(new RegExp(`(?:^|\\s)${escaped}\\s*=\\s*["']([^"']+)["']`))
return match ? match[1].trim() : ''
}
function handlerName(expression) {
const match = String(expression || '').match(/(?:^|[;\s])([A-Za-z_$][\w$]*)\s*(?:\(|$)/)
return match ? match[1] : ''
}
function matchingTooltip(template, index) {
const before = template.slice(0, index)
const openingIndex = before.lastIndexOf('<el-tooltip')
const closingIndex = before.lastIndexOf('</el-tooltip>')
if (openingIndex < 0 || openingIndex < closingIndex) return ''
const opening = before.slice(openingIndex, before.indexOf('>', openingIndex) + 1)
return literalAttribute(opening, 'content')
}
function extractBlock(script, method) {
if (!method) return ''
const escaped = method.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const pattern = new RegExp(`(?:async\\s+)?${escaped}\\s*\\([^)]*\\)\\s*\\{`, 'm')
const match = pattern.exec(script)
if (!match) return ''
const start = match.index + match[0].lastIndexOf('{')
let depth = 0
let quote = ''
let lineComment = false
let blockComment = false
for (let index = start; index < script.length; index++) {
const char = script[index]
const next = script[index + 1]
if (lineComment) {
if (char === '\n') lineComment = false
continue
}
if (blockComment) {
if (char === '*' && next === '/') {
blockComment = false
index++
}
continue
}
if (quote) {
if (char === '\\') {
index++
} else if (char === quote) {
quote = ''
}
continue
}
if (char === '/' && next === '/') {
lineComment = true
index++
continue
}
if (char === '/' && next === '*') {
blockComment = true
index++
continue
}
if (char === '"' || char === "'" || char === '`') {
quote = char
continue
}
if (char === '{') depth++
if (char === '}') {
depth--
if (depth === 0) return script.slice(start + 1, index)
}
}
return ''
}
function methodDetails(script, method) {
const body = extractBlock(script, method)
const procedures = unique([...body.matchAll(/CreateData\s*\([^,]+,\s*["']([^"']+)["']/g)].map(match => match[1]))
const parameters = unique([...body.matchAll(/\[\s*["']([^"']+)["']\s*,/g)].map(match => match[1]))
const calls = unique([...body.matchAll(/this\.([A-Za-z_$][\w$]*)\s*\(/g)].map(match => match[1])
.filter(name => !['CreateData', 'ExecDatabase'].includes(name)))
const closesDialog = /this\.[A-Za-z_$][\w$]*(?:Visible|dialog[A-Za-z_$]*)\s*=\s*false/.test(body)
const refreshesList = calls.some(name => /^(?:search|get|query|load|refresh|init)/i.test(name))
return { procedures, parameters, calls, closesDialog, refreshesList }
}
function addAction(actions, template, index, attributes, body, fallbackLabel = '') {
const expression = literalAttribute(attributes, '@click') ||
literalAttribute(attributes, '@click.native') ||
literalAttribute(attributes, '@row-click')
if (!expression) return
const label = cleanText(body) || matchingTooltip(template, index) || fallbackLabel
if (!label) return
actions.push({ label, expression, method: handlerName(expression) })
}
const results = []
for (const module of modules) {
for (const [title, route, source] of module.pages) {
const sourcePath = path.resolve(root, 'src', 'views', source)
const content = fs.readFileSync(sourcePath, 'utf8')
const scriptStart = content.indexOf('<script')
const template = (scriptStart >= 0 ? content.slice(0, scriptStart) : content)
.replace(/<!--[\s\S]*?-->/g, '')
const script = scriptStart >= 0 ? content.slice(scriptStart) : ''
const actions = []
for (const match of template.matchAll(/<el-button\b([^>]*?)>([\s\S]*?)<\/el-button>/g)) {
addAction(actions, template, match.index, match[1], match[2])
}
for (const match of template.matchAll(/<el-button\b([^>]*?)\/>/g)) {
addAction(actions, template, match.index, match[1], '', '图标操作')
}
for (const match of template.matchAll(/<el-dropdown-item\b([^>]*)>([\s\S]*?)<\/el-dropdown-item>/g)) {
addAction(actions, template, match.index, match[1], match[2])
}
for (const match of template.matchAll(/<el-table\b([^>]*@row-click[^>]*)>/g)) {
addAction(actions, template, match.index, match[1], '', '点击列表行')
}
const deduplicated = []
const keys = new Set()
for (const action of actions) {
const key = `${action.label}|${action.method}|${action.expression}`
if (keys.has(key)) continue
keys.add(key)
deduplicated.push({ ...action, ...methodDetails(script, action.method) })
}
results.push({ module: module.title, title, route, source, actions: deduplicated })
}
}
fs.writeFileSync(outputPath, JSON.stringify(results, null, 2), 'utf8')
console.log(`Audited ${results.length} pages`)
console.log(`Actions: ${results.reduce((total, page) => total + page.actions.length, 0)}`)
console.log(`Actions with database effects: ${results.reduce((total, page) => total + page.actions.filter(action => action.procedures.length).length, 0)}`)