Ver código fonte

更新说明文档,清理无关文件

stoney 1 semana atrás
pai
commit
a2f854b651

+ 32 - 8
README.md

@@ -33,20 +33,44 @@
 
 **需求详细说明**
 
-[点击查看需求说明文档](../prd/prd.md)
+[点击查看需求说明文档](../prd/需求规格说明书.md)
 
 ### 2 仓库说明
 
 仓库包含内容如下:
 
 ```bash
-exam           #学习教育与考试
-├── LICENSE    
-├── prd        #产出的需交付eelitech的需求说明文档
-├── prototype  #产品原型及演示demo
-├── README.md  
-└── userReq    #客户定制化需求
-    └── 学校教育考试系统需求.md
+exam                                   
+├── assets                                    # 资源文件
+│   ├── ~$report.docx
+│   ├── images
+│   │   └── 截屏2026-04-21 01.32.37.png
+│   ├── report_images
+│   │   ├── image1.png
+│   │   ├── image2.png
+│   │   ├── image3.png
+│   │   ├── image4.png
+│   │   ├── image5.png
+│   │   ├── image6.png
+│   │   ├── image7.png
+│   │   └── image8.png
+│   └── report.docx
+├── LICENSE
+├── prd                                       # 需求文档
+│   ├── 需求规格说明书.md
+├── prototype                                 # 高保真交互原型
+│   ├── css
+│   │   └── styles.css
+│   ├── index.html
+│   ├── js
+│   │   ├── app.js
+│   │   └── data.js
+│   └── vendor
+│       └── echarts.min.js
+├── README.md
+└── userReq                                   # 原始需求采集记录
+    ├── 学校教育考试系统需求.md
+    └── prompt.md
 ```
 
 ### 3 其他

+ 0 - 2
prd/reqIntro.md

@@ -1,2 +0,0 @@
-# 安全教育与考试系统
-

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 1560
prototype/assets/app.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 1237
prototype/assets/styles.css


prototype_opus/css/styles.css → prototype/css/styles.css


+ 561 - 5
prototype/index.html

@@ -3,12 +3,568 @@
 <head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>安全教育与考试系统工作台 Demo</title>
-  <link rel="stylesheet" href="./assets/styles.css">
+  <title>安全教育与考试 - 工作台</title>
+  <!-- Element UI CSS -->
+  <link rel="stylesheet" href="https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css">
+  <!-- Custom Styles -->
+  <link rel="stylesheet" href="./css/styles.css">
 </head>
 <body>
-  <div id="app"></div>
+  <div id="app">
+    <div class="layout">
+      <!-- 侧边栏 -->
+      <aside class="sidebar">
+        <div class="sidebar-logo">
+          <i class="el-icon-s-cooperation logo-icon"></i>
+          <span class="logo-text">安全教育与考试</span>
+        </div>
+        <ul class="nav-menu">
+          <li v-for="item in menuItems" :key="item.key"
+              :class="['nav-item', { active: activePage === item.key }]"
+              @click="activePage = item.key">
+            <i :class="item.icon"></i>
+            <span>{{ item.label }}</span>
+          </li>
+        </ul>
+      </aside>
+
+      <!-- 主内容区 -->
+      <div class="main-wrapper">
+        <!-- 顶部栏 -->
+        <header class="topbar">
+          <div class="breadcrumb">
+            <span>首页</span>
+            <span class="sep">/</span>
+            <span class="current">{{ currentPageLabel }}</span>
+          </div>
+          <div class="user-badge">
+            <el-avatar :size="32" icon="el-icon-user-solid"></el-avatar>
+            <span class="user-name">{{ currentRole.name }}</span>
+          </div>
+        </header>
+
+        <!-- 工作台页面 -->
+        <main class="content" v-if="activePage === 'workbench'">
+          <!-- 角色切换器 -->
+          <div class="page-toolbar">
+            <h2 class="page-title">工作台</h2>
+            <div class="role-switcher">
+              <el-button type="info" size="small" icon="el-icon-document" @click="activePage = 'prd'">需求说明</el-button>
+              <el-button type="success" size="small" icon="el-icon-s-operation" @click="activePage = 'flow'">业务流程说明</el-button>
+              <el-button v-for="btn in roleButtons" :key="btn.key"
+                :type="activeRole === btn.key ? 'primary' : 'default'"
+                size="small" @click="switchRole(btn.key)">
+                {{ btn.label }}
+              </el-button>
+            </div>
+          </div>
+
+          <!-- 用户信息卡 -->
+          <section class="panel">
+            <div class="panel-head">
+              <div class="panel-title"><h2>用户信息卡</h2></div>
+              <div class="panel-desc">当前推荐资料类型:{{ resolvedResourceTab }}</div>
+            </div>
+            <div class="user-card-grid">
+              <!-- 左:个人信息 -->
+              <div class="card-box meta-box">
+                <div class="meta-grid">
+                  <div class="meta-item"><span>用户姓名</span><strong>{{ currentRole.name }}</strong></div>
+                  <div class="meta-item"><span>学工号</span><strong>{{ currentRole.code }}</strong></div>
+                  <div class="meta-item"><span>用户类型</span><strong>{{ currentRole.type }}</strong></div>
+                  <div class="meta-item"><span>所属单位</span><strong>{{ currentRole.org }}</strong></div>
+                  <div class="meta-item" v-if="!isStaffRole"><span>实验室信息</span><strong>{{ currentRole.lab ? currentRole.lab.name + '(' + currentRole.lab.room + ')' : '暂无绑定实验室' }}</strong></div>
+                  <div class="meta-item" v-if="!isStaffRole"><span>负责人</span><strong>{{ currentRole.lab ? currentRole.lab.director : '不适用' }}</strong></div>
+                </div>
+                <div class="summary-tip" v-if="!isStaffRole">
+                  绑定状态:<strong>{{ bindingStatusText }}</strong>
+                  <el-button v-if="currentRole.bindingStatus === 'unbound'" type="text" size="mini" @click="openBindDialog('graduate')" style="margin-left:8px">去绑定</el-button>
+                </div>
+              </div>
+              <!-- 中:学时进度 -->
+              <div class="card-box status-box">
+                <div class="tag-row">
+                  <span class="mini-tag normal">{{ currentRole.term }}</span>
+                  <span v-if="currentRole.lab" :class="['mini-tag', levelClass(currentRole.lab.level)]">{{ currentRole.lab.level }}级实验室</span>
+                  <span v-if="currentRole.lab" class="mini-tag normal">{{ currentRole.lab.category }}</span>
+                </div>
+                <template v-if="currentRole.requiredHours > 0">
+                  <div class="hours-progress">
+                    <div class="progress-head">
+                      <span>年度学时进度</span>
+                      <strong>{{ currentRole.completedHours }} / {{ currentRole.requiredHours }} 学时</strong>
+                    </div>
+                    <div class="progress-bar"><span :style="{ width: hoursPercentage + '%' }"></span></div>
+                  </div>
+                  <div class="kpi-row">
+                    <div class="kpi-box">
+                      <span>完成度</span>
+                      <strong>{{ hoursPercentage }}<em>%</em></strong>
+                    </div>
+                    <div class="kpi-box">
+                      <span>目标考试</span>
+                      <strong class="kpi-exam">{{ currentRole.examType }}</strong>
+                    </div>
+                  </div>
+                </template>
+                <template v-else>
+                  <div class="kpi-row" style="margin-top:12px">
+                    <div class="kpi-box">
+                      <span>学时要求</span>
+                      <strong class="kpi-exam">无</strong>
+                    </div>
+                    <div class="kpi-box">
+                      <span>目标考试</span>
+                      <strong class="kpi-exam">{{ currentRole.examType }}</strong>
+                    </div>
+                  </div>
+                </template>
+              </div>
+              <!-- 右:任务状态 -->
+              <div class="card-box status-box">
+                <div class="panel-desc" style="margin-bottom:10px">当前学年任务状态</div>
+                <div class="status-list">
+                  <div class="status-item">
+                    <span>考试状态</span>
+                    <span :class="['status-chip', examChipClass]">{{ currentRole.examStatus }}</span>
+                  </div>
+                  <div class="status-item">
+                    <span>证书状态</span>
+                    <span :class="['status-chip', certChipClass]">{{ currentRole.certificateStatus }}</span>
+                  </div>
+                  <div class="status-item" v-if="currentRole.bindingStatus !== 'none'">
+                    <span>审批状态</span>
+                    <span :class="['status-chip', approvalChipClass]">{{ bindingStatusText }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </section>
+
+          <!-- 功能卡片区 -->
+          <section class="panel">
+            <div class="panel-head">
+              <div class="panel-title"><h2>工作台功能卡片区</h2></div>
+            </div>
+            <div class="feature-grid">
+              <!-- 学年数据统计 -->
+              <article class="feature-card">
+                <h3>学年数据统计</h3>
+                <p>按学年统计历年考试通过情况与成绩</p>
+                <div class="record-list record-list--fixed">
+                  <div class="record-item" v-for="(item, idx) in currentRole.yearlyRecords" :key="idx">
+                    <strong>{{ item.year }}</strong>
+                    <span>{{ item.status }} · {{ item.score }}</span>
+                  </div>
+                </div>
+              </article>
+              <!-- 学习任务 -->
+              <article class="feature-card">
+                <h3>学习任务</h3>
+                <p>学院下发的学习任务</p>
+                <div class="record-list record-list--scroll">
+                  <div class="record-item" v-for="(task, idx) in collegeTasks" :key="idx">
+                    <strong>{{ task.title }}</strong>
+                    <span><i :class="['task-dot', task.status]"></i> {{ task.note }}</span>
+                  </div>
+                </div>
+              </article>
+              <!-- 资料类型 -->
+              <article class="feature-card">
+                <h3>资料类型</h3>
+                <p>{{ currentRole.lab ? '根据实验室 ' + currentRole.lab.category + ' 分类推荐' : '默认推荐应知应会通识资源' }}</p>
+                <div class="record-list">
+                  <div class="record-item" v-for="(res, ri) in currentRole.resources" :key="ri">
+                    <strong>{{ res.title }}</strong>
+                    <span>{{ res.desc }}</span>
+                  </div>
+                </div>
+              </article>
+              <!-- 模拟练习 -->
+              <article class="feature-card">
+                <h3>模拟练习</h3>
+                <p>刷题进度和正确率</p>
+                <div class="meter-box">
+                  <strong>{{ currentRole.practice.done }} / {{ currentRole.practice.total }}</strong>
+                  <span>已完成题目数 / 题库总数</span>
+                </div>
+                <div class="meter-box">
+                  <strong>{{ currentRole.practice.correctRate }}</strong>
+                  <span>当前模拟练习正确率</span>
+                </div>
+              </article>
+              <!-- 考试信息 -->
+              <article class="feature-card">
+                <h3>{{ currentRole.examType }}</h3>
+                <p>{{ examCardDesc }}</p>
+                <div class="record-list record-list--fixed">
+                  <div class="record-item">
+                    <strong>学时完成</strong>
+                    <span>{{ currentRole.requiredHours > 0 ? currentRole.completedHours + ' / ' + currentRole.requiredHours + ' 学时' : '无学时要求' }}</span>
+                  </div>
+                  <div class="record-item">
+                    <strong>考试状态</strong>
+                    <span><i :class="['task-dot', currentRole.examStatus === '已通过' ? 'in-progress' : 'locked']"></i> {{ currentRole.examStatus }}</span>
+                  </div>
+                </div>
+                <el-button v-if="currentRole.examUnlocked && currentRole.examStatus !== '已通过'" type="primary" size="small" style="margin-top:auto" @click="simulatePassExam">模拟通过考试</el-button>
+                <el-button v-if="!currentRole.examUnlocked && currentRole.requiredHours > 0" type="info" size="small" disabled style="margin-top:auto">完成学时后解锁</el-button>
+                <el-button v-if="activeRole === 'newUndergraduate' && currentRole.examStatus === '已通过'" type="warning" size="small" style="margin-top:8px" @click="openBindDialog('undergraduate')">绑定实验室</el-button>
+              </article>
+            </div>
+          </section>
+
+          <!-- 安全素养资源区 -->
+          <div class="resource-section">
+            <div class="section-header">
+              <h3 class="section-title">实验室安全素养资源</h3>
+              <span class="section-hint" v-if="currentRole.lab">
+                <el-tag size="mini" type="warning">当前绑定:{{ currentRole.lab.category }}</el-tag>
+              </span>
+            </div>
+            <el-tabs v-model="activeResourceTab" type="card">
+              <el-tab-pane v-for="tab in resourceTabs" :key="tab" :label="tab" :name="tab">
+                <div class="resource-content resource-split">
+                  <!-- 左:资料区 -->
+                  <div class="resource-block resource-left">
+                    <div class="block-header">
+                      <h5><i class="el-icon-folder-opened"></i> 学习资料</h5>
+                      <el-button type="text" size="mini" icon="el-icon-more">查看更多</el-button>
+                    </div>
+                    <div class="doc-list">
+                      <div class="doc-card" v-for="(doc, i) in getResourceDocs(tab)" :key="i" @click="viewDocDetail(doc)">
+                        <div class="doc-icon">
+                          <i :class="doc.type === 'PDF' ? 'el-icon-document' : 'el-icon-data-board'"></i>
+                        </div>
+                        <div class="doc-body">
+                          <span class="doc-title">{{ doc.title }}</span>
+                          <div class="doc-meta-row">
+                            <span class="doc-meta">{{ doc.type }} · {{ doc.pages }}页</span>
+                            <span class="doc-duration"><i class="el-icon-time"></i> {{ doc.duration }}</span>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <!-- 右:题库区 -->
+                  <div class="resource-block resource-right">
+                    <div class="block-header">
+                      <h5><i class="el-icon-edit-outline"></i> 题库</h5>
+                      <el-button type="text" size="mini" icon="el-icon-more">查看更多</el-button>
+                    </div>
+                    <div class="question-grid">
+                      <div class="question-card" v-for="(cat, ci) in getResourceQuestions(tab).categories" :key="ci">
+                        <div class="question-card-icon"><i class="el-icon-notebook-2"></i></div>
+                        <span class="question-cat-name">{{ cat.name }}</span>
+                        <span class="question-cat-count">{{ cat.count }} 题</span>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-tab-pane>
+            </el-tabs>
+          </div>
+
+          <!-- 视频资源区 -->
+          <div class="video-section">
+            <h3 class="section-title" style="margin-bottom:16px">视频学习资源</h3>
+            <div class="video-toolbar">
+              <el-tabs v-model="activeVideoTab" @tab-click="videoPage = 1">
+                <el-tab-pane v-for="tab in videoTabs" :key="tab" :label="tab" :name="tab"></el-tab-pane>
+              </el-tabs>
+              <el-input v-model="videoSearch" placeholder="搜索视频" prefix-icon="el-icon-search" size="small" style="width:200px" clearable></el-input>
+            </div>
+            <div class="video-grid">
+              <div class="video-card" v-for="video in paginatedVideos" :key="video.id">
+                <div class="video-cover">
+                  <i class="el-icon-video-play"></i>
+                  <span class="video-duration">{{ video.duration }}</span>
+                </div>
+                <div class="video-info">
+                  <p class="video-title">{{ video.title }}</p>
+                  <p class="video-meta">{{ video.views }} 次观看</p>
+                </div>
+              </div>
+            </div>
+            <div class="video-pagination">
+              <el-pagination small layout="prev, pager, next" :total="filteredVideos.length" :page-size="4" :current-page.sync="videoPage"></el-pagination>
+            </div>
+          </div>
+        </main>
+
+        <!-- 数据统计页面 -->
+        <main class="content" v-if="activePage === 'statistics'">
+          <div class="page-toolbar">
+            <h2 class="page-title">数据统计</h2>
+          </div>
+          <el-tabs v-model="statsTopTab" type="border-card">
+            <el-tab-pane label="报表" name="current">
+              <!-- 考试情况统计 -->
+              <section class="stats-section">
+                <div class="stats-section-header">
+                  <h3>考试情况统计</h3>
+                  <div class="stats-section-actions">
+                    <el-radio-group v-model="statsExamType" size="mini" @change="renderStatsCharts">
+                      <el-radio-button label="graduate">研究生</el-radio-button>
+                      <el-radio-button label="undergraduate">本科生</el-radio-button>
+                    </el-radio-group>
+                    <el-button size="mini" type="primary" icon="el-icon-download">导出数据</el-button>
+                  </div>
+                </div>
+                <div class="stats-chart-row">
+                  <div class="stats-chart-box" ref="examBarChart"></div>
+                  <div class="stats-chart-box" ref="examPieChart"></div>
+                </div>
+                <div class="stats-filter-row">
+                  <el-select v-model="examFilterCollege" placeholder="筛选学院" size="mini" clearable style="width:160px">
+                    <el-option v-for="c in statisticsData.colleges" :key="c" :label="c" :value="c"></el-option>
+                  </el-select>
+                  <el-select v-model="examFilterExamType" placeholder="考试类型" size="mini" clearable style="width:140px">
+                    <el-option v-if="statsExamType === 'graduate'" label="新生分级考试" value="新生分级考试"></el-option>
+                    <el-option v-if="statsExamType === 'graduate'" label="应知应会考试" value="应知应会考试"></el-option>
+                    <el-option v-if="statsExamType === 'undergraduate'" label="新生入学考试" value="新生入学考试"></el-option>
+                    <el-option v-if="statsExamType === 'undergraduate'" label="应知应会考试" value="应知应会考试"></el-option>
+                    <el-option label="汇总" value="汇总"></el-option>
+                  </el-select>
+                </div>
+                <!-- 研究生:区分新生分级考试和老生应知应会 -->
+                <el-table v-if="statsExamType === 'graduate'" :data="filteredGraduateData" border size="small" style="width:100%;margin-top:8px" :span-method="examSpanMethod">
+                  <el-table-column prop="college" label="学院" min-width="150"></el-table-column>
+                  <el-table-column prop="examType" label="考试类型" min-width="120"></el-table-column>
+                  <el-table-column prop="total" label="应考人数" min-width="90"></el-table-column>
+                  <el-table-column prop="passed" label="通过人数" min-width="90"></el-table-column>
+                  <el-table-column prop="failed" label="未通过人数" min-width="100"></el-table-column>
+                  <el-table-column prop="absent" label="未考人数" min-width="90"></el-table-column>
+                  <el-table-column prop="rate" label="通过率" min-width="80"></el-table-column>
+                </el-table>
+                <!-- 本科生 -->
+                <el-table v-if="statsExamType === 'undergraduate'" :data="filteredUndergraduateData" border size="small" style="width:100%;margin-top:8px" :span-method="examSpanMethod">
+                  <el-table-column prop="college" label="学院" min-width="150"></el-table-column>
+                  <el-table-column prop="examType" label="考试类型" min-width="120"></el-table-column>
+                  <el-table-column prop="total" label="应考人数" min-width="90"></el-table-column>
+                  <el-table-column prop="passed" label="通过人数" min-width="90"></el-table-column>
+                  <el-table-column prop="failed" label="未通过人数" min-width="100"></el-table-column>
+                  <el-table-column prop="absent" label="未考人数" min-width="90"></el-table-column>
+                  <el-table-column prop="rate" label="通过率" min-width="80"></el-table-column>
+                </el-table>
+              </section>
+
+              <!-- 实验室与人员匹配 -->
+              <section class="stats-section">
+                <div class="stats-section-header">
+                  <h3>实验室与人员匹配</h3>
+                  <div class="stats-section-actions">
+                    <el-button size="mini" type="primary" icon="el-icon-download">导出各学院数据</el-button>
+                    <el-button size="mini" type="success" icon="el-icon-download">导出实验室人员明细</el-button>
+                  </div>
+                </div>
+                <div class="stats-chart-row">
+                  <div class="stats-chart-box stats-chart-full" ref="labLevelChart"></div>
+                </div>
+                <div class="stats-filter-row">
+                  <el-select v-model="labFilterCollege" placeholder="筛选学院" size="mini" clearable style="width:160px">
+                    <el-option v-for="c in statisticsData.colleges" :key="c" :label="c" :value="c"></el-option>
+                  </el-select>
+                  <el-select v-model="labFilterLevel" placeholder="实验室等级" size="mini" clearable style="width:140px">
+                    <el-option label="Ⅰ级" value="Ⅰ"></el-option>
+                    <el-option label="Ⅱ级" value="Ⅱ"></el-option>
+                    <el-option label="Ⅲ级" value="Ⅲ"></el-option>
+                    <el-option label="Ⅳ级" value="Ⅳ"></el-option>
+                  </el-select>
+                </div>
+                <el-table :data="filteredLabCollegeData" border size="small" style="width:100%;margin-top:8px">
+                  <el-table-column prop="college" label="学院" min-width="160"></el-table-column>
+                  <el-table-column prop="level1" label="Ⅰ级人数" min-width="100"></el-table-column>
+                  <el-table-column prop="level2" label="Ⅱ级人数" min-width="100"></el-table-column>
+                  <el-table-column prop="level3" label="Ⅲ级人数" min-width="100"></el-table-column>
+                  <el-table-column prop="level4" label="Ⅳ级人数" min-width="100"></el-table-column>
+                  <el-table-column prop="totalCount" label="总人数" min-width="90"></el-table-column>
+                </el-table>
+              </section>
+
+              <!-- 学时统计 -->
+              <section class="stats-section">
+                <div class="stats-section-header">
+                  <h3>学时统计</h3>
+                  <div class="stats-section-actions">
+                    <el-button size="mini" type="primary" icon="el-icon-download">导出数据</el-button>
+                  </div>
+                </div>
+                <div class="stats-chart-row">
+                  <div class="stats-chart-box stats-chart-full" ref="hoursChart"></div>
+                </div>
+                <div class="stats-filter-row">
+                  <el-select v-model="hoursFilterType" placeholder="用户类型" size="mini" clearable style="width:120px">
+                    <el-option label="研究生" value="研究生"></el-option>
+                    <el-option label="本科生" value="本科生"></el-option>
+                    <el-option label="教职工" value="教职工"></el-option>
+                  </el-select>
+                  <el-select v-model="hoursFilterCollege" placeholder="学院" size="mini" clearable style="width:160px">
+                    <el-option v-for="c in statisticsData.colleges" :key="c" :label="c" :value="c"></el-option>
+                  </el-select>
+                  <el-select v-model="hoursFilterGrade" placeholder="年级" size="mini" clearable style="width:120px">
+                    <el-option v-for="g in gradeOptions" :key="g" :label="g" :value="g"></el-option>
+                  </el-select>
+                  <el-input v-model="hoursFilterKey" placeholder="学号/姓名" size="mini" prefix-icon="el-icon-search" clearable style="width:160px"></el-input>
+                </div>
+                <el-table :data="filteredHoursDetail" border size="small" style="width:100%">
+                  <el-table-column prop="name" label="姓名" min-width="80"></el-table-column>
+                  <el-table-column prop="code" label="学号" min-width="120"></el-table-column>
+                  <el-table-column prop="grade" label="年级" min-width="80"></el-table-column>
+                  <el-table-column prop="college" label="所属学院" min-width="140"></el-table-column>
+                  <el-table-column prop="y1" label="第一年" min-width="70"></el-table-column>
+                  <el-table-column prop="y2" label="第二年" min-width="70"></el-table-column>
+                  <el-table-column prop="y3" label="第三年" min-width="70"></el-table-column>
+                  <el-table-column prop="y4" label="第四年" min-width="70"></el-table-column>
+                  <el-table-column prop="totalHours" label="总时长" min-width="80"></el-table-column>
+                </el-table>
+                <div style="text-align:center;margin-top:12px">
+                  <el-pagination small layout="prev, pager, next" :total="hoursDetailFiltered.length" :page-size="hoursPageSize" :current-page.sync="hoursPage"></el-pagination>
+                </div>
+              </section>
+            </el-tab-pane>
+          </el-tabs>
+        </main>
+
+        <!-- 系统配置页面 -->
+        <main class="content" v-if="activePage === 'config'">
+          <div class="page-toolbar">
+            <h2 class="page-title">分级学时配置</h2>
+          </div>
+          <div class="config-card">
+            <el-table :data="configTableData" border style="width: 100%">
+              <el-table-column prop="levelLabel" label="风险等级" width="200"></el-table-column>
+              <el-table-column prop="level" label="级别" width="100"></el-table-column>
+              <el-table-column label="准入学时要求(第一年)">
+                <template slot-scope="scope">
+                  <el-input-number v-model="scope.row.firstYear" :min="0" :max="100" size="small"></el-input-number>
+                </template>
+              </el-table-column>
+              <el-table-column label="年度学时要求(第二年起)">
+                <template slot-scope="scope">
+                  <el-input-number v-model="scope.row.annual" :min="0" :max="100" size="small"></el-input-number>
+                </template>
+              </el-table-column>
+            </el-table>
+            <div style="margin-top:16px;text-align:right">
+              <el-button type="primary" @click="saveConfig">保存配置</el-button>
+            </div>
+          </div>
+        </main>
+
+        <!-- 其他页面占位 -->
+        <main class="content" v-if="!['workbench','config','flow','prd','statistics'].includes(activePage)">
+          <div class="placeholder-page">
+            <i class="el-icon-s-opportunity"></i>
+            <p>{{ currentPageLabel }} - 功能开发中</p>
+          </div>
+        </main>
+
+        <!-- 需求说明页面 -->
+        <main class="content" v-if="activePage === 'prd'">
+          <div class="page-toolbar">
+            <h2 class="page-title">需求规格说明书</h2>
+            <el-button size="small" icon="el-icon-back" @click="activePage = 'workbench'">返回工作台</el-button>
+          </div>
+          <div class="prd-page" v-html="prdContent"></div>
+        </main>
+
+        <!-- 业务流程说明页面 -->
+        <main class="content" v-if="activePage === 'flow'">
+          <div class="page-toolbar">
+            <h2 class="page-title">业务流程说明</h2>
+            <el-button size="small" icon="el-icon-back" @click="activePage = 'workbench'">返回工作台</el-button>
+          </div>
+          <div class="flow-page">
+            <div class="flow-card" v-for="(flow, fi) in flowData" :key="fi">
+              <h3 class="flow-title">{{ flow.role }}</h3>
+              <div class="flow-chart">
+                <div class="flow-step" v-for="(step, si) in flow.steps" :key="si">
+                  <div class="flow-node" :class="step.type || ''">
+                    <span class="flow-num">{{ si + 1 }}</span>
+                    <span class="flow-text">{{ step.text }}</span>
+                  </div>
+                  <div class="flow-arrow" v-if="si < flow.steps.length - 1">
+                    <i class="el-icon-arrow-down"></i>
+                    <span class="flow-condition" v-if="step.condition">{{ step.condition }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </main>
+      </div>
+    </div>
+
+    <!-- 绑定实验室弹窗 -->
+    <el-dialog title="绑定实验室" :visible.sync="showBindDialog" width="620px" :close-on-click-modal="false">
+      <div class="bind-dialog-content">
+        <!-- 单一可搜索下拉框 -->
+        <el-select v-model="selectedLabId" placeholder="输入实验室名称、房号或楼栋关键词进行搜索" style="width:100%" filterable :filter-method="labFilterMethod" @change="onLabSelect">
+          <el-option v-for="lab in filteredLabs" :key="lab.id"
+            :label="lab.college + '-' + lab.name + '(' + lab.room + ')-' + lab.building"
+            :value="lab.id">
+          </el-option>
+        </el-select>
+        <p class="search-hint">此处请选择需要后续进入的科研实验室的名称,输入实验室名称、房号或楼栋关键词,再从检索结果中选择目标实验室。</p>
+
+        <!-- 选中后展示完整信息 -->
+        <div class="selected-lab-info" v-if="selectedLabDetail">
+          <el-descriptions :column="2" size="small" border>
+            <el-descriptions-item label="实验室名称">{{ selectedLabDetail.name }}({{ selectedLabDetail.room }})</el-descriptions-item>
+            <el-descriptions-item label="所在位置">{{ selectedLabDetail.building }}</el-descriptions-item>
+            <el-descriptions-item label="负责人">{{ selectedLabDetail.directorMasked }}</el-descriptions-item>
+            <el-descriptions-item label="所属学院">{{ selectedLabDetail.college }}</el-descriptions-item>
+            <el-descriptions-item label="实验室分级">
+              <el-tag :type="levelTagType(selectedLabDetail.level)" size="mini">{{ selectedLabDetail.level }}级</el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="实验室分类">{{ selectedLabDetail.category }}</el-descriptions-item>
+            <el-descriptions-item label="实验室类型">{{ selectedLabDetail.type }}</el-descriptions-item>
+          </el-descriptions>
+        </div>
+
+        <!-- 进入原因(本科生专属) -->
+        <div v-if="bindDialogMode === 'undergraduate'" style="margin-top:16px">
+          <el-input v-model="bindReason" type="textarea" placeholder="请填写进入实验室原因(必填)" :rows="2"></el-input>
+          <p class="search-hint" style="margin-top:6px">毕业论文或毕业设计,科创等相关需要在具体的实验室开展业务的情况可申请绑定实验室,绑定完毕后需完成对应的分级考试。</p>
+        </div>
+
+        <!-- 无需进入实验室(研究生专属) -->
+        <div v-if="bindDialogMode === 'graduate'" class="no-lab-option" style="margin-top:16px">
+          <el-checkbox v-model="noLabMode">无需进入实验室</el-checkbox>
+          <p class="search-hint" style="margin-top:4px">人文、体育等相关学院无需进入实验室的新研究生可选择此项,选择后学时要求为4小时,需通过应知应会考试。</p>
+        </div>
+
+        <!-- 须知复选框 -->
+        <div class="bind-notice" style="margin-top:16px">
+          <el-checkbox v-model="noticeChecked">我已阅读并同意<el-button type="text" @click="showNoticeDetail = true">《新生绑定实验室须知及责任说明》</el-button></el-checkbox>
+        </div>
+      </div>
+      <span slot="footer">
+        <el-button @click="showBindDialog = false">取消</el-button>
+        <el-button type="primary" @click="confirmBind" :disabled="!canBind">确认绑定</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 须知详情弹窗 -->
+    <el-dialog title="新生绑定实验室须知及责任说明" :visible.sync="showNoticeDetail" width="520px" append-to-body>
+      <div class="notice-content">
+        <p>1. 保证进入的实验室为导师实际使用实验室或属于当前实验室所属课题组。</p>
+        <p>2. 研究生期间涉及到操作实验室内重要危险源,必须绑定实验室。</p>
+      </div>
+      <span slot="footer">
+        <el-button type="primary" @click="showNoticeDetail = false">我已知晓</el-button>
+      </span>
+    </el-dialog>
+  </div>
+
+  <!-- Vendor Scripts (CDN - 生产环境应本地化) -->
+  <script src="https://unpkg.com/vue@2.7.16/dist/vue.min.js"></script>
+  <script src="https://unpkg.com/element-ui@2.15.14/lib/index.js"></script>
   <script src="./vendor/echarts.min.js"></script>
-  <script src="./assets/app.js"></script>
+  <script src="https://unpkg.com/marked@9.1.6/marked.min.js"></script>
+  <!-- App Scripts -->
+  <script src="./js/data.js"></script>
+  <script src="./js/app.js"></script>
 </body>
-</html>
+</html>

prototype_opus/js/app.js → prototype/js/app.js


prototype_opus/js/data.js → prototype/js/data.js


+ 0 - 570
prototype_opus/index.html

@@ -1,570 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-  <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>安全教育与考试 - 工作台</title>
-  <!-- Element UI CSS -->
-  <link rel="stylesheet" href="https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css">
-  <!-- Custom Styles -->
-  <link rel="stylesheet" href="./css/styles.css">
-</head>
-<body>
-  <div id="app">
-    <div class="layout">
-      <!-- 侧边栏 -->
-      <aside class="sidebar">
-        <div class="sidebar-logo">
-          <i class="el-icon-s-cooperation logo-icon"></i>
-          <span class="logo-text">安全教育与考试</span>
-        </div>
-        <ul class="nav-menu">
-          <li v-for="item in menuItems" :key="item.key"
-              :class="['nav-item', { active: activePage === item.key }]"
-              @click="activePage = item.key">
-            <i :class="item.icon"></i>
-            <span>{{ item.label }}</span>
-          </li>
-        </ul>
-      </aside>
-
-      <!-- 主内容区 -->
-      <div class="main-wrapper">
-        <!-- 顶部栏 -->
-        <header class="topbar">
-          <div class="breadcrumb">
-            <span>首页</span>
-            <span class="sep">/</span>
-            <span class="current">{{ currentPageLabel }}</span>
-          </div>
-          <div class="user-badge">
-            <el-avatar :size="32" icon="el-icon-user-solid"></el-avatar>
-            <span class="user-name">{{ currentRole.name }}</span>
-          </div>
-        </header>
-
-        <!-- 工作台页面 -->
-        <main class="content" v-if="activePage === 'workbench'">
-          <!-- 角色切换器 -->
-          <div class="page-toolbar">
-            <h2 class="page-title">工作台</h2>
-            <div class="role-switcher">
-              <el-button type="info" size="small" icon="el-icon-document" @click="activePage = 'prd'">需求说明</el-button>
-              <el-button type="success" size="small" icon="el-icon-s-operation" @click="activePage = 'flow'">业务流程说明</el-button>
-              <el-button v-for="btn in roleButtons" :key="btn.key"
-                :type="activeRole === btn.key ? 'primary' : 'default'"
-                size="small" @click="switchRole(btn.key)">
-                {{ btn.label }}
-              </el-button>
-            </div>
-          </div>
-
-          <!-- 用户信息卡 -->
-          <section class="panel">
-            <div class="panel-head">
-              <div class="panel-title"><h2>用户信息卡</h2></div>
-              <div class="panel-desc">当前推荐资料类型:{{ resolvedResourceTab }}</div>
-            </div>
-            <div class="user-card-grid">
-              <!-- 左:个人信息 -->
-              <div class="card-box meta-box">
-                <div class="meta-grid">
-                  <div class="meta-item"><span>用户姓名</span><strong>{{ currentRole.name }}</strong></div>
-                  <div class="meta-item"><span>学工号</span><strong>{{ currentRole.code }}</strong></div>
-                  <div class="meta-item"><span>用户类型</span><strong>{{ currentRole.type }}</strong></div>
-                  <div class="meta-item"><span>所属单位</span><strong>{{ currentRole.org }}</strong></div>
-                  <div class="meta-item" v-if="!isStaffRole"><span>实验室信息</span><strong>{{ currentRole.lab ? currentRole.lab.name + '(' + currentRole.lab.room + ')' : '暂无绑定实验室' }}</strong></div>
-                  <div class="meta-item" v-if="!isStaffRole"><span>负责人</span><strong>{{ currentRole.lab ? currentRole.lab.director : '不适用' }}</strong></div>
-                </div>
-                <div class="summary-tip" v-if="!isStaffRole">
-                  绑定状态:<strong>{{ bindingStatusText }}</strong>
-                  <el-button v-if="currentRole.bindingStatus === 'unbound'" type="text" size="mini" @click="openBindDialog('graduate')" style="margin-left:8px">去绑定</el-button>
-                </div>
-              </div>
-              <!-- 中:学时进度 -->
-              <div class="card-box status-box">
-                <div class="tag-row">
-                  <span class="mini-tag normal">{{ currentRole.term }}</span>
-                  <span v-if="currentRole.lab" :class="['mini-tag', levelClass(currentRole.lab.level)]">{{ currentRole.lab.level }}级实验室</span>
-                  <span v-if="currentRole.lab" class="mini-tag normal">{{ currentRole.lab.category }}</span>
-                </div>
-                <template v-if="currentRole.requiredHours > 0">
-                  <div class="hours-progress">
-                    <div class="progress-head">
-                      <span>年度学时进度</span>
-                      <strong>{{ currentRole.completedHours }} / {{ currentRole.requiredHours }} 学时</strong>
-                    </div>
-                    <div class="progress-bar"><span :style="{ width: hoursPercentage + '%' }"></span></div>
-                  </div>
-                  <div class="kpi-row">
-                    <div class="kpi-box">
-                      <span>完成度</span>
-                      <strong>{{ hoursPercentage }}<em>%</em></strong>
-                    </div>
-                    <div class="kpi-box">
-                      <span>目标考试</span>
-                      <strong class="kpi-exam">{{ currentRole.examType }}</strong>
-                    </div>
-                  </div>
-                </template>
-                <template v-else>
-                  <div class="kpi-row" style="margin-top:12px">
-                    <div class="kpi-box">
-                      <span>学时要求</span>
-                      <strong class="kpi-exam">无</strong>
-                    </div>
-                    <div class="kpi-box">
-                      <span>目标考试</span>
-                      <strong class="kpi-exam">{{ currentRole.examType }}</strong>
-                    </div>
-                  </div>
-                </template>
-              </div>
-              <!-- 右:任务状态 -->
-              <div class="card-box status-box">
-                <div class="panel-desc" style="margin-bottom:10px">当前学年任务状态</div>
-                <div class="status-list">
-                  <div class="status-item">
-                    <span>考试状态</span>
-                    <span :class="['status-chip', examChipClass]">{{ currentRole.examStatus }}</span>
-                  </div>
-                  <div class="status-item">
-                    <span>证书状态</span>
-                    <span :class="['status-chip', certChipClass]">{{ currentRole.certificateStatus }}</span>
-                  </div>
-                  <div class="status-item" v-if="currentRole.bindingStatus !== 'none'">
-                    <span>审批状态</span>
-                    <span :class="['status-chip', approvalChipClass]">{{ bindingStatusText }}</span>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </section>
-
-          <!-- 功能卡片区 -->
-          <section class="panel">
-            <div class="panel-head">
-              <div class="panel-title"><h2>工作台功能卡片区</h2></div>
-            </div>
-            <div class="feature-grid">
-              <!-- 学年数据统计 -->
-              <article class="feature-card">
-                <h3>学年数据统计</h3>
-                <p>按学年统计历年考试通过情况与成绩</p>
-                <div class="record-list record-list--fixed">
-                  <div class="record-item" v-for="(item, idx) in currentRole.yearlyRecords" :key="idx">
-                    <strong>{{ item.year }}</strong>
-                    <span>{{ item.status }} · {{ item.score }}</span>
-                  </div>
-                </div>
-              </article>
-              <!-- 学习任务 -->
-              <article class="feature-card">
-                <h3>学习任务</h3>
-                <p>学院下发的学习任务</p>
-                <div class="record-list record-list--scroll">
-                  <div class="record-item" v-for="(task, idx) in collegeTasks" :key="idx">
-                    <strong>{{ task.title }}</strong>
-                    <span><i :class="['task-dot', task.status]"></i> {{ task.note }}</span>
-                  </div>
-                </div>
-              </article>
-              <!-- 资料类型 -->
-              <article class="feature-card">
-                <h3>资料类型</h3>
-                <p>{{ currentRole.lab ? '根据实验室 ' + currentRole.lab.category + ' 分类推荐' : '默认推荐应知应会通识资源' }}</p>
-                <div class="record-list">
-                  <div class="record-item" v-for="(res, ri) in currentRole.resources" :key="ri">
-                    <strong>{{ res.title }}</strong>
-                    <span>{{ res.desc }}</span>
-                  </div>
-                </div>
-              </article>
-              <!-- 模拟练习 -->
-              <article class="feature-card">
-                <h3>模拟练习</h3>
-                <p>刷题进度和正确率</p>
-                <div class="meter-box">
-                  <strong>{{ currentRole.practice.done }} / {{ currentRole.practice.total }}</strong>
-                  <span>已完成题目数 / 题库总数</span>
-                </div>
-                <div class="meter-box">
-                  <strong>{{ currentRole.practice.correctRate }}</strong>
-                  <span>当前模拟练习正确率</span>
-                </div>
-              </article>
-              <!-- 考试信息 -->
-              <article class="feature-card">
-                <h3>{{ currentRole.examType }}</h3>
-                <p>{{ examCardDesc }}</p>
-                <div class="record-list record-list--fixed">
-                  <div class="record-item">
-                    <strong>学时完成</strong>
-                    <span>{{ currentRole.requiredHours > 0 ? currentRole.completedHours + ' / ' + currentRole.requiredHours + ' 学时' : '无学时要求' }}</span>
-                  </div>
-                  <div class="record-item">
-                    <strong>考试状态</strong>
-                    <span><i :class="['task-dot', currentRole.examStatus === '已通过' ? 'in-progress' : 'locked']"></i> {{ currentRole.examStatus }}</span>
-                  </div>
-                </div>
-                <el-button v-if="currentRole.examUnlocked && currentRole.examStatus !== '已通过'" type="primary" size="small" style="margin-top:auto" @click="simulatePassExam">模拟通过考试</el-button>
-                <el-button v-if="!currentRole.examUnlocked && currentRole.requiredHours > 0" type="info" size="small" disabled style="margin-top:auto">完成学时后解锁</el-button>
-                <el-button v-if="activeRole === 'newUndergraduate' && currentRole.examStatus === '已通过'" type="warning" size="small" style="margin-top:8px" @click="openBindDialog('undergraduate')">绑定实验室</el-button>
-              </article>
-            </div>
-          </section>
-
-          <!-- 安全素养资源区 -->
-          <div class="resource-section">
-            <div class="section-header">
-              <h3 class="section-title">实验室安全素养资源</h3>
-              <span class="section-hint" v-if="currentRole.lab">
-                <el-tag size="mini" type="warning">当前绑定:{{ currentRole.lab.category }}</el-tag>
-              </span>
-            </div>
-            <el-tabs v-model="activeResourceTab" type="card">
-              <el-tab-pane v-for="tab in resourceTabs" :key="tab" :label="tab" :name="tab">
-                <div class="resource-content resource-split">
-                  <!-- 左:资料区 -->
-                  <div class="resource-block resource-left">
-                    <div class="block-header">
-                      <h5><i class="el-icon-folder-opened"></i> 学习资料</h5>
-                      <el-button type="text" size="mini" icon="el-icon-more">查看更多</el-button>
-                    </div>
-                    <div class="doc-list">
-                      <div class="doc-card" v-for="(doc, i) in getResourceDocs(tab)" :key="i" @click="viewDocDetail(doc)">
-                        <div class="doc-icon">
-                          <i :class="doc.type === 'PDF' ? 'el-icon-document' : 'el-icon-data-board'"></i>
-                        </div>
-                        <div class="doc-body">
-                          <span class="doc-title">{{ doc.title }}</span>
-                          <div class="doc-meta-row">
-                            <span class="doc-meta">{{ doc.type }} · {{ doc.pages }}页</span>
-                            <span class="doc-duration"><i class="el-icon-time"></i> {{ doc.duration }}</span>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                  <!-- 右:题库区 -->
-                  <div class="resource-block resource-right">
-                    <div class="block-header">
-                      <h5><i class="el-icon-edit-outline"></i> 题库</h5>
-                      <el-button type="text" size="mini" icon="el-icon-more">查看更多</el-button>
-                    </div>
-                    <div class="question-grid">
-                      <div class="question-card" v-for="(cat, ci) in getResourceQuestions(tab).categories" :key="ci">
-                        <div class="question-card-icon"><i class="el-icon-notebook-2"></i></div>
-                        <span class="question-cat-name">{{ cat.name }}</span>
-                        <span class="question-cat-count">{{ cat.count }} 题</span>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </el-tab-pane>
-            </el-tabs>
-          </div>
-
-          <!-- 视频资源区 -->
-          <div class="video-section">
-            <h3 class="section-title" style="margin-bottom:16px">视频学习资源</h3>
-            <div class="video-toolbar">
-              <el-tabs v-model="activeVideoTab" @tab-click="videoPage = 1">
-                <el-tab-pane v-for="tab in videoTabs" :key="tab" :label="tab" :name="tab"></el-tab-pane>
-              </el-tabs>
-              <el-input v-model="videoSearch" placeholder="搜索视频" prefix-icon="el-icon-search" size="small" style="width:200px" clearable></el-input>
-            </div>
-            <div class="video-grid">
-              <div class="video-card" v-for="video in paginatedVideos" :key="video.id">
-                <div class="video-cover">
-                  <i class="el-icon-video-play"></i>
-                  <span class="video-duration">{{ video.duration }}</span>
-                </div>
-                <div class="video-info">
-                  <p class="video-title">{{ video.title }}</p>
-                  <p class="video-meta">{{ video.views }} 次观看</p>
-                </div>
-              </div>
-            </div>
-            <div class="video-pagination">
-              <el-pagination small layout="prev, pager, next" :total="filteredVideos.length" :page-size="4" :current-page.sync="videoPage"></el-pagination>
-            </div>
-          </div>
-        </main>
-
-        <!-- 数据统计页面 -->
-        <main class="content" v-if="activePage === 'statistics'">
-          <div class="page-toolbar">
-            <h2 class="page-title">数据统计</h2>
-          </div>
-          <el-tabs v-model="statsTopTab" type="border-card">
-            <el-tab-pane label="报表" name="current">
-              <!-- 考试情况统计 -->
-              <section class="stats-section">
-                <div class="stats-section-header">
-                  <h3>考试情况统计</h3>
-                  <div class="stats-section-actions">
-                    <el-radio-group v-model="statsExamType" size="mini" @change="renderStatsCharts">
-                      <el-radio-button label="graduate">研究生</el-radio-button>
-                      <el-radio-button label="undergraduate">本科生</el-radio-button>
-                    </el-radio-group>
-                    <el-button size="mini" type="primary" icon="el-icon-download">导出数据</el-button>
-                  </div>
-                </div>
-                <div class="stats-chart-row">
-                  <div class="stats-chart-box" ref="examBarChart"></div>
-                  <div class="stats-chart-box" ref="examPieChart"></div>
-                </div>
-                <div class="stats-filter-row">
-                  <el-select v-model="examFilterCollege" placeholder="筛选学院" size="mini" clearable style="width:160px">
-                    <el-option v-for="c in statisticsData.colleges" :key="c" :label="c" :value="c"></el-option>
-                  </el-select>
-                  <el-select v-model="examFilterExamType" placeholder="考试类型" size="mini" clearable style="width:140px">
-                    <el-option v-if="statsExamType === 'graduate'" label="新生分级考试" value="新生分级考试"></el-option>
-                    <el-option v-if="statsExamType === 'graduate'" label="应知应会考试" value="应知应会考试"></el-option>
-                    <el-option v-if="statsExamType === 'undergraduate'" label="新生入学考试" value="新生入学考试"></el-option>
-                    <el-option v-if="statsExamType === 'undergraduate'" label="应知应会考试" value="应知应会考试"></el-option>
-                    <el-option label="汇总" value="汇总"></el-option>
-                  </el-select>
-                </div>
-                <!-- 研究生:区分新生分级考试和老生应知应会 -->
-                <el-table v-if="statsExamType === 'graduate'" :data="filteredGraduateData" border size="small" style="width:100%;margin-top:8px" :span-method="examSpanMethod">
-                  <el-table-column prop="college" label="学院" min-width="150"></el-table-column>
-                  <el-table-column prop="examType" label="考试类型" min-width="120"></el-table-column>
-                  <el-table-column prop="total" label="应考人数" min-width="90"></el-table-column>
-                  <el-table-column prop="passed" label="通过人数" min-width="90"></el-table-column>
-                  <el-table-column prop="failed" label="未通过人数" min-width="100"></el-table-column>
-                  <el-table-column prop="absent" label="未考人数" min-width="90"></el-table-column>
-                  <el-table-column prop="rate" label="通过率" min-width="80"></el-table-column>
-                </el-table>
-                <!-- 本科生 -->
-                <el-table v-if="statsExamType === 'undergraduate'" :data="filteredUndergraduateData" border size="small" style="width:100%;margin-top:8px" :span-method="examSpanMethod">
-                  <el-table-column prop="college" label="学院" min-width="150"></el-table-column>
-                  <el-table-column prop="examType" label="考试类型" min-width="120"></el-table-column>
-                  <el-table-column prop="total" label="应考人数" min-width="90"></el-table-column>
-                  <el-table-column prop="passed" label="通过人数" min-width="90"></el-table-column>
-                  <el-table-column prop="failed" label="未通过人数" min-width="100"></el-table-column>
-                  <el-table-column prop="absent" label="未考人数" min-width="90"></el-table-column>
-                  <el-table-column prop="rate" label="通过率" min-width="80"></el-table-column>
-                </el-table>
-              </section>
-
-              <!-- 实验室与人员匹配 -->
-              <section class="stats-section">
-                <div class="stats-section-header">
-                  <h3>实验室与人员匹配</h3>
-                  <div class="stats-section-actions">
-                    <el-button size="mini" type="primary" icon="el-icon-download">导出各学院数据</el-button>
-                    <el-button size="mini" type="success" icon="el-icon-download">导出实验室人员明细</el-button>
-                  </div>
-                </div>
-                <div class="stats-chart-row">
-                  <div class="stats-chart-box stats-chart-full" ref="labLevelChart"></div>
-                </div>
-                <div class="stats-filter-row">
-                  <el-select v-model="labFilterCollege" placeholder="筛选学院" size="mini" clearable style="width:160px">
-                    <el-option v-for="c in statisticsData.colleges" :key="c" :label="c" :value="c"></el-option>
-                  </el-select>
-                  <el-select v-model="labFilterLevel" placeholder="实验室等级" size="mini" clearable style="width:140px">
-                    <el-option label="Ⅰ级" value="Ⅰ"></el-option>
-                    <el-option label="Ⅱ级" value="Ⅱ"></el-option>
-                    <el-option label="Ⅲ级" value="Ⅲ"></el-option>
-                    <el-option label="Ⅳ级" value="Ⅳ"></el-option>
-                  </el-select>
-                </div>
-                <el-table :data="filteredLabCollegeData" border size="small" style="width:100%;margin-top:8px">
-                  <el-table-column prop="college" label="学院" min-width="160"></el-table-column>
-                  <el-table-column prop="level1" label="Ⅰ级人数" min-width="100"></el-table-column>
-                  <el-table-column prop="level2" label="Ⅱ级人数" min-width="100"></el-table-column>
-                  <el-table-column prop="level3" label="Ⅲ级人数" min-width="100"></el-table-column>
-                  <el-table-column prop="level4" label="Ⅳ级人数" min-width="100"></el-table-column>
-                  <el-table-column prop="totalCount" label="总人数" min-width="90"></el-table-column>
-                </el-table>
-              </section>
-
-              <!-- 学时统计 -->
-              <section class="stats-section">
-                <div class="stats-section-header">
-                  <h3>学时统计</h3>
-                  <div class="stats-section-actions">
-                    <el-button size="mini" type="primary" icon="el-icon-download">导出数据</el-button>
-                  </div>
-                </div>
-                <div class="stats-chart-row">
-                  <div class="stats-chart-box stats-chart-full" ref="hoursChart"></div>
-                </div>
-                <div class="stats-filter-row">
-                  <el-select v-model="hoursFilterType" placeholder="用户类型" size="mini" clearable style="width:120px">
-                    <el-option label="研究生" value="研究生"></el-option>
-                    <el-option label="本科生" value="本科生"></el-option>
-                    <el-option label="教职工" value="教职工"></el-option>
-                  </el-select>
-                  <el-select v-model="hoursFilterCollege" placeholder="学院" size="mini" clearable style="width:160px">
-                    <el-option v-for="c in statisticsData.colleges" :key="c" :label="c" :value="c"></el-option>
-                  </el-select>
-                  <el-select v-model="hoursFilterGrade" placeholder="年级" size="mini" clearable style="width:120px">
-                    <el-option v-for="g in gradeOptions" :key="g" :label="g" :value="g"></el-option>
-                  </el-select>
-                  <el-input v-model="hoursFilterKey" placeholder="学号/姓名" size="mini" prefix-icon="el-icon-search" clearable style="width:160px"></el-input>
-                </div>
-                <el-table :data="filteredHoursDetail" border size="small" style="width:100%">
-                  <el-table-column prop="name" label="姓名" min-width="80"></el-table-column>
-                  <el-table-column prop="code" label="学号" min-width="120"></el-table-column>
-                  <el-table-column prop="grade" label="年级" min-width="80"></el-table-column>
-                  <el-table-column prop="college" label="所属学院" min-width="140"></el-table-column>
-                  <el-table-column prop="y1" label="第一年" min-width="70"></el-table-column>
-                  <el-table-column prop="y2" label="第二年" min-width="70"></el-table-column>
-                  <el-table-column prop="y3" label="第三年" min-width="70"></el-table-column>
-                  <el-table-column prop="y4" label="第四年" min-width="70"></el-table-column>
-                  <el-table-column prop="totalHours" label="总时长" min-width="80"></el-table-column>
-                </el-table>
-                <div style="text-align:center;margin-top:12px">
-                  <el-pagination small layout="prev, pager, next" :total="hoursDetailFiltered.length" :page-size="hoursPageSize" :current-page.sync="hoursPage"></el-pagination>
-                </div>
-              </section>
-            </el-tab-pane>
-          </el-tabs>
-        </main>
-
-        <!-- 系统配置页面 -->
-        <main class="content" v-if="activePage === 'config'">
-          <div class="page-toolbar">
-            <h2 class="page-title">分级学时配置</h2>
-          </div>
-          <div class="config-card">
-            <el-table :data="configTableData" border style="width: 100%">
-              <el-table-column prop="levelLabel" label="风险等级" width="200"></el-table-column>
-              <el-table-column prop="level" label="级别" width="100"></el-table-column>
-              <el-table-column label="准入学时要求(第一年)">
-                <template slot-scope="scope">
-                  <el-input-number v-model="scope.row.firstYear" :min="0" :max="100" size="small"></el-input-number>
-                </template>
-              </el-table-column>
-              <el-table-column label="年度学时要求(第二年起)">
-                <template slot-scope="scope">
-                  <el-input-number v-model="scope.row.annual" :min="0" :max="100" size="small"></el-input-number>
-                </template>
-              </el-table-column>
-            </el-table>
-            <div style="margin-top:16px;text-align:right">
-              <el-button type="primary" @click="saveConfig">保存配置</el-button>
-            </div>
-          </div>
-        </main>
-
-        <!-- 其他页面占位 -->
-        <main class="content" v-if="!['workbench','config','flow','prd','statistics'].includes(activePage)">
-          <div class="placeholder-page">
-            <i class="el-icon-s-opportunity"></i>
-            <p>{{ currentPageLabel }} - 功能开发中</p>
-          </div>
-        </main>
-
-        <!-- 需求说明页面 -->
-        <main class="content" v-if="activePage === 'prd'">
-          <div class="page-toolbar">
-            <h2 class="page-title">需求规格说明书</h2>
-            <el-button size="small" icon="el-icon-back" @click="activePage = 'workbench'">返回工作台</el-button>
-          </div>
-          <div class="prd-page" v-html="prdContent"></div>
-        </main>
-
-        <!-- 业务流程说明页面 -->
-        <main class="content" v-if="activePage === 'flow'">
-          <div class="page-toolbar">
-            <h2 class="page-title">业务流程说明</h2>
-            <el-button size="small" icon="el-icon-back" @click="activePage = 'workbench'">返回工作台</el-button>
-          </div>
-          <div class="flow-page">
-            <div class="flow-card" v-for="(flow, fi) in flowData" :key="fi">
-              <h3 class="flow-title">{{ flow.role }}</h3>
-              <div class="flow-chart">
-                <div class="flow-step" v-for="(step, si) in flow.steps" :key="si">
-                  <div class="flow-node" :class="step.type || ''">
-                    <span class="flow-num">{{ si + 1 }}</span>
-                    <span class="flow-text">{{ step.text }}</span>
-                  </div>
-                  <div class="flow-arrow" v-if="si < flow.steps.length - 1">
-                    <i class="el-icon-arrow-down"></i>
-                    <span class="flow-condition" v-if="step.condition">{{ step.condition }}</span>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </main>
-      </div>
-    </div>
-
-    <!-- 绑定实验室弹窗 -->
-    <el-dialog title="绑定实验室" :visible.sync="showBindDialog" width="620px" :close-on-click-modal="false">
-      <div class="bind-dialog-content">
-        <!-- 单一可搜索下拉框 -->
-        <el-select v-model="selectedLabId" placeholder="输入实验室名称、房号或楼栋关键词进行搜索" style="width:100%" filterable :filter-method="labFilterMethod" @change="onLabSelect">
-          <el-option v-for="lab in filteredLabs" :key="lab.id"
-            :label="lab.college + '-' + lab.name + '(' + lab.room + ')-' + lab.building"
-            :value="lab.id">
-          </el-option>
-        </el-select>
-        <p class="search-hint">此处请选择需要后续进入的科研实验室的名称,输入实验室名称、房号或楼栋关键词,再从检索结果中选择目标实验室。</p>
-
-        <!-- 选中后展示完整信息 -->
-        <div class="selected-lab-info" v-if="selectedLabDetail">
-          <el-descriptions :column="2" size="small" border>
-            <el-descriptions-item label="实验室名称">{{ selectedLabDetail.name }}({{ selectedLabDetail.room }})</el-descriptions-item>
-            <el-descriptions-item label="所在位置">{{ selectedLabDetail.building }}</el-descriptions-item>
-            <el-descriptions-item label="负责人">{{ selectedLabDetail.directorMasked }}</el-descriptions-item>
-            <el-descriptions-item label="所属学院">{{ selectedLabDetail.college }}</el-descriptions-item>
-            <el-descriptions-item label="实验室分级">
-              <el-tag :type="levelTagType(selectedLabDetail.level)" size="mini">{{ selectedLabDetail.level }}级</el-tag>
-            </el-descriptions-item>
-            <el-descriptions-item label="实验室分类">{{ selectedLabDetail.category }}</el-descriptions-item>
-            <el-descriptions-item label="实验室类型">{{ selectedLabDetail.type }}</el-descriptions-item>
-          </el-descriptions>
-        </div>
-
-        <!-- 进入原因(本科生专属) -->
-        <div v-if="bindDialogMode === 'undergraduate'" style="margin-top:16px">
-          <el-input v-model="bindReason" type="textarea" placeholder="请填写进入实验室原因(必填)" :rows="2"></el-input>
-          <p class="search-hint" style="margin-top:6px">毕业论文或毕业设计,科创等相关需要在具体的实验室开展业务的情况可申请绑定实验室,绑定完毕后需完成对应的分级考试。</p>
-        </div>
-
-        <!-- 无需进入实验室(研究生专属) -->
-        <div v-if="bindDialogMode === 'graduate'" class="no-lab-option" style="margin-top:16px">
-          <el-checkbox v-model="noLabMode">无需进入实验室</el-checkbox>
-          <p class="search-hint" style="margin-top:4px">人文、体育等相关学院无需进入实验室的新研究生可选择此项,选择后学时要求为4小时,需通过应知应会考试。</p>
-        </div>
-
-        <!-- 须知复选框 -->
-        <div class="bind-notice" style="margin-top:16px">
-          <el-checkbox v-model="noticeChecked">我已阅读并同意<el-button type="text" @click="showNoticeDetail = true">《新生绑定实验室须知及责任说明》</el-button></el-checkbox>
-        </div>
-      </div>
-      <span slot="footer">
-        <el-button @click="showBindDialog = false">取消</el-button>
-        <el-button type="primary" @click="confirmBind" :disabled="!canBind">确认绑定</el-button>
-      </span>
-    </el-dialog>
-
-    <!-- 须知详情弹窗 -->
-    <el-dialog title="新生绑定实验室须知及责任说明" :visible.sync="showNoticeDetail" width="520px" append-to-body>
-      <div class="notice-content">
-        <p>1. 保证进入的实验室为导师实际使用实验室或属于当前实验室所属课题组。</p>
-        <p>2. 研究生期间涉及到操作实验室内重要危险源,必须绑定实验室。</p>
-      </div>
-      <span slot="footer">
-        <el-button type="primary" @click="showNoticeDetail = false">我已知晓</el-button>
-      </span>
-    </el-dialog>
-  </div>
-
-  <!-- Vendor Scripts (CDN - 生产环境应本地化) -->
-  <script src="https://unpkg.com/vue@2.7.16/dist/vue.min.js"></script>
-  <script src="https://unpkg.com/element-ui@2.15.14/lib/index.js"></script>
-  <script src="./vendor/echarts.min.js"></script>
-  <script src="https://unpkg.com/marked@9.1.6/marked.min.js"></script>
-  <!-- App Scripts -->
-  <script src="./js/data.js"></script>
-  <script src="./js/app.js"></script>
-</body>
-</html>

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 45
prototype_opus/vendor/echarts.min.js


+ 36 - 2
userReq/学校教育考试系统需求.md

@@ -1,4 +1,4 @@
-#### 学校教育考试系统需求
+#### 学校教育考试系统需求 (手记草稿)
 
 - 工作台顶栏,用户信息,归属类型,学生/导师属于那个等级,属于什么类别的要求,前置条件及执行进度。
 -  // 等级优先,分类优先化学--> 一个学生/用户分级准入考试仅考一次,取最高等级。
@@ -43,7 +43,37 @@
 
 
 
-数据统计:以获取数据统计表,现需要设计
+数据统计:以获取数据统计表,现需要领导设计
+
+
+
+**需求**  4月底汇报收集
+
+1.绑定弹窗提示文字优化,例如选择需要后续进入科研实验室的名称,模糊搜索提示词。 ✅
+
+2.无需进入实验室提示标明无需进入实验室的范围,例如人文等
+
+3.绑定分为两个选项,一个是需要进入实验室进行科研场所,另外一项为例如语言文化,人文等文科类相关可选择无需绑定实验室。✅
+
+4.绑定责任说明内容
+
+​	1.保证进入的实验室为导师实际使用实验室或属于当前实验室所属课题组;
+
+​	2.研究生期间涉及到操作实验室内重要危险源,必须绑定实验室
+
+5.教职工只需要完成应知应会考试,无学时要求,新的老师需要完成学时。
+
+6.本科生后续需要完成应知应会考试✅
+
+7.本科生放开绑定实验室按钮,需提供说明,毕业论文或毕业设计,科创等相关需要在具体的实验室开展业务的情况中。 ✅
+
+5.涉及到研究生转博,⚠️??? 学号问题
+
+
+
+others
+
+/*
 
 安全检查:
 
@@ -51,6 +81,10 @@
 
 2.支持根据条款查询数据透视,指定某项3级条款可查看有隐患的实验室及重复隐患条款。
 
+*/
+
+
+