import { createProfile, deleteProfile as deleteProfileRequest, getApiErrorMessage, getProfileDefaults, listProfiles } from '../../utils/api' import { addMinutes, formatUpdatedAt } from '../../utils/time' import type { StudentDraft, StudentProfile, StudentProfileDefaults, Term } from '../../utils/types' const grades = ['艺术类', '幼儿园', '一年级', '二年级', '三年级', '四年级', '五年级', '六年级'] const subjects = ['美术', '书法', '音乐', '舞蹈', '语文', '数学', '英语'] const terms: Term[] = ['春', '暑', '秋', '寒'] const years = Array.from({ length: 5 }, (_, index) => new Date().getFullYear() - 1 + index) const initialDefaults: StudentProfileDefaults = { grade: '艺术类', subject: '美术', lessonDurationMinutes: 60 } type InputEvent = { detail: { value: string } } type PickerEvent = { detail: { value: string | number } } function getCurrentTerm(): Term { const month = new Date().getMonth() + 1 if (month >= 6 && month <= 8) return '暑' if (month >= 9 && month <= 12) return '秋' if (month <= 2) return '寒' return '春' } function buildDraft(defaults: StudentProfileDefaults = initialDefaults): StudentDraft { const startTime = '18:00' return { name: '', grade: defaults.grade, subject: defaults.subject, academicYear: new Date().getFullYear(), term: getCurrentTerm(), guardianTitle: '', startTime, endTime: addMinutes(startTime, defaults.lessonDurationMinutes) } } function filterProfiles( profiles: StudentProfile[], query: string, filterGrade: string, filterSubject: string ): StudentProfile[] { const keyword = query.trim().toLocaleLowerCase() return profiles.filter((profile) => { const searchable = [profile.name, profile.grade, profile.subject, profile.guardianTitle] .join(' ') .toLocaleLowerCase() const matchesKeyword = !keyword || searchable.includes(keyword) const matchesGrade = !filterGrade || profile.grade === filterGrade const matchesSubject = !filterSubject || profile.subject === filterSubject return matchesKeyword && matchesGrade && matchesSubject }) } function getLastUpdatedText(profiles: StudentProfile[]): string { if (!profiles.length) return '--' const latest = Math.max(...profiles.map((profile) => profile.updatedAt)) return formatUpdatedAt(latest) } Page({ data: { profiles: [] as StudentProfile[], filteredProfiles: [] as StudentProfile[], query: '', filterVisible: false, filterGrade: '', filterSubject: '', grades, subjects, terms, years, sheetVisible: false, draft: buildDraft(), guardianAuto: true, refreshDraftFromDefaults: false, lastUpdatedText: '--', loading: false, saving: false, deletingId: '', errorMessage: '' }, onShow() { this.getTabBar()?.setData({ selected: 1 }) void this.refreshProfiles() if (this.data.sheetVisible && this.data.refreshDraftFromDefaults) { void this.refreshDraftDefaults() } }, async refreshProfiles(showSuccess = false) { this.setData({ loading: true, errorMessage: '' }) try { const profiles = await listProfiles() const filteredProfiles = filterProfiles( profiles, this.data.query, this.data.filterGrade, this.data.filterSubject ) this.setData({ profiles, filteredProfiles, lastUpdatedText: getLastUpdatedText(profiles) }) if (showSuccess) wx.showToast({ title: '档案已刷新', icon: 'success' }) } catch (error) { this.setData({ errorMessage: getApiErrorMessage(error) }) } finally { this.setData({ loading: false }) } }, onRefresh() { void this.refreshProfiles(true) }, retryLoad() { void this.refreshProfiles() }, onSearch(event: InputEvent) { const query = event.detail.value this.setData({ query, filteredProfiles: filterProfiles( this.data.profiles, query, this.data.filterGrade, this.data.filterSubject ) }) }, toggleFilter() { this.setData({ filterVisible: !this.data.filterVisible }) }, selectFilter(event: WechatMiniprogram.TouchEvent) { const { type, value } = event.currentTarget.dataset as { type: 'grade' | 'subject'; value: string } const filterGrade = type === 'grade' ? (value === 'all' ? '' : value) : this.data.filterGrade const filterSubject = type === 'subject' ? (value === 'all' ? '' : value) : this.data.filterSubject this.setData({ filterGrade, filterSubject, filteredProfiles: filterProfiles(this.data.profiles, this.data.query, filterGrade, filterSubject) }) }, clearFilters() { this.setData({ query: '', filterGrade: '', filterSubject: '', filteredProfiles: this.data.profiles }) }, async openCreate() { this.setData({ sheetVisible: true, guardianAuto: true, draft: buildDraft() }) await this.refreshDraftDefaults() }, async refreshDraftDefaults() { try { const defaults = await getProfileDefaults() if (!this.data.sheetVisible) return this.setData({ 'draft.grade': defaults.grade, 'draft.subject': defaults.subject, 'draft.endTime': addMinutes(this.data.draft.startTime, defaults.lessonDurationMinutes), refreshDraftFromDefaults: false }) } catch (error) { this.setData({ refreshDraftFromDefaults: false }) wx.showToast({ title: getApiErrorMessage(error), icon: 'none' }) } }, closeCreate() { this.setData({ sheetVisible: false }) }, noop() {}, goDefaults() { this.setData({ refreshDraftFromDefaults: true }) wx.navigateTo({ url: '/pages/defaults/index' }) }, onNameInput(event: InputEvent) { const name = event.detail.value const nextData: Record = { 'draft.name': name } if (this.data.guardianAuto || !this.data.draft.guardianTitle) { nextData['draft.guardianTitle'] = name ? `${name}妈妈` : '' nextData.guardianAuto = true } this.setData(nextData) }, onGuardianInput(event: InputEvent) { this.setData({ 'draft.guardianTitle': event.detail.value, guardianAuto: false }) }, onGradeChange(event: PickerEvent) { this.setData({ 'draft.grade': grades[Number(event.detail.value)] }) }, onSubjectChange(event: PickerEvent) { this.setData({ 'draft.subject': subjects[Number(event.detail.value)] }) }, onYearChange(event: PickerEvent) { this.setData({ 'draft.academicYear': years[Number(event.detail.value)] }) }, onTermChange(event: PickerEvent) { this.setData({ 'draft.term': terms[Number(event.detail.value)] }) }, onStartTimeChange(event: InputEvent) { const startTime = event.detail.value this.setData({ 'draft.startTime': startTime }) void getProfileDefaults() .then((defaults) => { this.setData({ 'draft.endTime': addMinutes(startTime, defaults.lessonDurationMinutes) }) }) .catch(() => { this.setData({ 'draft.endTime': addMinutes(startTime, initialDefaults.lessonDurationMinutes) }) }) }, onEndTimeChange(event: InputEvent) { this.setData({ 'draft.endTime': event.detail.value }) }, async saveStudent() { const draft = this.data.draft if (!draft.name.trim()) { wx.showToast({ title: '请填写学生姓名', icon: 'none' }) return } this.setData({ saving: true }) try { const profile = await createProfile({ ...draft, name: draft.name.trim(), guardianTitle: draft.guardianTitle.trim() || `${draft.name.trim()}妈妈` }) const profiles = [profile, ...this.data.profiles] this.setData({ sheetVisible: false, profiles, filteredProfiles: filterProfiles(profiles, this.data.query, this.data.filterGrade, this.data.filterSubject), lastUpdatedText: formatUpdatedAt(profile.updatedAt), errorMessage: '' }) wx.showToast({ title: '学生档案已保存', icon: 'success' }) } catch (error) { wx.showToast({ title: getApiErrorMessage(error), icon: 'none' }) } finally { this.setData({ saving: false }) } }, deleteProfile(event: WechatMiniprogram.TouchEvent) { const { id } = event.currentTarget.dataset as { id: string } const profile = this.data.profiles.find((item) => item.id === id) if (!profile) return wx.showModal({ title: '删除学生档案', content: `确认删除“${profile.name}”吗?`, confirmColor: '#D14343', success: async (result) => { if (!result.confirm) return this.setData({ deletingId: id }) try { await deleteProfileRequest(id) const profiles = this.data.profiles.filter((item) => item.id !== id) this.setData({ profiles, filteredProfiles: filterProfiles(profiles, this.data.query, this.data.filterGrade, this.data.filterSubject), lastUpdatedText: getLastUpdatedText(profiles), errorMessage: '' }) wx.showToast({ title: '已删除', icon: 'success' }) } catch (error) { wx.showToast({ title: getApiErrorMessage(error), icon: 'none' }) } finally { this.setData({ deletingId: '' }) } } }) } })