Files
piano-plan/docs/FRONTEND_ARCH.md
hmo e50a9207b4 feat: v1.4.0 - 典型方案采纳、推荐方案列表、审计字段、导航优化
- 添加典型方案采纳功能 (POST /api/plans/<id>/adopt)
- 添加推荐方案列表 (GET /api/students/<id>/recommended-plans)
- PracticePlan 新增 created_by/updated_by/updated_at 审计字段
- 方案编辑/详情页导航优化 (bfcache 处理、pageshow 事件)
- 方案列表支持删除功能
- 学员列表'暂无方案/问题'样式统一
- 更新文档:问题文件已废弃(迁移到数据库)
- 更新部署脚本和验证清单
2026-04-27 02:01:22 +08:00

8.8 KiB
Raw Permalink Blame History

前端架构规范

本文档定义钢琴练习方案系统的模板架构设计,作为前端开发的遵循规范。

1. 模板架构

1.1 继承模式

所有业务页面必须继承自 base.html

base.html (基础模板)
├── index.html (学员管理)
├── home.html (默认首页)
├── student.html (学员详情)
├── plan_edit.html (方案编辑)
├── settings.html (问题配置)
├── classes.html (班级管理)
├── users.html (用户管理)
├── templates.html (模板管理)
└── api_settings.html (API设置)

1.2 base.html 结构

base.html 统一管理以下公共部分:

部分 说明
CDN 引入 Bootstrap 5.3, Bootstrap Icons, EasyMDE, Tabulator
基础 CSS body, sidebar, main-content, card 样式
移动端响应式 汉堡菜单、侧边栏展开/收起
顶部导航栏 移动端显示(固定在顶部)
侧边栏结构 桌面端显示
公共 JS toggleMobileNav, logout, showChangePasswordModal
修改密码弹窗 统一放在 base 中

1.3 Jinja2 Blocks

Block 名称 位置 用途
title <head><title> 页面标题
extra_css <head> 末尾 额外 CSS(如 CDN 样式链接)
page_css <style> 末尾 页面特定 CSS
sidebar_nav 侧边栏 <nav> 导航链接
content 主内容区 页面主要内容 HTML
extra_js </body> 额外 JS(如 CDN 脚本、页面业务逻辑)

2. 新页面创建规范

2.1 最小模板示例

{% extends "base.html" %}

{% block title %}页面标题 - 钢琴练习方案系统{% endblock %}

{% block page_css %}
<style>
/* 仅放置页面特有的 CSS */
</style>
{% endblock %}

{% block sidebar_nav %}
<!-- 复制 base.html 的导航结构,根据当前页面的 active 状态调整 -->
<a class="nav-link" href="/">
    <i class="bi bi-people"></i> 学员管理
</a>
<!-- ... 其他链接 ... -->
{% endblock %}

{% block content %}
<!-- 页面主要内容 HTML -->
{% endblock %}

{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/xxx/xxx.min.js"></script>
<script>
// 页面业务逻辑 JS
</script>
{% endblock %}

2.2 侧边栏导航 active 状态

当前页面对应的链接需要添加 active class

<a class="nav-link active" href="/settings">
    <i class="bi bi-gear"></i> 问题配置
</a>

2.3 用户信息显示

base.html 在移动端和桌面端都有用户信息占位符,页面 JS 需要在 DOMContentLoaded 中设置:

document.addEventListener('DOMContentLoaded', function() {
    fetch('/api/check-login').then(r => r.json()).then(data => {
        const userDisplay = data.username + ' (' + (data.role === 'admin' ? '管理员' : '普通用户') + ')';
        document.getElementById('currentUserDisplay').textContent = userDisplay;
        const mobileDisplay = document.getElementById('mobileUserDisplay');
        if (mobileDisplay) mobileDisplay.textContent = userDisplay;
    });
});

3. 移动端响应式设计

3.1 断点

断点 屏幕宽度 布局
桌面端 ≥ 768px 侧边栏固定在左侧,主内容区在右侧
移动端 < 768px 顶部导航栏 + 隐藏侧边栏(汉堡按钮展开)

3.2 CSS 规范

所有移动端样式统一写在 base.html@media (max-width: 767.98px) 块中,各页面不需要重复定义。

侧边栏移动端样式(base.html):

/* 移动端响应式 */
@media (max-width: 767.98px) {
    .sidebar {
        position: fixed;
        top: 60px;              /* 向下偏移,避免被顶部导航栏挡住 */
        left: 0;
        width: 100%;
        min-height: auto;
        max-height: calc(100vh - 60px);  /* 最大高度为屏幕高度减去导航栏 */
        z-index: 1040;
        overflow-y: auto;
        transform: translateY(-100%);      /* 默认隐藏 */
        transition: transform 0.3s ease;
    }
    .sidebar.collapsed {
        transform: translateY(-100%);       /* 隐藏状态 */
    }
    .sidebar:not(.collapsed) {
        transform: translateY(0);          /* 显示状态 */
    }
    .main-content {
        margin-top: 60px;
        padding: 10px;
    }
    .mobile-nav-toggle {
        display: flex !important;
    }
}

3.3 移动端导航栏 HTMLbase.html

<nav class="mobile-nav-toggle navbar navbar-dark bg-dark d-flex d-md-none"
     style="position:fixed;top:0;left:0;right:0;z-index:1050;padding:10px;">
    <div class="container-fluid d-flex justify-content-between align-items-center">
        <div>
            <span class="navbar-brand mb-0"><i class="bi bi-music-note-beamed"></i> 钢琴方案</span>
            <small id="mobileUserDisplay" class="d-block text-white-50" style="font-size:10px;"></small>
        </div>
        <button class="btn btn-outline-light btn-sm" onclick="toggleMobileNav()">
            <i class="bi bi-list"></i>
        </button>
    </div>
</nav>

注意

  • mobile-nav-toggle 类控制显示/隐藏(桌面端隐藏,移动端显示)
  • z-index: 1050 确保在侧边栏之上(侧边栏是 1040)

4. JavaScript 规范

4.1 公共函数(base.html 提供)

函数 用途
toggleMobileNav() 切换侧边栏展开/收起
logout() 退出登录
showChangePasswordModal() 显示修改密码弹窗

4.2 页面 JS 放置

  • 公共函数(如 showToast)放在 extra_js block 中
  • 页面初始化逻辑放在 document.addEventListener('DOMContentLoaded', ...)
  • 业务函数在 extra_js block 中定义

4.3 修改密码

base.html 已包含修改密码弹窗 HTML 和 JS。各页面不需要重复定义 showChangePasswordModal() 和相关 DOM 绑定。

5. 避免重复代码

5.1 禁止重复的内容

以下内容禁止在各页面模板中重复定义:

  • <head> 标签内容(meta, title, CDN 链接)
  • 基础 CSSbody, sidebar, card 等)
  • 移动端响应式 CSS
  • 移动端顶部导航栏 HTML
  • 侧边栏 HTML 结构
  • Bootstrap JS CDN
  • toggleMobileNav(), logout(), showChangePasswordModal() 函数
  • 修改密码弹窗 HTML 和 JS

5.2 正确做法

如果需要... 应该...
页面特定 CSS {% block page_css %} 中添加
额外 JS 库 {% block extra_js %} 开头引入
页面业务逻辑 {% block extra_js %} 中定义
导航 active 状态 {% block sidebar_nav %} 中设置

6. 文件结构

app/templates/
├── base.html          # 基础模板(核心,统一侧边栏)
├── index.html         # 学员管理
├── home.html          # 默认首页(统计信息)
├── student.html       # 学员详情(URL导航)
├── plan_edit.html     # 方案编辑(URL导航)
├── settings.html      # 问题配置
├── classes.html       # 班级管理
├── users.html         # 用户管理
├── templates.html     # 模板管理
├── api_settings.html # API设置
├── login.html         # 登录页(独立,无侧边栏)
├── setup.html        # 初始化页(独立)
└── wechat_card.html  # 微信卡片(独立)

注意:login.htmlsetup.htmlwechat_card.html 是独立页面,不继承 base.html。

7. URL 导航模式

系统支持两种导航模式:

模式 说明 示例
SPA 模式 点击学员卡片弹窗查看详情 原 index.html 模式
URL 模式 通过 URL 直接访问 /student/<id>, /plan/<id>/edit

推荐使用 URL 模式,便于分享和书签。

8. 更新日志

日期 版本 变更内容
2026-04-21 v1.0 初始文档,定义 base.html 模板继承模式
2026-04-23 v1.1 添加 URL 导航模式说明;新增 home.html, student.html, plan_edit.html
2026-04-27 v1.2 方案编辑/详情页导航优化:bfcache 处理、pageshow 事件、sessionStorage 标记

9. 方案编辑页面导航

9.1 编辑流程

学员详情/方案列表 → 方案详情 → 编辑 → 保存 → 返回方案详情/学员详情/方案列表

9.2 导航实现

操作 实现方式
保存后返回 history.back() 返回上一页(编辑页),浏览器从 bfcache 恢复方案详情页
方案详情加载 pageshow 事件检测 bfcache 恢复,自动调用 loadPlan() 刷新数据
返回按钮 history.back() 返回上一页

9.3 sessionStorage 标记

标记 用途
plan_detail_referrer 记录方案详情页的来源(student/plans),编辑保存后用于决定跳转目标
needs_refresh_recommended 标记需要刷新推荐方案列表
plans_needs_refresh 标记需要刷新方案列表页