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
b8cdc054
Commit
b8cdc054
authored
Sep 04, 2025
by
Administrator
Browse files
added Story page
parent
1a5a30c9
Changes
16
Expand all
Hide whitespace changes
Inline
Side-by-side
src/api/storyService.js
0 → 100644
View file @
b8cdc054
import
apiClient
from
'
./index.js
'
/**
* 通用的获取所有分页数据的函数
* @param {Function} apiCall - API调用函数
* @param {Object} params - 查询参数
* @returns {Promise<Array>} 所有页面的数据数组
*/
const
fetchAllPages
=
async
(
apiCall
,
params
=
{})
=>
{
let
allData
=
[]
let
page
=
1
let
hasNext
=
true
console
.
log
(
'
Starting to fetch all pages for API call...
'
)
while
(
hasNext
)
{
try
{
const
currentParams
=
{
...
params
,
page
,
page_size
:
100
}
// 每页100条,减少请求次数
console
.
log
(
`Fetching page
${
page
}
with params:`
,
currentParams
)
const
response
=
await
apiCall
(
currentParams
)
if
(
response
&&
response
.
results
)
{
// 分页格式响应
allData
=
[...
allData
,
...
response
.
results
]
hasNext
=
!!
response
.
next
console
.
log
(
`Page
${
page
}
: Got
${
response
.
results
.
length
}
items, total so far:
${
allData
.
length
}
, hasNext:
${
hasNext
}
`
)
}
else
if
(
Array
.
isArray
(
response
))
{
// 非分页格式响应(向后兼容)
allData
=
response
hasNext
=
false
console
.
log
(
'
Non-paginated response detected, got all data at once:
'
,
allData
.
length
,
'
items
'
)
}
else
{
console
.
log
(
'
Unexpected response format, stopping pagination
'
)
hasNext
=
false
}
page
++
// 安全检查:防止无限循环
if
(
page
>
100
)
{
console
.
warn
(
'
Reached maximum page limit (100), stopping pagination
'
)
break
}
}
catch
(
error
)
{
console
.
error
(
`Error fetching page
${
page
}
:`
,
error
)
throw
error
}
}
console
.
log
(
`Finished fetching all pages. Total items:
${
allData
.
length
}
`
)
return
allData
}
// API端点定义
const
apiEndpoints
=
{
STORIES
:
{
LIST
:
'
/api/stories/
'
,
DETAIL
:
(
storyId
)
=>
`/api/stories/
${
storyId
}
/`
,
UPDATE
:
(
storyId
)
=>
`/api/stories/
${
storyId
}
/`
,
DELETE
:
(
storyId
)
=>
`/api/stories/
${
storyId
}
/`
}
}
/**
* 单页API调用函数(内部使用)
* @param {Object} params - 查询参数(包含分页参数)
* @returns {Promise} API响应
*/
const
getStoriesPage
=
async
(
params
=
{})
=>
{
const
response
=
await
apiClient
.
get
(
apiEndpoints
.
STORIES
.
LIST
,
{
params
})
return
response
.
data
}
/**
* 获取所有系列任务
* @param {Object} params - 查询参数
* @returns {Promise} 系列任务列表(完整数据,已处理分页)
*/
export
const
getStories
=
async
(
params
=
{})
=>
{
try
{
// 构建查询参数,过滤掉null和undefined值
const
queryParams
=
{}
Object
.
entries
(
params
).
forEach
(([
key
,
value
])
=>
{
if
(
value
!==
null
&&
value
!==
undefined
)
{
queryParams
[
key
]
=
value
}
})
console
.
log
(
'
Stories API request params:
'
,
queryParams
)
// 使用通用分页函数获取所有数据
const
allStories
=
await
fetchAllPages
(
getStoriesPage
,
queryParams
)
console
.
log
(
'
Stories API final result:
'
,
{
totalCount
:
allStories
.
length
,
sampleData
:
allStories
.
slice
(
0
,
3
)
// 显示前3条数据作为样例
})
return
allStories
}
catch
(
error
)
{
console
.
error
(
'
Failed to fetch stories:
'
,
error
)
throw
error
}
}
/**
* 根据ID获取系列任务
* @param {number} storyId - 系列任务ID
* @returns {Promise} 系列任务详情
*/
export
const
getStoryById
=
async
(
storyId
)
=>
{
try
{
const
response
=
await
apiClient
.
get
(
apiEndpoints
.
STORIES
.
DETAIL
(
storyId
))
console
.
log
(
'
Story detail API response:
'
,
response
.
data
)
return
response
.
data
}
catch
(
error
)
{
console
.
error
(
'
Failed to fetch story detail:
'
,
error
)
// 提供更详细的错误信息
if
(
error
.
response
)
{
// 服务器响应了错误状态码
throw
new
Error
(
`获取系列任务详情失败:
${
error
.
response
.
status
}
-
${
error
.
response
.
statusText
}
`
)
}
else
if
(
error
.
request
)
{
// 请求已发出但没有收到响应
throw
new
Error
(
'
网络错误: 无法连接到服务器
'
)
}
else
{
// 其他错误
throw
new
Error
(
`请求配置错误:
${
error
.
message
}
`
)
}
}
}
/**
* 创建新的系列任务
* @param {Object} storyData - 系列任务数据
* @returns {Promise} 创建的系列任务
*/
export
const
createStory
=
async
(
storyData
)
=>
{
try
{
const
response
=
await
apiClient
.
post
(
apiEndpoints
.
STORIES
.
LIST
,
storyData
)
console
.
log
(
'
Create story API response:
'
,
response
.
data
)
return
response
.
data
}
catch
(
error
)
{
console
.
error
(
'
Failed to create story:
'
,
error
)
// 提供更详细的错误信息
if
(
error
.
response
)
{
// 服务器响应了错误状态码
throw
new
Error
(
`创建系列任务失败:
${
error
.
response
.
status
}
-
${
error
.
response
.
statusText
}
`
)
}
else
if
(
error
.
request
)
{
// 请求已发出但没有收到响应
throw
new
Error
(
'
网络错误: 无法连接到服务器
'
)
}
else
{
// 其他错误
throw
new
Error
(
`请求配置错误:
${
error
.
message
}
`
)
}
}
}
/**
* 更新系列任务
* @param {number} storyId - 系列任务ID
* @param {Object} storyData - 更新的系列任务数据
* @returns {Promise} 更新后的系列任务
*/
export
const
updateStory
=
async
(
storyId
,
storyData
)
=>
{
try
{
const
response
=
await
apiClient
.
patch
(
apiEndpoints
.
STORIES
.
UPDATE
(
storyId
),
storyData
)
console
.
log
(
'
Update story API response:
'
,
response
.
data
)
return
response
.
data
}
catch
(
error
)
{
console
.
error
(
'
Failed to update story:
'
,
error
)
// 提供更详细的错误信息
if
(
error
.
response
)
{
// 服务器响应了错误状态码
throw
new
Error
(
`更新系列任务失败:
${
error
.
response
.
status
}
-
${
error
.
response
.
statusText
}
`
)
}
else
if
(
error
.
request
)
{
// 请求已发出但没有收到响应
throw
new
Error
(
'
网络错误: 无法连接到服务器
'
)
}
else
{
// 其他错误
throw
new
Error
(
`请求配置错误:
${
error
.
message
}
`
)
}
}
}
/**
* 删除系列任务
* @param {number} storyId - 系列任务ID
* @returns {Promise} 删除结果
*/
export
const
deleteStory
=
async
(
storyId
)
=>
{
try
{
const
response
=
await
apiClient
.
delete
(
apiEndpoints
.
STORIES
.
DELETE
(
storyId
))
console
.
log
(
'
Delete story API response:
'
,
response
.
data
)
return
response
.
data
}
catch
(
error
)
{
console
.
error
(
'
Failed to delete story:
'
,
error
)
// 提供更详细的错误信息
if
(
error
.
response
)
{
// 服务器响应了错误状态码
throw
new
Error
(
`删除系列任务失败:
${
error
.
response
.
status
}
-
${
error
.
response
.
statusText
}
`
)
}
else
if
(
error
.
request
)
{
// 请求已发出但没有收到响应
throw
new
Error
(
'
网络错误: 无法连接到服务器
'
)
}
else
{
// 其他错误
throw
new
Error
(
`请求配置错误:
${
error
.
message
}
`
)
}
}
}
src/components/AppDrawer.vue
View file @
b8cdc054
...
...
@@ -8,16 +8,23 @@
<v-list-item
:title=
"$t('navigation.welcome.title')"
:subtitle=
"$t('navigation.welcome.subtitle')"
></v-list-item>
<v-divider></v-divider>
<v-list
v-model:opened=
"opened"
>
<v-list
v-model:opened=
"opened"
nav
>
<template
v-for=
"item in filteredMenu"
:key=
"item.path || item.nameKey"
>
<!-- 分组(有 children):点击父项仅用于展开,不路由 -->
<v-list-group
v-if=
"item.children && item.children.length"
:value=
"item.path"
:prepend-icon=
"item.mdiIcon"
slim
>
<template
#activator
="
{ props }">
<v-list-item
v-bind=
"props"
>
<v-list-item
v-bind=
"props"
:ripple=
"true"
:disabled=
"false"
nav
density=
"comfortable"
>
<v-list-item-title
class=
"text-body-1"
>
{{
$t
(
item
.
nameKey
)
}}
</v-list-item-title>
</v-list-item>
</
template
>
...
...
@@ -30,6 +37,7 @@
:ripple=
"child.hasRouter"
:disabled=
"!child.hasRouter"
nav
density=
"compact"
>
<v-list-item-title
class=
"text-body-1"
>
{{ $t(child.nameKey) }}
</v-list-item-title>
</v-list-item>
...
...
@@ -43,13 +51,14 @@
:ripple=
"item.hasRouter"
:disabled=
"false"
@
click=
"!item.hasRouter ? noop() : undefined"
nav
>
nav
density=
"comfortable"
>
<v-list-item-title
class=
"text-body-1"
>
{{ $t(item.nameKey) }}
</v-list-item-title>
</v-list-item>
</template>
</v-list>
</v-navigation-drawer>
</template>
<
script
setup
>
...
...
@@ -104,4 +113,9 @@ const noop = () => {}
</
script
>
<
style
scoped
>
/* 使用Vuetify的类来确保一致的外观 */
.v-list-group--activator
.v-list-item
{
/* 确保分组标题与普通列表项有相同的密度设置 */
--v-list-item-density
:
-1
;
}
</
style
>
src/components/constants/menuItems.js
View file @
b8cdc054
...
...
@@ -34,6 +34,12 @@ export const GLOBAL_MENU_ITEMS = [
path
:
'
/master-data/terms
'
,
mdiIcon
:
'
mdi-calendar-month-outline
'
,
hasRouter
:
true
},
{
nameKey
:
'
navigation.menu.stories
'
,
path
:
'
/master-data/stories
'
,
mdiIcon
:
'
mdi-book-multiple-outline
'
,
hasRouter
:
true
}
]
},
...
...
src/locales/en/common/table.js
0 → 100644
View file @
b8cdc054
export
default
{
pagination
:
{
itemsPerPage
:
'
Items per page
'
,
itemsPerPageAll
:
'
All
'
,
noDataText
:
'
No data available
'
,
loadingText
:
'
Loading items...
'
,
pageText
:
'
{0}-{1} of {2}
'
,
rowsPerPageText
:
'
Rows per page:
'
},
actions
:
{
edit
:
'
Edit
'
,
delete
:
'
Delete
'
,
view
:
'
View
'
},
filter
:
{
search
:
'
Search
'
,
clear
:
'
Clear
'
,
apply
:
'
Apply
'
}
}
\ No newline at end of file
src/locales/en/index.js
View file @
b8cdc054
...
...
@@ -3,10 +3,12 @@ import buttons from './common/buttons'
import
messages
from
'
./common/messages
'
import
status
from
'
./common/status
'
import
validation
from
'
./common/validation
'
import
table
from
'
./common/table
'
import
student
from
'
./modules/student
'
import
subject
from
'
./modules/subject
'
import
term
from
'
./modules/term
'
import
task
from
'
./modules/task
'
import
story
from
'
./modules/story
'
import
auth
from
'
./modules/auth
'
import
navigation
from
'
./modules/navigation
'
import
appHeader
from
'
./components/app-header
'
...
...
@@ -17,12 +19,14 @@ export default {
buttons
,
messages
,
status
,
validation
validation
,
table
},
student
,
subject
,
term
,
task
,
story
,
auth
,
navigation
,
components
:
{
...
...
src/locales/en/modules/navigation.js
View file @
b8cdc054
...
...
@@ -12,6 +12,7 @@ export default {
students
:
'
Students
'
,
subjects
:
'
Subjects
'
,
terms
:
'
Terms
'
,
stories
:
'
Story
'
,
tasks
:
'
Task Management
'
,
dashboard
:
'
Dashboard
'
,
settings
:
'
Settings
'
,
...
...
@@ -24,6 +25,7 @@ export default {
students
:
'
Students
'
,
subjects
:
'
Subjects
'
,
terms
:
'
Terms
'
,
stories
:
'
Story
'
,
tasks
:
'
Task Management
'
,
profile
:
'
Profile
'
},
...
...
src/locales/en/modules/story.js
0 → 100644
View file @
b8cdc054
export
default
{
title
:
'
Story
'
,
tableHeader
:
{
storyId
:
'
Story ID
'
,
storyName
:
'
Story Name
'
,
description
:
'
Description
'
,
studentId
:
'
Student
'
,
subjectId
:
'
Subject
'
,
termId
:
'
Term
'
,
trackedFlag
:
'
Tracked
'
,
createdBy
:
'
Created By
'
,
creationDate
:
'
Creation Date
'
,
lastUpdatedBy
:
'
Last Updated By
'
,
lastUpdateDate
:
'
Last Update Date
'
,
actions
:
'
Actions
'
},
form
:
{
storyName
:
'
Story Name
'
,
description
:
'
Description
'
,
studentId
:
'
Student
'
,
subjectId
:
'
Subject
'
,
termId
:
'
Term
'
,
trackedFlag
:
'
Tracked
'
},
dialog
:
{
create
:
'
Create Story
'
,
edit
:
'
Edit Story
'
,
delete
:
'
Delete Story
'
},
button
:
{
create
:
'
Create Story
'
,
edit
:
'
Edit
'
,
delete
:
'
Delete
'
,
save
:
'
Save
'
,
cancel
:
'
Cancel
'
,
confirm
:
'
Confirm
'
},
message
:
{
createSuccess
:
'
Story created successfully
'
,
updateSuccess
:
'
Story updated successfully
'
,
deleteSuccess
:
'
Story deleted successfully
'
,
createError
:
'
Failed to create story
'
,
updateError
:
'
Failed to update story
'
,
deleteError
:
'
Failed to delete story
'
,
loadError
:
'
Failed to load stories
'
},
deleteConfirm
:
{
title
:
'
Delete Story
'
,
message
:
'
Are you sure you want to delete this story?
'
,
description
:
'
This action cannot be undone.
'
,
warning
:
'
This action cannot be undone. Please proceed with caution.
'
,
storyInfo
:
'
{name}
'
},
validation
:
{
storyNameRequired
:
'
Story name is required
'
,
studentRequired
:
'
Student is required
'
,
subjectRequired
:
'
Subject is required
'
,
termRequired
:
'
Term is required
'
},
status
:
{
tracked
:
'
Tracked
'
,
notTracked
:
'
Not Tracked
'
,
enabled
:
'
Enabled
'
,
disabled
:
'
Disabled
'
}
}
src/locales/zh-CN/common/table.js
0 → 100644
View file @
b8cdc054
export
default
{
pagination
:
{
itemsPerPage
:
'
每页显示
'
,
itemsPerPageAll
:
'
全部
'
,
noDataText
:
'
暂无数据
'
,
loadingText
:
'
加载中...
'
,
pageText
:
'
{0}-{1} 共 {2}
'
,
rowsPerPageText
:
'
每页行数:
'
},
actions
:
{
edit
:
'
编辑
'
,
delete
:
'
删除
'
,
view
:
'
查看
'
},
filter
:
{
search
:
'
搜索
'
,
clear
:
'
清除
'
,
apply
:
'
应用
'
}
}
\ No newline at end of file
src/locales/zh-CN/index.js
View file @
b8cdc054
...
...
@@ -3,10 +3,12 @@ import buttons from './common/buttons'
import
messages
from
'
./common/messages
'
import
status
from
'
./common/status
'
import
validation
from
'
./common/validation
'
import
table
from
'
./common/table
'
import
student
from
'
./modules/student
'
import
subject
from
'
./modules/subject
'
import
term
from
'
./modules/term
'
import
task
from
'
./modules/task
'
import
story
from
'
./modules/story
'
import
auth
from
'
./modules/auth
'
import
navigation
from
'
./modules/navigation
'
import
appHeader
from
'
./components/app-header
'
...
...
@@ -17,12 +19,14 @@ export default {
buttons
,
messages
,
status
,
validation
validation
,
table
},
student
,
subject
,
term
,
task
,
story
,
auth
,
navigation
,
components
:
{
...
...
src/locales/zh-CN/modules/navigation.js
View file @
b8cdc054
...
...
@@ -12,6 +12,7 @@ export default {
students
:
'
学生
'
,
subjects
:
'
学科
'
,
terms
:
'
学期
'
,
stories
:
'
系列任务
'
,
tasks
:
'
作业管理
'
,
dashboard
:
'
仪表盘
'
,
settings
:
'
设置
'
,
...
...
@@ -24,6 +25,7 @@ export default {
students
:
'
学生
'
,
subjects
:
'
学科
'
,
terms
:
'
学期
'
,
stories
:
'
系列任务
'
,
tasks
:
'
作业管理
'
,
profile
:
'
个人资料
'
},
...
...
src/locales/zh-CN/modules/story.js
0 → 100644
View file @
b8cdc054
export
default
{
title
:
'
系列任务
'
,
tableHeader
:
{
storyId
:
'
系列任务ID
'
,
storyName
:
'
系列任务名称
'
,
description
:
'
描述
'
,
studentId
:
'
学生
'
,
subjectId
:
'
学科
'
,
termId
:
'
学期
'
,
trackedFlag
:
'
跟踪状态
'
,
createdBy
:
'
创建人
'
,
creationDate
:
'
创建日期
'
,
lastUpdatedBy
:
'
最后更新人
'
,
lastUpdateDate
:
'
最后更新日期
'
,
actions
:
'
操作
'
},
form
:
{
storyName
:
'
系列任务名称
'
,
description
:
'
描述
'
,
studentId
:
'
学生
'
,
subjectId
:
'
学科
'
,
termId
:
'
学期
'
,
trackedFlag
:
'
跟踪状态
'
},
dialog
:
{
create
:
'
创建系列任务
'
,
edit
:
'
编辑系列任务
'
,
delete
:
'
删除系列任务
'
},
button
:
{
create
:
'
创建系列任务
'
,
edit
:
'
编辑
'
,
delete
:
'
删除
'
,
save
:
'
保存
'
,
cancel
:
'
取消
'
,
confirm
:
'
确认
'
},
message
:
{
createSuccess
:
'
系列任务创建成功
'
,
updateSuccess
:
'
系列任务更新成功
'
,
deleteSuccess
:
'
系列任务删除成功
'
,
createError
:
'
创建系列任务失败
'
,
updateError
:
'
更新系列任务失败
'
,
deleteError
:
'
删除系列任务失败
'
,
loadError
:
'
加载系列任务失败
'
},
deleteConfirm
:
{
title
:
'
删除系列任务
'
,
message
:
'
确定要删除此系列任务吗?
'
,
description
:
'
此操作无法撤销。
'
,
warning
:
'
此操作无法撤销,请谨慎操作。
'
,
storyInfo
:
'
{name}
'
},
validation
:
{
storyNameRequired
:
'
系列任务名称不能为空
'
,
studentRequired
:
'
学生不能为空
'
,
subjectRequired
:
'
学科不能为空
'
,
termRequired
:
'
学期不能为空
'
},
status
:
{
tracked
:
'
已跟踪
'
,
notTracked
:
'
未跟踪
'
,
enabled
:
'
启用
'
,
disabled
:
'
禁用
'
}
}
src/router/index.js
View file @
b8cdc054
...
...
@@ -6,6 +6,7 @@ import ProfileView from '../views/ProfileView.vue'
import
StudentView
from
'
../views/StudentView.vue
'
import
SubjectView
from
'
../views/SubjectView.vue
'
import
TermView
from
'
../views/TermView.vue
'
import
StoryView
from
'
../views/StoryView.vue
'
import
TaskView
from
'
../views/TaskView.vue
'
const
router
=
createRouter
({
...
...
@@ -84,8 +85,7 @@ const router = createRouter({
]
},
},
{
path
:
'
/master-data/terms
'
,
{
path
:
'
/master-data/terms
'
,
name
:
'
terms
'
,
component
:
TermView
,
meta
:
{
...
...
@@ -102,6 +102,24 @@ const router = createRouter({
]
},
},
{
path
:
'
/master-data/stories
'
,
name
:
'
stories
'
,
component
:
StoryView
,
meta
:
{
requiresAuth
:
true
,
layout
:
'
default
'
,
title
:
'
Stories
'
,
breadcrumb
:
{
key
:
'
navigation.breadcrumb.stories
'
},
breadcrumbPath
:
[
{
key
:
'
navigation.breadcrumb.home
'
,
to
:
'
/
'
,
disabled
:
false
},
{
key
:
'
navigation.breadcrumb.masterData
'
,
to
:
null
,
disabled
:
true
},
{
key
:
'
navigation.breadcrumb.stories
'
,
to
:
'
/master-data/stories
'
,
disabled
:
true
}
]
},
},
{
path
:
'
/tasks
'
,
name
:
'
tasks
'
,
...
...
src/views/StoryView.vue
0 → 100644
View file @
b8cdc054
This diff is collapsed.
Click to expand it.
src/views/StudentView.vue
View file @
b8cdc054
...
...
@@ -577,7 +577,7 @@ const saveStudent = async () => {
</div>
</template>
<
template
v-slot:
[`
item.enabled
`
]=
"{ item }"
>
<v-chip
:color=
"item.enabled === 'Y' ? 'success' : 'error'"
size=
"small"
variant=
"flat
"
>
<v-chip
:color=
"item.enabled === 'Y' ? 'success' : 'error'"
text-color=
"white"
size=
"small
"
>
{{
item
.
enabled
===
'
Y
'
?
$t
(
'
common.status.enabled
'
)
:
$t
(
'
common.status.disabled
'
)
}}
</v-chip>
</
template
>
...
...
@@ -714,6 +714,9 @@ const saveStudent = async () => {
v-model=
"editForm.enabled"
:label=
"$t('student.form.enabledStatus')"
color=
"primary"
:true-value=
"true"
:false-value=
"false"
inset
/>
</v-col>
</v-row>
...
...
@@ -871,6 +874,9 @@ const saveStudent = async () => {
v-model=
"createForm.enabled"
:label=
"$t('student.form.enabledStatus') + ' *'"
color=
"primary"
:true-value=
"true"
:false-value=
"false"
inset
/>
</v-col>
</v-row>
...
...
src/views/SubjectView.vue
View file @
b8cdc054
...
...
@@ -390,12 +390,12 @@ const getCalendarColorValue = (colorCode) => {
fixed-header
>
<template
v-slot:
[`
item.enabled_flag
`
]=
"
{ item }">
<v-chip
:color=
"item.enabled_flag === 'Y' ? 'success' : 'error'"
size=
"small"
variant=
"flat
"
>
<v-chip
:color=
"item.enabled_flag === 'Y' ? 'success' : 'error'"
text-color=
"white"
size=
"small
"
>
{{
item
.
enabled_flag
===
'
Y
'
?
$t
(
'
subject.status.enabled
'
)
:
$t
(
'
subject.status.disabled
'
)
}}
</v-chip>
</
template
>
<
template
v-slot:
[`
item.primary_flag
`
]=
"{ item }"
>
<v-chip
:color=
"item.primary_flag === 'Y' ? 'primary' : 'default'"
size=
"small"
variant=
"flat
"
>
<v-chip
:color=
"item.primary_flag === 'Y' ? 'primary' : 'default'"
text-color=
"white"
size=
"small
"
>
{{
item
.
primary_flag
===
'
Y
'
?
$t
(
'
subject.status.primary
'
)
:
$t
(
'
subject.status.notPrimary
'
)
}}
</v-chip>
</
template
>
...
...
@@ -484,6 +484,7 @@ const getCalendarColorValue = (colorCode) => {
color=
"primary"
true-value=
"Y"
false-value=
"N"
inset
/>
</v-col>
<v-col
cols=
"6"
>
...
...
@@ -493,6 +494,7 @@ const getCalendarColorValue = (colorCode) => {
color=
"primary"
true-value=
"Y"
false-value=
"N"
inset
/>
</v-col>
</v-row>
...
...
@@ -596,6 +598,7 @@ const getCalendarColorValue = (colorCode) => {
color=
"primary"
true-value=
"Y"
false-value=
"N"
inset
/>
</v-col>
<v-col
cols=
"6"
>
...
...
@@ -605,6 +608,7 @@ const getCalendarColorValue = (colorCode) => {
color=
"primary"
true-value=
"Y"
false-value=
"N"
inset
/>
</v-col>
</v-row>
...
...
src/views/TaskView.vue
View file @
b8cdc054
...
...
@@ -278,7 +278,7 @@
{{ errorMessage }}
</v-alert>
</div>
<v-card-title>
<span>
{{ isCreateMode ? $t('task.edit.createTitle') : $t('task.edit.title') }}
</span>
<v-spacer
/>
...
...
@@ -345,7 +345,7 @@
/>
</v-col>
<v-col
cols=
"12"
md=
"6"
>
<v-text-field
<!--
<v-text-field
v-model="editForm.completion_percent"
:label="$t('task.task.completionPercent') + ' *'"
:placeholder="$t('task.task.completionPercent')"
...
...
@@ -355,7 +355,29 @@
min="0"
max="100"
required
/>
/> -->
<v-slider
v-model=
"editForm.completion_percent"
:label=
"$t('task.task.completionPercent') + ' *'"
:min=
"0"
:max=
"100"
:step=
"1"
thumb-label=
"always"
persistent-hint
:hint=
"`${editForm.completion_percent}%`"
>
<
template
#append
>
<v-text-field
v-model=
"editForm.completion_percent"
type=
"number"
min=
"0"
max=
"100"
hide-details
density=
"compact"
style=
"width: 80px;"
/>
</
template
>
</v-slider>
</v-col>
<v-col
cols=
"12"
md=
"6"
>
<v-text-field
...
...
@@ -598,15 +620,15 @@
{{ 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"
...
...
@@ -999,7 +1021,7 @@ const onEventClick = async (event) => {
const
onDateClick
=
(
date
)
=>
{
console
.
log
(
'
onDateClick called with date:
'
,
date
);
// v-calendar的click:date事件应该传递日期对象,而不是点击事件
// 但如果收到的是点击事件,我们需要处理这种情况
if
(
date
)
{
...
...
@@ -1177,7 +1199,7 @@ 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
])
{
...
...
@@ -1185,7 +1207,7 @@ const openEditDialog = async (task) => {
imageCache
.
value
[
key
]
=
null
;
}
});
// 清理图片删除标记
for
(
let
i
=
1
;
i
<=
5
;
i
++
)
{
const
imageKey
=
`image_0
${
i
}
`
;
...
...
@@ -1268,7 +1290,7 @@ const openCreateDialog = (date) => {
if
(
date
)
{
console
.
log
(
'
date properties:
'
,
Object
.
keys
(
date
));
}
// 清理图像缓存
Object
.
keys
(
imageCache
.
value
).
forEach
(
key
=>
{
if
(
imageCache
.
value
[
key
])
{
...
...
@@ -1276,7 +1298,7 @@ const openCreateDialog = (date) => {
imageCache
.
value
[
key
]
=
null
;
}
});
// 清理图片删除标记
for
(
let
i
=
1
;
i
<=
5
;
i
++
)
{
const
imageKey
=
`image_0
${
i
}
`
;
...
...
@@ -1291,7 +1313,7 @@ const openCreateDialog = (date) => {
// 设置为创建模式
isCreateMode
.
value
=
true
;
editingTask
.
value
=
null
;
// 初始化表单数据 - 增强对各种date参数格式的处理
let
formattedDate
;
try
{
...
...
@@ -1331,7 +1353,7 @@ const openCreateDialog = (date) => {
console
.
warn
(
'
Error processing date, using current date:
'
,
error
);
formattedDate
=
new
Date
().
toISOString
().
split
(
'
T
'
)[
0
];
}
editForm
.
value
=
{
task_name
:
''
,
task_description
:
''
,
...
...
@@ -1353,7 +1375,7 @@ const openCreateDialog = (date) => {
image_05
:
null
,
image_05_mime_type
:
null
};
console
.
log
(
'
Creating new task with date:
'
,
formattedDate
);
editDialog
.
value
=
true
;
}
...
...
@@ -1362,11 +1384,11 @@ const closeEditDialog = () => {
editDialog
.
value
=
false
;
editingTask
.
value
=
null
;
isCreateMode
.
value
=
false
;
// 清除错误消息状态
isError
.
value
=
false
;
errorMessage
.
value
=
''
;
editForm
.
value
=
{
task_name
:
''
,
task_description
:
''
,
...
...
@@ -1542,51 +1564,51 @@ 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
);
...
...
@@ -1596,12 +1618,12 @@ const uploadImage = async (index) => {
isError
.
value
=
false
;
errorMessage
.
value
=
''
;
},
3000
);
// 重置加载状态
imageLoading
.
value
[
index
]
=
false
;
}
};
// 触发文件选择器
input
.
click
();
};
...
...
@@ -1613,13 +1635,13 @@ const deleteImage = (index) => {
// 更新表单数据,标记为删除
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
;
...
...
@@ -1714,7 +1736,7 @@ const updateTask = async () => {
isSaving
.
value
=
true
try
{
console
.
log
(
'
Form data before update/create:
'
,
editForm
.
value
)
// 验证必填字段
if
(
!
editForm
.
value
.
task_name
)
{
throw
new
Error
(
t
(
'
task.validation.nameRequired
'
)
||
'
作业名称不能为空
'
)
...
...
@@ -1750,21 +1772,21 @@ 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
}
`
);
}
// 处理图片删除 - 只有在没有新上传图片的情况下才处理删除
...
...
@@ -1777,13 +1799,13 @@ const updateTask = async () => {
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
);
...
...
@@ -1791,7 +1813,7 @@ const updateTask = async () => {
// 更新现有任务
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
)
{
...
...
@@ -1808,7 +1830,7 @@ const updateTask = async () => {
errorMessage
.
value
=
error
.
message
||
(
isCreateMode
.
value
?
'
创建作业失败
'
:
t
(
'
task.messages.updateError
'
));
isError
.
value
=
true
;
// 不再使用setTimeout自动清除错误消息,而是让用户手动关闭或在对话框关闭时清除
// 滚动到对话框顶部,确保错误消息可见
setTimeout
(()
=>
{
const
dialogElement
=
document
.
querySelector
(
'
.v-dialog--active
'
);
...
...
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