Files
wechat-clock-mini/pages/plan/plan.wxml
王宏豪 ee3abed69f feat(app): 添加课程页面并优化打卡和计划功能
- 新增 pages/course/course 页面,集成 web-view 显示线上课程列表
- 在 app.json 中注册课程页面并添加底部 tabbar 导航
- 优化打卡页面,添加学号显示和改进成员列表布局
- 重构计划页面,添加内容详情弹层和优化加载逻辑
- 添加阿里云 OSS 上传工具模块用于文件上传功能
- 统一姓名和学号在同一行显示,改善 UI 体验
- 修复多个页面中的绑定事件和样式问题
2026-09-20 16:53:36 +08:00

296 lines
14 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="*this"
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="openPeriodPicker" hover-class="{{periodList.length > 0 ? 'head-hover' : 'none'}}">
{{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="openPeriodPicker"
hover-class="{{periodList.length > 0 ? 'head-hover' : 'none'}}"
>
<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>
<!-- 学员小组 tab:只展示成员信息,不展示任何人的打卡内容,也不提供详情入口 -->
<view class="member-list" wx:elif="{{members.length > 0}}">
<view class="member" wx:for="{{members}}" wx:key="user_id" wx:for-item="member" wx:for-index="mi">
<view class="member-row">
<view class="member-left">
<view class="member-avatar {{member.filled ? 'done' : 'unchecked'}}">{{mi + 1}}</view>
<view class="member-info">
<view class="member-line">
<text class="member-name">{{member.name}}</text>
<text class="member-sid" wx:if="{{member.student_id}}">学号 {{member.student_id}}</text>
</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>
<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>
<!-- 卡片直接铺内容:正文 3 行截断、缩略图最多 2 张;全部文字与图片在「查看详情」弹层里看 -->
<view
class="history-card"
wx:for="{{history}}"
wx:key="id"
wx:for-index="hi"
>
<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:for-index="ti"
wx:key="*this"
src="{{thumb}}"
mode="aspectFill"
></image>
</view>
<view class="tap-hint" data-hi="{{hi}}" hover-class="tap-hint-hover" bindtap="openHistoryDetail">查看详情 ›</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="openPeriodPicker" hover-class="{{periodList.length > 0 ? 'head-hover' : 'none'}}">
{{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" wx:for-index="mi">
<!-- 已提交者整行可点,全文与全部图片在详情弹层里看(未提交者 handler 内部会直接返回) -->
<view
class="member-row"
data-mi="{{mi}}"
hover-class="{{member.filled ? 'member-row-hover' : 'none'}}"
bindtap="openMemberDetail"
>
<view class="member-left">
<view class="member-avatar {{member.filled ? 'done' : 'unchecked'}}">{{mi + 1}}</view>
<view class="member-info">
<view class="member-line">
<text class="member-name">{{member.name}}</text>
<text class="member-sid" wx:if="{{member.student_id}}">学号 {{member.student_id}}</text>
</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>
<!-- 提示文字是 .member-row 的兄弟节点,必须自己绑事件,否则点它没有任何节点接收
(不要挪进 .member-row 里 —— tap 会冒泡到父行导致 openMemberDetail 执行两次,
同 tick 连发 setData 会触发 recursive update detected) -->
<view
class="tap-hint"
wx:if="{{member.filled}}"
data-mi="{{mi}}"
hover-class="tap-hint-hover"
bindtap="openMemberDetail"
>点击查看详情 ›</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 class="cd-mask" wx:if="{{contentDetail}}" bindtap="closeContentDetail" catchtouchmove="noop"></view>
<view class="cd-panel" wx:if="{{contentDetail}}">
<view class="cd-head">
<view class="cd-head-text">
<view class="cd-title">{{contentDetail.title}}</view>
<view class="cd-sub" wx:if="{{contentDetail.sub}}">{{contentDetail.sub}}</view>
</view>
<view class="cd-close" hover-class="cd-close-hover" bindtap="closeContentDetail">✕</view>
</view>
<view class="cd-meta" wx:if="{{contentDetail.meta}}">{{contentDetail.meta}}</view>
<scroll-view class="cd-body" scroll-y>
<view class="cd-content" wx:if="{{contentDetail.content}}">{{contentDetail.content}}</view>
<view class="cd-no-content" wx:if="{{!contentDetail.content}}">未填写文字内容</view>
<view class="cd-thumbs" wx:if="{{contentDetail.thumbs.length > 0}}">
<image
class="cd-thumb"
wx:for="{{contentDetail.thumbs}}"
wx:for-item="pic"
wx:for-index="ci"
wx:key="*this"
src="{{pic}}"
mode="widthFix"
data-ci="{{ci}}"
bindtap="previewDetailImage"
></image>
</view>
</scroll-view>
</view>
</view>