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

added StudentView page to handle CURD operations.

parent 38326bc5
...@@ -22,6 +22,9 @@ export const apiEndpoints = { ...@@ -22,6 +22,9 @@ export const apiEndpoints = {
// 学生相关 // 学生相关
STUDENTS: { STUDENTS: {
LIST: '/api/student/', LIST: '/api/student/',
CREATE: '/api/student/',
UPDATE: (studentId) => `/api/student/${studentId}/`,
DELETE: (studentId) => `/api/student/${studentId}/`,
}, },
} }
......
import apiClient, { apiEndpoints } from './index.js' import apiClient, { apiEndpoints } from './index.js'
// 获取学生列表 // Get student list
export const getStudents = async () => { export const getStudents = async () => {
try { try {
const response = await apiClient.get(apiEndpoints.STUDENTS.LIST) const response = await apiClient.get(apiEndpoints.STUDENTS.LIST)
...@@ -9,3 +9,118 @@ export const getStudents = async () => { ...@@ -9,3 +9,118 @@ export const getStudents = async () => {
throw new Error(error?.response?.data?.detail || error?.message || 'Failed to fetch students') throw new Error(error?.response?.data?.detail || error?.message || 'Failed to fetch students')
} }
} }
// 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)
}
}
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import axios from 'axios' import apiClient from '@/api/index.js'
import { login as authLogin, refreshAccessToken as authRefreshToken } from '@/api/authService.js' import { login as authLogin, refreshAccessToken as authRefreshToken } from '@/api/authService.js'
export const useAuthStore = defineStore('auth', () => { export const useAuthStore = defineStore('auth', () => {
...@@ -58,9 +58,10 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -58,9 +58,10 @@ export const useAuthStore = defineStore('auth', () => {
const setAxiosAuthHeader = (token) => { const setAxiosAuthHeader = (token) => {
if (token) { if (token) {
axios.defaults.headers.common.Authorization = `Bearer ${token}` // 设置apiClient默认headers
apiClient.defaults.headers.common.Authorization = `Bearer ${token}`
} else { } else {
delete axios.defaults.headers.common.Authorization delete apiClient.defaults.headers.common.Authorization
} }
} }
...@@ -92,8 +93,8 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -92,8 +93,8 @@ export const useAuthStore = defineStore('auth', () => {
const setupAxiosInterceptors = () => { const setupAxiosInterceptors = () => {
if (interceptorsInitialized.value) return if (interceptorsInitialized.value) return
// 请求拦截:总是携带最新的访问令牌 // 请求拦截:总是携带最新的访问令牌
axios.interceptors.request.use((config) => { apiClient.interceptors.request.use((config) => {
if (accessToken.value) { if (accessToken.value) {
config.headers = config.headers || {} config.headers = config.headers || {}
config.headers.Authorization = `Bearer ${accessToken.value}` config.headers.Authorization = `Bearer ${accessToken.value}`
...@@ -101,8 +102,8 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -101,8 +102,8 @@ export const useAuthStore = defineStore('auth', () => {
return config return config
}) })
// 响应拦截:遇到 401 尝试刷新一次并重试原请求 // 响应拦截:遇到 401 尝试刷新一次并重试原请求
axios.interceptors.response.use( apiClient.interceptors.response.use(
(response) => response, (response) => response,
async (error) => { async (error) => {
const originalRequest = error.config || {} const originalRequest = error.config || {}
...@@ -121,7 +122,7 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -121,7 +122,7 @@ export const useAuthStore = defineStore('auth', () => {
await refreshAccessToken() await refreshAccessToken()
originalRequest.headers = originalRequest.headers || {} originalRequest.headers = originalRequest.headers || {}
originalRequest.headers.Authorization = `Bearer ${accessToken.value}` originalRequest.headers.Authorization = `Bearer ${accessToken.value}`
return axios(originalRequest) return apiClient(originalRequest)
} catch (e) { } catch (e) {
logout() logout()
return Promise.reject(e) return Promise.reject(e)
......
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { useAuthStore } from '../stores/auth' import { useAuthStore } from '../stores/auth'
import loginImage from '@/assets/images/login.jpg'
const authStore = useAuthStore() const authStore = useAuthStore()
const username = ref('') const username = ref('')
...@@ -84,6 +83,14 @@ const handleLogin = async () => { ...@@ -84,6 +83,14 @@ const handleLogin = async () => {
variant="outlined" variant="outlined"
density="comfortable" density="comfortable"
/> />
<v-alert
v-if="error"
type="error"
variant="tonal"
class="mb-4"
>
{{ error }}
</v-alert>
<v-btn <v-btn
color="primary" color="primary"
block block
......
This diff is collapsed.
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