Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
Clark Lin
student-app-frontend
Commits
1a5a30c9
Commit
1a5a30c9
authored
Sep 03, 2025
by
Administrator
Browse files
completed Task page CRUD
parent
ede4c3bf
Changes
5
Hide whitespace changes
Inline
Side-by-side
src/locales/en/common/messages.js
View file @
1a5a30c9
...
...
@@ -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.
'
}
src/locales/en/modules/task.js
View file @
1a5a30c9
...
...
@@ -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
'
}
}
src/locales/zh-CN/common/messages.js
View file @
1a5a30c9
...
...
@@ -28,5 +28,9 @@ export default {
delete
:
'
确定要删除此项吗?
'
,
unsavedChanges
:
'
您有未保存的更改,确定要离开吗?
'
,
irreversible
:
'
此操作无法撤销,请谨慎操作。
'
}
},
dialogs
:
{
deleteConfirmation
:
'
删除确认
'
},
deleteWarning
:
'
此操作无法撤销,删除后数据将无法恢复。
'
}
src/locales/zh-CN/modules/task.js
View file @
1a5a30c9
...
...
@@ -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
:
'
请选择截止日期
'
}
}
src/views/TaskView.vue
View file @
1a5a30c9
...
...
@@ -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
update
Data
=
{
const
task
Data
=
{
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
;
/* 悬停时显示黑色边框 */
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment