Vereinfache Bedienungsdoku und ergänze Bankportal-Anleitung
This commit is contained in:
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { promises as fs } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const ROOT = process.cwd()
|
||||
const FRONTEND_PAGES_DIR = path.join(ROOT, 'frontend', 'pages')
|
||||
const OUT_FILE = path.join(ROOT, 'docs', 'bedienung', 'frontend', 'alle-seiten-und-felder.md')
|
||||
|
||||
function normalizePosix(p) {
|
||||
return p.split(path.sep).join('/')
|
||||
}
|
||||
|
||||
async function walkFiles(dir, extension) {
|
||||
const result = []
|
||||
|
||||
async function walk(current) {
|
||||
const entries = await fs.readdir(current, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
const full = path.join(current, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
await walk(full)
|
||||
} else if (entry.isFile() && full.endsWith(extension)) {
|
||||
result.push(full)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await walk(dir)
|
||||
return result.sort()
|
||||
}
|
||||
|
||||
function filePathToNuxtRoute(filePath, baseDir) {
|
||||
const relative = normalizePosix(path.relative(baseDir, filePath))
|
||||
let route = relative.replace(/\.vue$/, '')
|
||||
|
||||
route = route
|
||||
.replace(/\.client$/, '')
|
||||
.replace(/\.server$/, '')
|
||||
.replace(/\[\[\.\.\.(.+?)\]\]/g, ':$1*?')
|
||||
.replace(/\[\.\.\.(.+?)\]/g, ':$1*')
|
||||
.replace(/\[\[(.+?)\]\]/g, ':$1?')
|
||||
.replace(/\[(.+?)\]/g, ':$1')
|
||||
|
||||
route = route.replace(/\/index$/g, '')
|
||||
if (route === 'index') route = ''
|
||||
if (!route.startsWith('/')) route = `/${route}`
|
||||
return route || '/'
|
||||
}
|
||||
|
||||
function pickAttr(tag, attr) {
|
||||
const doubleQuoted = new RegExp(`${attr}\\s*=\\s*"([^"]+)"`)
|
||||
const singleQuoted = new RegExp(`${attr}\\s*=\\s*'([^']+)'`)
|
||||
return tag.match(doubleQuoted)?.[1] || tag.match(singleQuoted)?.[1] || null
|
||||
}
|
||||
|
||||
function detectFields(source) {
|
||||
const fields = []
|
||||
const seen = new Set()
|
||||
|
||||
const formFieldRegex = /<UForm(?:Field|Group)\b([\s\S]*?)>/g
|
||||
let match
|
||||
while ((match = formFieldRegex.exec(source)) !== null) {
|
||||
const attrs = match[1] || ''
|
||||
const label = pickAttr(attrs, 'label')
|
||||
const key = `form:${label || ''}`
|
||||
if (label && !seen.has(key)) {
|
||||
seen.add(key)
|
||||
fields.push({
|
||||
name: label,
|
||||
component: 'UFormField',
|
||||
description: `Eingabebereich für „${label}“.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const inputRegex = /<(UInput|UTextarea|USelectMenu|USelect|USwitch|UCheckbox|URadioGroup|UInputNumber|UDatePicker)\b([\s\S]*?)>/g
|
||||
while ((match = inputRegex.exec(source)) !== null) {
|
||||
const component = match[1]
|
||||
const attrs = match[2] || ''
|
||||
|
||||
const label = pickAttr(attrs, 'label')
|
||||
const placeholder = pickAttr(attrs, 'placeholder')
|
||||
const model = pickAttr(attrs, 'v-model') || pickAttr(attrs, 'v-model:model-value')
|
||||
|
||||
const name = label || placeholder || model || '(ohne Bezeichnung)'
|
||||
const key = `${component}:${name}`
|
||||
if (seen.has(key)) continue
|
||||
seen.add(key)
|
||||
|
||||
let description = 'Eingabefeld in dieser Seite.'
|
||||
if (label) description = `Eingabefeld für „${label}“.`
|
||||
else if (placeholder) description = `Eingabefeld mit Platzhalter „${placeholder}“.`
|
||||
else if (model) description = `Eingabefeld für den internen Wert „${model}“.`
|
||||
|
||||
fields.push({ name, component, description })
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
function getPagePurpose(route) {
|
||||
if (route.startsWith('/createDocument')) return 'Dokumentenerstellung und Verwaltung von Ausgangsbelegen.'
|
||||
if (route.startsWith('/support') || route.startsWith('/helpdesk')) return 'Support- und Kommunikationsprozesse.'
|
||||
if (route.startsWith('/staff')) return 'Mitarbeiterbezogene Verwaltung und Zeiterfassung.'
|
||||
if (route.startsWith('/settings')) return 'Konfiguration und Stammdatenpflege.'
|
||||
if (route.startsWith('/accounting') || route.startsWith('/banking')) return 'Finanz- und Buchhaltungsfunktionen.'
|
||||
return 'Funktionsseite im FEDEO-Frontend.'
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const files = await walkFiles(FRONTEND_PAGES_DIR, '.vue')
|
||||
|
||||
let output = '# Frontend-Seiten und Eingabefelder\n\n'
|
||||
output += 'Diese Übersicht dient als Nutzer-Bedienung für alle Seiten des Web-Frontends.\n'
|
||||
output += 'Die Felder wurden technisch aus den Seiten erkannt und nutzerorientiert beschrieben.\n\n'
|
||||
|
||||
for (const file of files) {
|
||||
const source = await fs.readFile(file, 'utf-8')
|
||||
const route = filePathToNuxtRoute(file, FRONTEND_PAGES_DIR)
|
||||
const fields = detectFields(source)
|
||||
const relativeFile = normalizePosix(path.relative(ROOT, file))
|
||||
|
||||
output += `## ${route}\n\n`
|
||||
output += `- Datei: \`${relativeFile}\`\n`
|
||||
output += `- Seitenfunktion: ${getPagePurpose(route)}\n\n`
|
||||
|
||||
output += '### Felder\n\n'
|
||||
if (fields.length === 0) {
|
||||
output += 'Auf dieser Seite wurden keine direkten Eingabefelder erkannt.\n\n'
|
||||
continue
|
||||
}
|
||||
|
||||
output += '| Feld | Komponente | Erklärung |\n'
|
||||
output += '|---|---|---|\n'
|
||||
for (const field of fields) {
|
||||
output += `| ${field.name.replace(/\|/g, '\\|')} | ${field.component} | ${field.description.replace(/\|/g, '\\|')} |\n`
|
||||
}
|
||||
output += '\n'
|
||||
}
|
||||
|
||||
await fs.writeFile(OUT_FILE, output, 'utf-8')
|
||||
console.log(`Frontend-Bedienungsübersicht erzeugt: ${OUT_FILE}`)
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Fehler beim Erzeugen der Frontend-Bedienungsübersicht', err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user