Commit 1a5a30c9 authored by Administrator's avatar Administrator
Browse files

completed Task page CRUD

parent ede4c3bf
......@@ -28,5 +28,9 @@ export default {
delete: 'Are you sure you want to delete this item?',
unsavedChanges: 'You have unsaved changes. Are you sure you want to leave?',
irreversible: 'This operation cannot be undone. Please proceed with caution.'
}
},
dialogs: {
deleteConfirmation: 'Delete Confirmation'
},
deleteWarning: 'This action cannot be undone. Deleted data cannot be recovered.'
}
......@@ -34,7 +34,9 @@ export default {
image: 'Image',
noImage: 'No Image',
clickToLoad: 'Click to load image',
loadingImage: 'Loading image...'
loadingImage: 'Loading image...',
updateImage: 'Update Image',
deleteImage: 'Delete Image'
},
filters: {
title: 'Filters',
......@@ -49,12 +51,28 @@ export default {
expandToReselect: 'Click to Expand and Reselect'
},
edit: {
title: 'Edit Task'
title: 'Edit Task',
createTitle: 'Create New Task'
},
messages: {
loadError: 'Failed to load task data',
noData: 'No task data available',
updateError: 'Failed to update task',
loadImageError: 'Failed to load image'
loadImageError: 'Failed to load image',
confirmDeleteImage: 'Are you sure you want to delete this image?',
uploadImageError: 'Failed to upload image',
deleteSuccess: 'Task {name} has been successfully deleted',
deleteError: 'Failed to delete task',
deleteConfirmation: 'Are you sure you want to delete this task? This action cannot be undone.'
},
validation: {
nameRequired: 'Task name is required',
descriptionRequired: 'Task description is optional',
studentRequired: 'Please select a student',
subjectRequired: 'Please select a subject',
termRequired: 'Please select a term',
completionRequired: 'Please enter completion percentage',
startDateRequired: 'Please select a start date',
endDateRequired: 'Please select an end date'
}
}
......@@ -28,5 +28,9 @@ export default {
delete: '确定要删除此项吗?',
unsavedChanges: '您有未保存的更改,确定要离开吗?',
irreversible: '此操作无法撤销,请谨慎操作。'
}
},
dialogs: {
deleteConfirmation: '删除确认'
},
deleteWarning: '此操作无法撤销,删除后数据将无法恢复。'
}
......@@ -34,7 +34,9 @@ export default {
image: '图像',
noImage: '暂无图像',
clickToLoad: '点击加载图像',
loadingImage: '正在加载图像...'
loadingImage: '正在加载图像...',
updateImage: '更新图像',
deleteImage: '删除图像'
},
filters: {
title: '筛选条件',
......@@ -49,12 +51,28 @@ export default {
expandToReselect: '点击展开重新选择'
},
edit: {
title: '编辑作业'
title: '编辑作业',
createTitle: '新增作业'
},
messages: {
loadError: '加载作业数据失败',
noData: '暂无作业数据',
updateError: '更新作业失败',
loadImageError: '加载图像失败'
loadImageError: '加载图像失败',
confirmDeleteImage: '确定要删除此图像吗?',
uploadImageError: '上传图像失败',
deleteSuccess: '作业 {name} 已成功删除',
deleteError: '删除作业失败',
deleteConfirmation: '您确定要删除此作业吗?此操作无法撤销。'
},
validation: {
nameRequired: '作业名称不能为空',
descriptionRequired: '作业描述为可选项',
studentRequired: '请选择学生',
subjectRequired: '请选择学科',
termRequired: '请选择学期',
completionRequired: '请输入完成度',
startDateRequired: '请选择开始日期',
endDateRequired: '请选择截止日期'
}
}
......@@ -89,17 +89,7 @@
:class="{ 'calendar-full-width': !isFilterExpanded }"
style="height: calc(100vh - 270px); overflow-y: auto;"
>
<!-- 错误提示 -->
<v-alert
v-if="isError"
type="error"
variant="tonal"
closable
class="mb-4"
@click:close="isError = false"
>
{{ errorMessage }}
</v-alert>
<!-- 错误提示已移至编辑对话框内部 -->
<!-- 日历组件 -->
<v-card
......@@ -275,8 +265,22 @@
<!-- 任务编辑对话框 -->
<v-dialog v-model="editDialog" max-width="600">
<v-card>
<!-- 固定在对话框顶部的错误消息 -->
<div class="error-message-container" v-if="isError">
<v-alert
type="error"
variant="tonal"
closable
class="mb-0"
style="width: 100%;"
@click:close="isError = false"
>
{{ errorMessage }}
</v-alert>
</div>
<v-card-title>
<span>{{ $t('task.edit.title') }}</span>
<span>{{ isCreateMode ? $t('task.edit.createTitle') : $t('task.edit.title') }}</span>
<v-spacer />
<v-btn
icon="mdi-close"
......@@ -290,7 +294,7 @@
<v-col cols="12">
<v-text-field
v-model="editForm.task_name"
:label="$t('task.task.name')"
:label="$t('task.task.name') + ' *'"
:placeholder="$t('task.task.name')"
variant="outlined"
density="compact"
......@@ -311,7 +315,7 @@
<v-select
v-model="editForm.student_id"
:items="studentOptions"
:label="$t('task.task.student')"
:label="$t('task.task.student') + ' *'"
variant="outlined"
density="compact"
required
......@@ -321,7 +325,7 @@
<v-select
v-model="editForm.subject_id"
:items="subjectOptions"
:label="$t('task.task.subject')"
:label="$t('task.task.subject') + ' *'"
variant="outlined"
density="compact"
required
......@@ -331,30 +335,32 @@
<v-select
v-model="editForm.term_id"
:items="editTermOptions"
:label="$t('task.task.term')"
:label="$t('task.task.term') + ' *'"
:disabled="!editForm.student_id"
variant="outlined"
density="compact"
:hint="!editForm.student_id ? $t('task.filters.selectStudentFirst') : ''"
persistent-hint
required
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="editForm.completion_percent"
:label="$t('task.task.completionPercent')"
:label="$t('task.task.completionPercent') + ' *'"
:placeholder="$t('task.task.completionPercent')"
variant="outlined"
density="compact"
type="number"
min="0"
max="100"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="editForm.start_date"
:label="$t('task.task.startDate')"
:label="$t('task.task.startDate') + ' *'"
variant="outlined"
density="compact"
type="date"
......@@ -364,7 +370,7 @@
<v-col cols="12" md="6">
<v-text-field
v-model="editForm.end_date"
:label="$t('task.task.endDate')"
:label="$t('task.task.endDate') + ' *'"
variant="outlined"
density="compact"
type="date"
......@@ -392,6 +398,24 @@
class="preview-image"
/>
</div>
<div v-if="imageCache[index]" class="image-actions-bottom">
<v-btn
icon="mdi-upload"
size="small"
color="primary"
variant="tonal"
@click.stop="uploadImage(index)"
:title="$t('task.task.updateImage')"
/>
<v-btn
icon="mdi-delete"
size="small"
color="error"
variant="tonal"
@click.stop="deleteImage(index)"
:title="$t('task.task.deleteImage')"
/>
</div>
<div
v-else-if="imageLoading[index]"
class="image-preview loading"
......@@ -408,14 +432,33 @@
class="image-preview empty"
>
<v-icon size="large" color="grey">mdi-image-off</v-icon>
<div class="upload-placeholder">{{ $t('task.task.noImage') }}</div>
</div>
<div v-if="!imageCache[index] && !imageLoading[index]" class="image-actions-bottom">
<v-btn
icon="mdi-upload"
size="small"
color="primary"
variant="tonal"
@click.stop="uploadImage(index)"
:title="$t('task.task.updateImage')"
/>
</div>
<!-- 移除重复的no-image显示,因为image-preview empty已经处理了无图片的情况 -->
</div>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions>
<v-btn
v-if="!isCreateMode"
color="error"
variant="text"
prepend-icon="mdi-delete"
@click="openDeleteDialog(editingTask)"
>
{{ $t('common.buttons.delete') }}
</v-btn>
<v-spacer />
<v-btn
@click="closeEditDialog"
......@@ -429,7 +472,57 @@
variant="flat"
:loading="isSaving"
>
{{ $t('common.buttons.save') }}
{{ isCreateMode ? $t('common.buttons.create') : $t('common.buttons.save') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 删除确认对话框 -->
<v-dialog v-model="deleteDialog" max-width="400px">
<v-card>
<v-card-title class="text-h5 text-error">
<v-icon icon="mdi-alert" class="mr-2" />
{{ $t('task.dialog.delete') }}
</v-card-title>
<v-card-text>
<div class="text-body-1 mb-4">
{{ $t('task.deleteConfirmation.message') }}
</div>
<div v-if="taskToDelete" class="bg-grey-lighten-4 pa-3 rounded">
<div class="d-flex align-center mb-2">
<div>
<div class="font-weight-medium">{{ taskToDelete.task_name }}</div>
<div class="text-caption text-medium-emphasis">
{{ getStudentName(taskToDelete.student_id) }} | {{ formatDate(taskToDelete.start_date) }} - {{ formatDate(taskToDelete.end_date) }}
</div>
</div>
</div>
</div>
<div class="text-body-2 text-error mt-4">
<v-icon icon="mdi-alert-circle" size="small" class="mr-1" />
{{ $t('task.deleteConfirmation.warning') }}
</div>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="grey-darken-1"
variant="text"
@click="closeDeleteDialog"
:disabled="isDeleting"
>
{{ $t('common.buttons.cancel') }}
</v-btn>
<v-btn
color="error"
variant="elevated"
prepend-icon="mdi-delete"
:loading="isDeleting"
:disabled="isDeleting"
@click="confirmDeleteTask"
>
{{ $t('common.buttons.confirm') + ' ' + $t('common.buttons.delete') }}
</v-btn>
</v-card-actions>
</v-card>
......@@ -483,6 +576,65 @@
</v-card-text>
</v-card>
</v-dialog>
<!-- 删除确认对话框 -->
<v-dialog
v-model="deleteDialog"
max-width="500px"
persistent
>
<v-card>
<v-card-title class="text-h5 bg-error text-white">
{{ $t('common.messages.dialogs.deleteConfirmation') }}
</v-card-title>
<v-card-text class="pt-4">
<div v-if="isError" class="mb-4">
<v-alert
type="error"
variant="tonal"
closable
class="mb-2"
>
{{ errorMessage }}
</v-alert>
</div>
<p class="text-body-1 mb-4">{{ $t('task.messages.deleteConfirmation') }}</p>
<v-card variant="outlined" class="mb-4">
<v-card-item>
<v-card-title>{{ taskToDelete?.task_name }}</v-card-title>
</v-card-item>
</v-card>
<v-alert
type="warning"
variant="tonal"
icon="mdi-alert-circle"
class="mb-0"
>
{{ $t('common.messages.deleteWarning') }}
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
@click="closeDeleteDialog"
variant="outlined"
>
{{ $t('common.buttons.cancel') }}
</v-btn>
<v-btn
color="error"
variant="flat"
:loading="isDeleting"
@click="confirmDeleteTask"
>
{{ $t('common.buttons.delete') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
......@@ -490,7 +642,7 @@
import { onMounted, ref, computed, watch, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '@/stores/auth'
import { getTasks, getTaskById, getTaskImage, updateTask as updateTaskAPI } from '@/api/taskService.js'
import { getTasks, getTaskById, getTaskImage, updateTask as updateTaskAPI, deleteTask } from '@/api/taskService.js'
import { getStudents } from '@/api/studentService.js'
import { getSubjects } from '@/api/subjectService.js'
import { getTerms } from '@/api/termService.js'
......@@ -512,6 +664,12 @@ const authStore = useAuthStore()
const isLoading = ref(false)
const isError = ref(false)
const errorMessage = ref('')
const successMessage = ref('')
// 删除功能相关状态
const deleteDialog = ref(false)
const taskToDelete = ref(null)
const isDeleting = ref(false)
const tasks = ref([])
// 日历相关状态
......@@ -840,7 +998,30 @@ const onEventClick = async (event) => {
}
const onDateClick = (date) => {
selectedDate.value = date
console.log('onDateClick called with date:', date);
// v-calendar的click:date事件应该传递日期对象,而不是点击事件
// 但如果收到的是点击事件,我们需要处理这种情况
if (date) {
if (date.target) {
// 这是一个DOM事件对象,而不是日期
console.log('Received DOM event instead of date, using current date');
selectedDate.value = new Date();
// 传递null给openCreateDialog,它会使用当前日期
openCreateDialog(null);
} else {
// 这是一个正常的日期对象
console.log('Received valid date object');
selectedDate.value = date;
// 点击日历空白处,打开新增对话框
openCreateDialog(date);
}
} else {
// 如果没有收到日期参数,使用当前日期
console.log('No date received, using current date');
selectedDate.value = new Date();
openCreateDialog(null);
}
}
const toggleFullscreen = () => {
......@@ -912,6 +1093,7 @@ const onTermChange = async (termId) => {
// 编辑功能相关状态
const editDialog = ref(false)
const isCreateMode = ref(false) // 标记是否为创建模式
const editingTask = ref(null)
const editForm = ref({
task_name: '',
......@@ -991,14 +1173,24 @@ const imageCache = ref({
})
// 编辑功能方法
const openEditDialog = (task) => {
// 清理之前的图像缓存
const openEditDialog = async (task) => {
// 设置为编辑模式
isCreateMode.value = false;
console.log('Setting isCreateMode to false for editing existing task');
// 清理图像缓存
Object.keys(imageCache.value).forEach(key => {
if (imageCache.value[key]) {
URL.revokeObjectURL(imageCache.value[key]);
imageCache.value[key] = null;
}
});
// 清理图片删除标记
for (let i = 1; i <= 5; i++) {
const imageKey = `image_0${i}`;
editForm.value[`delete_${imageKey}`] = false;
}
// 重置加载状态
Object.keys(imageLoading.value).forEach(key => {
......@@ -1068,9 +1260,113 @@ const openEditDialog = (task) => {
}
}
// 打开创建对话框
const openCreateDialog = (date) => {
// 添加调试信息,记录传入的date参数
console.log('openCreateDialog called with date:', date);
console.log('date type:', typeof date);
if (date) {
console.log('date properties:', Object.keys(date));
}
// 清理图像缓存
Object.keys(imageCache.value).forEach(key => {
if (imageCache.value[key]) {
URL.revokeObjectURL(imageCache.value[key]);
imageCache.value[key] = null;
}
});
// 清理图片删除标记
for (let i = 1; i <= 5; i++) {
const imageKey = `image_0${i}`;
editForm.value[`delete_${imageKey}`] = false;
}
// 重置加载状态
Object.keys(imageLoading.value).forEach(key => {
imageLoading.value[key] = false;
});
// 设置为创建模式
isCreateMode.value = true;
editingTask.value = null;
// 初始化表单数据 - 增强对各种date参数格式的处理
let formattedDate;
try {
// 处理不同格式的日期参数
if (date) {
if (date.date) {
// v-calendar可能传递{date: '2023-01-01'}格式
console.log('Using date.date property:', date.date);
formattedDate = new Date(date.date).toISOString().split('T')[0];
} else if (date instanceof Date) {
// 直接传递Date对象
console.log('Using Date object');
formattedDate = date.toISOString().split('T')[0];
} else if (typeof date === 'string') {
// 直接传递日期字符串
console.log('Using date string');
formattedDate = new Date(date).toISOString().split('T')[0];
} else if (date.year && date.month && date.day) {
// v-calendar可能传递{year: 2023, month: 1, day: 1}格式
console.log('Using year/month/day properties');
formattedDate = new Date(date.year, date.month - 1, date.day).toISOString().split('T')[0];
} else {
// 其他情况,尝试转换为日期
console.log('Attempting to convert unknown date format');
const dateObj = new Date(date);
if (!isNaN(dateObj.getTime())) {
formattedDate = dateObj.toISOString().split('T')[0];
} else {
throw new Error('Invalid date format');
}
}
} else {
throw new Error('No date provided');
}
} catch (error) {
// 如果日期处理出错,使用当前日期
console.warn('Error processing date, using current date:', error);
formattedDate = new Date().toISOString().split('T')[0];
}
editForm.value = {
task_name: '',
task_description: '',
student_id: filters.value.studentId || null,
subject_id: filters.value.subjectId || null,
term_id: filters.value.termId || null,
start_date: formattedDate,
end_date: formattedDate,
completion_percent: 0,
// 图像数据初始化为空
image_01: null,
image_01_mime_type: null,
image_02: null,
image_02_mime_type: null,
image_03: null,
image_03_mime_type: null,
image_04: null,
image_04_mime_type: null,
image_05: null,
image_05_mime_type: null
};
console.log('Creating new task with date:', formattedDate);
editDialog.value = true;
}
const closeEditDialog = () => {
editDialog.value = false
editingTask.value = null
editDialog.value = false;
editingTask.value = null;
isCreateMode.value = false;
// 清除错误消息状态
isError.value = false;
errorMessage.value = '';
editForm.value = {
task_name: '',
task_description: '',
......@@ -1115,6 +1411,60 @@ const closeImageDialog = () => {
resetPosition() // 关闭对话框时重置位置
}
// 删除功能方法
const openDeleteDialog = (task) => {
taskToDelete.value = task
deleteDialog.value = true
}
const closeDeleteDialog = () => {
deleteDialog.value = false
taskToDelete.value = null
}
const confirmDeleteTask = async () => {
if (!taskToDelete.value?.task_id) {
console.error('Unable to get task ID')
return
}
isDeleting.value = true
try {
console.log('Deleting task:', taskToDelete.value)
// 1. 调用API删除任务
await deleteTask(taskToDelete.value.task_id)
// 2. 从本地列表中移除任务
const index = tasks.value.findIndex(t => t.task_id === taskToDelete.value.task_id)
if (index !== -1) {
tasks.value.splice(index, 1)
}
// 3. 显示成功消息
successMessage.value = t('task.messages.deleteSuccess', { name: taskToDelete.value.task_name })
setTimeout(() => {
successMessage.value = ''
}, 3000)
closeDeleteDialog()
closeEditDialog() // 如果正在编辑,也关闭编辑对话框
} catch (error) {
console.error('Deletion failed:', error)
// 在删除对话框中显示错误,但不关闭对话框
errorMessage.value = error.message || t('task.messages.deleteError')
isError.value = true
setTimeout(() => {
isError.value = false
errorMessage.value = ''
}, 3000)
} finally {
isDeleting.value = false
}
}
// 图像缩放功能 - 这些方法已被鼠标滚轮缩放功能替代,可以移除
// const zoomIn = () => {
// if (zoomLevel.value < 3) { // 最大放大3倍
......@@ -1186,6 +1536,97 @@ const handleWheel = (event) => {
zoomLevel.value = Math.max(0.5, Math.min(3, zoomLevel.value))
}
// 上传图片方法
const uploadImage = async (index) => {
// 创建文件选择器
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*'; // 只接受图片文件
// 监听文件选择事件
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
// 设置加载状态
imageLoading.value[index] = true;
// 读取文件为base64
const reader = new FileReader();
reader.onload = (event) => {
const base64String = event.target.result;
// 更新表单数据
const imageKey = `image_0${index}`;
// 重要:如果之前标记了删除,现在上传新图片,需要取消删除标记
editForm.value[`delete_${imageKey}`] = false;
// 清除旧的图片数据,确保不会有冲突
editForm.value[imageKey] = null;
// 设置新的图片数据
editForm.value[`${imageKey}_mime_type`] = file.type;
editForm.value[`${imageKey}_file_name`] = file.name;
editForm.value[`${imageKey}_base64`] = base64String;
// 更新图片缓存,立即显示上传的图片
if (imageCache.value[index]) {
URL.revokeObjectURL(imageCache.value[index]);
}
imageCache.value[index] = URL.createObjectURL(file);
console.log(`Image ${index} uploaded successfully:`, {
fileName: file.name,
mimeType: file.type,
hasBase64: !!base64String,
deleteFlag: editForm.value[`delete_${imageKey}`]
});
// 重置加载状态
imageLoading.value[index] = false;
};
reader.readAsDataURL(file);
} catch (error) {
console.error(`Failed to upload image ${index}:`, error);
errorMessage.value = t('task.messages.uploadImageError');
isError.value = true;
setTimeout(() => {
isError.value = false;
errorMessage.value = '';
}, 3000);
// 重置加载状态
imageLoading.value[index] = false;
}
};
// 触发文件选择器
input.click();
};
// 删除图片方法
const deleteImage = (index) => {
// 确认删除
if (confirm(t('task.messages.confirmDeleteImage'))) {
// 更新表单数据,标记为删除
const imageKey = `image_0${index}`;
editForm.value[`delete_${imageKey}`] = true;
// 清除图片缓存
if (imageCache.value[index]) {
URL.revokeObjectURL(imageCache.value[index]);
imageCache.value[index] = null;
}
// 清除其他相关字段
editForm.value[`${imageKey}_mime_type`] = null;
editForm.value[`${imageKey}_file_name`] = null;
editForm.value[`${imageKey}_base64`] = null;
}
};
// 图像加载方法
const loadTaskImage = async (taskId, imageIndex) => {
// 检查缓存
......@@ -1266,13 +1707,40 @@ const openImageDialog = async (index) => {
}
}
// 导入创建任务API
import { createTask as createTaskAPI } from '@/api/taskService'
const updateTask = async () => {
isSaving.value = true
try {
console.log('Form data before update:', editForm.value)
console.log('Form data before update/create:', editForm.value)
// 验证必填字段
if (!editForm.value.task_name) {
throw new Error(t('task.validation.nameRequired') || '作业名称不能为空')
}
// 作业描述可以为空,不需要验证
if (!editForm.value.student_id) {
throw new Error(t('task.validation.studentRequired') || '请选择学生')
}
if (!editForm.value.subject_id) {
throw new Error(t('task.validation.subjectRequired') || '请选择学科')
}
if (!editForm.value.term_id) {
throw new Error(t('task.validation.termRequired') || '请选择学期')
}
if (editForm.value.completion_percent === undefined || editForm.value.completion_percent === null) {
throw new Error(t('task.validation.completionRequired') || '请输入完成度')
}
if (!editForm.value.start_date) {
throw new Error(t('task.validation.startDateRequired') || '请选择开始日期')
}
if (!editForm.value.end_date) {
throw new Error(t('task.validation.endDateRequired') || '请选择截止日期')
}
// 构建要发送的数据
const updateData = {
const taskData = {
task_name: editForm.value.task_name,
task_description: editForm.value.task_description,
student_id: parseInt(editForm.value.student_id),
......@@ -1282,32 +1750,74 @@ const updateTask = async () => {
end_date: new Date(editForm.value.end_date).toISOString(),
completion_percent: parseInt(editForm.value.completion_percent)
}
// 添加图片上传和删除相关数据
for (let i = 1; i <= 5; i++) {
const imageKey = `image_0${i}`;
// 处理图片上传 - 优先处理上传,因为上传会覆盖删除标记
if (editForm.value[`${imageKey}_base64`]) {
// 如果有新上传的图片,确保删除标记为false
taskData[`delete_${imageKey}`] = false;
// 添加图片数据
taskData[`${imageKey}_mime_type`] = editForm.value[`${imageKey}_mime_type`];
taskData[`${imageKey}_file_name`] = editForm.value[`${imageKey}_file_name`];
taskData[`${imageKey}_base64`] = editForm.value[`${imageKey}_base64`];
console.log(`Sending new image data for ${imageKey}`);
}
// 处理图片删除 - 只有在没有新上传图片的情况下才处理删除
else if (editForm.value[`delete_${imageKey}`]) {
taskData[`delete_${imageKey}`] = true;
console.log(`Marking ${imageKey} for deletion`);
}
}
console.log('Sending data:', updateData)
// 1. 调用API更新任务
const updatedTask = await updateTaskAPI(editingTask.value.task_id, updateData)
// 2. 更新成功后,更新本地列表中的任务
const index = tasks.value.findIndex(t => t.task_id === editingTask.value.task_id)
if (index !== -1) {
tasks.value[index] = { ...tasks.value[index], ...updatedTask }
console.log('Sending data:', taskData)
let result;
// 根据模式决定是创建还是更新任务
if (isCreateMode.value) {
// 创建新任务
console.log('Creating new task...');
result = await createTaskAPI(taskData);
// 将新任务添加到任务列表
tasks.value.push(result);
console.log('Task created successfully:', result);
} else {
// 更新现有任务
console.log('Updating existing task...');
result = await updateTaskAPI(editingTask.value.task_id, taskData);
// 更新本地列表中的任务
const index = tasks.value.findIndex(t => t.task_id === editingTask.value.task_id);
if (index !== -1) {
tasks.value[index] = { ...tasks.value[index], ...result };
}
console.log('Task updated successfully:', result);
}
// 3. 显示成功消息
console.log('Task updated successfully:', updatedTask)
closeEditDialog()
// 关闭对话框
closeEditDialog();
} catch (error) {
console.error('Update failed:', error)
errorMessage.value = error.message || t('task.messages.updateError')
isError.value = true
console.error('Operation failed:', error);
errorMessage.value = error.message || (isCreateMode.value ? '创建作业失败' : t('task.messages.updateError'));
isError.value = true;
// 不再使用setTimeout自动清除错误消息,而是让用户手动关闭或在对话框关闭时清除
// 滚动到对话框顶部,确保错误消息可见
setTimeout(() => {
isError.value = false
errorMessage.value = ''
}, 3000)
const dialogElement = document.querySelector('.v-dialog--active');
if (dialogElement) {
dialogElement.scrollTop = 0;
}
}, 100);
} finally {
isSaving.value = false
isSaving.value = false;
}
}
......@@ -1368,6 +1878,14 @@ watch(() => editForm.value.student_id, (newStudentId, oldStudentId) => {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* 错误消息容器样式 */
.error-message-container {
position: sticky;
top: 0;
z-index: 100;
width: 100%;
}
/* 自定义事件样式 */
.custom-event {
position: relative;
......@@ -1383,6 +1901,39 @@ watch(() => editForm.value.student_id, (newStudentId, oldStudentId) => {
transition: all 0.2s;
}
/* 图片操作按钮样式 */
.image-actions {
position: absolute;
top: 5px;
right: 5px;
display: flex;
gap: 5px;
opacity: 0;
transition: opacity 0.2s;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 4px;
padding: 2px;
}
.image-preview:hover .image-actions {
opacity: 1;
}
/* 图片下方操作按钮样式 */
.image-actions-bottom {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 8px;
}
.upload-placeholder {
margin-top: 5px;
font-size: 0.8rem;
color: #666;
text-align: center;
}
.custom-event:hover {
transform: scale(1.1); /* 悬停时放大10% */
border-color: #000000; /* 悬停时显示黑色边框 */
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment