Files
railwaysystem/docs/visual/data-model.html
T

658 lines
36 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>核心数据模型与实体关系 (ER) - RailwaySystem SYFJGYD学员管理系统</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%231d4ed8' d='M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2.23l2-2H14l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-3.58-4-8-4z'/></svg>">
<link rel="stylesheet" href="assets/docs.css">
<style>
/* ER 关系图视觉容器 */
.er-canvas-card {
background: #ffffff;
border: 1px solid var(--border-light);
border-radius: var(--radius-lg);
padding: 2rem;
box-shadow: var(--shadow-card);
margin-bottom: 2.5rem;
}
.er-svg-wrap {
width: 100%;
overflow-x: auto;
padding: 0.5rem 0;
}
.er-svg {
min-width: 960px;
width: 100%;
height: auto;
display: block;
}
.er-entity {
cursor: pointer;
transition: all 0.15s ease;
}
.er-entity:hover {
filter: drop-shadow(0 4px 10px rgba(30, 64, 175, 0.2));
}
.er-entity:hover rect.entity-box {
stroke: var(--rail-blue);
stroke-width: 2px;
}
/* 实体网格卡片 */
.entity-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.25rem;
margin-top: 1.5rem;
}
.entity-card {
background: #ffffff;
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: 1.25rem;
box-shadow: var(--shadow-card);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
}
.entity-card:hover {
border-color: var(--border-medium);
box-shadow: var(--shadow-card-hover);
transform: translateY(-2px);
}
</style>
</head>
<body>
<!-- 顶部主导航 -->
<nav class="navbar">
<div class="nav-brand-wrap">
<a href="index.html" class="nav-brand">
<svg viewBox="0 0 24 24"><path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2.23l2-2H14l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-3.58-4-8-4zM7.5 17c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm9 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6H6V6h12v5z"/></svg>
<div>
<div class="brand-title">RailwaySystem</div>
<div class="brand-sub">SYFJGYD学员管理系统</div>
</div>
</a>
<span class="brand-pill">Final Candidate · 待正式部署</span>
</div>
<ul class="nav-menu">
<li><a href="index.html" class="nav-link">首页</a></li>
<li><a href="business-flow.html" class="nav-link">业务</a></li>
<li><a href="architecture.html" class="nav-link">系统</a></li>
<li><a href="testing.html" class="nav-link">质量</a></li>
<li><a href="release-status.html" class="nav-link">发布</a></li>
<li><a href="modules.html" class="nav-link">14大业务模块清单</a></li>
<li><a href="data-model.html" class="nav-link active">实体关系模型(ER)</a></li>
<li><a href="deployment.html" class="nav-link">部署拓扑与脚本Delta</a></li>
<li><a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/handbook/08-deployment-and-release.md" target="_blank" rel="noopener noreferrer" class="nav-handbook-link">&gt; 查阅部署发布手册</a></li>
<li><a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/README.md" target="_blank" rel="noopener noreferrer" class="nav-back-link">&larr; 文档中心</a></li>
</ul>
</nav>
<!-- 页面主体容器 -->
<main class="page-container">
<header class="page-hero">
<div class="hero-pretitle">Entity Relationship & Persistence</div>
<h1 class="hero-title-main">核心业务实体关系模型 (ER)</h1>
<p class="hero-lead-text">
聚焦于 10 大核心教务实体的关联拓扑与物理约束。杜绝平铺冗长数据库大表,点击任意实体卡片或图谱节点即可查看物理表名、PK主键生成机制、外键引用与唯一性约束。
</p>
</header>
<!-- 简化 ER 关系图 SVG 容器 -->
<section class="er-canvas-card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 0.75rem;">
<div>
<h2 style="font-size: 1.25rem; font-weight: 700; color: var(--navy-900);">10 大核心教务实体关系拓扑</h2>
<p style="font-size: 0.82rem; color: var(--text-muted); margin-top: 0.2rem;">点击实体节点展开物理表与约束细节</p>
</div>
<div style="font-size: 0.8rem; color: var(--text-muted);">
<span>1:N 一对多关联</span> · <span>N:M 多对多关联</span> · <span>XOR 互斥排他</span>
</div>
</div>
<div class="er-svg-wrap">
<svg class="er-svg" viewBox="0 0 1000 520" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="er-arrow" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
<path d="M0,1 L7,4 L0,7 Z" fill="#1d4ed8" />
</marker>
</defs>
<!-- 关系连线 -->
<!-- Plan -> PlanCourse (1:N) -->
<line x1="420" y1="100" x2="490" y2="100" stroke="#1d4ed8" stroke-width="2" marker-end="url(#er-arrow)" />
<text x="455" y="90" text-anchor="middle" font-size="10" fill="#64748b">1:N</text>
<!-- Course -> PlanCourse (1:N) -->
<line x1="720" y1="100" x2="650" y2="100" stroke="#1d4ed8" stroke-width="2" marker-end="url(#er-arrow)" />
<text x="685" y="90" text-anchor="middle" font-size="10" fill="#64748b">1:N</text>
<!-- Plan -> Enrollment (1:N) -->
<line x1="340" y1="140" x2="340" y2="210" stroke="#1d4ed8" stroke-width="2" marker-end="url(#er-arrow)" />
<text x="355" y="180" font-size="10" fill="#64748b">1:N</text>
<!-- Student -> Enrollment (1:N) -->
<line x1="170" y1="250" x2="260" y2="250" stroke="#1d4ed8" stroke-width="2" marker-end="url(#er-arrow)" />
<text x="215" y="240" text-anchor="middle" font-size="10" fill="#64748b">1:N</text>
<!-- PlanCourse -> Lesson (1:N) -->
<line x1="570" y1="140" x2="570" y2="210" stroke="#1d4ed8" stroke-width="2" marker-end="url(#er-arrow)" />
<text x="585" y="180" font-size="10" fill="#64748b">1:N</text>
<!-- Classroom -> Lesson (1:N, XOR) -->
<line x1="780" y1="220" x2="650" y2="240" stroke="#1d4ed8" stroke-width="1.5" stroke-dasharray="3 3" marker-end="url(#er-arrow)" />
<text x="720" y="225" font-size="10" fill="#64748b">XOR</text>
<!-- Site -> Lesson (1:N, XOR) -->
<line x1="780" y1="280" x2="650" y2="260" stroke="#1d4ed8" stroke-width="1.5" stroke-dasharray="3 3" marker-end="url(#er-arrow)" />
<text x="720" y="280" font-size="10" fill="#64748b">XOR</text>
<!-- Enrollment -> Report (1:1) -->
<line x1="340" y1="290" x2="340" y2="360" stroke="#1d4ed8" stroke-width="2" marker-end="url(#er-arrow)" />
<text x="355" y="330" font-size="10" fill="#64748b">1:1</text>
<!-- Enrollment -> Checkin (1:N) -->
<line x1="410" y1="280" x2="490" y2="380" stroke="#1d4ed8" stroke-width="1.5" marker-end="url(#er-arrow)" />
<!-- Lesson -> Checkin (1:N) -->
<line x1="570" y1="290" x2="570" y2="360" stroke="#1d4ed8" stroke-width="2" marker-end="url(#er-arrow)" />
<text x="585" y="330" font-size="10" fill="#64748b">1:N</text>
<!-- 1. Plan 实体 -->
<g class="er-entity" onclick="showEntityDetail('plan')">
<rect class="entity-box" x="260" y="60" width="160" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="260" y="60" width="160" height="26" rx="8" fill="#eff6ff" />
<text x="340" y="78" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">Plan (培训计划)</text>
<text x="275" y="102" font-size="11" fill="#334155">PK: plan_id (UUID)</text>
<text x="275" y="122" font-size="10" fill="#64748b">plan_status, recruit_num</text>
</g>
<!-- 2. PlanCourse 实体 -->
<g class="er-entity" onclick="showEntityDetail('plancourse')">
<rect class="entity-box" x="490" y="60" width="160" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="490" y="60" width="160" height="26" rx="8" fill="#eff6ff" />
<text x="570" y="78" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">PlanCourse (计划课程)</text>
<text x="505" y="102" font-size="11" fill="#334155">PK: plan_course_id</text>
<text x="505" y="122" font-size="10" fill="#64748b">FK: plan_id, c_id, t_id</text>
</g>
<!-- 3. Course 实体 -->
<g class="er-entity" onclick="showEntityDetail('course')">
<rect class="entity-box" x="720" y="60" width="150" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="720" y="60" width="150" height="26" rx="8" fill="#eff6ff" />
<text x="795" y="78" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">Course (课程大纲)</text>
<text x="735" y="102" font-size="11" fill="#334155">PK: c_id</text>
<text x="735" y="122" font-size="10" fill="#64748b">c_code (UK), c_duration</text>
</g>
<!-- 4. Student 实体 -->
<g class="er-entity" onclick="showEntityDetail('student')">
<rect class="entity-box" x="20" y="210" width="150" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="20" y="210" width="150" height="26" rx="8" fill="#eff6ff" />
<text x="95" y="228" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">Student (学员档案)</text>
<text x="35" y="252" font-size="11" fill="#334155">PK: s_id (UUID)</text>
<text x="35" y="272" font-size="10" fill="#64748b">id_card_last4, status</text>
</g>
<!-- 5. Enrollment 实体 -->
<g class="er-entity" onclick="showEntityDetail('enrollment')">
<rect class="entity-box" x="260" y="210" width="160" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="260" y="210" width="160" height="26" rx="8" fill="#eff6ff" />
<text x="340" y="228" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">Enrollment (参训名单)</text>
<text x="275" y="252" font-size="11" fill="#334155">PK: enrollment_id</text>
<text x="275" y="272" font-size="10" fill="#64748b">UK(plan_id, student_id)</text>
</g>
<!-- 6. Lesson 实体 -->
<g class="er-entity" onclick="showEntityDetail('lesson')">
<rect class="entity-box" x="490" y="210" width="160" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="490" y="210" width="160" height="26" rx="8" fill="#eff6ff" />
<text x="570" y="228" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">Lesson (排课课次)</text>
<text x="505" y="252" font-size="11" fill="#334155">PK: lesson_id</text>
<text x="505" y="272" font-size="10" fill="#64748b">venue_type (XOR互斥)</text>
</g>
<!-- 7. Classroom 实体 -->
<g class="er-entity" onclick="showEntityDetail('classroom')">
<rect class="entity-box" x="780" y="195" width="160" height="55" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="780" y="195" width="160" height="22" rx="8" fill="#eff6ff" />
<text x="860" y="211" text-anchor="middle" font-size="11" font-weight="700" fill="#1e40af">Classroom (理论教室)</text>
<text x="795" y="235" font-size="10" fill="#334155">PK: class_room_id, capacity</text>
</g>
<!-- 8. Site 实体 -->
<g class="er-entity" onclick="showEntityDetail('site')">
<rect class="entity-box" x="780" y="265" width="160" height="55" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="780" y="265" width="160" height="22" rx="8" fill="#eff6ff" />
<text x="860" y="281" text-anchor="middle" font-size="11" font-weight="700" fill="#1e40af">Site (实训场地)</text>
<text x="795" y="305" font-size="10" fill="#334155">PK: site_id (UUID), capacity</text>
</g>
<!-- 9. Report 实体 -->
<g class="er-entity" onclick="showEntityDetail('report')">
<rect class="entity-box" x="260" y="360" width="160" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="260" y="360" width="160" height="26" rx="8" fill="#eff6ff" />
<text x="340" y="378" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">Report (现场报到)</text>
<text x="275" y="402" font-size="11" fill="#334155">PK: report_id</text>
<text x="275" y="422" font-size="10" fill="#64748b">FK: enrollment_id, time</text>
</g>
<!-- 10. Checkin 实体 -->
<g class="er-entity" onclick="showEntityDetail('checkin')">
<rect class="entity-box" x="490" y="360" width="160" height="80" rx="8" fill="#ffffff" stroke="#1d4ed8" stroke-width="1.5" />
<rect x="490" y="360" width="160" height="26" rx="8" fill="#eff6ff" />
<text x="570" y="378" text-anchor="middle" font-size="12" font-weight="700" fill="#1e40af">Checkin (课堂考勤)</text>
<text x="505" y="402" font-size="11" fill="#334155">PK: checkin_id</text>
<text x="505" y="422" font-size="10" fill="#64748b">FK: enrollment_id, lesson_id</text>
</g>
</svg>
</div>
</section>
<!-- 实体卡片速查网格 (点击展开 Drawer) -->
<section class="content-section">
<div class="section-header">
<div>
<h2 class="section-title">10 大核心实体物理映射与核心约束</h2>
<p class="section-subtitle">点击卡片可快速展开物理表结构、主键生成机制与外键约束详情。</p>
</div>
</div>
<div class="entity-grid">
<div class="entity-card" onclick="showEntityDetail('plan')">
<div class="card-top">
<span class="badge badge-pass">实体 1</span>
<span class="card-code">train_class_plan</span>
</div>
<h3 class="card-title">培训班计划 (ClassPlan)</h3>
<p class="card-body-text">主键为 32 位 UUID,严格维护起止时间窗口与生命周期状态(0草稿/1进行中/2已结束)。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('plancourse')">
<div class="card-top">
<span class="badge badge-pass">实体 2</span>
<span class="card-code">train_plan_course</span>
</div>
<h3 class="card-title">计划绑定课程 (PlanCourse)</h3>
<p class="card-body-text">班级与课程 N:M 映射枢纽,关联授课教师,外键依赖计划与课程主表。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('course')">
<div class="card-top">
<span class="badge badge-pass">实体 3</span>
<span class="card-code">train_course</span>
</div>
<h3 class="card-title">课程主档 (Course)</h3>
<p class="card-body-text">课程编码全局唯一(c_code),维护理论学时与实操学时,供开班总学时累加。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('enrollment')">
<div class="card-top">
<span class="badge badge-pass">实体 4</span>
<span class="card-code">train_enrollment</span>
</div>
<h3 class="card-title">参训报名名单 (Enrollment)</h3>
<p class="card-body-text">物理唯一约束 uk_plan_student 杜绝重复入班,维护学员在班级的参训状态流转。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('student')">
<div class="card-top">
<span class="badge badge-pass">实体 5</span>
<span class="card-code">train_student</span>
</div>
<h3 class="card-title">学员基础档案 (Student)</h3>
<p class="card-body-text">32位UUID主键,身份证密文哈希存储与末4位掩码展示,维护学员主档状态(1正常/0停用)。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('lesson')">
<div class="card-top">
<span class="badge badge-pass">实体 6</span>
<span class="card-code">train_lesson</span>
</div>
<h3 class="card-title">排课课次 (Lesson)</h3>
<p class="card-body-text">上课时段与师资配置,严格校验场地 XOR 互斥约束,禁止跨计划绑定课程。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('classroom')">
<div class="card-top">
<span class="badge badge-pass">实体 7</span>
<span class="card-code">train_classroom_info</span>
</div>
<h3 class="card-title">理论教室 (Classroom)</h3>
<p class="card-body-text">主键 class_room_id,维护物理容量上限与可用状态,支撑 0/1/N 电子班牌挂载。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('site')">
<div class="card-top">
<span class="badge badge-pass">实体 8</span>
<span class="card-code">train_training_site</span>
</div>
<h3 class="card-title">实训演练场地 (Site)</h3>
<p class="card-body-text">主键 32 位 UUID,维护专业演练场地类型与最大容量,排课中参与场地互斥冲突校验。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('report')">
<div class="card-top">
<span class="badge badge-pass">实体 9</span>
<span class="card-code">train_class_report</span>
</div>
<h3 class="card-title">现场开班报到流水 (Report)</h3>
<p class="card-body-text">学员现场核验凭证,外键强关联 enrollment_id,报到成功强事务联动学员参训状态为已报到。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
<div class="entity-card" onclick="showEntityDetail('checkin')">
<div class="card-top">
<span class="badge badge-pass">实体 10</span>
<span class="card-code">train_student_checkin</span>
</div>
<h3 class="card-title">课次考勤打卡 (StudentCheckin)</h3>
<p class="card-body-text">记录学员课次打卡时间,保障签到签退幂等性防覆盖,人工代签自动置 manual_flag=1 审计留痕。</p>
<div style="font-size: 0.78rem; color: var(--rail-blue); font-weight: 600;">查看物理定义 &rarr;</div>
</div>
</div>
</section>
<!-- 完整 21 表事实源引导 (不平铺冗长表格,提供事实源折叠与指引) -->
<section class="content-section" style="margin-bottom: 0;">
<div class="accordion-item">
<div class="accordion-header">
<div class="accordion-title-group">
<span class="badge badge-neutral">完整 21 张物理业务表</span>
<span style="font-size: 0.95rem; font-weight: 600; color: var(--navy-900);">查看系统全量数据表归属与数据库事实源索引</span>
</div>
<svg class="accordion-arrow" viewBox="0 0 20 20"><path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/></svg>
</div>
<div class="accordion-body">
<p style="margin-bottom: 1rem; color: var(--text-secondary);">
系统一期由 <strong>21 张培训业务表</strong> (<code>train_*</code>) 与 <strong>8 张平台底座权限审计表</strong> (<code>sys_*</code>) 构成,表清单契约与 DDL 严格冻结于 <code>docs/database-v2/</code> 目录下:
</p>
<div class="data-table-wrap">
<table class="data-table">
<thead>
<tr>
<th>表分类</th>
<th>物理表清单</th>
<th>职责描述</th>
<th>事实源文件</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>基础资料表 (9)</strong></td>
<td><code>train_teacher</code>, <code>train_student</code>, <code>train_course</code>, <code>train_resource</code>, <code>train_course_resource</code>, <code>train_classroom_info</code>, <code>train_training_site</code>, <code>train_electronic_board</code>, <code>train_dormitory_info</code></td>
<td>师资、学员、课程、统一资源、教室、场地、班牌设备、宿舍房间基础信息</td>
<td><code>docs/database-v2/01_table_inventory.md</code></td>
</tr>
<tr>
<td><strong>培训组织表 (4)</strong></td>
<td><code>train_class_plan</code>, <code>train_plan_course</code>, <code>train_enrollment</code>, <code>train_lesson</code></td>
<td>开班计划、课程绑定、参训报名名单、教学活动课次核心教务编排</td>
<td><code>docs/database-v2/01_table_inventory.md</code></td>
</tr>
<tr>
<td><strong>培训执行表 (4)</strong></td>
<td><code>train_accommodation</code>, <code>train_dorm_auth</code>, <code>train_class_report</code>, <code>train_student_checkin</code></td>
<td>学员住宿安排、门禁授权同步、现场报到核验、课次打卡考勤流水</td>
<td><code>docs/database-v2/01_table_inventory.md</code></td>
</tr>
<tr>
<td><strong>培训结果表 (4)</strong></td>
<td><code>train_score_item</code>, <code>train_student_score</code>, <code>train_study_duration</code>, <code>train_student_portrait</code></td>
<td>考核项配置、学员单项成绩、自主学时、学员全期画像评估 (只读模型 PASS)</td>
<td><code>docs/database-v2/01_table_inventory.md</code></td>
</tr>
<tr>
<td><strong>系统底座表 (8)</strong></td>
<td><code>sys_user</code>, <code>sys_role</code>, <code>sys_menu</code>, <code>sys_dept</code>, <code>sys_oper_log</code>, <code>sys_logininfor</code>, <code>sys_dict_type</code>, <code>sys_dict_data</code></td>
<td>RBAC 鉴权、路由菜单、操作审计日志与系统字典底座</td>
<td><code>docs/database-v2/00_init_ruoyi_sys.sql</code></td>
</tr>
</tbody>
</table>
</div>
<div style="margin-top: 1rem;">
<a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/handbook/04-data-model-overview.md" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="font-size: 0.82rem;">查阅数据库全量设计手册 &rarr;</a>
</div>
</div>
</div>
</section>
</main>
<!-- 侧边抽屉组件 (Drawer) -->
<div class="drawer-backdrop" id="drawer-backdrop">
<div class="drawer-panel">
<div class="drawer-header">
<h3 class="drawer-title" id="drawer-title">实体定义</h3>
<button class="drawer-close" id="drawer-close-btn" type="button" aria-label="关闭">&times;</button>
</div>
<div class="drawer-body" id="drawer-body">
<!-- 动态注入实体详情 -->
</div>
</div>
</div>
<!-- 页脚 -->
<footer class="site-footer">
<p>RailwaySystem SYFJGYD学员管理系统 · 交付候选版 (Final Candidate)</p>
<p style="margin-top: 0.35rem; color: var(--text-muted);">
数据模型与实体关系 · <a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/handbook/04-data-model-overview.md" target="_blank" rel="noopener noreferrer">查阅数据模型手册</a>
</p>
</footer>
<script src="assets/docs.js"></script>
<script>
const entityDict = {
plan: {
title: "实体定义: ClassPlan (培训班主档计划)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_class_plan</code></p>
<p><strong>主键</strong>:<code>plan_id VARCHAR(32)</code>(后端自动生成 32 位 UUID,全局唯一)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">核心业务字段与类型</div>
<ul style="padding-left:1.2rem; line-height:1.8;">
<li><code>plan_name VARCHAR(100)</code>:培训班名称</li>
<li><code>plan_status INT</code>:0:草稿, 1:进行中, 2:已结束</li>
<li><code>start_time TIMESTAMP</code>:培训开始时间</li>
<li><code>end_time TIMESTAMP</code>:培训结束时间</li>
<li><code>report_deadline TIMESTAMP</code>:现场报到截止时间</li>
<li><code>recruit_num INT</code>:招生容量上限(硬拦截依据)</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">核心关联与业务约束</div>
<p>作为教务编排的根实体,下游一对多关联 <code>train_plan_course</code>、<code>train_enrollment</code>;结训时通过强事务批量完成关联活动学员。</p>
</div>
`
},
plancourse: {
title: "实体定义: PlanCourse (计划绑定课程)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_plan_course</code></p>
<p><strong>主键</strong>:<code>plan_course_id BIGINT</code>(自增/序列主键)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">核心关联外键</div>
<ul style="padding-left:1.2rem; line-height:1.8;">
<li><code>plan_id VARCHAR(32)</code>:所属培训计划外键</li>
<li><code>c_id BIGINT</code>:绑定课程大纲外键</li>
<li><code>t_id VARCHAR(32)</code>:指派主讲教师外键</li>
</ul>
</div>
<div class="drawer-section">
<div class="drawer-section-title">核心约束</div>
<p>作为排课课次 <code>train_lesson</code> 的唯一课程依赖,排课时强校验课次所属计划与 <code>plan_course_id</code> 严格一致,严禁跨计划错链。</p>
</div>
`
},
course: {
title: "实体定义: Course (课程大纲主档)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_course</code></p>
<p><strong>主键</strong>:<code>c_id BIGINT</code>(主键标识)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">唯一约束与核心字段</div>
<ul style="padding-left:1.2rem; line-height:1.8;">
<li><code>c_code VARCHAR(64) UNIQUE</code>:课程编码全局唯一</li>
<li><code>c_name VARCHAR(100)</code>:课程名称</li>
<li><code>c_duration INT</code>:标准课时数(大于 0)</li>
<li><code>status INT</code>:1:可用, 0:停用</li>
</ul>
</div>
`
},
enrollment: {
title: "实体定义: Enrollment (参训报名档案)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_enrollment</code></p>
<p><strong>主键</strong>:<code>enrollment_id BIGINT</code>(自增/序列主键)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">核心唯一约束 (防重复报名)</div>
<p><code>UNIQUE KEY uk_plan_student (plan_id, student_id)</code>:数据库层面强制杜绝同一学员在同一班级重复入班。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">参训状态机字段</div>
<p><code>enrollment_status INT</code>:0:待报到, 1:已报到, 2:请假, 3:退训, 4:已完成</p>
<p><code>complete_time TIMESTAMP</code>:结训完成时间(退训学员保持为空,受防破坏保护)</p>
</div>
`
},
student: {
title: "实体定义: Student (学员基础档案)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_student</code></p>
<p><strong>主键</strong>:<code>s_id VARCHAR(32)</code>(32 位 UUID)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">敏感信息脱敏与存储安全</div>
<ul style="padding-left:1.2rem; line-height:1.8;">
<li><code>id_card_ciphertext VARCHAR(255)</code>:身份证密文</li>
<li><code>id_card_hash VARCHAR(64)</code>:身份证哈希索引(用于精确检索)</li>
<li><code>id_card_last4 VARCHAR(4)</code>:末4位掩码(接口返回显示)</li>
<li><code>status INT</code>:1:正常, 0:停用(停用学员禁止报名)</li>
</ul>
</div>
`
},
lesson: {
title: "实体定义: Lesson (排课课次)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_lesson</code></p>
<p><strong>主键</strong>:<code>lesson_id BIGINT</code>(自增主键)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">场地互斥约束 (Venue XOR)</div>
<p><code>venue_type INT</code> (1:理论教室, 2:实训场地)。物理业务逻辑强校验 <code>class_room_id</code> 与 <code>site_id</code> 满足二选一严格互斥,不可同时为空或同时赋值。</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">时间与状态约束</div>
<p>必须满足 <code>start_time &lt; end_time</code>,且计划结训后强阻断新增或修改课次。</p>
</div>
`
},
classroom: {
title: "实体定义: Classroom (理论教室)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_classroom_info</code></p>
<p><strong>主键</strong>:<code>class_room_id VARCHAR(32)</code></p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">字段与物联扩展</div>
<p><code>room_name VARCHAR(100)</code>:教室门牌名称</p>
<p><code>capacity INT</code>:标准座位容量</p>
<p>支持下游一对多绑定 <code>train_electronic_board</code> 电子班牌终端。</p>
</div>
`
},
site: {
title: "实体定义: TrainingSite (实训演练场地)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_training_site</code></p>
<p><strong>主键</strong>:<code>site_id VARCHAR(32)</code>(32 位 UUID)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">专业场地特征</div>
<p><code>site_name VARCHAR(100)</code>:场地名称(如信号演练场、机务整备场)</p>
<p><code>capacity INT</code>:最大容纳实训人数</p>
<p>排课预检中触发 <code>SITE</code> 级别 Warning 预警。</p>
</div>
`
},
report: {
title: "实体定义: ClassReport (开班报到流水)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_class_report</code></p>
<p><strong>主键</strong>:<code>report_id BIGINT</code>(自增主键)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">强事务联动与防重约束</div>
<p><code>enrollment_id BIGINT</code>:外键关联参训记录。</p>
<p>报到动作自动流转 <code>enrollment_status = 1</code>,若已报到则强行拦截防重复报到 (40050)。</p>
</div>
`
},
checkin: {
title: "实体定义: StudentCheckin (课次考勤打卡)",
html: `
<div class="drawer-section">
<div class="drawer-section-title">物理表与主键</div>
<p><strong>物理表名</strong>:<code>train_student_checkin</code></p>
<p><strong>主键</strong>:<code>checkin_id BIGINT</code>(自增主键)</p>
</div>
<div class="drawer-section">
<div class="drawer-section-title">幂等性与人工审计标志</div>
<ul style="padding-left:1.2rem; line-height:1.8;">
<li><code>sign_in_time TIMESTAMP</code>:首次签到时间(重复签到保持首次不变)</li>
<li><code>sign_out_time TIMESTAMP</code>:首次签退时间(重复签退保持首次不变)</li>
<li><code>manual_flag INT</code>:0:自动打卡, 1:人工代签/补签(审计标志)</li>
</ul>
</div>
`
}
};
function showEntityDetail(key) {
const data = entityDict[key];
if (!data) return;
window.openDrawer(data.title, data.html);
}
</script>
</body>
</html>