Files
wechat-clock-mini/pages/plan/plan.wxml
王宏豪 cd04c1b5ef feat(database): 迁移用户关联字段并优化打卡系统
- 修改数据库配置将MYSQL_DB从mys_info_v3改为mys_info_test
- 新增auth_users表wechat_openid字段并建立唯一索引
- 为plan_checkins表添加计划性能索引(idx_plan_period_student)
- 将checkin_records_old表关联方式从student_id改为user_id
- 将plan_checkins表关联方式从student_id改为user_id并更新附件路径
- 移除routes/auth.py中的uuid导入使用安全token生成函数
- 重构routes/checkin.py打卡逻辑去除course_type字段并优化返回结构
- 添加打卡时间限制验证功能防止过期打卡操作
- 实现小组统计功能支持老师查看各组打卡情况
- 更新主页index.html界面适配新的打卡和计划功能
- 调整计划提交时间规则为周五至周四周期
- 添加月度和年度计划提交状态验证机制
2026-09-10 14:11:58 +08:00

224 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<view class="page">
<block wx:if="{{view === 'overview'}}">
<view class="view-root">
<!-- 顶部类型切换 -->
<view class="type-tabs">
<view
class="type-tab {{typeIndex === index ? 'active' : ''}}"
wx:for="{{types}}"
wx:key="index"
data-index="{{index}}"
hover-class="type-tab-hover"
bindtap="onTypeChange"
>{{item}}</view>
</view>
<scroll-view
class="view-scroll"
scroll-y
refresher-enabled="{{true}}"
refresher-triggered="{{refreshing}}"
bindrefresherrefresh="onRefresh"
>
<!-- 头部:类型 + 我的状态 -->
<view class="overview-head">
<view class="head-title-wrap">
<view class="head-title">{{types[typeIndex]}}</view>
<view class="head-sub" wx:if="{{isTeacher}}" bindtap="{{periodList.length > 0 ? 'openPeriodPicker' : ''}}" hover-class="{{periodList.length > 0 ? 'head-hover' : ''}}">
{{periodKey}}
<text class="head-arrow" wx:if="{{periodList.length > 0}}"> ▾</text>
</view>
<view class="head-sub" wx:else>{{periodKey}}</view>
</view>
<view class="head-pill {{myChecked ? 'checked' : ''}}">{{myChecked ? '已提交' : '待提交'}}</view>
</view>
<!-- 我的计划卡片 -->
<view class="card my-status {{myChecked ? 'checked' : 'unchecked'}}">
<view class="my-state-icon {{myChecked ? 'done' : 'todo'}}">{{myChecked ? '✓' : '●'}}</view>
<view class="my-status-left">
<view class="my-status-label">{{myChecked ? '已完成' : '未完成'}}</view>
<view class="my-status-text">我的{{types[typeIndex]}}</view>
</view>
<button
class="btn-mystatus {{myChecked ? 'outline' : ''}}"
bindtap="goSubmit"
hover-class="btn-hover"
disabled="{{!canSubmit}}"
>{{myChecked ? '修改' : '提交计划'}}</button>
</view>
<!-- 提交时间窗口提示 -->
<view class="window-msg" wx:if="{{message}}">{{message}}</view>
<!-- 加载状态 -->
<view class="state-block" wx:if="{{loading && groups.length === 0}}">
<view class="state-icon">◌</view>
<view>加载中...</view>
</view>
<!-- 老师:显示所有小组列表 -->
<block wx:if="{{isTeacher}}">
<view class="section-title">小组统计</view>
<view class="group-card" hover-class="group-card-hover" wx:for="{{groups}}" wx:key="group_name" wx:for-item="group">
<view class="group-main" data-group="{{group.group_name}}" bindtap="openGroup">
<view class="group-row">
<view class="group-avatar">{{index + 1}}</view>
<view class="group-name">{{group.group_name}}</view>
<view class="group-counts">
<text class="count done">已提交 {{group.checked_count}}</text>
<text class="count total">{{group.total}}人</text>
</view>
<view class="chevron">›</view>
</view>
<view class="progress-track">
<view class="progress-bar" style="{{group.progressStyle}}"></view>
</view>
</view>
</view>
<view class="unassigned" wx:if="{{unassignedCount > 0}}">另有未分组 {{unassignedCount}} 人</view>
<view class="empty" wx:if="{{!loading && groups.length === 0}}">暂无小组</view>
</block>
<!-- 学员:tab 切换(小组/个人) -->
<block wx:else>
<view class="student-tabs">
<view class="student-tab {{studentTab === 'group' ? 'active' : ''}}" data-tab="group" hover-class="tab-hover" bindtap="switchStudentTab">小组</view>
<view class="student-tab {{studentTab === 'personal' ? 'active' : ''}}" data-tab="personal" hover-class="tab-hover" bindtap="switchStudentTab">个人</view>
</view>
<!-- 小组 tab:本组成员列表 -->
<view wx:if="{{studentTab === 'group'}}" class="student-group-panel">
<view
class="group-period-row"
wx:if="{{myGroup}}"
bindtap="{{periodList.length > 0 ? 'openPeriodPicker' : ''}}"
hover-class="{{periodList.length > 0 ? 'head-hover' : ''}}"
>
<text class="group-period-text">{{myGroup}} · {{periodKey}}</text>
<text class="head-arrow" wx:if="{{periodList.length > 0}}"> ▾</text>
</view>
<view class="state-block" wx:if="{{loading && members.length === 0}}">
<view class="state-icon">◌</view>
<view>加载中...</view>
</view>
<view class="member-list" wx:elif="{{members.length > 0}}">
<view class="member" wx:for="{{members}}" wx:key="user_id" wx:for-item="member">
<view class="member-left">
<view class="member-avatar {{member.filled ? 'done' : 'unchecked'}}">{{index + 1}}</view>
<view class="member-info">
<view class="member-name">{{member.name}}</view>
<view class="member-meta" wx:if="{{member.filled && member.record}}">{{member.record.checked_at}} 提交</view>
</view>
</view>
<view class="badge {{member.filled ? 'done' : 'unchecked'}}">{{member.filled ? '已完成' : '未完成'}}</view>
</view>
</view>
<view class="empty" wx:elif="{{!loading && !myGroup}}">
<view class="empty-icon">👥</view>
<view class="empty-text">暂无小组,请联系管理员分配</view>
</view>
<view class="empty" wx:elif="{{!loading && members.length === 0}}">
<view class="empty-icon">📭</view>
<view class="empty-text">暂无成员数据</view>
</view>
</view>
<!-- 个人 tab:历史记录 -->
<view wx:else class="student-history-panel">
<view class="empty" wx:if="{{history.length === 0}}">
<view class="empty-icon">📒</view>
<view class="empty-text">暂无{{types[typeIndex]}}记录</view>
</view>
<view class="history-card" wx:for="{{history}}" wx:key="id">
<view class="history-head">
<view class="history-period">{{item.period_key}}</view>
<view class="history-status" style="color:{{item.statusColor}};background:{{item.statusBg}}">{{item.status}}</view>
</view>
<view class="history-meta" wx:if="{{item.checked_at}}">{{item.checked_at}} 提交</view>
<view class="history-content" wx:if="{{item.content}}">{{item.content}}</view>
<view class="history-thumbs" wx:if="{{item.thumbs.length > 0}}">
<image class="history-thumb" wx:for="{{item.thumbs}}" wx:for-item="thumb" wx:key="*this" src="{{thumb}}" mode="aspectFill"></image>
</view>
</view>
</view>
</block>
<!-- 底部留白 -->
<view class="bottom-space"></view>
</scroll-view>
<!-- 周期选择弹层 -->
<view class="picker-mask" wx:if="{{showPeriodPicker}}" bindtap="closePeriodPicker"></view>
<view class="picker-panel {{showPeriodPicker ? 'show' : ''}}" wx:if="{{showPeriodPicker}}">
<view class="picker-title">选择周期</view>
<scroll-view class="picker-list" scroll-y>
<view
class="picker-item {{item === periodKey ? 'active' : ''}}"
wx:for="{{periodList}}"
wx:key="*this"
data-period="{{item}}"
bindtap="onPeriodSelect"
>
<text>{{item}}</text>
<text class="picker-check" wx:if="{{item === periodKey}}">✓</text>
</view>
</scroll-view>
</view>
</view>
</block>
<!-- 小组详情页 -->
<block wx:else>
<view class="view-root">
<view class="detail-head">
<view class="back" hover-class="back-hover" bindtap="backToOverview">‹ 返回</view>
<view class="detail-title-wrap">
<view class="head-title">{{selectedGroupName}}</view>
<view class="head-sub" bindtap="{{periodList.length > 0 ? 'openPeriodPicker' : ''}}" hover-class="{{periodList.length > 0 ? 'head-hover' : ''}}">
{{periodKey}} <text class="head-arrow" wx:if="{{periodList.length > 0}}"> ▾</text>
</view>
</view>
</view>
<scroll-view class="view-scroll" scroll-y>
<view class="state-block" wx:if="{{loading && members.length === 0}}">
<view class="state-icon">◌</view>
<view>加载中...</view>
</view>
<view class="member-list">
<view class="member" wx:for="{{members}}" wx:key="user_id" wx:for-item="member">
<view class="member-left">
<view class="member-avatar {{member.filled ? 'done' : 'unchecked'}}">{{index + 1}}</view>
<view class="member-info">
<view class="member-name">{{member.name}}</view>
<view class="member-meta" wx:if="{{member.filled && member.record}}">{{member.record.checked_at}} 提交</view>
</view>
</view>
<view class="badge {{member.filled ? 'done' : 'unchecked'}}">{{member.filled ? '已完成' : '未完成'}}</view>
</view>
</view>
<view class="empty" wx:if="{{!loading && members.length === 0}}">暂无成员</view>
</scroll-view>
<!-- 周期选择弹层(详情页也可用) -->
<view class="picker-mask" wx:if="{{showPeriodPicker}}" bindtap="closePeriodPicker"></view>
<view class="picker-panel {{showPeriodPicker ? 'show' : ''}}" wx:if="{{showPeriodPicker}}">
<view class="picker-title">选择周期</view>
<scroll-view class="picker-list" scroll-y>
<view
class="picker-item {{item === periodKey ? 'active' : ''}}"
wx:for="{{periodList}}"
wx:key="*this"
data-period="{{item}}"
bindtap="onPeriodSelect"
>
<text>{{item}}</text>
<text class="picker-check" wx:if="{{item === periodKey}}">✓</text>
</view>
</scroll-view>
</view>
</view>
</block>
</view>