Files
school-developer/scripts/fix-encoding-corruption.py
2026-01-30 18:03:01 +08:00

259 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复项目中因编码损坏产生的 U+FFFD 替换字符及未闭合字符串。
遍历 src 下所有 .vue .ts .js 文件,应用预定义的替换规则。
"""
import os
import sys
# U+FFFD 替换字符
R = '\ufffd'
# 替换规则:(错误片段, 正确文本) 错误片段中含 R 表示 U+FFFD
# 顺序:长的、具体的放前面,避免短规则误替换
REPLACEMENTS = [
# content="列设? placement= 未闭合
('content="列设' + R + '? placement="top"', 'content="列设置" placement="top"'),
# 筛选条件
('筛选条' + R + '?', '筛选条件'),
# 列表/条件
('助学金批次列' + R + '?', '助学金批次列表'),
('学生处分列' + R + '?', '学生处分列表'),
('勤工助学考勤列' + R + '?', '勤工助学考勤列表'),
('评优评先奖项列' + R + '?', '评优评先奖项列表'),
# 列设置
('列设' + R + '?', '列设置'),
# 表格列配置
('表格列配' + R + '?', '表格列配置'),
# 日期 placeholder
('start-placeholder="开始日' + R + '?', 'start-placeholder="开始日期'),
('end-placeholder="结束日' + R + '?', 'end-placeholder="结束日期'),
('开始日' + R + '?', '开始日期'),
('到期日' + R + '?', '到期日'),
('结束日' + R + '?', '结束日期'),
# range-separator
('range-separator="' + R + '?', 'range-separator="'),
('range-separator="' + R + R, 'range-separator="'),
# 格式化学期/季度/类别
('格式化学' + R + '?', '格式化学期'),
('格式化季' + R + '?', '格式化季度'),
('格式化类' + R + '?', '格式化类别'),
# 负责人/所属类别
('负责' + R + '?', '负责人'),
('所属类' + R + '?', '所属类别'),
# placeholder 未闭合 (缺结尾引号)
('请选择所属类' + R + '? ', '请选择所属类别" '),
('请输入姓' + R + '? ', '请输入姓名" '),
('请输入学' + R + '? ', '请输入学号" '),
('请输入班' + R + '? ', '请输入班号" '),
# 班主任
('班主任姓' + R + '?', '班主任姓名'),
('班主任电' + R + '?', '班主任电话'),
# 注释:用于图标
('用于图标' + R + '?', '用于图标显示)'),
('用于图标' + R + '?', '用于图标显示'),
# 初始化/数据
('初始' + R + '?', '初始化'),
('自动获取数' + R + '?', '自动获取数据'),
# 删除批次
('删除批' + R + '?"', '删除批次"'),
('删除批' + R + '?', '删除批次'),
# 特殊模板 注释
('学期列特殊模' + R + '?-->', '学期列特殊模板 -->'),
('人数列特殊模' + R + '?-->', '人数列特殊模板 -->'),
('成立时间列特殊模' + R + '?-->', '成立时间列特殊模板 -->'),
('需关爱类型列特殊模' + R + '?-->', '需关爱类型列特殊模板 -->'),
('记录时间列特殊模' + R + '?-->', '记录时间列特殊模板 -->'),
('干预结果列特殊模' + R + '?-->', '干预结果列特殊模板 -->'),
('考勤类型列特殊模' + R + '?-->', '考勤类型列特殊模板 -->'),
('落实情况列特殊模' + R + '?-->', '落实情况列特殊模板 -->'),
# tableColumns label 未闭合
("label: '班主任姓" + R + "? },", "label: '班主任姓名' }, "),
("label: '班主任电" + R + "? },", "label: '班主任电话' }, "),
("label: '开始日" + R + "?, icon:", "label: '开始日期', icon:"),
# 宿舍/班级/状态等
('宿舍' + R + '?', '宿舍号'),
('班级简' + R + '?', '班级简称'),
('操行平均' + R + '?', '操行平均分'),
('总评成绩平均' + R + '?', '总评成绩平均分'),
('处分状' + R + '?', '处分状态'),
('待审' + R + '?', '待审核'),
('' + R + '状态', '审核状态'),
# 更多常见
('请输入团员编' + R + '? ', '请输入团员编号" '),
('教学楼管理列' + R + '?', '教学楼管理列表'),
('原班' + R + '?', '原班级'),
("label: '请假开始时" + R + "?', icon:", "label: '请假开始时间', icon:"),
('类型列特殊模' + R + '?-->', '类型列特殊模板 -->'),
('入团时间列特殊模' + R + '?-->', '入团时间列特殊模板 -->'),
('考核时间列特殊模' + R + '?-->', '考核时间列特殊模板 -->'),
# 确定要删除...吗?
('教学楼吗' + R + '?)', '教学楼吗?)'),
# label 请假开始时间(逗号结尾变体)
("label: '请假开始时" + R + "?,", "label: '请假开始时间',"),
# range-separator 双替换符
('range-separator="' + R + R + '"', 'range-separator=""'),
('range-separator="' + R + R + '\n', 'range-separator=""\n'),
# range/start/end placeholder 缺结尾引号
('range-separator="\n start-placeholder="开始日期\n', 'range-separator=""\n start-placeholder="开始日期"\n'),
('start-placeholder="开始日期\n end-placeholder="结束日期"', 'start-placeholder="开始日期"\n end-placeholder="结束日期"'),
# 工学交替/带班教师 列特殊模板
('工学交替结束时间列特殊模' + R + '?-->', '工学交替结束时间列特殊模板 -->'),
('带班教师列特殊模' + R + '?-->', '带班教师列特殊模板 -->'),
# 班级简称 缺引号
("label: '班级简称 ", "label: '班级简称', "),
("label: '工学交替开始时" + R + "?, width:", "label: '工学交替开始时间', width:"),
# 确定要删除这条记录吗?
('这条记录吗' + R + ')', '这条记录吗?)'),
# 音/频 等(多字符损坏)
('播放' + R + R + R + '', '播放音频'),
# 团内职务/异动时间/附件列特殊模板
('团内职务列特殊模' + R + '?-->', '团内职务列特殊模板 -->'),
('异动时间列特殊模' + R + '?-->', '异动时间列特殊模板 -->'),
('附件列特殊模' + R + '?-->', '附件列特殊模板 -->'),
# 现班号/格式化日期/异动类型/转制类型
("label: '现班" + R + "? },", "label: '现班号' },"),
('现班' + R + '?', '现班号'),
('格式化日' + R + '?', '格式化日期'),
('格式化异动类' + R + '?', '格式化异动类型'),
('格式化转制类' + R + '?', '格式化转制类型'),
# 按钮文字:导出/新增
('' + R + '', '导出'),
('' + R + '', '新增'),
# 班主任label 等)
("label: '班主" + R + "?,", "label: '班主任',"),
("label: '班主" + R + "? ", "label: '班主任' "),
# 将文件拖到此处,<em>R?em 或 Rem
('将文件拖到此处,' + R + '?em>', '将文件拖到此处,<em>'),
('将文件拖到此处,' + R + 'em>', '将文件拖到此处,<em>'),
# 取消/确定 按钮(>??</el-button>
('= false">' + R + R + '</el-button>', '= false">取消</el-button>'),
('importLoading">' + R + R + '</el-button>', 'importLoading">确定</el-button>'),
(':loading="importLoading">' + R + R + '</el-button>', ':loading="importLoading">确定</el-button>'),
('' + R + '消</el-button>', '取消</el-button>'),
('' + R + '定</el-button>', '确定</el-button>'),
# confirm 记录吗R? 或 R
('记录吗' + R + '?)', "记录吗?')"),
('记录吗' + R + ')', '记录吗?)'),
# 注释与文案
('不实' + R + '?', '不实现'),
('只能上传一个文' + R + '?', '只能上传一个文件'),
('文件不存' + R + '?', '文件不存在'),
# 时间
('日期时' + R + '?', '日期时间'),
('和时' + R + '?', '和时间'),
("label: '开始时" + R + "?,", "label: '开始时间',"),
('开始时' + R + '?', '开始时间'),
]
# 字面问号乱码(非 U+FFFD的通用替换仅用于明确无歧义的
LITERAL_QUESTION_REPLACEMENTS = [
('content="???" placement="top"', 'content="列设置" placement="top"'),
("col.prop !== '??'", "col.prop !== '操作'"),
('<el-table-column type="index" label="??" width="70"', '<el-table-column type="index" label="序号" width="70"'),
('<el-table-column label="??" width="150"', '<el-table-column label="操作" width="150"'),
('<el-table-column label="??" width="200"', '<el-table-column label="操作" width="200"'),
('<el-table-column label="??" width="250"', '<el-table-column label="操作" width="250"'),
('<span style="margin-left: 4px">??</span>\n </template>', '<span style="margin-left: 4px">操作</span>\n </template>'),
('@click="handleEdit(scope.row)">\n ??\n </el-button>', '@click="handleEdit(scope.row)">\n 编辑\n </el-button>'),
('@click="handleDelete(scope.row)">\n ??\n </el-button>', '@click="handleDelete(scope.row)">\n 删除\n </el-button>'),
('<!-- ?? -->\n <el-table', '<!-- 表格 -->\n <el-table'),
('<!-- ?? -->\n <div class="pagination-wrapper">', '<!-- 分页 -->\n <div class="pagination-wrapper">'),
('<el-button @click="importDialogVisible = false">??</el-button>', '<el-button @click="importDialogVisible = false">取消</el-button>'),
('@click="confirmInit" :loading="initLoading">??</el-button>', '@click="confirmInit" :loading="initLoading">确定</el-button>'),
(':disabled="!importFile || importLoading">??</el-button>', ':disabled="!importFile || importLoading">确定</el-button>'),
# label + prop 上下文
('label="??" prop="schoolYear"', 'label="学年" prop="schoolYear"'),
('label="??" prop="schoolTerm"', 'label="学期" prop="schoolTerm"'),
('label="??" prop="deptCode"', 'label="学院" prop="deptCode"'),
('label="??" prop="classCode"', 'label="班级" prop="classCode"'),
('label="??" prop="realName"', 'label="姓名" prop="realName"'),
('label="??" prop="gradeCurr"', 'label="年级" prop="gradeCurr"'),
('label="??" prop="year"', 'label="学年" prop="year"'),
('label="??" prop="period"', 'label="学期" prop="period"'),
('label="??" prop="buildNo"', 'label="楼号" prop="buildNo"'),
('label="??" prop="conductType"', 'label="类型" prop="conductType"'),
('label="??" prop="stuNo"', 'label="学号" prop="stuNo"'),
('label="??" prop="source"', 'label="来源" prop="source"'),
('label="??" prop="termId"', 'label="学期" prop="termId"'),
('label="???" prop="roomNo"', 'label="宿舍号" prop="roomNo"'),
('label="????" prop="grade"', 'label="入学年份" prop="grade"'),
('label="????" prop="checkStatus"', 'label="审核状态" prop="checkStatus"'),
('label="????" prop="effectiveMoney"', 'label="可用余额" prop="effectiveMoney"'),
('label="????" prop="isLiveNum"', 'label="在住人数" prop="isLiveNum"'),
('label="????" prop="costMoney"', 'label="补贴金额" prop="costMoney"'),
('label="????" prop="punlishMonth"', 'label="处分月份" prop="punlishMonth"'),
('label="????" prop="punlishLevel"', 'label="处分级别" prop="punlishLevel"'),
('label="????" prop="ruleName"', 'label="奖项名称" prop="ruleName"'),
# placeholder 按上下文(需避免误替换,用较长片段)
('placeholder="?????" \n clearable\n filterable', 'placeholder="请选择学年" \n clearable\n filterable'),
('placeholder="?????" \n clearable\n style="width: 200px">', 'placeholder="请选择学期" \n clearable\n style="width: 200px">'),
('placeholder="?????" \n clearable\n filterable\n style="width: 200px">', 'placeholder="请选择学院" \n clearable\n filterable\n style="width: 200px">'),
('placeholder="?????" \n clearable\n style="width: 200px" />', 'placeholder="请输入姓名" \n clearable\n style="width: 200px" />'),
('placeholder="??????" \n clearable', 'placeholder="请输入宿舍号" \n clearable'),
('placeholder="???????" \n clearable', 'placeholder="请输入奖项名称" \n clearable'),
('placeholder="??????" \n clearable', 'placeholder="请输入年级" \n clearable'),
('placeholder="???????" \n clearable', 'placeholder="请选择审核状态" \n clearable'),
('placeholder="??????" \n :min="0"', 'placeholder="请输入可用余额" \n :min="0"'),
('placeholder="???" \n clearable', 'placeholder="请输入人数" \n clearable'),
('placeholder="?????" \n clearable\n filterable\n style="width: 200px"\n @change', 'placeholder="请选择学院" \n clearable\n filterable\n style="width: 200px"\n @change'),
('placeholder="??????" \n style="width: 100%"', 'placeholder="请输入补贴金额" \n style="width: 100%"'),
('placeholder="??????" \n clearable\n style="width: 200px">', 'placeholder="请选择处分月份" \n clearable\n style="width: 200px">'),
('placeholder="???????" \n clearable\n style="width: 200px">', 'placeholder="请选择处分级别" \n clearable\n style="width: 200px">'),
# 按钮文字
('@click="handleSearch">??</el-button>', '@click="handleSearch">查询</el-button>'),
('@click="handleReset">??</el-button>', '@click="handleReset">重置</el-button>'),
# 选项
('<el-option label="????" value="history"', '<el-option label="历史数据" value="history"'),
('<el-option label="??" value="attendance"', '<el-option label="考勤" value="attendance"'),
]
def fix_file(filepath):
try:
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
except Exception as e:
print(f" [SKIP] read error: {e}", file=sys.stderr)
return False
original = content
for wrong, right in REPLACEMENTS:
if wrong in content:
content = content.replace(wrong, right)
for wrong, right in LITERAL_QUESTION_REPLACEMENTS:
if wrong in content:
content = content.replace(wrong, right)
if content != original:
try:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return True
except Exception as e:
print(f" [SKIP] write error: {e}", file=sys.stderr)
return False
return False
def main():
root = os.path.join(os.path.dirname(__file__), '..', 'src')
root = os.path.abspath(root)
exts = ('.vue', '.ts', '.js')
modified = []
for dirpath, _dirnames, filenames in os.walk(root):
for name in filenames:
if not name.endswith(exts):
continue
path = os.path.join(dirpath, name)
rel = os.path.relpath(path, root)
if fix_file(path):
modified.append(rel)
if modified:
print(f"Fixed {len(modified)} file(s):")
for p in sorted(modified):
print(f" {p}")
else:
print("No files needed fixing.")
return 0
if __name__ == '__main__':
sys.exit(main())