Files
wechat-clock-mini/pages/plan/plan.wxml
王宏豪 ba2d9245ef feat(api): 重构图片地址管理与上传机制
- 替换开发环境API基础URL为本地服务地址
- 实现签名URL本地缓存机制,解决微信按URL缓存图片导致重复下载问题
- 添加缓存清理功能,包括过期清理和按需删除特定缓存项
- 集成阿里云OSS上传功能,支持直传减少服务器带宽占用
- 优化头像地址处理逻辑,统一使用签名缓存避免地址频繁变化
- 移除过时的OSS上传示例文件oss-upload.js
- 更新课程页面实现跳转第三方小程序功能
- 调整打卡记录图片地址获取方式,统一使用新的签名缓存逻辑
- 优化图片上传流程,增加压缩处理和批量上传支持
- 完善认证模块中的缓存清理逻辑,切换账号时清除相关图片缓存
- 添加后端环境变量配置模板和代理文档说明
2026-09-23 17:46:40 +08:00

302 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 张;点卡片任意位置(文字或图片)
打开该条详情弹层,弹层里看全文全图。
注意:绑定只能在这层,卡片内不得再绑 openHistoryDetail —— 子节点再绑一次会先执行自己的
handler 再冒泡到卡片,同 tick 连发 2 次 setData 触发 recursive update detected。
缩略图故意不加 catchtap,让点击冒泡到卡片即打开弹层(放大预览在弹层内做)。 -->
<view
class="history-card"
wx:for="{{history}}"
wx:key="id"
wx:for-index="hi"
data-hi="{{hi}}"
hover-class="history-card-hover"
bindtap="openHistoryDetail"
>
<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>
</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>