| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- /**
- * 将带 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)
|