171 lines
8.9 KiB
Plaintext
171 lines
8.9 KiB
Plaintext
<view class="page-shell feedback-page">
|
||
<text class="eyebrow">课堂反馈</text>
|
||
<view class="page-heading">
|
||
<text class="page-title">反馈生成</text>
|
||
</view>
|
||
<text class="page-subtitle">填写课堂表现并保存到教学反馈记录。</text>
|
||
|
||
<view wx:if="{{errorMessage}}" class="status-banner feedback-error-banner">
|
||
<view class="feedback-error-content">
|
||
<text class="feedback-error-message">{{errorMessage}}</text>
|
||
<button class="feedback-error-close" aria-label="关闭提示" bindtap="dismissError">
|
||
<icon type="clear" size="16" color="#9B2C27" />
|
||
</button>
|
||
</view>
|
||
<view class="feedback-error-actions {{errorRequestId ? '' : 'feedback-error-actions-single'}}">
|
||
<button wx:if="{{errorRequestId}}" class="feedback-error-copy" aria-label="复制问题编号" bindtap="copyRequestId">复制编号</button>
|
||
<button class="feedback-error-retry" loading="{{errorRetrying}}" disabled="{{errorRetrying}}" bindtap="retryError">重试</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="feedback-form surface">
|
||
<picker class="feedback-field" mode="selector" range="{{profileOptions}}" value="{{selectedProfileIndex}}" disabled="{{loading || recording || voiceBusy}}" bindchange="onProfileChange">
|
||
<text class="form-label">学生档案</text>
|
||
<text class="form-value">{{loading ? '正在加载...' : profileOptions[selectedProfileIndex]}}</text>
|
||
</picker>
|
||
|
||
<picker class="feedback-field" mode="date" value="{{draft.feedbackDate}}" disabled="{{recording || voiceBusy}}" bindchange="onDateChange">
|
||
<text class="form-label">反馈日期</text>
|
||
<text class="form-value">{{draft.feedbackDate}}</text>
|
||
</picker>
|
||
|
||
<view class="content-field">
|
||
<view class="content-label-row">
|
||
<text class="form-label">反馈内容</text>
|
||
<view class="content-actions">
|
||
<text class="content-count">{{contentLength}} / 2000</text>
|
||
<button
|
||
wx:if="{{contentLength > 0}}"
|
||
class="content-clear-button"
|
||
disabled="{{recording || voiceBusy}}"
|
||
bindtap="clearFeedbackContent"
|
||
>清空</button>
|
||
</view>
|
||
</view>
|
||
<textarea
|
||
class="feedback-textarea"
|
||
value="{{draft.content}}"
|
||
maxlength="2000"
|
||
disabled="{{recording || voiceBusy}}"
|
||
placeholder="记录课堂完成情况、进步与后续建议"
|
||
bindinput="onContentInput"
|
||
/>
|
||
|
||
<view class="voice-input-row">
|
||
<button
|
||
class="voice-button {{recording ? 'voice-button-recording' : ''}}"
|
||
disabled="{{saving || voiceBusy}}"
|
||
bindtap="toggleRecording"
|
||
aria-label="{{recording ? '结束录音' : '开始录音'}}"
|
||
>
|
||
<text class="voice-button-icon">{{recording ? '■' : '●'}}</text>
|
||
<text>{{recording ? '结束录音' : '语音录入'}}</text>
|
||
</button>
|
||
<view class="voice-state {{voiceActionable ? 'voice-state-actionable' : ''}}" aria-role="button" aria-label="{{voiceDetailsExpanded ? '收起语音记录' : '展开语音记录'}}" bindtap="toggleVoiceDetails">
|
||
<text class="voice-status">{{voiceStatus}}</text>
|
||
<text wx:if="{{recording}}" class="recording-time">{{recordingTime}}</text>
|
||
<view wx:elif="{{voiceActionable}}" class="voice-state-arrow {{voiceDetailsExpanded ? 'voice-state-arrow-expanded' : ''}}"></view>
|
||
</view>
|
||
<view wx:if="{{voiceDetailsExpanded}}" class="voice-record-list">
|
||
<view class="voice-record-list-heading">
|
||
<text>语音记录</text>
|
||
<view class="voice-summary-selection-heading">
|
||
<text>已选 {{selectedSummaryLessonCount}} / {{eligibleSummaryLessonCount}}</text>
|
||
<button
|
||
wx:if="{{eligibleSummaryLessonCount > 0}}"
|
||
class="voice-record-text-button"
|
||
disabled="{{recording || voiceBusy || summaryBusy || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing'))}}"
|
||
bindtap="toggleAllSummaryLessons"
|
||
>{{allSummaryLessonsSelected ? '取消全选' : '全选'}}</button>
|
||
</view>
|
||
</view>
|
||
<checkbox-group bindchange="onSummarySelectionChange">
|
||
<view
|
||
wx:for="{{voiceLessonViews}}"
|
||
wx:key="id"
|
||
wx:for-item="lesson"
|
||
class="voice-record-item"
|
||
>
|
||
<view class="voice-record-header">
|
||
<view class="voice-record-title-row">
|
||
<checkbox
|
||
wx:if="{{lesson.summaryEligible}}"
|
||
class="voice-summary-checkbox"
|
||
value="{{lesson.id}}"
|
||
checked="{{lesson.summarySelected}}"
|
||
disabled="{{recording || voiceBusy || summaryBusy || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing'))}}"
|
||
color="#007aff"
|
||
/>
|
||
<view>
|
||
<text class="voice-record-title">第{{lesson.sequence}}节</text>
|
||
<text class="voice-record-duration">{{lesson.durationText}} · {{lesson.transcriptLength}}字</text>
|
||
</view>
|
||
</view>
|
||
<text class="voice-record-status">{{lesson.statusText}}</text>
|
||
</view>
|
||
<text
|
||
wx:if="{{lesson.transcript}}"
|
||
selectable
|
||
class="voice-record-transcript {{expandedVoiceLessonId === lesson.id ? 'voice-record-transcript-expanded' : ''}}"
|
||
>{{lesson.transcript}}</text>
|
||
<text wx:else class="voice-record-empty">{{lesson.emptyText}}</text>
|
||
<view wx:if="{{lesson.transcript}}" class="voice-record-actions">
|
||
<button
|
||
wx:if="{{lesson.transcriptExpandable}}"
|
||
class="voice-record-text-button"
|
||
data-lesson-id="{{lesson.id}}"
|
||
bindtap="toggleVoiceLessonTranscript"
|
||
>{{expandedVoiceLessonId === lesson.id ? '收起' : '展开'}}</button>
|
||
<button
|
||
class="voice-record-add-button"
|
||
data-lesson-id="{{lesson.id}}"
|
||
disabled="{{!lesson.canAdd || voiceBusy}}"
|
||
bindtap="addVoiceLessonToDraft"
|
||
>{{lesson.addActionText}}</button>
|
||
</view>
|
||
</view>
|
||
</checkbox-group>
|
||
<view wx:if="{{eligibleSummaryLessonCount > 0}}" class="voice-summary-controls">
|
||
<button
|
||
class="voice-summary-button"
|
||
loading="{{summaryBusy}}"
|
||
disabled="{{recording || voiceBusy || summaryBusy || selectedSummaryLessonCount === 0 || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing'))}}"
|
||
bindtap="generateFeedbackSummary"
|
||
>{{summaryActionText}}({{selectedSummaryLessonCount}}节)</button>
|
||
</view>
|
||
<view wx:if="{{summaryRun}}" class="voice-summary-preview {{summaryRun.status === 'failed' ? 'voice-summary-preview-failed' : ''}}">
|
||
<view class="voice-summary-preview-heading">
|
||
<text>总结预览</text>
|
||
<text>{{summaryStatusText}}</text>
|
||
</view>
|
||
<text wx:if="{{summaryRun.content}}" selectable class="voice-summary-preview-content">{{summaryRun.content}}</text>
|
||
<text wx:elif="{{summaryRun.status === 'queued' || summaryRun.status === 'processing'}}" class="voice-summary-preview-note">已选择 {{summaryRun.lessonIds.length}} 节录音</text>
|
||
<button
|
||
wx:if="{{summaryRun.status === 'ready'}}"
|
||
class="voice-summary-apply-button"
|
||
loading="{{summaryBusy}}"
|
||
disabled="{{summaryBusy}}"
|
||
bindtap="applyGeneratedSummary"
|
||
>替换输入框</button>
|
||
</view>
|
||
</view>
|
||
<view wx:if="{{session && session.failedSegmentCount > 0}}" class="voice-failure-recovery">
|
||
<text class="voice-failure-hint">{{voiceFailureHint}}</text>
|
||
<button
|
||
class="voice-discard-button"
|
||
disabled="{{saving || summaryBusy || recording || voiceBusy}}"
|
||
bindtap="discardFailedSegments"
|
||
>放弃失败录音</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<button class="save-feedback primary-button" loading="{{saving}}" disabled="{{saving || summaryBusy || recording || voiceBusy || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing')) || (session && session.processingSegmentCount > 0 && session.failedSegmentCount === 0)}}" bindtap="saveFeedback">{{primaryActionText}}</button>
|
||
</view>
|
||
|
||
<view wx:if="{{!loading && profiles.length === 0}}" class="profiles-hint">
|
||
<text>当前没有学生档案,反馈仍可不关联学生直接保存。</text>
|
||
<text class="status-action" bindtap="goStudents">新建档案</text>
|
||
</view>
|
||
</view>
|