#!/usr/bin/env python3
# 为列表页自动注入「详情」按钮 + DetailDrawer 抽屉。
# 字段从已有 抽取(中文 label 复用,type:time 自动识别)。
import re, sys, os
SKIP_LABELS = {'操作', '图片', '操作人', '附件', '明细', '记录', '步骤'}
def parse_columns(tpl):
fields = []
seen = set()
col_re = re.compile(r']*?)(/?>)', re.S)
i = 0
while True:
m = col_re.search(tpl, i)
if not m:
break
attrs = m.group(1)
closing = m.group(2)
if closing == '/>':
block = m.group(0)
i = m.end()
else:
k = tpl.find('', m.end())
if k < 0:
i = m.end()
continue
block = tpl[m.start():k + len('')]
i = k + len('')
m_prop = re.search(r'prop="([^"]+)"', block)
m_label = re.search(r'label="([^"]+)"', block)
m_time = re.search(r'fmtTime\(row\.(\w+)\)', block)
m_dynamic = re.search(r':label=', block)
if not m_label or m_dynamic:
continue
label = m_label.group(1)
if label in SKIP_LABELS:
continue
if m_time:
key = m_time.group(1)
if key not in seen:
fields.append({'key': key, 'label': label, 'type': 'time'})
seen.add(key)
continue
if m_prop:
key = m_prop.group(1)
if key not in seen:
fields.append({'key': key, 'label': label})
seen.add(key)
return fields
def fmt_field(f):
if f.get('type') == 'time':
return " { key: '%s', label: '%s', type: 'time' }," % (f['key'], f['label'])
return " { key: '%s', label: '%s' }," % (f['key'], f['label'])
def inject(path):
s = open(path, encoding='utf-8').read()
if 'DetailDrawer' in s:
print(' SKIP (already has DetailDrawer):', path)
return False
if 'openDetail(' in s:
print(' SKIP (already has openDetail):', path)
return False
# script + template
m_script = re.search(r'', s, re.S)
m_tpl = re.search(r'(.*?)', s, re.S)
if not m_script or not m_tpl:
print(' SKIP (no script/template):', path)
return False
script = m_script.group(1)
tpl = m_tpl.group(1)
fields = parse_columns(tpl)
if not fields:
print(' SKIP (no columns parsed):', path)
return False
# 1) import
imports = list(re.finditer(r"^\s*import .* from '[^']+'\s*$", script, re.M))
if not imports:
print(' SKIP (no import lines):', path)
return False
last_import = imports[-1]
ins_import = "import DetailDrawer from '../components/DetailDrawer.vue'\n"
# 若路径是 ../../components(子目录页)则调整
if '../components/' not in script and '../../components/' in s:
ins_import = "import DetailDrawer from '../../components/DetailDrawer.vue'\n"
script_new = script[:last_import.end()] + "\n" + ins_import + script[last_import.end():]
# 2) openDetail + detail state before
fields_block = ",\n".join(fmt_field(f) for f in fields)
open_detail = (
"\n// 详情抽屉\n"
"const detail = reactive({ show: false, title: '', fields: [], row: null })\n"
"function openDetail(row) {\n"
" detail.row = row\n"
" detail.title = `详情 · #${row.id}`\n"
" detail.fields = [\n" + fields_block + "\n ]\n"
" detail.show = true\n"
"}\n"
)
script_new = script_new.rstrip()
if not script_new.endswith('}'):
script_new = script_new + "\n"
script_new = script_new + open_detail
# 3) 详情 column right after \n"
" \n"
" 详情\n"
" \n"
" \n"
)
mt = re.search(r']*>', tpl)
if not mt:
print(' SKIP (no el-table):', path)
return False
# 插入到 之后(同一行或下一行)
insert_pos = mt.end()
tpl_new = tpl[:insert_pos] + "\n" + detail_col + tpl[insert_pos:]
# 4) DetailDrawer before last in template
last_div = tpl_new.rfind('')
drawer = (
"\n \n"
)
tpl_new = tpl_new[:last_div] + drawer + tpl_new[last_div:]
new_s = s
new_s = new_s[:m_script.start(1)] + script_new + new_s[m_script.end(1):]
# 重新定位 template(script 长度变化不影响 template 区域位置,因 template 在 script 之后)
new_s = new_s[:m_tpl.start(1)] + tpl_new + new_s[m_tpl.end(1):]
open(path, 'w', encoding='utf-8').write(new_s)
print(' OK fields=%d : %s' % (len(fields), path))
return True
if __name__ == '__main__':
for p in sys.argv[1:]:
print('FILE', p)
try:
inject(p)
except Exception as e:
print(' ERROR', p, e)