feat(ui): refine profile workflows and visual system
This commit is contained in:
@@ -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' })
|
||||
|
||||
Reference in New Issue
Block a user