Files
teaching-feedback-assistant/pages/records/index.ts
shay7sev a875fe9f63 feat(observability): add structured application logging
Emit correlated JSON logs from the API and FunASR services, propagate request IDs to the client, and configure bounded opt-in Docker logging.

Document the host-wide Grafana/Loki query workflow and keep observability deployment ownership outside the application repository.
2026-07-22 13:52:40 +08:00

105 lines
3.3 KiB
TypeScript

import {
copyApiRequestId,
deleteFeedbackRecord,
getApiErrorMessage,
getApiRequestId,
listFeedbackRecords,
listProfiles
} from '../../utils/api'
import type { FeedbackRecord, StudentProfile } from '../../utils/types'
type PickerEvent = { detail: { value: string | number } }
type FeedbackRecordView = FeedbackRecord & { profileName: string }
Page({
data: {
profiles: [] as StudentProfile[],
records: [] as FeedbackRecordView[],
profileOptions: ['全部学生'],
selectedProfileIndex: 0,
loading: false,
deletingId: '',
errorMessage: '',
errorRequestId: ''
},
onShow() {
this.getTabBar()?.setData({ selected: 2 })
void this.loadData()
},
async loadData() {
this.setData({ loading: true, errorMessage: '', errorRequestId: '' })
try {
const profiles = await listProfiles()
const selectedProfile = profiles[this.data.selectedProfileIndex - 1]
const records = await listFeedbackRecords(selectedProfile?.id)
this.setData({
profiles,
profileOptions: ['全部学生', ...profiles.map((profile) => profile.name)],
selectedProfileIndex: selectedProfile ? this.data.selectedProfileIndex : 0,
records: this.withProfileNames(records, profiles)
})
} catch (error) {
this.setData({ errorMessage: getApiErrorMessage(error), errorRequestId: getApiRequestId(error) })
} finally {
this.setData({ loading: false })
}
},
withProfileNames(records: FeedbackRecord[], profiles: StudentProfile[]): FeedbackRecordView[] {
return records.map((record) => ({
...record,
profileName: profiles.find((profile) => profile.id === record.profileId)?.name || '未关联学生'
}))
},
retryLoad() {
void this.loadData()
},
copyRequestId() {
copyApiRequestId(this.data.errorRequestId)
},
async onProfileChange(event: PickerEvent) {
const selectedProfileIndex = Number(event.detail.value)
const profile = this.data.profiles[selectedProfileIndex - 1]
this.setData({ selectedProfileIndex, loading: true, errorMessage: '', errorRequestId: '' })
try {
const records = await listFeedbackRecords(profile?.id)
this.setData({ records: this.withProfileNames(records, this.data.profiles) })
} catch (error) {
this.setData({ errorMessage: getApiErrorMessage(error), errorRequestId: getApiRequestId(error) })
} finally {
this.setData({ loading: false })
}
},
deleteRecord(event: WechatMiniprogram.TouchEvent) {
const { id } = event.currentTarget.dataset as { id: string }
wx.showModal({
title: '删除反馈记录',
content: '删除后无法恢复,确认继续吗?',
confirmColor: '#D14343',
success: async (result) => {
if (!result.confirm) return
this.setData({ deletingId: id })
try {
await deleteFeedbackRecord(id)
this.setData({ records: this.data.records.filter((record) => record.id !== id) })
wx.showToast({ title: '记录已删除', icon: 'success' })
} catch (error) {
wx.showToast({ title: getApiErrorMessage(error), icon: 'none' })
} finally {
this.setData({ deletingId: '' })
}
}
})
},
goFeedback() {
wx.switchTab({ url: '/pages/feedback/index' })
}
})