189 lines
5.2 KiB
JavaScript
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)
|