1188 lines
34 KiB
Plaintext
1188 lines
34 KiB
Plaintext
<template>
|
||
<scroll-view class="create-assignment-container">
|
||
<!-- 标题栏 -->
|
||
<view class="page-header">
|
||
<text class="page-title">{{ isEditMode ? '编辑作业' : '创建作业' }}</text>
|
||
</view>
|
||
<!-- 可滚动内容区域 -->
|
||
<scroll-view class="scroll-container" direction="vertical" :show-scrollbar="false">
|
||
<view class="content-wrapper" :class="{ 'large-screen': isLargeScreen }">
|
||
<!-- 训练项目数据 -->
|
||
<supadb collection="ak_training_projects" :filter="projectsFilter" @process-data="handleProjectsData"
|
||
#default="{ data, loading: projectsLoading }">
|
||
<view style="display: none;"></view>
|
||
</supadb>
|
||
|
||
<!-- 班级数据 -->
|
||
<supadb collection="ak_classes" @process-data="handleClassesData"
|
||
#default="{ data, loading: classesLoading }">
|
||
<view style="display: none;"></view>
|
||
</supadb>
|
||
|
||
<form @submit.prevent="submitAssignment">
|
||
<!-- 基本信息 -->
|
||
<view class="form-section">
|
||
<view class="section-title">基本信息</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">作业标题</text>
|
||
<text class="required-mark">*</text>
|
||
</view>
|
||
<input :value="formData.title" @input="onTitleInput" placeholder="请输入作业标题" maxlength="50" class="text-input" />
|
||
<view class="word-count">{{ formData.title.length }}/50</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">作业描述</text>
|
||
</view> <textarea :value="formData.description" @input="onDescriptionInput" placeholder="请输入作业描述(可选)" maxlength="200"
|
||
class="textarea-input" />
|
||
<view class="word-count">{{ formData.description.length }}/200</view>
|
||
</view> <!-- 训练项目选择 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">训练项目</text>
|
||
<text class="required-mark">*</text>
|
||
</view>
|
||
<view v-if="projectsLoading==false" class="picker-field"
|
||
@click="showProjectPickerActionSheet">
|
||
<text class="picker-text">{{ getSelectedProjectText() ?? '请选择训练项目' }}</text>
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
<view v-else class="loading-picker">
|
||
<text>加载项目中..</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 项目详情预览 -->
|
||
<view v-if="selectedProject != null" class="project-preview">
|
||
<view class="preview-header">
|
||
<text class="preview-title">项目详情</text>
|
||
</view>
|
||
<view class="project-info">
|
||
<view class="info-row">
|
||
<text class="info-label">项目名称:</text>
|
||
<text class="info-value">{{ getProjectDisplayName(selectedProject) }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">难度等级:</text>
|
||
<text class="info-value difficulty" :class="getDifficultyClass(selectedProject)">
|
||
{{ getProjectDifficulty(selectedProject) }}
|
||
</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">类别:</text>
|
||
<text class="info-value">{{ getProjectCategory(selectedProject) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 时间设置 -->
|
||
<view class="form-section">
|
||
<view class="section-title">时间设置</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">截止日期</text>
|
||
<text class="required-mark">*</text>
|
||
</view>
|
||
<picker-date :value="formData.dueDate" placeholder="请选择截止日期" class="date-picker"
|
||
@change="onDatePickerChange" />
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">截止时间</text>
|
||
</view> <picker-time :value="convertTimeStringToArray(formData.dueTime)"
|
||
@change="onTimePickerChange" placeholder="请选择截止时间" class="time-picker" />
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">预计用时</text>
|
||
</view>
|
||
<view class="duration-input"> <input :value="formData.estimatedMinutes" @input="onEstimatedMinutesInput" type="number" placeholder="30"
|
||
class="number-input" />
|
||
<text class="unit-text">分钟</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 目标设置 -->
|
||
<view class="form-section">
|
||
<view class="section-title">目标设置</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">目标描述</text>
|
||
</view> <textarea :value="formData.targetDescription" @input="onTargetDescriptionInput" placeholder="请描述本次作业的训练目标(可选)"
|
||
maxlength="300" class="textarea-input" />
|
||
<view class="word-count">{{ formData.targetDescription.length }}/300</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">最低完成要求</text>
|
||
</view> <textarea :value="formData.minimumRequirement" @input="onMinimumRequirementInput" placeholder="请描述最低完成标准(可选)" maxlength="200"
|
||
class="textarea-input" />
|
||
<view class="word-count">{{ formData.minimumRequirement.length }}/200</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 评分设置 -->
|
||
<view class="form-section">
|
||
<view class="section-title">评分设置</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">总分</text>
|
||
</view>
|
||
<view class="score-input"> <input :value="formData.totalScore" @input="onTotalScoreInput" type="number" placeholder="100"
|
||
class="number-input" />
|
||
<text class="unit-text">分</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">及格分数</text>
|
||
</view>
|
||
<view class="score-input"> <input :value="formData.passingScore" @input="onPassingScoreInput" type="number" placeholder="60"
|
||
class="number-input" />
|
||
<text class="unit-text">分</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item checkbox-item">
|
||
<switch :value="formData.allowRetake" @change="onAllowRetakeChange" class="switch-input" />
|
||
<text class="checkbox-label">允许重新提交</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 高级设置 -->
|
||
<view class="form-section">
|
||
<view class="section-title">高级设置</view> <!-- 班级选择 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">指定班级</text>
|
||
</view>
|
||
<view v-if="classesLoading==false" class="picker-field" @click="showClassPickerActionSheet">
|
||
<text class="picker-text">{{ formData.selectedClassName ?? '请选择班级(可选)' }}</text>
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
<view v-else class="loading-picker">
|
||
<text>加载班级中..</text>
|
||
</view>
|
||
<text class="help-text">不选择则对所有班级开放</text>
|
||
</view>
|
||
|
||
<!-- 提交设置 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">提交要求</text>
|
||
</view>
|
||
<view class="checkbox-group">
|
||
<view class="checkbox-item">
|
||
<switch :value="formData.requireVideo" @change="onRequireVideoChange" class="switch-input" />
|
||
<text class="checkbox-label">要求提交视频</text>
|
||
</view>
|
||
<view class="checkbox-item">
|
||
<switch :value="formData.requirePhoto" @change="onRequirePhotoChange" class="switch-input" />
|
||
<text class="checkbox-label">要求提交照片</text>
|
||
</view>
|
||
<view class="checkbox-item">
|
||
<switch :value="formData.requireText" @change="onRequireTextChange" class="switch-input" />
|
||
<text class="checkbox-label">要求文字说明</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 通知设置 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">通知设置</text>
|
||
</view>
|
||
<view class="checkbox-group">
|
||
<view class="checkbox-item">
|
||
<switch :value="formData.sendNotification" @change="onSendNotificationChange" class="switch-input" />
|
||
<text class="checkbox-label">发布时通知学生</text>
|
||
</view>
|
||
<view class="checkbox-item">
|
||
<switch :value="formData.remindBeforeDeadline" @change="onRemindBeforeDeadlineChange" class="switch-input" />
|
||
<text class="checkbox-label">截止前提醒</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 评分细则 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">评分细则</text>
|
||
</view> <textarea :value="formData.gradingCriteria" @input="onGradingCriteriaInput" placeholder="请详细描述评分标准和要求(可选)" maxlength="500"
|
||
class="textarea-input large" />
|
||
<view class="word-count">{{ formData.gradingCriteria.length }}/500</view>
|
||
</view>
|
||
|
||
<!-- 备注信息 -->
|
||
<view class="form-item">
|
||
<view class="form-label">
|
||
<text class="label-text">备注信息</text>
|
||
</view> <textarea :value="formData.notes" @input="onNotesInput" placeholder="其他需要说明的信息(可选)" maxlength="300"
|
||
class="textarea-input" />
|
||
<view class="word-count">{{ formData.notes.length }}/300</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 提交按钮 -->
|
||
<view class="submit-section">
|
||
<button @click="saveDraft" class="draft-btn" :disabled="submitting">
|
||
<text>保存草稿</text>
|
||
</button>
|
||
<button @click="submitAssignment" class="submit-btn" :disabled="submitting || !isFormValid">
|
||
<text>{{ isEditMode ? '更新作业' : '发布作业' }}</text>
|
||
</button>
|
||
</view>
|
||
</form>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
</scroll-view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
import { getCurrentUserId } from '@/utils/store.uts'
|
||
import {
|
||
getProjectId,
|
||
getProjectDisplayName,
|
||
getProjectCategory,
|
||
getProjectDifficulty,
|
||
getDifficultyClass
|
||
} from '@/pages/sport/types.uts'
|
||
type AssignmentFormData = {
|
||
title : string
|
||
description : string
|
||
projectId : string
|
||
classId : string
|
||
dueDate : string
|
||
dueTime : string
|
||
estimatedMinutes : string
|
||
targetDescription : string
|
||
minimumRequirement : string
|
||
totalScore : string
|
||
passingScore : string
|
||
allowRetake : boolean
|
||
// 高级设置
|
||
selectedClassName : string
|
||
requireVideo : boolean
|
||
requirePhoto : boolean
|
||
requireText : boolean
|
||
sendNotification : boolean
|
||
remindBeforeDeadline : boolean
|
||
gradingCriteria : string
|
||
notes : string
|
||
}
|
||
export default {
|
||
data() {
|
||
|
||
return {
|
||
screenWidth: uni.getSystemInfoSync().windowWidth,
|
||
isEditMode: false,
|
||
submitting: false,
|
||
assignmentId: '', // Store assignment ID for edit mode// Form data
|
||
formData: {
|
||
title: '',
|
||
description: '',
|
||
projectId: '',
|
||
classId: '', // Store class ID instead of class name
|
||
dueDate: '',
|
||
dueTime: '23:59',
|
||
estimatedMinutes: '30',
|
||
targetDescription: '',
|
||
minimumRequirement: '',
|
||
totalScore: '100',
|
||
passingScore: '60',
|
||
allowRetake: true,
|
||
selectedClassName: '', // Keep for display purposes
|
||
requireVideo: false,
|
||
requirePhoto: false,
|
||
requireText: false,
|
||
sendNotification: false,
|
||
remindBeforeDeadline: false,
|
||
gradingCriteria: '',
|
||
notes: ''
|
||
} as AssignmentFormData,
|
||
// Projects data
|
||
availableProjects: [] as UTSJSONObject[],
|
||
selectedProject: null as UTSJSONObject | null,
|
||
projectsLoading: true,
|
||
// Classes data
|
||
availableClasses: [] as UTSJSONObject[],
|
||
classesLoading: true,// Filters
|
||
projectsFilter: {
|
||
is_active: true // 只获取已激活的项目
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
isFormValid() : boolean {
|
||
console.log(this.formData)
|
||
return this.formData.title.trim().length > 0 &&
|
||
|
||
this.formData.dueDate.length > 0
|
||
},
|
||
isLargeScreen() : boolean {
|
||
return (this.screenWidth as number) >= 768
|
||
}
|
||
},
|
||
onLoad(options : UTSJSONObject) {
|
||
const id = options.getString('id')
|
||
if (id != null && id.length > 0) {
|
||
this.isEditMode = true
|
||
this.assignmentId = id
|
||
this.loadAssignmentData(id)
|
||
}
|
||
},
|
||
onMounted() {
|
||
// Initialize screen width
|
||
this.screenWidth = uni.getSystemInfoSync().windowWidth
|
||
},
|
||
onResize(size : OnResizeOptions) {
|
||
this.screenWidth = size.size.windowWidth
|
||
},
|
||
methods: {
|
||
// Form input handlers
|
||
onTitleInput(event : InputEvent) {
|
||
this.formData.title = event.detail.value
|
||
},
|
||
|
||
onDescriptionInput(event : InputEvent) {
|
||
this.formData.description = event.detail.value
|
||
},
|
||
|
||
onEstimatedMinutesInput(event : InputEvent) {
|
||
this.formData.estimatedMinutes = event.detail.value
|
||
},
|
||
|
||
onTargetDescriptionInput(event : InputEvent) {
|
||
this.formData.targetDescription = event.detail.value
|
||
},
|
||
|
||
onMinimumRequirementInput(event : InputEvent) {
|
||
this.formData.minimumRequirement = event.detail.value
|
||
},
|
||
|
||
onTotalScoreInput(event : InputEvent) {
|
||
this.formData.totalScore = event.detail.value
|
||
},
|
||
|
||
onPassingScoreInput(event : InputEvent) {
|
||
this.formData.passingScore = event.detail.value
|
||
},
|
||
|
||
onGradingCriteriaInput(event : InputEvent) {
|
||
this.formData.gradingCriteria = event.detail.value
|
||
},
|
||
|
||
onNotesInput(event : InputEvent) {
|
||
this.formData.notes = event.detail.value
|
||
},
|
||
|
||
// Switch handlers
|
||
onAllowRetakeChange(event : SwitchChangeEvent) {
|
||
this.formData.allowRetake = event.detail.value
|
||
},
|
||
|
||
onRequireVideoChange(event : SwitchChangeEvent) {
|
||
this.formData.requireVideo = event.detail.value
|
||
},
|
||
|
||
onRequirePhotoChange(event : SwitchChangeEvent) {
|
||
this.formData.requirePhoto = event.detail.value
|
||
},
|
||
|
||
onRequireTextChange(event : SwitchChangeEvent) {
|
||
this.formData.requireText = event.detail.value
|
||
},
|
||
|
||
onSendNotificationChange(event : SwitchChangeEvent) {
|
||
this.formData.sendNotification = event.detail.value
|
||
},
|
||
|
||
onRemindBeforeDeadlineChange(event : SwitchChangeEvent) {
|
||
this.formData.remindBeforeDeadline = event.detail.value
|
||
}, // 获取当前教师ID(全局实现替换本地实现)
|
||
getCurrentTeacherId(): string {
|
||
const teacherId = getCurrentUserId()
|
||
if (teacherId == null || teacherId.length == 0) {
|
||
uni.navigateTo({ url: '/pages/user/login' })
|
||
return ''
|
||
}
|
||
return teacherId
|
||
}, // Time picker conversion methods
|
||
convertTimeStringToArray(timeString : string) : number[] {
|
||
if (timeString == null || timeString.length == 0) {
|
||
return [23, 59] // Default to 23:59
|
||
}
|
||
const parts = timeString.split(':')
|
||
if (parts.length >= 2) {
|
||
|
||
const hour = parseInt(parts[0]) ?? 0
|
||
const minute = parseInt(parts[1]) ?? 0
|
||
return [hour, minute]
|
||
}
|
||
return [23, 59] // Default fallback
|
||
}, convertTimeArrayToString(timeArray : number[]) : string {
|
||
if (timeArray == null || timeArray.length < 2) {
|
||
return '23:59' // Default fallback
|
||
} const hour = timeArray[0] ?? 0
|
||
const minute = timeArray[1] ?? 0
|
||
const hourStr = hour < 10 ? '0' + hour : hour.toString()
|
||
const minuteStr = minute < 10 ? '0' + minute : minute.toString()
|
||
return hourStr + ':' + minuteStr
|
||
}, onTimePickerChange(timeArray : number[]) {
|
||
this.formData.dueTime = this.convertTimeArrayToString(timeArray)
|
||
},
|
||
|
||
onDatePickerChange(dateString : string) {
|
||
this.formData.dueDate = dateString
|
||
}, handleProjectsData(data : UTSJSONObject) {
|
||
const dataArray = data.get("data")
|
||
if (dataArray != null && Array.isArray(dataArray)) {
|
||
this.availableProjects = dataArray as Array<UTSJSONObject>
|
||
} else {
|
||
this.availableProjects = []
|
||
}
|
||
console.log('DEBUG: Projects data received:', this.availableProjects)
|
||
console.log(this.availableProjects)
|
||
this.projectsLoading = false // 如果是编辑模式且已有项目ID,设置选中的项目
|
||
if (this.isEditMode && this.formData.projectId.length > 0) {
|
||
this.setSelectedProjectById(this.formData.projectId)
|
||
}
|
||
}, handleClassesData(data : UTSJSONObject) {
|
||
console.log('DEBUG: Classes data processing started')
|
||
const dataArray = data.get("data")
|
||
if (dataArray != null && Array.isArray(dataArray)) {
|
||
this.availableClasses = dataArray as Array<UTSJSONObject>
|
||
} else {
|
||
this.availableClasses = []
|
||
}
|
||
console.log('DEBUG: Classes data received:', this.availableClasses)
|
||
this.classesLoading = false },async loadAssignmentData(assignmentId : string) { try {
|
||
console.log('加载作业数据:', assignmentId)
|
||
// 从 Supabase 加载作业数据
|
||
const result = await supa.from('ak_assignments').select('*', null).eq('id', assignmentId).execute()
|
||
|
||
if (result.status >= 200 && result.status < 300 && result.data != null && Array.isArray(result.data) && (result.data as Array<any>).length > 0) {
|
||
const dataArray = result.data as Array<any>
|
||
const assignment = dataArray[0] as UTSJSONObject // 填充表单数据
|
||
this.formData.title = assignment.getString('title') ?? ''
|
||
this.formData.description = assignment.getString('description') ?? '' // 解析截止时间
|
||
const dueDate = assignment.getString('due_date') ?? ''
|
||
if (dueDate != null && dueDate.length > 0) {
|
||
const date = new Date(dueDate)
|
||
this.formData.dueDate = date.toISOString().split('T')[0] // YYYY-MM-DD
|
||
this.formData.dueTime = date.toTimeString().substring(0, 5) // HH:MM
|
||
}
|
||
|
||
// 班级信息
|
||
const classId = assignment.getString('class_id') ?? ''
|
||
if (classId != null && classId.length > 0) {
|
||
this.formData.classId = classId // 尝试从可用班级中找到匹配的班级名称
|
||
const matchedClass = this.availableClasses.find((c : UTSJSONObject) : boolean => c.getString('id') === classId)
|
||
if (matchedClass != null) {
|
||
this.formData.selectedClassName = matchedClass?.getString('name') ?? ''
|
||
}
|
||
}
|
||
|
||
// 项目相关信息
|
||
const projectName = assignment.getString('project_name') ?? ''
|
||
if (projectName != null && projectName.length > 0) { // 尝试从可用项目中找到匹配的项目
|
||
const matchedProject = this.availableProjects.find((p : UTSJSONObject) : boolean => p.getString('name') === projectName)
|
||
if (matchedProject != null) {
|
||
this.selectProject(matchedProject)
|
||
}
|
||
}
|
||
|
||
console.log('作业数据加载成功:', this.formData)
|
||
} else {
|
||
console.error('加载作业数据失败:', result.error)
|
||
uni.showToast({ title: '加载作业数据失败', icon: 'none' })
|
||
}
|
||
} catch (error) {
|
||
console.error('加载作业数据异常:', error)
|
||
uni.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||
}
|
||
},
|
||
showProjectPickerActionSheet() {
|
||
console.log('DEBUG: Available projects count:', this.availableProjects.length)
|
||
|
||
if (this.availableProjects.length === 0) {
|
||
uni.showToast({
|
||
title: '暂无可用项目',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
const itemList : string[] = []
|
||
for (let i = 0; i < this.availableProjects.length; i++) {
|
||
const project = this.availableProjects[i]
|
||
console.log(project)
|
||
itemList.push(getProjectDisplayName(project))
|
||
}
|
||
|
||
console.log('DEBUG: ActionSheet itemList:', itemList)
|
||
|
||
uni.showActionSheet({
|
||
itemList,
|
||
success: (res) => {
|
||
if (typeof res.tapIndex === 'number') {
|
||
const selectedProject = this.availableProjects[res.tapIndex]
|
||
this.selectProject(selectedProject)
|
||
}
|
||
}
|
||
})
|
||
|
||
}, showClassPickerActionSheet() {
|
||
console.log('DEBUG: Available classes count:', this.availableClasses.length)
|
||
|
||
if (this.classesLoading) {
|
||
uni.showToast({
|
||
title: '班级数据加载中...',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (this.availableClasses.length === 0) {
|
||
uni.showToast({
|
||
title: '暂无可用班级',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
} // 构建班级选择列表,添加"全部班级"选项
|
||
const itemList = ['全部班级']
|
||
for (let i = 0; i < this.availableClasses.length; i++) {
|
||
const classData = this.availableClasses[i]
|
||
const className = classData.getString('name') ?? '未知班级'
|
||
itemList.push(className)
|
||
}
|
||
|
||
console.log('DEBUG: Class ActionSheet itemList:', itemList)
|
||
|
||
uni.showActionSheet({
|
||
itemList,
|
||
success: (res) => {
|
||
if (typeof res.tapIndex === 'number') {
|
||
if (res.tapIndex === 0) {
|
||
// 选择了"全部班级"
|
||
this.formData.selectedClassName = ''
|
||
this.formData.classId = '' } else {
|
||
// 选择了具体班级
|
||
const selectedClass = this.availableClasses[res.tapIndex - 1]
|
||
this.formData.selectedClassName = selectedClass.getString('name') ?? '未知班级'
|
||
this.formData.classId = selectedClass.getString('id') ?? ''
|
||
console.log('DEBUG: Selected class:', {
|
||
name: this.formData.selectedClassName,
|
||
id: this.formData.classId
|
||
})
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}, selectProject(project : UTSJSONObject) {
|
||
this.selectedProject = project
|
||
this.formData.projectId = getProjectId(project) as string
|
||
},setSelectedProjectById(projectId : string) {
|
||
const project = this.availableProjects.find((p : UTSJSONObject) : boolean => getProjectId(p) === projectId)
|
||
if (project != null) {
|
||
this.selectedProject = project
|
||
}
|
||
},
|
||
getSelectedProjectText() : string | null {
|
||
if (this.selectedProject != null) {
|
||
const selectedProject = this.selectedProject
|
||
return getProjectDisplayName(selectedProject!!)
|
||
}
|
||
return null
|
||
},
|
||
async saveDraft() {
|
||
this.submitting = true
|
||
try {
|
||
const teacherId = this.getCurrentTeacherId()
|
||
if (teacherId == null || teacherId.length == 0) {
|
||
uni.showToast({ title: '用户未登录,请重新登录', icon: 'none' })
|
||
this.submitting = false
|
||
return
|
||
} // 构建草稿数据
|
||
const draftData = {
|
||
teacher_id: teacherId,
|
||
title: this.formData.title ?? '未命名草稿',
|
||
description: this.formData.description,
|
||
project_name: this.selectedProject?.getString('name') ?? '',
|
||
class_id: this.formData.classId ?? null, // 使用实际的班级ID due_date: (this.formData.dueDate != null && this.formData.dueDate.length > 0) ? `${this.formData.dueDate}T${this.formData.dueTime}:00.000Z` : null,
|
||
start_time: new Date().toISOString(),
|
||
end_time: (this.formData.dueDate != null && this.formData.dueDate.length > 0) ? `${this.formData.dueDate}T${this.formData.dueTime}:00.000Z` : null,
|
||
status: 'draft',
|
||
created_at: new Date().toISOString(),
|
||
updated_at: new Date().toISOString()
|
||
} as UTSJSONObject
|
||
console.log('保存草稿数据:', draftData)
|
||
// 使用 Supabase 插入数据
|
||
const result = await supa.from('ak_assignments').insert(draftData).execute()
|
||
|
||
console.log('保存草稿结果:', result)
|
||
|
||
if (result.status >= 200 && result.status < 300) {
|
||
uni.showToast({
|
||
title: '草稿已保存',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
console.error('保存草稿失败:', result.error)
|
||
uni.showToast({
|
||
title: '保存失败: ' + (result.error?.message ?? '未知错误'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('保存草稿失败:', error)
|
||
uni.showToast({
|
||
title: '网络错误,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.submitting = false
|
||
}
|
||
}, async submitAssignment() {
|
||
console.log('submitAssignment 0')
|
||
if (!this.isFormValid) {
|
||
uni.showToast({
|
||
title: '请完善必填信息',
|
||
icon: 'error',
|
||
duration: 4000
|
||
}
|
||
)
|
||
return
|
||
}
|
||
console.log('submitAssignment')
|
||
this.submitting = true
|
||
try {
|
||
// 获取当前教师ID
|
||
const teacherId = this.getCurrentTeacherId()
|
||
if (teacherId == null || teacherId.length == 0) {
|
||
uni.showToast({ title: '用户未登录,请重新登录', icon: 'none' })
|
||
this.submitting = false
|
||
return
|
||
}
|
||
|
||
if (this.isEditMode && this.assignmentId != null && this.assignmentId.length > 0) {
|
||
// 编辑模式 - 更新现有作业
|
||
const updateData = {
|
||
title: this.formData.title,
|
||
description: this.formData.description,
|
||
project_name: this.selectedProject?.getString('name') ?? '',
|
||
due_date: `${this.formData.dueDate}T${this.formData.dueTime}:00.000Z`,
|
||
end_time: `${this.formData.dueDate}T${this.formData.dueTime}:00.000Z`,
|
||
status: 'active',
|
||
updated_at: new Date().toISOString()
|
||
} as UTSJSONObject
|
||
console.log('更新作业数据:', updateData)
|
||
|
||
const result = await supa.from('ak_assignments').update(updateData).eq('id', this.assignmentId).execute()
|
||
|
||
console.log('更新结果:', result)
|
||
|
||
if (result.status >= 200 && result.status < 300) {
|
||
uni.showToast({
|
||
title: '作业已更新',
|
||
icon: 'success'
|
||
})
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
} else {
|
||
console.error('更新失败:', result.error)
|
||
uni.showToast({
|
||
title: '更新失败: ' + (result.error?.message ?? '未知错误'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
} else {
|
||
// 创建模式 - 新建作业
|
||
const assignmentData = {
|
||
teacher_id: teacherId,
|
||
title: this.formData.title,
|
||
description: this.formData.description,
|
||
project_name: this.selectedProject?.getString('name') ?? '',
|
||
class_id: this.formData.classId ?? null, // 使用实际的班级ID
|
||
due_date: `${this.formData.dueDate}T${this.formData.dueTime}:00.000Z`,
|
||
start_time: new Date().toISOString(), // 当前时间作为开始时间
|
||
end_time: `${this.formData.dueDate}T${this.formData.dueTime}:00.000Z`,
|
||
status: 'active',
|
||
created_at: new Date().toISOString(),
|
||
updated_at: new Date().toISOString()
|
||
} as UTSJSONObject
|
||
console.log('创建作业数据:', assignmentData)
|
||
|
||
const result = await supa.from('ak_assignments').insert(assignmentData).execute()
|
||
|
||
console.log('创建结果:', result)
|
||
|
||
if (result.status >= 200 && result.status < 300) {
|
||
uni.showToast({
|
||
title: '作业已发布',
|
||
icon: 'success'
|
||
})
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
} else {
|
||
console.error('创建失败:', result.error)
|
||
uni.showToast({
|
||
title: '发布失败: ' + (result.error?.message ?? '未知错误'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('提交作业失败:', error)
|
||
uni.showToast({
|
||
title: '网络错误,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.submitting = false
|
||
}
|
||
}, debugProjectsData() {
|
||
console.log('=== MANUAL DEBUG ===')
|
||
console.log('availableProjects.length:', this.availableProjects.length)
|
||
console.log('availableProjects:', this.availableProjects)
|
||
console.log('projectsLoading:', this.projectsLoading)
|
||
console.log('projectsFilter:', this.projectsFilter)
|
||
|
||
// 显示具体的项目信息
|
||
for (let i = 0; i < this.availableProjects.length; i++) {
|
||
const project = this.availableProjects[i]
|
||
console.log(`Project ${i}:`, project)
|
||
try {
|
||
console.log(` - ID: ${getProjectId(project)}`)
|
||
console.log(` - DisplayName: ${getProjectDisplayName(project)}`)
|
||
console.log(` - Category: ${getProjectCategory(project)}`)
|
||
console.log(` - Difficulty: ${getProjectDifficulty(project)}`)
|
||
} catch (e) {
|
||
console.log(` - Error processing project:`, e)
|
||
}
|
||
}
|
||
|
||
uni.showToast({
|
||
title: `找到 ${this.availableProjects.length} 个项目`,
|
||
icon: 'none'
|
||
})
|
||
},
|
||
|
||
debugClassesData() {
|
||
console.log('=== CLASSES DEBUG ===')
|
||
console.log('availableClasses.length:', this.availableClasses.length)
|
||
console.log('availableClasses:', this.availableClasses)
|
||
console.log('classesLoading:', this.classesLoading) // 显示具体的班级信息
|
||
for (let i = 0; i < this.availableClasses.length; i++) {
|
||
const classData = this.availableClasses[i]
|
||
console.log(`Class ${i}:`, classData)
|
||
console.log(` - ID: ${classData.getString('id')}`)
|
||
console.log(` - Name: ${classData.getString('name')}`)
|
||
console.log(` - Grade ID: ${classData.getString('grade_id')}`)
|
||
}
|
||
|
||
uni.showToast({
|
||
title: `找到 ${this.availableClasses.length} 个班级`,
|
||
icon: 'none'
|
||
}) },
|
||
|
||
// Template helper wrapper methods
|
||
getProjectDisplayName(project : UTSJSONObject | null) : string {
|
||
if (project == null) return ''
|
||
return getProjectDisplayName(project)
|
||
},
|
||
|
||
getProjectDifficulty(project : UTSJSONObject | null) : number {
|
||
if (project == null) return 1
|
||
return getProjectDifficulty(project)
|
||
},
|
||
|
||
getProjectCategory(project : UTSJSONObject | null) : string {
|
||
if (project == null) return ''
|
||
return getProjectCategory(project)
|
||
},
|
||
|
||
getDifficultyClass(project : UTSJSONObject | null) : string {
|
||
if (project == null) return ''
|
||
return getDifficultyClass(project)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.create-assignment-container {
|
||
flex: 1;
|
||
background-image: linear-gradient(to bottom right, #667eea, #764ba2);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.page-header {
|
||
padding: 40rpx 30rpx 30rpx;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(10px);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.scroll-container {
|
||
flex: 1;
|
||
height: 0;
|
||
/* 确保滚动容器能正确工作 */
|
||
}
|
||
|
||
.content-wrapper {
|
||
padding: 30rpx;
|
||
background: #F8FAFC;
|
||
border-radius: 30rpx 30rpx 0 0;
|
||
margin-top: 20rpx;
|
||
min-height: calc(100vh - 160rpx);
|
||
/* 确保内容至少填满屏幕 */
|
||
}
|
||
|
||
.content-wrapper.large-screen {
|
||
padding: 40rpx;
|
||
max-width: 800rpx;
|
||
margin: 20rpx auto 0;
|
||
border-radius: 30rpx;
|
||
}
|
||
|
||
.form-section {
|
||
background: #FFFFFF;
|
||
padding: 30rpx;
|
||
border-radius: 20rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #1E293B;
|
||
margin-bottom: 30rpx;
|
||
padding-bottom: 15rpx;
|
||
border-bottom: 2rpx solid #E2E8F0;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.form-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.form-label {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.label-text {
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.required-mark {
|
||
color: #EF4444;
|
||
font-size: 28rpx;
|
||
margin-left: 5rpx;
|
||
}
|
||
|
||
.text-input,
|
||
.textarea-input {
|
||
width: 100%;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 15rpx;
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
background: #FFFFFF;
|
||
}
|
||
|
||
.text-input:focus,
|
||
.textarea-input:focus {
|
||
border-color: #6366F1;
|
||
outline: none;
|
||
}
|
||
|
||
.textarea-input {
|
||
height: 150rpx;
|
||
text-align: top;
|
||
}
|
||
|
||
.word-count {
|
||
text-align: right;
|
||
font-size: 24rpx;
|
||
color: #9CA3AF;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
.picker-field {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 15rpx;
|
||
background: #FFFFFF;
|
||
min-height: 80rpx;
|
||
}
|
||
|
||
.picker-text {
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
flex: 1;
|
||
}
|
||
|
||
.picker-arrow {
|
||
font-size: 24rpx;
|
||
color: #9CA3AF;
|
||
}
|
||
|
||
.loading-picker {
|
||
padding: 20rpx;
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 15rpx;
|
||
background: #F9FAFB;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 80rpx;
|
||
}
|
||
|
||
.date-picker,
|
||
.time-picker {
|
||
width: 100%;
|
||
height: 80rpx;
|
||
padding: 0 20rpx;
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 15rpx;
|
||
background: #FFFFFF;
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
}
|
||
|
||
.duration-input,
|
||
.score-input {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.duration-input .number-input,
|
||
.score-input .number-input {
|
||
margin-right: 15rpx;
|
||
}
|
||
|
||
.duration-input .unit-text,
|
||
.score-input .unit-text {
|
||
margin-left: 0;
|
||
}
|
||
|
||
.number-input {
|
||
flex: 1;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 15rpx;
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
background: #FFFFFF;
|
||
text-align: center;
|
||
}
|
||
|
||
.unit-text {
|
||
font-size: 28rpx;
|
||
color: #6B7280;
|
||
}
|
||
|
||
.checkbox-item {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.checkbox-item .switch-input {
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.switch-input {
|
||
transform: scale(0.8);
|
||
}
|
||
|
||
.checkbox-label {
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
}
|
||
|
||
/* Project Preview */
|
||
.project-preview {
|
||
background: #F8FAFC;
|
||
border: 2rpx solid #E2E8F0;
|
||
border-radius: 15rpx;
|
||
overflow: hidden;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.preview-header {
|
||
background: #EDE9FE;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.preview-title {
|
||
font-size: 28rpx;
|
||
color: #7C3AED;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.project-info {
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.project-info .info-row {
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.project-info .info-row:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.info-row {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.info-row .info-label {
|
||
margin-right: 15rpx;
|
||
}
|
||
|
||
|
||
.info-label {
|
||
font-size: 26rpx;
|
||
color: #6B7280;
|
||
width: 120rpx;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 26rpx;
|
||
color: #374151;
|
||
flex: 1;
|
||
}
|
||
|
||
.info-value.difficulty {
|
||
font-weight: 400;
|
||
}
|
||
|
||
.info-value.difficulty.beginner {
|
||
color: #10B981;
|
||
}
|
||
|
||
.info-value.difficulty.intermediate {
|
||
color: #F59E0B;
|
||
}
|
||
|
||
.info-value.difficulty.advanced {
|
||
color: #EF4444;
|
||
}
|
||
|
||
/* Advanced Settings */
|
||
.help-text {
|
||
font-size: 24rpx;
|
||
color: #9CA3AF;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.checkbox-group {}
|
||
|
||
.checkbox-group .checkbox-item {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.checkbox-group .checkbox-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.checkbox-group .checkbox-item {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.checkbox-group .checkbox-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.textarea-input.large {
|
||
height: 200rpx;
|
||
}
|
||
|
||
/* Submit Section */
|
||
.submit-section {
|
||
flex-direction: row;
|
||
margin-top: 40rpx;
|
||
padding: 0 30rpx 40rpx;
|
||
}
|
||
|
||
.submit-section .draft-btn,
|
||
.submit-section .submit-btn {
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.submit-section .submit-btn:last-child {
|
||
margin-right: 0;
|
||
}
|
||
|
||
|
||
.draft-btn,
|
||
.submit-btn {
|
||
flex: 1;
|
||
height: 90rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 400;
|
||
border: none;
|
||
}
|
||
|
||
.draft-btn {
|
||
background: #F3F4F6;
|
||
color: #6B7280;
|
||
}
|
||
|
||
.draft-btn:active {
|
||
background: #E5E7EB;
|
||
}
|
||
|
||
.submit-btn {
|
||
background-image: linear-gradient(to bottom right, #6366F1, #8B5CF6);
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.submit-btn:active {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.submit-btn:disabled,
|
||
.draft-btn:disabled {
|
||
opacity: 0.5;
|
||
}
|
||
</style> |