| {% extends "base.html" %} | |
| {% block title %}用户控制台 | SCU 选课控制台{% endblock %} | |
| {% block content %} | |
| <section class="dashboard-shell" data-log-stream-url="{{ url_for('stream_user_logs', last_id=recent_logs[-1].id if recent_logs else 0) }}" data-status-url="{{ url_for('user_status') }}"> | |
| <header class="topbar reveal-up"> | |
| <div> | |
| <span class="eyebrow">User Console</span> | |
| <h1>{{ current_user.display_name or current_user.student_id }} 的选课面板</h1> | |
| <p>登录账号:{{ current_user.student_id }},你的课程目标与运行日志会实时同步到这里。</p> | |
| </div> | |
| <form method="post" action="{{ url_for('logout') }}"> | |
| <button type="submit" class="btn btn-ghost">退出登录</button> | |
| </form> | |
| </header> | |
| <section class="metric-grid reveal-up delay-1"> | |
| <article class="metric-card"> | |
| <span>当前任务</span> | |
| <strong id="task-status-text">{{ task_labels.get(task.status, '未启动') if task else '未启动' }}</strong> | |
| <small id="task-updated-at">最近更新时间:{{ task.updated_at if task else '暂无' }}</small> | |
| </article> | |
| <article class="metric-card"> | |
| <span>待选课程</span> | |
| <strong id="course-count">{{ courses|length }}</strong> | |
| <small>管理员可看到全部课程内容</small> | |
| </article> | |
| <article class="metric-card"> | |
| <span>累计尝试次数</span> | |
| <strong id="task-attempts">{{ task.total_attempts if task else 0 }}</strong> | |
| <small>每次课程尝试都会累计到这里</small> | |
| </article> | |
| <article class="metric-card"> | |
| <span>累计错误次数</span> | |
| <strong id="task-errors">{{ task.total_errors if task else 0 }}</strong> | |
| <small id="task-last-error">{{ task.last_error if task and task.last_error else '当前没有错误提示' }}</small> | |
| </article> | |
| <article class="metric-card"> | |
| <span>刷新间隔</span> | |
| <strong id="refresh-interval">{{ current_user.refresh_interval_seconds or default_refresh_interval_seconds }} 秒</strong> | |
| <small>未抢到课时,等待下一轮刷新的时间</small> | |
| </article> | |
| </section> | |
| <section class="content-grid dashboard-grid"> | |
| <article class="card reveal-up delay-2"> | |
| <div class="card-head"> | |
| <span class="kicker">个人信息</span> | |
| <h2>更新登录信息</h2> | |
| <p>密码会用于后台登录教务系统,请保持为最新有效密码。</p> | |
| </div> | |
| <form method="post" action="{{ url_for('update_profile') }}" class="form-grid"> | |
| <label class="field"> | |
| <span>显示名称</span> | |
| <input type="text" name="display_name" value="{{ current_user.display_name }}" placeholder="可选昵称"> | |
| </label> | |
| <label class="field"> | |
| <span>教务密码</span> | |
| <input type="password" name="password" value="" placeholder="重新输入当前有效密码" required> | |
| </label> | |
| <button type="submit" class="btn btn-primary">保存账号信息</button> | |
| </form> | |
| </article> | |
| <article class="card reveal-up delay-2"> | |
| <div class="card-head"> | |
| <span class="kicker">运行参数</span> | |
| <h2>调整刷新间隔</h2> | |
| <p>当本轮没有抢到目标课程时,任务会按这里的秒数等待下一轮刷新。</p> | |
| </div> | |
| <form method="post" action="{{ url_for('update_runtime_settings') }}" class="form-grid form-grid-compact"> | |
| <label class="field"> | |
| <span>未命中后刷新间隔</span> | |
| <input type="number" name="refresh_interval_seconds" min="{{ refresh_interval_min }}" max="{{ refresh_interval_max }}" value="{{ current_user.refresh_interval_seconds or default_refresh_interval_seconds }}" required> | |
| </label> | |
| <button type="submit" class="btn btn-secondary">更新刷新间隔</button> | |
| </form> | |
| </article> | |
| <article class="card reveal-up delay-2"> | |
| <div class="card-head"> | |
| <span class="kicker">课程目标</span> | |
| <h2>添加课程号与课序号</h2> | |
| <p>支持方案选课与自由选课,管理员会看到你录入的全部内容。</p> | |
| </div> | |
| <form method="post" action="{{ url_for('add_course') }}" class="form-grid form-grid-compact"> | |
| <label class="field"> | |
| <span>选课类型</span> | |
| <select name="category"> | |
| <option value="free">自由选课</option> | |
| <option value="plan">方案选课</option> | |
| </select> | |
| </label> | |
| <label class="field"> | |
| <span>课程号</span> | |
| <input type="text" name="course_id" placeholder="例如 888005010A59" autocapitalize="characters" required> | |
| </label> | |
| <label class="field"> | |
| <span>课序号</span> | |
| <input type="text" name="course_index" placeholder="例如 01 或 666" autocapitalize="characters" required> | |
| </label> | |
| <button type="submit" class="btn btn-secondary">加入抢课队列</button> | |
| </form> | |
| </article> | |
| <article class="card reveal-up delay-3 span-2"> | |
| <div class="card-head split"> | |
| <div> | |
| <span class="kicker">执行控制</span> | |
| <h2>启动与停止任务</h2> | |
| <p>后台会按照管理员设置的并行数进行排队和执行。</p> | |
| </div> | |
| <div class="button-row"> | |
| <form method="post" action="{{ url_for('start_task') }}"> | |
| <button type="submit" class="btn btn-primary">启动任务</button> | |
| </form> | |
| <form method="post" action="{{ url_for('stop_task') }}"> | |
| <button type="submit" class="btn btn-ghost danger">停止任务</button> | |
| </form> | |
| </div> | |
| </div> | |
| <div class="status-strip"> | |
| <span class="status-pill status-{{ task.status if task else 'idle' }}" id="task-status-pill">{{ task_labels.get(task.status, '未启动') if task else '未启动' }}</span> | |
| <span>创建时间:{{ task.created_at if task else '暂无任务' }}</span> | |
| <span>触发者:{{ task.requested_by if task else '暂无' }}</span> | |
| <span>尝试次数:<strong id="task-attempts-inline">{{ task.total_attempts if task else 0 }}</strong></span> | |
| <span>错误次数:<strong id="task-errors-inline">{{ task.total_errors if task else 0 }}</strong></span> | |
| </div> | |
| <div class="course-table-wrap"> | |
| <table class="data-table"> | |
| <thead> | |
| <tr> | |
| <th>类型</th> | |
| <th>课程号</th> | |
| <th>课序号</th> | |
| <th>操作</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% if courses %} | |
| {% for course in courses %} | |
| <tr> | |
| <td>{{ category_labels.get(course.category, course.category) }}</td> | |
| <td>{{ course.course_id }}</td> | |
| <td>{{ course.course_index }}</td> | |
| <td> | |
| <form method="post" action="{{ url_for('delete_course', course_target_id=course.id) }}"> | |
| <button type="submit" class="inline-action">移除</button> | |
| </form> | |
| </td> | |
| </tr> | |
| {% endfor %} | |
| {% else %} | |
| <tr> | |
| <td colspan="4" class="empty-cell">还没有课程目标,先添加课程号和课序号吧。</td> | |
| </tr> | |
| {% endif %} | |
| </tbody> | |
| </table> | |
| </div> | |
| </article> | |
| <article class="card reveal-up delay-3 span-2"> | |
| <div class="card-head split"> | |
| <div> | |
| <span class="kicker">实时日志</span> | |
| <h2>后台运行日志</h2> | |
| <p>这里会持续显示程序执行时的关键步骤、错误与结果。</p> | |
| </div> | |
| <span class="live-dot">LIVE</span> | |
| </div> | |
| <div class="log-console" id="log-console"> | |
| {% if recent_logs %} | |
| {% for log in recent_logs %} | |
| <div class="log-line level-{{ log.level|lower }}"> | |
| <span class="log-meta">{{ log.created_at }} | {{ log.scope }} | {{ log.level }}</span> | |
| <span>{{ log.message }}</span> | |
| </div> | |
| {% endfor %} | |
| {% else %} | |
| <div class="log-line level-info muted">暂无日志,启动任务后这里会自动刷新。</div> | |
| {% endif %} | |
| </div> | |
| </article> | |
| </section> | |
| </section> | |
| {% endblock %} | |