- 管理台:活动设置、时长登记、用户管理、数据导出(Excel/PNG) - 用户端:姓名查询个人义工时长与活动记录 - 技术栈:React + Vite + TailwindCSS / Node.js + Express + sql.js (SQLite) - 支持 Docker 部署,附带 Gitea Actions 自动部署工作流 - 响应式界面,兼容移动端与 PC 端 - 默认管理员账号:admin / admin123
87 lines
3.3 KiB
TypeScript
87 lines
3.3 KiB
TypeScript
import axios from 'axios';
|
|
import type {
|
|
WorkGroup, UserItem, UserDetail, ActivityItem, Participant,
|
|
HoursActivityResult, ExportResult,
|
|
} from '../types';
|
|
|
|
const api = axios.create({
|
|
baseURL: '/api',
|
|
timeout: 15000,
|
|
});
|
|
|
|
api.interceptors.request.use((config) => {
|
|
const token = localStorage.getItem('volunteer_admin_token');
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
return config;
|
|
});
|
|
|
|
api.interceptors.response.use(
|
|
(res) => res,
|
|
(err) => {
|
|
if (err.response?.status === 401) {
|
|
localStorage.removeItem('volunteer_admin_token');
|
|
localStorage.removeItem('volunteer_admin_username');
|
|
if (!window.location.pathname.startsWith('/admin/login')) {
|
|
window.location.href = '/admin/login';
|
|
}
|
|
}
|
|
return Promise.reject(err);
|
|
}
|
|
);
|
|
|
|
export const errMsg = (e: any, def = '操作失败'): string => {
|
|
return e?.response?.data?.error || e?.message || def;
|
|
};
|
|
|
|
export default api;
|
|
|
|
export const authApi = {
|
|
login: (username: string, password: string) =>
|
|
api.post<{ token: string; username: string }>('/auth/login', { username, password }).then(r => r.data),
|
|
changePassword: (oldPassword: string, newPassword: string) =>
|
|
api.post('/auth/change-password', { oldPassword, newPassword }).then(r => r.data),
|
|
};
|
|
|
|
export const groupsApi = {
|
|
list: () => api.get<WorkGroup[]>('/groups').then(r => r.data),
|
|
create: (name: string) => api.post<WorkGroup>('/groups', { name }).then(r => r.data),
|
|
update: (id: number, name: string) => api.put(`/groups/${id}`, { name }).then(r => r.data),
|
|
remove: (id: number) => api.delete(`/groups/${id}`).then(r => r.data),
|
|
};
|
|
|
|
export const usersApi = {
|
|
list: () => api.get<UserItem[]>('/users').then(r => r.data),
|
|
search: (name: string) => api.get<UserItem[]>('/users/search', { params: { name } }).then(r => r.data),
|
|
detail: (id: number) => api.get<UserDetail>(`/users/${id}`).then(r => r.data),
|
|
create: (data: Partial<UserItem>) => api.post('/users', data).then(r => r.data),
|
|
update: (id: number, data: Partial<UserItem>) => api.put(`/users/${id}`, data).then(r => r.data),
|
|
remove: (id: number) => api.delete(`/users/${id}`).then(r => r.data),
|
|
};
|
|
|
|
export const activitiesApi = {
|
|
list: () => api.get<ActivityItem[]>('/activities').then(r => r.data),
|
|
detail: (id: number) => api.get<ActivityItem>(`/activities/${id}`).then(r => r.data),
|
|
create: (data: any) => api.post('/activities', data).then(r => r.data),
|
|
update: (id: number, data: any) => api.put(`/activities/${id}`, data).then(r => r.data),
|
|
remove: (id: number) => api.delete(`/activities/${id}`).then(r => r.data),
|
|
participants: (id: number) => api.get<{ activity: ActivityItem; list: Participant[] }>(`/activities/${id}/participants`).then(r => r.data),
|
|
};
|
|
|
|
export const hoursApi = {
|
|
batch: (activity_id: number, items: Array<{ user_id: number; hours: number; remark: string }>) =>
|
|
api.post('/hours/batch', { activity_id, items }).then(r => r.data),
|
|
byActivity: (id: number) => api.get<HoursActivityResult>(`/hours/activity/${id}`).then(r => r.data),
|
|
};
|
|
|
|
export const exportApi = {
|
|
data: (opts: {
|
|
include_users?: boolean;
|
|
include_activities?: boolean;
|
|
include_hours?: boolean;
|
|
include_user_totals?: boolean;
|
|
include_user_activities?: boolean;
|
|
}) => api.post<ExportResult>('/export', opts).then(r => r.data),
|
|
};
|