feat: 同步全部系统改动与文档
This commit is contained in:
158
work/system-manual/audit-manual-actions.js
Normal file
158
work/system-manual/audit-manual-actions.js
Normal file
@@ -0,0 +1,158 @@
|
||||
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(/ /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)}`)
|
||||
Reference in New Issue
Block a user