Files
wechat-clock-mini/pages/plan/plan.js
王宏豪 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

319 lines
9.5 KiB
JavaScript
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.
const api = require('../../utils/api');
const auth = require('../../utils/auth');
// 计划类型常量
const TYPES = ['weekly', 'monthly', 'yearly'];
const TYPE_LABELS = ['周计划', '月计划', '年计划'];
Page({
data: {
// 类型切换相关
types: TYPE_LABELS,
typeIndex: 0,
// 视图模式:overview 总览 / detail 小组详情(仅老师)
view: 'overview',
// 加载状态
loading: false,
refreshing: false,
// 当前周期
periodKey: '',
// 提交相关
canSubmit: false,
message: '',
// 我的计划状态
myChecked: false,
myStatus: '',
myRecord: null,
myThumbs: [],
// 未分组人数(仅老师可见)
unassignedCount: 0,
// 小组列表
groups: [],
// 当前选中的小组
selectedGroupName: '',
// 小组成员列表(老师详情 & 学员小组 tab)
members: [],
// 用户角色
isTeacher: false,
myGroup: '',
// 学员视图 tab:group / personal
studentTab: 'group',
// 个人历史列表
history: [],
// 周期切换相关
periodList: [], // 可用周期列表
showPeriodPicker: false,
},
onShow() {
auth.ensureLogin().then((token) => {
if (!token) {
wx.showToast({ title: '请先登录', icon: 'none' });
setTimeout(() => wx.switchTab({ url: '/pages/profile/profile' }), 500);
return;
}
wx.removeStorageSync('plan_overview_dirty');
this.setData({ view: 'overview' });
// 先加载周期列表,再加载总览(总览接口会返回 is_teacher)
this.loadPeriods().then(() => {
this.loadOverview();
});
}).catch((err) => {
console.error('plan page init error:', err);
});
},
/** 切换计划类型(周/月/年) */
onTypeChange(e) {
const typeIndex = Number(e.currentTarget.dataset.index);
if (typeIndex === this.data.typeIndex) {
return;
}
this.setData({ typeIndex, view: 'overview', selectedGroupName: '', members: [], history: [], studentTab: 'group' });
// 切换类型后重新加载周期列表和总览
this.loadPeriods().then(() => {
this.loadOverview();
});
},
/** 下拉刷新 */
onRefresh() {
this.setData({ refreshing: true });
this.loadPeriods().then(() => {
return this.loadOverview();
}).then(() => {
// 学员个人 tab 也刷一下历史
if (!this.data.isTeacher && this.data.studentTab === 'personal') {
return this.loadHistory();
}
}).then(() => {
this.setData({ refreshing: false });
});
},
/** 加载可用周期列表 */
loadPeriods() {
const type = TYPES[this.data.typeIndex];
return api.request({ url: '/api/v1/plan/periods', data: { type } })
.then((res) => {
const periods = res.periods || [];
// 默认选第一个(最新)周期,或者后端返回的当前周期
let periodKey = periods[0] || res.current_period || '';
this.setData({
periodList: periods,
periodKey: periodKey || this.data.periodKey,
});
})
.catch(() => {
// 获取失败不影响主流程
console.warn('获取周期列表失败');
});
},
/** 打开周期选择器 */
openPeriodPicker() {
if (!this.data.periodList.length) {
return;
}
this.setData({ showPeriodPicker: true });
},
/** 关闭周期选择器 */
closePeriodPicker() {
this.setData({ showPeriodPicker: false });
},
/** 选择某个周期 */
onPeriodSelect(e) {
const period = e.currentTarget.dataset.period;
if (!period || period === this.data.periodKey) {
this.setData({ showPeriodPicker: false });
return;
}
this.setData({
periodKey: period,
showPeriodPicker: false,
});
if (this.data.view === 'detail') {
// 详情页:重新加载当前小组的该周期数据
this.loadDetail();
} else if (!this.data.isTeacher && this.data.studentTab === 'group' && this.data.myGroup) {
// 学员总览 + 小组 tab:刷新本组成员
this.loadOverview();
} else {
// 老师总览 / 学员个人 tab:重新加载总览
this.loadOverview();
}
},
/** 加载总览数据 */
loadOverview() {
const type = TYPES[this.data.typeIndex];
const periodKey = this.data.periodKey;
this.setData({ loading: true });
return api.request({ url: '/api/v1/plan/groups', data: { type, period_key: periodKey } })
.then((res) => {
const groups = (res.groups || []).map((g) => {
const percent = g.total ? Math.round(((g.checked_count || 0) / g.total) * 100) : 0;
return {
...g,
percent,
progressStyle: 'width:' + percent + '%;'
};
});
const isTeacher = !!res.is_teacher;
this.setData({
loading: false,
refreshing: false,
periodKey: res.period_key || periodKey,
canSubmit: !!res.can_submit,
message: res.message || '',
myChecked: !!res.my_checked,
myStatus: res.my_status || '',
myRecord: res.my_record || null,
myThumbs: ((res.my_record && res.my_record.attachments) || []).map((p) => api.planFileUrl(p)),
unassignedCount: res.unassigned_count || 0,
isTeacher,
myGroup: res.my_group || '',
groups,
});
// 学员:如果在总览 + 小组 tab,加载本组成员
if (!isTeacher && this.data.view === 'overview' && this.data.studentTab === 'group' && res.my_group) {
return this.loadDetailForGroup(res.my_group);
}
})
.catch(() => {
this.setData({ loading: false, refreshing: false });
wx.showToast({ title: '加载失败', icon: 'none' });
});
},
/** 打开小组详情 */
openGroup(e) {
const groupName = e.currentTarget.dataset.group;
if (!groupName) {
return;
}
this.setData({ selectedGroupName: groupName, view: 'detail', members: [] });
this.loadDetail();
},
/** 学员直接查看自己小组(只有一个小组时点击卡片直接进) */
openMyGroup() {
const groupName = this.data.myGroup;
if (!groupName) {
return;
}
this.setData({ selectedGroupName: groupName, view: 'detail', members: [] });
this.loadDetail();
},
/** 加载小组详情 */
loadDetail() {
const type = TYPES[this.data.typeIndex];
return this.loadDetailForGroup(this.data.selectedGroupName, type);
},
/** 加载指定小组的成员数据(不改变视图状态) */
loadDetailForGroup(groupName, type) {
type = type || TYPES[this.data.typeIndex];
this.setData({ loading: true });
return api.request({
url: '/api/v1/plan/group',
data: { type, group_name: groupName, period_key: this.data.periodKey }
}).then((res) => {
this.setData({
loading: false,
periodKey: res.period_key || this.data.periodKey,
canSubmit: !!res.can_submit,
message: res.message || '',
members: res.members || []
});
}).catch(() => {
this.setData({ loading: false });
wx.showToast({ title: '加载成员失败', icon: 'none' });
});
},
/** 学员切换 tab */
switchStudentTab(e) {
const tab = e.currentTarget.dataset.tab;
if (tab !== 'group' && tab !== 'personal') return;
this.setData({ studentTab: tab });
if (tab === 'personal') {
// 每次切到个人 tab 都刷新历史数据
this.loadHistory();
} else if (tab === 'group' && this.data.myGroup) {
// 每次切回小组 tab 都刷新小组成员数据
this.loadDetailForGroup(this.data.myGroup);
}
},
/** 加载个人计划历史 */
loadHistory() {
const type = TYPES[this.data.typeIndex];
return api.request({
url: '/api/v1/plan/history',
data: { type, page: 1, size: 20 }
}).then((res) => {
const history = (res.list || []).map((item) => ({
...item,
thumbs: (item.attachments || []).map((p) => api.planFileUrl(p)).slice(0, 3),
statusColor: item.status === '未完成' ? '#C67B30' : '#2E7D32',
statusBg: item.status === '未完成' ? '#FFF3E6' : '#E8F4EB',
}));
this.setData({ history });
}).catch(() => {});
},
/** 返回总览 */
backToOverview() {
this.setData({ view: 'overview', selectedGroupName: '', members: [] });
this.loadOverview();
},
/** 预览我的图片 */
previewMyImage(e) {
const index = Number(e.currentTarget.dataset.index);
const urls = this.data.myThumbs;
const src = urls[index];
if (!src) return;
wx.previewImage({ current: src, urls });
},
/** 去提交计划 */
goSubmit() {
const type = TYPES[this.data.typeIndex];
if (!this.data.canSubmit) {
wx.showToast({ title: this.data.message || '当前不可提交', icon: 'none' });
return;
}
const params = [
'plan_type=' + type,
'period_key=' + encodeURIComponent(this.data.periodKey),
'group_name='
].join('&');
wx.navigateTo({ url: '/pages/plan-submit/plan-submit?' + params });
},
// ---- 分享 ----
onShareAppMessage() {
const type = this.data.types[this.data.typeIndex];
const checked = this.data.myChecked;
const title = checked
? '我的' + type + '已提交,来明缘云道场一起成长吧'
: '明缘云道场 · ' + type + '管理工具';
return {
title,
path: '/pages/plan/plan'
};
},
onShareTimeline() {
return {
title: '明缘云道场 · 周/月/年计划,让成长有迹可循',
query: 'from=timeline'
};
}
});