"添加前端模板和运行代码模块"
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
|
||||
|
||||
interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
data: T;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ApiError {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
private instance: AxiosInstance;
|
||||
|
||||
constructor(baseURL: string = '/api') {
|
||||
this.instance = axios.create({
|
||||
baseURL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
this.setupInterceptors();
|
||||
}
|
||||
|
||||
private setupInterceptors() {
|
||||
// 请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
(config) => {
|
||||
// 可以在这里添加token等认证信息
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(response: AxiosResponse<ApiResponse>) => {
|
||||
const { code, data, message } = response.data;
|
||||
|
||||
// 处理业务错误
|
||||
if (code !== 200) {
|
||||
const error: ApiError = { code, message };
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
(error: AxiosError<ApiError>) => {
|
||||
if (error.response) {
|
||||
// 服务器返回错误状态码
|
||||
const { status, data } = error.response;
|
||||
console.error('API Error:', status, data?.message || error.message);
|
||||
} else if (error.request) {
|
||||
// 请求已经发出,但没有收到响应
|
||||
console.error('Network Error:', error.message);
|
||||
} else {
|
||||
// 在设置请求时发生错误
|
||||
console.error('Request Error:', error.message);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||
return this.instance.get(url, config);
|
||||
}
|
||||
|
||||
async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
||||
return this.instance.post(url, data, config);
|
||||
}
|
||||
|
||||
async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
||||
return this.instance.put(url, data, config);
|
||||
}
|
||||
|
||||
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||
return this.instance.delete(url, config);
|
||||
}
|
||||
|
||||
async patch<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
||||
return this.instance.patch(url, data, config);
|
||||
}
|
||||
}
|
||||
|
||||
// 便捷方法
|
||||
export const api = {
|
||||
get: <T>(url: string, config?: AxiosRequestConfig) => new ApiClient().get<T>(url, config),
|
||||
post: <T>(url: string, data?: any, config?: AxiosRequestConfig) => new ApiClient().post<T>(url, data, config),
|
||||
put: <T>(url: string, data?: any, config?: AxiosRequestConfig) => new ApiClient().put<T>(url, data, config),
|
||||
delete: <T>(url: string, config?: AxiosRequestConfig) => new ApiClient().delete<T>(url, config),
|
||||
patch: <T>(url: string, data?: any, config?: AxiosRequestConfig) => new ApiClient().patch<T>(url, data, config),
|
||||
};
|
||||
|
||||
export default ApiClient;
|
||||
@@ -0,0 +1,134 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
/**
|
||||
* 合并 Tailwind CSS 类名的工具函数
|
||||
* @param inputs - 类名数组或对象
|
||||
* @returns 合并后的类名字符串
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param date - 日期对象或字符串
|
||||
* @param options - 格式化选项
|
||||
* @returns 格式化后的日期字符串
|
||||
*/
|
||||
export function formatDate(
|
||||
date: Date | string,
|
||||
options: Intl.DateTimeFormatOptions = {}
|
||||
): string {
|
||||
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
||||
return new Intl.DateTimeFormat('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
...options,
|
||||
}).format(dateObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字
|
||||
* @param num - 数字
|
||||
* @param options - 格式化选项
|
||||
* @returns 格式化后的数字字符串
|
||||
*/
|
||||
export function formatNumber(
|
||||
num: number,
|
||||
options: Intl.NumberFormatOptions = {}
|
||||
): string {
|
||||
return new Intl.NumberFormat('zh-CN', options).format(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机 ID
|
||||
* @param length - ID 长度
|
||||
* @returns 随机 ID 字符串
|
||||
*/
|
||||
export function generateId(length: number = 8): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param func - 要防抖的函数
|
||||
* @param wait - 等待时间(毫秒)
|
||||
* @returns 防抖后的函数
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return (...args: Parameters<T>) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
* @param func - 要节流的函数
|
||||
* @param limit - 时间限制(毫秒)
|
||||
* @returns 节流后的函数
|
||||
*/
|
||||
export function throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
limit: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let inThrottle: boolean;
|
||||
return (...args: Parameters<T>) => {
|
||||
if (!inThrottle) {
|
||||
func(...args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为移动设备
|
||||
* @returns 是否为移动设备
|
||||
*/
|
||||
export function isMobile(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return window.innerWidth < 768;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为暗色模式
|
||||
* @returns 是否为暗色模式
|
||||
*/
|
||||
export function isDarkMode(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return document.documentElement.classList.contains('dark');
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换暗色模式
|
||||
*/
|
||||
export function toggleDarkMode(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark').toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化暗色模式
|
||||
*/
|
||||
export function initDarkMode(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
const saved = localStorage.getItem('darkMode');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
if (saved === 'true' || (saved === null && prefersDark)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user