Files
shanxia2.0/web-admin/scripts/migrate-list-table.mjs
T
2026-08-03 12:48:52 +08:00

189 lines
5.2 KiB
JavaScript

/**
* 将带 class="list-table" 的 <el-table> 批量替换为 <ListTable table-key="...">。
* 同文件多表自动追加 -2/-3;不会误匹配 el-table-column。
*
* Usage: node scripts/migrate-list-table.mjs
*/
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const srcRoot = path.resolve(__dirname, '../src')
const viewsRoot = path.resolve(srcRoot, 'views')
const componentsRoot = path.resolve(srcRoot, 'components')
/** 匹配真正的 el-table 开标签,排除 el-table-column / el-table-column-group 等 */
const OPEN_RE = /<el-table(?=[\s/>])/g
const CLOSE_TAG = '</el-table>'
function walk(dir, out = []) {
if (!fs.existsSync(dir)) return out
for (const name of fs.readdirSync(dir)) {
const full = path.join(dir, name)
const st = fs.statSync(full)
if (st.isDirectory()) {
// 跳过 ListTable 自身,避免把内部 el-table 替换掉
if (full.endsWith(`${path.sep}list-table`)) continue
walk(full, out)
} else if (name.endsWith('.vue')) out.push(full)
}
return out
}
function toKebab(s) {
return String(s)
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/[_\s]+/g, '-')
.replace(/-+/g, '-')
.toLowerCase()
}
function baseKeyFromFile(file) {
let rel
if (file.startsWith(viewsRoot + path.sep) || file.startsWith(viewsRoot + '/')) {
rel = path.relative(viewsRoot, file)
} else {
rel = path.relative(componentsRoot, file)
}
const parts = rel.split(path.sep)
const stem = path.basename(file, '.vue')
const dirs = parts.slice(0, -1).map(toKebab)
if (!['index', 'Index'].includes(stem)) dirs.push(toKebab(stem))
return dirs.filter(Boolean).join('-') || toKebab(stem)
}
function hasListTableClass(attrText) {
return /class\s*=\s*(['"`])([^'"`]*\blist-table\b[^'"`]*)\1/.test(attrText)
}
function findOpenTagEnd(content, nameEnd) {
let j = nameEnd
let quote = null
while (j < content.length) {
const ch = content[j]
if (quote) {
if (ch === quote) quote = null
} else if (ch === '"' || ch === "'" || ch === '`') {
quote = ch
} else if (ch === '>') {
return j
}
j++
}
return -1
}
function transform(content, file) {
const baseKey = baseKeyFromFile(file)
let tableIndex = 0
let i = 0
let out = ''
while (i < content.length) {
OPEN_RE.lastIndex = i
const m = OPEN_RE.exec(content)
if (!m) {
out += content.slice(i)
break
}
out += content.slice(i, m.index)
const start = m.index
const nameEnd = start + '<el-table'.length
const openEnd = findOpenTagEnd(content, nameEnd)
if (openEnd < 0) {
out += content.slice(start)
break
}
const attrText = content.slice(nameEnd, openEnd)
const selfClosing = content[openEnd - 1] === '/'
const isList = hasListTableClass(attrText)
if (!isList) {
out += content.slice(start, openEnd + 1)
i = openEnd + 1
continue
}
tableIndex += 1
const key = tableIndex === 1 ? baseKey : `${baseKey}-${tableIndex}`
let newAttrs = attrText
if (!/\btable-key\s*=/.test(newAttrs) && !/\btableKey\s*=/.test(newAttrs)) {
newAttrs = ` table-key="${key}"` + newAttrs
}
if (selfClosing) {
out += `<ListTable${newAttrs.slice(0, -1)} />`
i = openEnd + 1
continue
}
out += `<ListTable${newAttrs}>`
i = openEnd + 1
let depth = 1
while (i < content.length && depth > 0) {
OPEN_RE.lastIndex = i
const nextOpen = OPEN_RE.exec(content)
const nextCloseIdx = content.indexOf(CLOSE_TAG, i)
if (nextCloseIdx < 0) {
out += content.slice(i)
i = content.length
depth = 0
break
}
const openIdx = nextOpen ? nextOpen.index : -1
if (openIdx >= 0 && openIdx < nextCloseIdx) {
// 嵌套 el-table:原样拷贝开标签,depth++
out += content.slice(i, openIdx)
const nestedNameEnd = openIdx + '<el-table'.length
const nestedOpenEnd = findOpenTagEnd(content, nestedNameEnd)
if (nestedOpenEnd < 0) {
out += content.slice(openIdx)
i = content.length
depth = 0
break
}
out += content.slice(openIdx, nestedOpenEnd + 1)
i = nestedOpenEnd + 1
if (content[nestedOpenEnd - 1] !== '/') depth += 1
continue
}
// 关闭标签
out += content.slice(i, nextCloseIdx)
depth -= 1
if (depth === 0) {
out += '</ListTable>'
} else {
out += CLOSE_TAG
}
i = nextCloseIdx + CLOSE_TAG.length
}
}
return { content: out, count: tableIndex }
}
const files = [...walk(viewsRoot), ...walk(path.join(componentsRoot, 'business'))]
let total = 0
const changed = []
for (const file of files) {
const raw = fs.readFileSync(file, 'utf8')
if (!raw.includes('list-table') || !raw.includes('<el-table')) continue
const { content, count } = transform(raw, file)
if (count > 0 && content !== raw) {
fs.writeFileSync(file, content)
total += count
changed.push(`${path.relative(path.resolve(__dirname, '..'), file)} (${count})`)
}
}
console.log(`Migrated ${total} tables in ${changed.length} files:`)
for (const line of changed) console.log(' -', line)