feat(ui): refine profile workflows and visual system

This commit is contained in:
2026-07-21 20:50:52 +08:00
parent 12a000845a
commit 04e80b184c
16 changed files with 1105 additions and 334 deletions

View File

@@ -3,10 +3,11 @@ import {
deleteProfile as deleteProfileRequest,
getApiErrorMessage,
getProfileDefaults,
listFeedbackRecords,
listProfiles
} from '../../utils/api'
import { addMinutes, formatUpdatedAt } from '../../utils/time'
import type { StudentDraft, StudentProfile, StudentProfileDefaults, Term } from '../../utils/types'
import type { FeedbackRecord, StudentDraft, StudentProfile, StudentProfileDefaults, Term } from '../../utils/types'
const grades = ['初一', '初二', '初三']
const subjects = ['美术', '书法', '音乐', '舞蹈', '语文', '数学', '英语']
@@ -17,9 +18,23 @@ const initialDefaults: StudentProfileDefaults = {
subject: '数学',
lessonDurationMinutes: 60
}
const termLabels: Record<Term, string> = {
: '春季',
: '暑期',
: '秋季',
: '寒假'
}
type InputEvent = { detail: { value: string } }
type PickerEvent = { detail: { value: string | number } }
type StudentProfileView = StudentProfile & { academicTermLabel: string }
function withAcademicTermLabel(profile: StudentProfile): StudentProfileView {
return {
...profile,
academicTermLabel: `${profile.academicYear}${termLabels[profile.term]}`
}
}
function getCurrentTerm(): Term {
const month = new Date().getMonth() + 1
@@ -44,11 +59,11 @@ function buildDraft(defaults: StudentProfileDefaults = initialDefaults): Student
}
function filterProfiles(
profiles: StudentProfile[],
profiles: StudentProfileView[],
query: string,
filterGrade: string,
filterSubject: string
): StudentProfile[] {
): StudentProfileView[] {
const keyword = query.trim().toLocaleLowerCase()
return profiles.filter((profile) => {
const searchable = [profile.name, profile.grade, profile.subject, profile.guardianTitle]
@@ -69,12 +84,17 @@ function getLastUpdatedText(profiles: StudentProfile[]): string {
Page({
data: {
profiles: [] as StudentProfile[],
filteredProfiles: [] as StudentProfile[],
profiles: [] as StudentProfileView[],
filteredProfiles: [] as StudentProfileView[],
query: '',
filterVisible: false,
filterGrade: '',
filterSubject: '',
filterCount: 0,
expandedProfileId: '',
recentFeedbackByProfile: {} as Record<string, FeedbackRecord[]>,
feedbackLoadingByProfile: {} as Record<string, boolean>,
feedbackErrorsByProfile: {} as Record<string, string>,
grades,
subjects,
terms,
@@ -101,7 +121,7 @@ Page({
async refreshProfiles(showSuccess = false) {
this.setData({ loading: true, errorMessage: '' })
try {
const profiles = await listProfiles()
const profiles = (await listProfiles()).map(withAcademicTermLabel)
const filteredProfiles = filterProfiles(
profiles,
this.data.query,
@@ -111,7 +131,11 @@ Page({
this.setData({
profiles,
filteredProfiles,
lastUpdatedText: getLastUpdatedText(profiles)
lastUpdatedText: getLastUpdatedText(profiles),
expandedProfileId: '',
recentFeedbackByProfile: {},
feedbackLoadingByProfile: {},
feedbackErrorsByProfile: {}
})
if (showSuccess) wx.showToast({ title: '档案已刷新', icon: 'success' })
} catch (error) {
@@ -153,6 +177,7 @@ Page({
this.setData({
filterGrade,
filterSubject,
filterCount: Number(Boolean(filterGrade)) + Number(Boolean(filterSubject)),
filteredProfiles: filterProfiles(this.data.profiles, this.data.query, filterGrade, filterSubject)
})
},
@@ -162,10 +187,67 @@ Page({
query: '',
filterGrade: '',
filterSubject: '',
filterCount: 0,
filteredProfiles: this.data.profiles
})
},
toggleProfile(event: WechatMiniprogram.TouchEvent) {
const { id } = event.currentTarget.dataset as { id: string }
if (this.data.expandedProfileId === id) {
this.setData({ expandedProfileId: '' })
return
}
const hasFeedback = Object.prototype.hasOwnProperty.call(this.data.recentFeedbackByProfile, id)
this.setData({
expandedProfileId: id,
...(hasFeedback
? {}
: {
feedbackLoadingByProfile: { ...this.data.feedbackLoadingByProfile, [id]: true },
feedbackErrorsByProfile: { ...this.data.feedbackErrorsByProfile, [id]: '' }
})
})
if (!hasFeedback) void this.loadRecentFeedback(id)
},
async loadRecentFeedback(profileId: string) {
try {
const records = await listFeedbackRecords(profileId)
this.setData({
recentFeedbackByProfile: {
...this.data.recentFeedbackByProfile,
[profileId]: records.slice(0, 3)
}
})
} catch (error) {
this.setData({
feedbackErrorsByProfile: {
...this.data.feedbackErrorsByProfile,
[profileId]: getApiErrorMessage(error)
}
})
} finally {
this.setData({
feedbackLoadingByProfile: {
...this.data.feedbackLoadingByProfile,
[profileId]: false
}
})
}
},
retryRecentFeedback(event: WechatMiniprogram.TouchEvent) {
const { id } = event.currentTarget.dataset as { id: string }
if (this.data.feedbackLoadingByProfile[id]) return
this.setData({
feedbackLoadingByProfile: { ...this.data.feedbackLoadingByProfile, [id]: true },
feedbackErrorsByProfile: { ...this.data.feedbackErrorsByProfile, [id]: '' }
})
void this.loadRecentFeedback(id)
},
async openCreate() {
this.setData({
sheetVisible: true,
@@ -262,11 +344,13 @@ Page({
this.setData({ saving: true })
try {
const profile = await createProfile({
...draft,
name: draft.name.trim(),
guardianTitle: draft.guardianTitle.trim() || `${draft.name.trim()}妈妈`
})
const profile = withAcademicTermLabel(
await createProfile({
...draft,
name: draft.name.trim(),
guardianTitle: draft.guardianTitle.trim() || `${draft.name.trim()}妈妈`
})
)
const profiles = [profile, ...this.data.profiles]
this.setData({
sheetVisible: false,
@@ -284,25 +368,48 @@ Page({
}
},
deleteProfile(event: WechatMiniprogram.TouchEvent) {
openProfileActions(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.showActionSheet({
itemList: ['删除学生档案'],
itemColor: '#D13F3A',
success: (result) => {
if (result.tapIndex === 0) this.confirmDeleteProfile(id)
}
})
},
confirmDeleteProfile(id: string) {
const profile = this.data.profiles.find((item) => item.id === id)
if (!profile) return
wx.showModal({
title: '删除学生档案',
content: `确认删除“${profile.name}”吗?`,
confirmColor: '#D14343',
confirmColor: '#D13F3A',
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)
const recentFeedbackByProfile = { ...this.data.recentFeedbackByProfile }
const feedbackLoadingByProfile = { ...this.data.feedbackLoadingByProfile }
const feedbackErrorsByProfile = { ...this.data.feedbackErrorsByProfile }
delete recentFeedbackByProfile[id]
delete feedbackLoadingByProfile[id]
delete feedbackErrorsByProfile[id]
this.setData({
profiles,
filteredProfiles: filterProfiles(profiles, this.data.query, this.data.filterGrade, this.data.filterSubject),
lastUpdatedText: getLastUpdatedText(profiles),
expandedProfileId: this.data.expandedProfileId === id ? '' : this.data.expandedProfileId,
recentFeedbackByProfile,
feedbackLoadingByProfile,
feedbackErrorsByProfile,
errorMessage: ''
})
wx.showToast({ title: '已删除', icon: 'success' })