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
Compare Revisions
38326bc57a2549379b98b0bb2270cf90b59af77e...0caedf2fbecb23b0290d392bd69b30ea341fcc71
Commits (2)
added StudentView page to handle CURD operations.
· 1f7f2977
Administrator
authored
Aug 28, 2025
1f7f2977
fixed navigation drawer and footer layout issue
· 0caedf2f
Administrator
authored
Aug 29, 2025
0caedf2f
Expand all
Hide whitespace changes
Inline
Side-by-side
.gitignore
View file @
0caedf2f
...
...
@@ -28,3 +28,5 @@ coverage
*.sw?
*.tsbuildinfo
.qoder*
\ No newline at end of file
src/api/authService.js
View file @
0caedf2f
...
...
@@ -18,4 +18,4 @@ export const refreshAccessToken = async (refreshToken) => {
}
catch
(
error
)
{
throw
new
Error
(
error
?.
response
?.
data
?.
detail
||
error
?.
message
||
'
Failed to refresh token
'
)
}
}
\ No newline at end of file
}
src/api/index.js
View file @
0caedf2f
...
...
@@ -22,7 +22,10 @@ export const apiEndpoints = {
// 学生相关
STUDENTS
:
{
LIST
:
'
/api/student/
'
,
CREATE
:
'
/api/student/
'
,
UPDATE
:
(
studentId
)
=>
`/api/student/
${
studentId
}
/`
,
DELETE
:
(
studentId
)
=>
`/api/student/
${
studentId
}
/`
,
},
}
export
default
apiClient
\ No newline at end of file
export
default
apiClient
src/api/studentService.js
View file @
0caedf2f
import
apiClient
,
{
apiEndpoints
}
from
'
./index.js
'
//
获取学生列表
//
Get student list
export
const
getStudents
=
async
()
=>
{
try
{
const
response
=
await
apiClient
.
get
(
apiEndpoints
.
STUDENTS
.
LIST
)
...
...
@@ -8,4 +8,119 @@ export const getStudents = async () => {
}
catch
(
error
)
{
throw
new
Error
(
error
?.
response
?.
data
?.
detail
||
error
?.
message
||
'
Failed to fetch students
'
)
}
}
\ No newline at end of file
}
// Update student information
export
const
updateStudent
=
async
(
studentId
,
studentData
)
=>
{
try
{
// Convert data format to match API requirements
const
apiData
=
{
student_name
:
studentData
.
student_name
||
''
,
enabled
:
studentData
.
enabled
?
'
Y
'
:
'
N
'
,
grade
:
studentData
.
grade
||
''
,
age
:
parseInt
(
studentData
.
age
)
||
0
,
}
// If avatar data is included, add avatar-related fields
if
(
studentData
.
avatar
&&
studentData
.
avatar_mime_type
)
{
apiData
.
avatar_base64
=
studentData
.
avatar
apiData
.
avatar_mime_type
=
studentData
.
avatar_mime_type
apiData
.
avatar_file_name
=
studentData
.
avatar_file_name
||
''
}
console
.
log
(
'
Sending update request:
'
,
{
studentId
,
url
:
apiEndpoints
.
STUDENTS
.
UPDATE
(
studentId
),
data
:
{
...
apiData
,
avatar_base64
:
apiData
.
avatar_base64
?
'
[base64 data]
'
:
undefined
}
// Don't print full base64
})
const
response
=
await
apiClient
.
patch
(
apiEndpoints
.
STUDENTS
.
UPDATE
(
studentId
),
apiData
)
return
response
.
data
}
catch
(
error
)
{
console
.
error
(
'
API error details:
'
,
{
status
:
error
?.
response
?.
status
,
statusText
:
error
?.
response
?.
statusText
,
data
:
error
?.
response
?.
data
,
headers
:
error
?.
response
?.
headers
})
const
errorMessage
=
error
?.
response
?.
data
?.
detail
||
error
?.
response
?.
data
?.
message
||
error
?.
response
?.
data
?.
error
||
`HTTP
${
error
?.
response
?.
status
}
:
${
error
?.
response
?.
statusText
}
`
||
error
.
message
||
'
Failed to update student
'
throw
new
Error
(
errorMessage
)
}
}
// Create student
export
const
createStudent
=
async
(
studentData
)
=>
{
try
{
// Convert data format to match API requirements
const
apiData
=
{
student_name
:
studentData
.
student_name
||
''
,
enabled
:
studentData
.
enabled
?
'
Y
'
:
'
N
'
,
grade
:
studentData
.
grade
||
''
,
age
:
parseInt
(
studentData
.
age
)
||
0
,
}
// If avatar data is included, add avatar-related fields
if
(
studentData
.
avatar
&&
studentData
.
avatar_mime_type
)
{
apiData
.
avatar_base64
=
studentData
.
avatar
apiData
.
avatar_mime_type
=
studentData
.
avatar_mime_type
apiData
.
avatar_file_name
=
studentData
.
avatar_file_name
||
''
}
console
.
log
(
'
Sending create request:
'
,
{
url
:
apiEndpoints
.
STUDENTS
.
CREATE
,
data
:
{
...
apiData
,
avatar_base64
:
apiData
.
avatar_base64
?
'
[base64 data]
'
:
undefined
}
})
const
response
=
await
apiClient
.
post
(
apiEndpoints
.
STUDENTS
.
CREATE
,
apiData
)
return
response
.
data
}
catch
(
error
)
{
console
.
error
(
'
API error details:
'
,
{
status
:
error
?.
response
?.
status
,
statusText
:
error
?.
response
?.
statusText
,
data
:
error
?.
response
?.
data
,
headers
:
error
?.
response
?.
headers
})
const
errorMessage
=
error
?.
response
?.
data
?.
detail
||
error
?.
response
?.
data
?.
message
||
error
?.
response
?.
data
?.
error
||
`HTTP
${
error
?.
response
?.
status
}
:
${
error
?.
response
?.
statusText
}
`
||
error
.
message
||
'
Failed to create student
'
throw
new
Error
(
errorMessage
)
}
}
// Delete student
export
const
deleteStudent
=
async
(
studentId
)
=>
{
try
{
console
.
log
(
'
Sending delete request:
'
,
{
studentId
,
url
:
apiEndpoints
.
STUDENTS
.
DELETE
(
studentId
)
})
const
response
=
await
apiClient
.
delete
(
apiEndpoints
.
STUDENTS
.
DELETE
(
studentId
))
return
response
.
data
}
catch
(
error
)
{
console
.
error
(
'
API error details:
'
,
{
status
:
error
?.
response
?.
status
,
statusText
:
error
?.
response
?.
statusText
,
data
:
error
?.
response
?.
data
,
headers
:
error
?.
response
?.
headers
})
const
errorMessage
=
error
?.
response
?.
data
?.
detail
||
error
?.
response
?.
data
?.
message
||
error
?.
response
?.
data
?.
error
||
`HTTP
${
error
?.
response
?.
status
}
:
${
error
?.
response
?.
statusText
}
`
||
error
.
message
||
'
Failed to delete student
'
throw
new
Error
(
errorMessage
)
}
}
src/components/AppLayout.vue
View file @
0caedf2f
...
...
@@ -8,6 +8,7 @@
>
<v-layout
height=
"100vh"
style=
"background: rgba(255, 255, 255, 0.85);"
>
<AppHeader
:drawer-active=
"drawerActive"
@
toggle-drawer=
"toggleDrawer"
@
logout=
"showLogoutDialog = true"
/>
<AppFooter
/>
<AppDrawer
v-model=
"drawerActive"
/>
<v-main>
<v-container
fluid
class=
"pt-4 pb-0"
>
...
...
@@ -28,7 +29,6 @@
</v-container>
<slot></slot>
</v-main>
<AppFooter
/>
</v-layout>
</v-img>
<!-- 登出确认对话框 -->
...
...
src/stores/auth.js
View file @
0caedf2f
import
{
ref
,
computed
}
from
'
vue
'
import
{
defineStore
}
from
'
pinia
'
import
{
useRouter
}
from
'
vue-router
'
import
a
xios
from
'
axio
s
'
import
a
piClient
from
'
@/api/index.j
s
'
import
{
login
as
authLogin
,
refreshAccessToken
as
authRefreshToken
}
from
'
@/api/authService.js
'
export
const
useAuthStore
=
defineStore
(
'
auth
'
,
()
=>
{
...
...
@@ -58,9 +58,10 @@ export const useAuthStore = defineStore('auth', () => {
const
setAxiosAuthHeader
=
(
token
)
=>
{
if
(
token
)
{
axios
.
defaults
.
headers
.
common
.
Authorization
=
`Bearer
${
token
}
`
// 设置apiClient默认headers
apiClient
.
defaults
.
headers
.
common
.
Authorization
=
`Bearer
${
token
}
`
}
else
{
delete
a
xios
.
defaults
.
headers
.
common
.
Authorization
delete
a
piClient
.
defaults
.
headers
.
common
.
Authorization
}
}
...
...
@@ -92,8 +93,8 @@ export const useAuthStore = defineStore('auth', () => {
const
setupAxiosInterceptors
=
()
=>
{
if
(
interceptorsInitialized
.
value
)
return
// 请求拦截:总是携带最新的访问令牌
a
xios
.
interceptors
.
request
.
use
((
config
)
=>
{
// 请求拦截
器
:总是携带最新的访问令牌
a
piClient
.
interceptors
.
request
.
use
((
config
)
=>
{
if
(
accessToken
.
value
)
{
config
.
headers
=
config
.
headers
||
{}
config
.
headers
.
Authorization
=
`Bearer
${
accessToken
.
value
}
`
...
...
@@ -101,8 +102,8 @@ export const useAuthStore = defineStore('auth', () => {
return
config
})
// 响应拦截:遇到 401 尝试刷新一次并重试原请求
a
xios
.
interceptors
.
response
.
use
(
// 响应拦截
器
:遇到 401 尝试刷新一次并重试原请求
a
piClient
.
interceptors
.
response
.
use
(
(
response
)
=>
response
,
async
(
error
)
=>
{
const
originalRequest
=
error
.
config
||
{}
...
...
@@ -121,7 +122,7 @@ export const useAuthStore = defineStore('auth', () => {
await
refreshAccessToken
()
originalRequest
.
headers
=
originalRequest
.
headers
||
{}
originalRequest
.
headers
.
Authorization
=
`Bearer
${
accessToken
.
value
}
`
return
a
xios
(
originalRequest
)
return
a
piClient
(
originalRequest
)
}
catch
(
e
)
{
logout
()
return
Promise
.
reject
(
e
)
...
...
src/views/LoginView.vue
View file @
0caedf2f
<
script
setup
>
import
{
ref
}
from
'
vue
'
import
{
useAuthStore
}
from
'
../stores/auth
'
import
loginImage
from
'
@/assets/images/login.jpg
'
const
authStore
=
useAuthStore
()
const
username
=
ref
(
''
)
...
...
@@ -84,6 +83,14 @@ const handleLogin = async () => {
variant=
"outlined"
density=
"comfortable"
/>
<v-alert
v-if=
"error"
type=
"error"
variant=
"tonal"
class=
"mb-4"
>
{{
error
}}
</v-alert>
<v-btn
color=
"primary"
block
...
...
src/views/StudentView.vue
View file @
0caedf2f
This diff is collapsed.
Click to expand it.