Enhance backend functionality and frontend UI

- Updated main.py to include authentication for brand, model, and OTA routers.
- Added new OTA schemas in schemas.py for version management.
- Enhanced model retrieval with sorting options in models.py.
- Improved model update functionality to support multipart/form-data uploads.
- Updated frontend layout and styles for a more modern look, including new font integration.
- Implemented login route and authentication checks in router/index.js.
- Added sorting capabilities in model table and improved file handling in model view.
- Updated requirements.txt to include PyJWT for token management.
This commit is contained in:
yangy
2026-05-14 17:54:36 +08:00
parent b1d088a755
commit 661b85ce62
23 changed files with 2104 additions and 119 deletions
+40
View File
@@ -0,0 +1,40 @@
const TOKEN_KEY = 'dashboard_access_token'
export function getToken() {
return localStorage.getItem(TOKEN_KEY)
}
export function setToken(token) {
if (token) {
localStorage.setItem(TOKEN_KEY, token)
} else {
localStorage.removeItem(TOKEN_KEY)
}
}
export function clearToken() {
localStorage.removeItem(TOKEN_KEY)
}
/** 解析 JWT 过期时间(毫秒时间戳),失败返回 null */
export function getTokenExpiresAt(token) {
if (!token) return null
try {
const part = token.split('.')[1]
if (!part) return null
let base64 = part.replace(/-/g, '+').replace(/_/g, '/')
const pad = base64.length % 4
if (pad) base64 += '='.repeat(4 - pad)
const payload = JSON.parse(atob(base64))
if (payload.exp == null) return null
return payload.exp * 1000
} catch {
return null
}
}
export function isTokenExpired(token = getToken()) {
const exp = getTokenExpiresAt(token)
if (!exp) return true
return Date.now() >= exp
}
+21
View File
@@ -1,5 +1,6 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { getToken, clearToken } from '@/utils/auth'
// 创建 axios 实例
const request = axios.create({
@@ -10,6 +11,11 @@ const request = axios.create({
// 请求拦截器
request.interceptors.request.use(
config => {
const token = getToken()
const url = config.url || ''
if (token && !url.includes('/auth/login')) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
@@ -32,6 +38,21 @@ request.interceptors.response.use(
return res
},
error => {
const status = error.response?.status
if (status === 401) {
clearToken()
const detail = error.response?.data?.detail
if (detail) {
ElMessage.warning(typeof detail === 'string' ? detail : '登录已失效')
}
const path = window.location.pathname || ''
if (!path.startsWith('/login')) {
window.location.assign(
`/login?redirect=${encodeURIComponent(path + (window.location.search || ''))}`
)
}
return Promise.reject(error)
}
console.error('响应错误:', error)
ElMessage.error(error.message || '网络错误')
return Promise.reject(error)