项目初始化
This commit is contained in:
王宏豪
2026-09-03 16:49:59 +08:00
commit d7fc8c8982
40 changed files with 2688 additions and 0 deletions

211
pages/checkin/checkin.js Normal file
View File

@@ -0,0 +1,211 @@
const api = require('../../utils/api');
const auth = require('../../utils/auth');
const STATUSES = ['正常', '迟到', '请假', '不参加讨论', '早退'];
const WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const STATUS_META = {
正常: '#2E7D32',
迟到: '#C67B30',
请假: '#3A7BD5',
早退: '#8B7A6A',
'不参加讨论': '#C0392B'
};
const STATUS_ITEMS = STATUSES.map((label) => ({
label,
color: STATUS_META[label] || '#C67B30'
}));
function errMessage(err) {
if (err && typeof err === 'object') {
if (err.detail) return String(err.detail);
if (err.error) return String(err.error);
if (err.message) return err.message;
}
return '';
}
function pad2(n) {
return n < 10 ? '0' + n : String(n);
}
function fmtTime(ts) {
if (!ts) return '';
const d = new Date(String(ts).replace(' ', 'T'));
if (isNaN(d.getTime())) return String(ts);
const now = new Date();
const startOfDay = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
const diff = Math.round((startOfDay(now) - startOfDay(d)) / 86400000);
const hm = pad2(d.getHours()) + ':' + pad2(d.getMinutes());
if (diff === 0) return '今天 ' + hm;
if (diff === 1) return '昨天 ' + hm;
return pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()) + ' ' + hm;
}
Page({
data: {
statuses: STATUSES,
statusItems: STATUS_ITEMS,
statusIndex: -1,
loading: true,
classDate: '',
weekday: '',
dayNum: '',
checked: false,
record: null,
statusColor: '',
statusBg: '',
history: [],
group: [],
groupName: '',
checkedCount: 0,
total: 0,
tab: 'group',
submitting: false,
refreshing: false,
loadError: '',
},
onShow() {
this.loadPage();
},
onPullDownRefresh() {
this.loadPage().then(() => wx.stopPullDownRefresh());
},
loadPage() {
this.setData({ loadError: '' });
return auth.ensureLogin()
.then((token) => {
if (!token) {
wx.switchTab({ url: '/pages/profile/profile' });
return;
}
return this.loadWeek();
})
.catch((err) => {
const msg = (err && (err.error || err.detail || err.message)) || '加载失败,请重试';
this.setData({ loading: false, loadError: msg });
wx.showToast({ title: msg, icon: 'none' });
});
},
onRetry() {
this.loadPage();
},
loadWeek() {
this.setData({ loading: true });
return api.request({ url: '/api/v1/checkin/week' }).then((res) => {
const classDate = res.class_date || '';
const weekday = WEEKDAYS[new Date(classDate + 'T00:00:00').getDay()] || '';
const dayNum = classDate.split('-')[2] || '';
const statusIndex = res.record && res.record.status
? Math.max(0, STATUSES.indexOf(res.record.status))
: -1;
const statusColor = res.record ? (STATUS_META[res.record.status] || '#C67B30') : '';
this.setData({
classDate,
weekday,
dayNum,
checked: !!res.checked,
record: res.record || null,
statusIndex,
statusColor,
statusBg: statusColor ? statusColor + '1A' : ''
});
return this.loadGroup();
}).then(() => {
this.setData({ loading: false });
}).catch(() => {
this.setData({ loading: false });
});
},
loadGroup() {
const data = this.data.classDate ? { class_date: this.data.classDate } : {};
return api.request({ url: '/api/v1/checkin/group', data }).then((res) => {
const members = (res.members || []).map((m) => ({
...m,
shortName: (m.name || '?').charAt(0),
timeText: m.checked && m.record ? fmtTime(m.record.checked_at) : ''
}));
const checkedCount = members.filter((m) => m.checked).length;
this.setData({
group: members,
groupName: res.group_name || '',
checkedCount,
total: members.length
});
}).catch(() => {});
},
loadHistory() {
return api.request({ url: '/api/v1/checkin/list', data: { page: 1, size: 20 } }).then((res) => {
const history = (res.list || []).map((item) => {
const color = STATUS_META[item.status] || '#C67B30';
return {
...item,
timeText: fmtTime(item.checked_at),
color,
bg: color + '1A'
};
});
this.setData({ history });
}).catch(() => {});
},
onStatusTap(e) {
if (this.data.submitting) {
return;
}
const statusIndex = Number(e.currentTarget.dataset.index);
const status = STATUSES[statusIndex];
if (!status) {
return;
}
const prevIndex = this.data.statusIndex;
this.setData({ statusIndex, submitting: true });
api.request({
url: '/api/v1/checkin',
method: 'POST',
data: {
status,
class_date: this.data.classDate,
course_type: '会员课程'
}
}).then((res) => {
const record = res.record || null;
const statusColor = record ? (STATUS_META[record.status] || '#C67B30') : '';
this.setData({ submitting: false, checked: true, record, statusColor, statusBg: statusColor ? statusColor + '1A' : '' });
wx.showToast({ title: '打卡成功', icon: 'success' });
this.loadGroup();
if (this.data.tab === 'personal') {
this.loadHistory();
}
}).catch((err) => {
this.setData({ submitting: false, statusIndex: prevIndex });
wx.showToast({ title: errMessage(err) || '打卡失败', icon: 'none' });
});
},
switchTab(e) {
const tab = e.currentTarget.dataset.tab;
if (tab !== 'group' && tab !== 'personal') {
return;
}
this.setData({ tab });
if (tab === 'personal') {
this.loadHistory();
}
},
onRefresh() {
this.setData({ refreshing: true });
const task = this.data.tab === 'group' ? this.loadWeek() : this.loadHistory();
Promise.resolve(task).then(() => {
this.setData({ refreshing: false });
});
}
});