国保列表页调整数据展示功能开发全记录

一、需求起点

BYP国保数据库已完整收录第四批至第八批全国重点文物保护单位共 5052 条数据,用户可按批次筛选查看。

但在实际使用中发现问题:

批次 公布年份 调整数量 问题
第四批 1996年 12处 查阅第六批列表时,看不到该批合并的106处项目
第五批 2001年 23处 用户需要了解某批次是否有合并/归入项目
第六批 2006年 106处 调整数据与列表数据分离,查阅不便
第七批 2013年 47处
第八批 2019年 50处

直接后果:用户在列表页查看"第六批"国保时,无法同时看到该批次的106处合并项目,需要额外翻阅文档,信息割裂。

核心需求:在国保列表页按批次展示对应的合并/归入项目,数据与源文档自动同步。


二、数据源

采用已整理的Markdown文档:

数据特点


三、整体方案

分三阶段推进,每阶段独立验证:

阶段 A 数据提取:国保调整.md → merge_adjustments.json(Python脚本自动解析)
阶段 B 后端集成:routes.py 读取JSON → 传递到模板
阶段 C 前端展示:list.html 可折叠卡片 + 响应式表格

四、阶段 A:构建JSON数据文件

1. 初始方案(手动录入)

最初手动创建 merge_adjustments.json,录入部分示例数据测试:

{
  "第六批": {
    "year": "2006",
    "type": "合并",
    "count": 106,
    "data": [
      {"name": "金界壕遗址", "time": "金", "location": "河北省丰宁满族自治县", "note": "归入..."},
      // ... 仅录入16条示例
    ]
  }
}

2. 问题发现:字段名冲突

第一个500错误:访问页面时报 TypeError: 'builtin_function_or_method' object is not iterable

根因分析: - JSON中使用 items 作为数据列表的key - 在Jinja2模板中 merge_data.items 被解析为Python字典的内置方法 .items() - 导致 for item in merge_data.items 尝试迭代方法对象而非列表数据

解决方案: - 将 items 改为 data,避免与Python内置方法名冲突 - 同步更新模板中所有 merge_data.items → merge_data.data

3. 问题发现:UTF-8 BOM编码

第二个500错误:JSON文件存在但解析失败

根因分析:

JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig)

解决方案:

# 修改前
with open(json_path, 'r', encoding='utf-8') as f:

# 修改后
with open(json_path, 'r', encoding='utf-8-sig') as f:

4. 自动化脚本:提取国保调整数据.py

为避免手动录入238条数据,编写Python脚本自动从Markdown提取:

脚本信息

核心逻辑

def parse_markdown_to_json(md_path):
    """从国保调整.md提取数据并生成JSON"""

    # 第四批、第五批、第六批:5列表格
    # | 序号 | 名称 | 时代 | 地址 | 备注 |
    rows = re.findall(r'\|\s*\d+\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|', table)

    # 第七批:7列表格(需跳过类别和编号列)
    # | 序号 | 类别 | 编号 | 名称 | 时代 | 地址 | 备注 |
    rows = re.findall(r'\|\s*\d+\s*\|\s*.*?\s*\|\s*.*?\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|', table)

    # 第八批:6列表格(需跳过编号列,分5个子类别)
    # | 序号 | 编号 | 名称 | 时代 | 地址 | 备注 |
    rows = re.findall(r'\|\s*\d+\s*\|\s*.*?\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*\|', table)

关键特性

执行结果

国保调整数据提取工具
创建日期:2026年5月7日
============================================================

源文件:...国保调整.md
输出文件:...merge_adjustments.json

开始从Markdown提取数据...

✓ JSON文件已生成
数据统计:
  ✓ 第四批: 12 处,实际提取 12 条
  ✓ 第五批: 23 处,实际提取 23 条
  ✓ 第六批: 106 处,实际提取 106 条
  ✓ 第七批: 47 处,实际提取 47 条
  ✓ 第八批: 50 处,实际提取 50 条

总计:238 条数据
============================================================
✓ 所有批次数据提取完整!

5. JSON数据结构

{
  "第四批": {
    "year": "1996",
    "type": "归入",
    "count": 12,
    "data": [
      {
        "name": "镇朔楼",
        "time": "明",
        "location": "河北省张家口市",
        "note": "归入清远楼"
      }
      // ... 共12条
    ]
  },
  // 第五批至第八批...
}

五、阶段 B:后端集成

路由改造(routes.py)

在 list_sites() 函数中增加JSON读取逻辑:

import json
from pathlib import Path

def list_sites():
    # ... 原有查询逻辑 ...

    # 读取国保调整数据(如果有批次筛选)
    merge_data = None
    if batch:
        try:
            json_path = Path(__file__).parent / 'merge_adjustments.json'
            if json_path.exists():
                with open(json_path, 'r', encoding='utf-8-sig') as f:
                    all_adjustments = json.load(f)
                merge_data = all_adjustments.get(batch)
        except Exception:
            merge_data = None  # 读取失败不影响正常显示

    return render_template('national_sites/list.html',
                         # ... 原有参数 ...
                         merge_data=merge_data)

设计要点

  1. 条件加载:仅在有 batch 参数时读取JSON
  2. 异常隔离:JSON读取失败不影响列表正常显示
  3. 编码安全:使用 utf-8-sig 处理BOM
  4. 路径自适应:Path(__file__).parent 确保容器内外路径一致

六、阶段 C:前端展示

1. 位置调整

将调整信息从页面底部移至列表前面(筛选条件之后、数据表格之前),更符合用户查阅习惯。

2. 可折叠设计

采用默认关闭、点击展开的交互方式:

{% if merge_data %}
<div class="merge-adjustments">
    <!-- 标题栏(可点击) -->
    <div class="merge-header" onclick="toggleMergeData()">
        <h3>📋 {{ batch }}合并/归入项目</h3>
        <div class="toggle-icon">▼</div>
    </div>

    <!-- 内容区(默认隐藏) -->
    <div id="mergeContent" style="display: none;">
        <!-- 调整说明 + 数据表格 -->
    </div>
</div>

<script>
function toggleMergeData() {
    var content = document.getElementById('mergeContent');
    var icon = document.querySelector('.toggle-icon');
    if (content.style.display === 'none') {
        content.style.display = 'block';
        icon.style.transform = 'rotate(180deg)';
    } else {
        content.style.display = 'none';
        icon.style.transform = 'rotate(0deg)';
    }
}
</script>
{% endif %}

3. 样式优化(三次迭代)

第一版:大红大紫渐变(❌ 被否决)

background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;

用户反馈:"背景大红大紫太难看了!"

第二版:浅灰色简洁风格(✅ 采用)

background: #f5f7fa;
color: #2c3e50;
border: 1px solid #e0e0e0;

符合整体页面风格,视觉舒适。

第三版:表格样式统一

4. 响应式适配

<div class="table-wrapper">
    <table class="sites-table">
        <thead>
            <tr>
                <th class="num-col">序号</th>
                <th>名称</th>
                <th class="time-col">时代</th>
                <th class="location-col">地址</th>
                <th>归入/合并说明</th>
            </tr>
        </thead>
        <tbody>
            {% for item in merge_data.data %}
            <tr>
                <td class="num-col">{{ loop.index }}</td>
                <td class="site-name">{{ item.name }}</td>
                <td class="time-col">{{ item.time }}</td>
                <td class="location-col">{{ item.location }}</td>
                <td class="note-col">{{ item.note }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
</div>

移动端效果:表格可横向滚动,不影响主列表浏览。


七、执行结果

功能验证

批次 访问URL 显示效果 数据完整性
第四批 ?batch=第四批 可折叠卡片 + 12条表格 ✓ 100%
第五批 ?batch=第五批 可折叠卡片 + 23条表格 ✓ 100%
第六批 ?batch=第六批 可折叠卡片 + 106条表格 ✓ 100%
第七批 ?batch=第七批 可折叠卡片 + 47条表格 ✓ 100%
第八批 ?batch=第八批 可折叠卡片 + 50条表格 ✓ 100%

用户体验提升

  1. 信息聚合:列表页同时展示国保单位和调整项目,无需翻阅文档
  2. 按需展开:默认折叠不占用空间,需要时点击展开
  3. 视觉统一:调整表格与主列表样式一致,阅读流畅
  4. 移动友好:表格横向滚动,手机端正常浏览

八、技术难点与解决方案

难点 1:Jinja2模板中Python字典方法名冲突

现象:TypeError: 'builtin_function_or_method' object is not iterable

根因:JSON字段名 items 与Python字典方法 .items() 冲突

解决:字段名改为 data,避免使用Python内置方法名

启示:Jinja2模板中应避免使用Python字典方法名(items、keys、values等)作为JSON字段名

难点 2:Windows UTF-8 BOM编码问题

现象:JSONDecodeError: Unexpected UTF-8 BOM

根因:Windows保存的JSON文件带BOM标记,Python默认utf-8无法处理

解决:使用 utf-8-sig 编码读取(自动跳过BOM)

启示:跨平台项目需特别注意文件编码差异,容器环境更严格

难点 3:不同批次表格格式差异

现象:第四批5列、第七批7列、第八批6列,列数不统一

解决:编写多套正则表达式,按批次特征提取对应列

启示:Markdown解析需考虑格式多样性,不能假设所有表格结构一致

难点 4:内联style中的Jinja2语法

现象:500错误(模板解析失败)

根因:在HTML style 属性中直接使用 {% if %} 语法

解决:改用CSS类方式(class="odd-row"),避免内联模板语法

启示:Jinja2模板语法不应嵌入HTML属性值中


九、复盘与方法论

成功要点

  1. 数据与文档分离:Markdown作为源文档,JSON作为应用数据,各司其职
  2. 自动化优先:编写提取脚本而非手动录入,保证数据一致性
  3. 异常隔离设计:JSON读取失败不影响主功能,增强系统健壮性
  4. 迭代式优化:从功能实现 → 用户反馈 → 样式调整,逐步完善
  5. 移动端适配:从一开始就考虑响应式设计,避免后期重构

数据治理启示

前端设计原则


十、后续优化方向

1. 数据完整性

2. 交互增强

3. 性能优化

4. 文档完善


十一、相关资源

代码文件

数据源

数据库

参考文档


十二、版本历史

日期 版本 说明
2026-05-07 v1.0 初始版本,完成核心功能开发
2026-05-07 v1.1 修复JSON字段名冲突和BOM编码问题
2026-05-07 v1.2 优化前端样式,实现可折叠卡片和响应式表格
2026-05-07 v1.3 编写自动化提取脚本,完成238条数据同步

总结:通过三阶段开发,成功实现了国保列表页的调整数据展示功能。从问题诊断、方案设计、技术攻关到用户反馈迭代,完整实践了数据治理与前端开发的最佳实践。自动化工具的引入大幅降低了后续维护成本,为知识库系统的持续优化奠定了坚实基础。

相关笔记