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.
105 lines
3.3 KiB
TypeScript
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' })
|
|
}
|
|
})
|