feat(admin): 新增模板管理功能并完善用户认证

- 新增模板管理模块,包含模板列表和详情页面
- 重构用户认证逻辑,支持用户ID存储和API调用
- 新增排行榜API接口和类型定义
- 更新环境配置,添加开发环境配置
- 优化表格操作钩子,增强类型安全性
- 新增文件上传工具类和验证工具函数
- 更新路由配置,支持模板详情页面导航
- 修复登录表单默认值问题
This commit is contained in:
2026-01-27 09:48:42 +08:00
parent 264f600250
commit 5a48a65ef8
56 changed files with 2907 additions and 434 deletions

View File

@ -6,20 +6,20 @@ import { request } from '../request'
* @param userName User name
* @param password Password
*/
export function fetchLogin(userName: string, password: string) {
export function fetchLogin(account: string, pwd: string) {
return request<Api.Auth.LoginToken>({
url: '/auth/login',
url: '/admin/v1/user/login',
method: 'post',
data: {
userName,
password,
account,
pwd,
},
})
}
/** Get user info */
export function fetchGetUserInfo() {
return request<Api.Auth.UserInfo>({ url: '/auth/getUserInfo' })
export function fetchGetUserInfo(userId: number) {
return request<Api.Auth.UserInfo>({ url: `admin/v1/manager/detail/${userId}` })
}
/**

View File

@ -1,3 +1,4 @@
export * from './auth'
export * from './rank'
export * from './route'
export * from './system-manage'
export * from './template'

View File

@ -0,0 +1,10 @@
import { request } from '../request'
/** 新增题目 */
export function fetchAddQuestion(data?: Api.Question.AddParams) {
return request<Api.Question.CommonRecord>({
url: '/Base/ActivityMain/AddBase_QuestionList',
method: 'post',
data: data || {},
})
}

View File

@ -0,0 +1,10 @@
import { request } from '../request'
/** get user list */
export function fetchRankList(data?: Api.Rank.UserSearchParams) {
return request<Api.Rank.CommonRecord>({
url: '/admin/v1/class/pagelist',
method: 'post',
data: data || {},
})
}

View File

@ -24,8 +24,8 @@ export function fetchGetAllRoles() {
/** get user list */
export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
return request<Api.SystemManage.UserList>({
url: '/systemManage/getUserList',
method: 'get',
url: '/admin/v1/class/pagelist',
method: 'post',
params,
})
}

View File

@ -0,0 +1,10 @@
import { request } from '../request'
/** get template list */
export function fetchTemplateList(data?: Api.Template.TemplateSearchParams) {
return request<Api.Common.PaginatingQueryRecord<Api.Template.CommonRecord>>({
url: '/admin/v1/book/list',
method: 'post',
data: data || {},
})
}

View File

@ -10,36 +10,45 @@ import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared'
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy)
/**
* 基础请求
*/
export const request = createFlatRequest(
{
baseURL,
headers: {
apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2',
'apifoxToken': 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2', // 用于 apifox 调试
'Custom-Platform': 'pc',
},
},
{
defaultState: {
errMsgStack: [],
refreshTokenPromise: null,
errMsgStack: [], // 错误信息栈
refreshTokenPromise: null, // 刷新token Promise
} as RequestInstanceState,
// 响应数据转换
transform(response: AxiosResponse<App.Service.Response<any>>) {
return response.data.data
},
// 请求配置转换
async onRequest(config) {
const Authorization = getAuthorization()
Object.assign(config.headers, { Authorization })
return config
},
// 响应状态判断
isBackendSuccess(response) {
// when the backend response code is "0000"(default), it means the request is success
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
// 当后端响应状态码为 "200"(默认值)时,表示请求成功
// 如果你想自行更改此逻辑,可以在 .env 文件中修改 VITE_SERVICE_SUCCESS_CODE
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE
},
// 响应错误处理
async onBackendFail(response, instance) {
const authStore = useAuthStore()
const responseCode = String(response.data.code)
//
function handleLogout() {
authStore.resetStore()
}
@ -51,19 +60,19 @@ export const request = createFlatRequest(
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg)
}
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
// 当后端响应状态码在 logoutCodes 中时,表示用户将被登出并重定向到登录页面
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []
if (logoutCodes.includes(responseCode)) {
handleLogout()
return null
}
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
// 当后端响应状态码在 modalLogoutCodes 中时,表示将通过显示模态框来使用户登出
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg]
// prevent the user from refreshing the page
// 防止用户刷新页面
window.addEventListener('beforeunload', handleLogout)
window.$dialog?.error({
@ -83,8 +92,8 @@ export const request = createFlatRequest(
return null
}
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
// 当后端响应状态码在 expiredTokenCodes 中时,表示 token 已过期,需要刷新 token
// refreshToken 接口不能返回 expiredTokenCodes 中的错误码,否则会形成死循环,应该返回 logoutCodesmodalLogoutCodes
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []
if (expiredTokenCodes.includes(responseCode)) {
const success = await handleExpiredRequest(request.state)
@ -99,24 +108,24 @@ export const request = createFlatRequest(
return null
},
onError(error) {
// when the request is fail, you can show error message
// 当请求失败时,可以显示错误信息
let message = error.message
let backendErrorCode = ''
// get backend error message and code
// 获取后端错误信息和状态码
if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.msg || message
backendErrorCode = String(error.response?.data?.code || '')
}
// the error message is displayed in the modal
// 错误信息已在模态框中显示
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []
if (modalLogoutCodes.includes(backendErrorCode)) {
return
}
// when the token is expired, refresh token and retry request, so no need to show error message
// 当 token 过期时,刷新 token 并重试请求,因此不需要显示错误信息
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []
if (expiredTokenCodes.includes(backendErrorCode)) {
return
@ -127,39 +136,48 @@ export const request = createFlatRequest(
},
)
/**
* 演示请求
*/
export const demoRequest = createRequest(
{
baseURL: otherBaseURL.demo,
},
/**
* 演示请求响应数据转换
*/
{
// 响应数据转换
transform(response: AxiosResponse<App.Service.DemoResponse>) {
return response.data.result
},
// 请求配置转换
async onRequest(config) {
const { headers } = config
// set token
// 设置 token
const token = localStg.get('token')
const Authorization = token ? `Bearer ${token}` : null
Object.assign(headers, { Authorization })
return config
},
// 响应状态判断
isBackendSuccess(response) {
// when the backend response code is "200", it means the request is success
// you can change this logic by yourself
// 当后端响应状态码为 "200" 时,表示请求成功
// 你可以自行更改此逻辑
return response.data.status === '200'
},
async onBackendFail(_response) {
// when the backend response code is not "200", it means the request is fail
// for example: the token is expired, refresh token and retry request
// 当后端响应状态码不是 "200" 时,表示请求失败
// 例如token 过期,刷新 token 并重试请求
},
onError(error) {
// when the request is fail, you can show error message
// 当请求失败时,可以显示错误信息
let message = error.message
// show backend error message
// 显示后端错误信息
if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.message || message
}

View File

@ -10,7 +10,9 @@ export function getAuthorization() {
return Authorization
}
/** refresh token */
/**
* 刷新token
*/
async function handleRefreshToken() {
const { resetStore } = useAuthStore()
@ -27,6 +29,9 @@ async function handleRefreshToken() {
return false
}
/**
* 处理过期token的请求
*/
export async function handleExpiredRequest(state: RequestInstanceState) {
if (!state.refreshTokenPromise) {
state.refreshTokenPromise = handleRefreshToken()
@ -41,6 +46,9 @@ export async function handleExpiredRequest(state: RequestInstanceState) {
return success
}
/**
* 显示错误信息
*/
export function showErrorMsg(state: RequestInstanceState, message: string) {
if (!state.errMsgStack?.length) {
state.errMsgStack = []

View File

@ -1,7 +1,7 @@
export interface RequestInstanceState {
/** the promise of refreshing token */
/** 刷新token Promise */
refreshTokenPromise: Promise<boolean> | null
/** the request error message stack */
/** 请求错误信息栈 */
errMsgStack: string[]
[key: string]: unknown
}