mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-05-22 00:30:14 +08:00
refactor: restructure directories to improve module organization Foxel.Models.Request.Picture - Foxel.Models.Request.Tag - Foxel.Models.Request.Auth - Foxel.Models.Request.Picture
This commit is contained in:
108
Web/src/api/AuthContext.tsx
Normal file
108
Web/src/api/AuthContext.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { createContext, useState, useEffect, useContext, useCallback } from 'react';
|
||||
import { getCurrentUser, isAuthenticated, clearAuthData, getStoredUser } from './index';
|
||||
import type { UserProfile } from './types';
|
||||
import { UserRole } from './types';
|
||||
|
||||
interface AuthContextType {
|
||||
user: UserProfile | null;
|
||||
loading: boolean;
|
||||
authenticated: boolean;
|
||||
authError: string | null;
|
||||
logout: () => void;
|
||||
refreshUser: () => Promise<void>;
|
||||
hasRole: (requiredRole: UserRole) => boolean;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
user: null,
|
||||
loading: true,
|
||||
authenticated: false,
|
||||
authError: null,
|
||||
logout: () => {},
|
||||
refreshUser: async () => {},
|
||||
hasRole: () => false
|
||||
});
|
||||
|
||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<UserProfile | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [authError, setAuthError] = useState<string | null>(null);
|
||||
|
||||
const refreshUser = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setAuthError(null);
|
||||
|
||||
try {
|
||||
const isLoggedIn = isAuthenticated();
|
||||
|
||||
if (isLoggedIn) {
|
||||
const storedUser = getStoredUser();
|
||||
if (storedUser) {
|
||||
setUser(storedUser);
|
||||
}
|
||||
|
||||
const response = await getCurrentUser();
|
||||
|
||||
if (response.success && response.data) {
|
||||
setUser(response.data);
|
||||
} else if (!storedUser) {
|
||||
setAuthError(response.message || '获取用户信息失败');
|
||||
clearAuthData();
|
||||
setUser(null);
|
||||
}
|
||||
} else {
|
||||
clearAuthData();
|
||||
setUser(null);
|
||||
}
|
||||
} catch (error: any) {
|
||||
setAuthError(error.message || '获取用户信息时发生错误');
|
||||
if (!getStoredUser()) {
|
||||
clearAuthData();
|
||||
setUser(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
clearAuthData();
|
||||
setUser(null);
|
||||
}, []);
|
||||
|
||||
const hasRole = useCallback((requiredRole: UserRole): boolean => {
|
||||
if (!user?.roleName) return false;
|
||||
|
||||
// 管理员拥有所有权限
|
||||
if (user.roleName === UserRole.Administrator) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 特定角色检查
|
||||
return user.roleName === requiredRole;
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshUser();
|
||||
}, [refreshUser]);
|
||||
|
||||
const contextValue = {
|
||||
user,
|
||||
loading,
|
||||
authenticated: !!user,
|
||||
authError,
|
||||
logout,
|
||||
refreshUser,
|
||||
hasRole
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuth = () => useContext(AuthContext);
|
||||
|
||||
export default AuthContext;
|
||||
117
Web/src/api/albumApi.ts
Normal file
117
Web/src/api/albumApi.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import type {
|
||||
PaginatedResult,
|
||||
AlbumResponse,
|
||||
CreateAlbumRequest,
|
||||
UpdateAlbumRequest,
|
||||
AlbumPictureRequest,
|
||||
AlbumPicturesRequest,
|
||||
BaseResult
|
||||
} from './types';
|
||||
import { fetchApi, BASE_URL } from './fetchClient';
|
||||
|
||||
// 获取相册列表
|
||||
export async function getAlbums(
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
userId?: number
|
||||
): Promise<PaginatedResult<AlbumResponse>> {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('page', page.toString());
|
||||
queryParams.append('pageSize', pageSize.toString());
|
||||
if (userId) {
|
||||
queryParams.append('userId', userId.toString());
|
||||
}
|
||||
|
||||
const url = `${BASE_URL}/album/get_albums?${queryParams.toString()}`;
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
return data as PaginatedResult<AlbumResponse>;
|
||||
} catch (error) {
|
||||
console.error('获取相册列表失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '网络请求失败,请检查您的网络连接',
|
||||
data: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
totalPages: 0,
|
||||
code: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取单个相册详情
|
||||
export async function getAlbumById(id: number): Promise<BaseResult<AlbumResponse>> {
|
||||
return fetchApi<AlbumResponse>(`/album/get_album/${id}`);
|
||||
}
|
||||
|
||||
// 创建相册
|
||||
export async function createAlbum(data: CreateAlbumRequest): Promise<BaseResult<AlbumResponse>> {
|
||||
return fetchApi<AlbumResponse>('/album/create_album', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// 更新相册
|
||||
export async function updateAlbum(data: UpdateAlbumRequest): Promise<BaseResult<AlbumResponse>> {
|
||||
return fetchApi<AlbumResponse>('/album/update_album', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// 删除相册
|
||||
export async function deleteAlbum(id: number): Promise<BaseResult<boolean>> {
|
||||
return fetchApi<boolean>('/album/delete_album', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(id),
|
||||
});
|
||||
}
|
||||
|
||||
// 添加多张图片到相册
|
||||
export async function addPicturesToAlbum(albumId: number, pictureIds: number[]): Promise<BaseResult<boolean>> {
|
||||
const data: AlbumPicturesRequest = {
|
||||
albumId,
|
||||
pictureIds,
|
||||
};
|
||||
return fetchApi<boolean>('/album/add_pictures', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// 添加图片到相册
|
||||
export async function addPictureToAlbum(albumId: number, pictureId: number): Promise<BaseResult<boolean>> {
|
||||
const data: AlbumPictureRequest = {
|
||||
albumId,
|
||||
pictureId,
|
||||
};
|
||||
return fetchApi<boolean>('/album/add_picture', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// 从相册移除图片
|
||||
export async function removePictureFromAlbum(albumId: number, pictureId: number): Promise<BaseResult<boolean>> {
|
||||
const data: AlbumPictureRequest = {
|
||||
albumId,
|
||||
pictureId,
|
||||
};
|
||||
return fetchApi<boolean>('/album/remove_picture', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
173
Web/src/api/authApi.ts
Normal file
173
Web/src/api/authApi.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import {type BaseResult, type AuthResponse, type LoginRequest, type RegisterRequest, type UserProfile, type UpdateUserRequest} from './types';
|
||||
import {fetchApi, BASE_URL} from './fetchClient';
|
||||
|
||||
// 认证数据本地存储键
|
||||
const TOKEN_KEY = 'token';
|
||||
const USER_KEY = 'user';
|
||||
|
||||
// 用户注册
|
||||
export async function register(data: RegisterRequest): Promise<BaseResult<AuthResponse>> {
|
||||
return fetchApi<AuthResponse>('/auth/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
export async function login(data: LoginRequest): Promise<BaseResult<AuthResponse>> {
|
||||
const response = await fetchApi<AuthResponse>('/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.success && response.data) {
|
||||
clearAuthData(); // 清除旧的认证数据
|
||||
console.log('登录成功,保存认证数据:', response.data);
|
||||
saveAuthData(response.data); // 保存新的认证数据
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 获取当前登录用户
|
||||
export async function getCurrentUser(): Promise<BaseResult<UserProfile>> {
|
||||
try {
|
||||
const token = getToken();
|
||||
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户未登录',
|
||||
code: 401
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetchApi<UserProfile>('/auth/get_current_user');
|
||||
|
||||
// 如果成功获取到用户数据,更新本地存储
|
||||
if (response.success && response.data) {
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(response.data));
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `获取用户信息失败: ${error.message}`,
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
export async function updateUserInfo(data: UpdateUserRequest): Promise<BaseResult<UserProfile>> {
|
||||
try {
|
||||
const response = await fetchApi<UserProfile>('/auth/update', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
// 如果成功更新用户数据,更新本地存储
|
||||
if (response.success && response.data) {
|
||||
const user = getStoredUser();
|
||||
if (user) {
|
||||
const updatedUser = {
|
||||
...user,
|
||||
...response.data
|
||||
};
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(updatedUser));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `更新用户信息失败: ${error.message}`,
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 保存认证数据到本地存储
|
||||
export const saveAuthData = (authData: AuthResponse): void => {
|
||||
localStorage.setItem(TOKEN_KEY, authData.token);
|
||||
if (authData.user) {
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(authData.user));
|
||||
}
|
||||
};
|
||||
|
||||
// 清除认证数据
|
||||
export const clearAuthData = (): void => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(USER_KEY);
|
||||
};
|
||||
|
||||
// 检查是否已认证
|
||||
export const isAuthenticated = (): boolean => {
|
||||
return !!getToken();
|
||||
};
|
||||
|
||||
// 获取存储的用户信息
|
||||
export const getStoredUser = (): UserProfile | null => {
|
||||
try {
|
||||
const userJson = localStorage.getItem(USER_KEY);
|
||||
if (!userJson) return null;
|
||||
|
||||
return JSON.parse(userJson) as UserProfile;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取存储的令牌
|
||||
export const getToken = (): string | null => {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
};
|
||||
|
||||
// 处理GitHub OAuth回调,接收token并保存
|
||||
export async function handleOAuthCallback(): Promise<boolean> {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get('token');
|
||||
const error = urlParams.get('error');
|
||||
|
||||
if (error) return false;
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
// 保存临时token,用于API调用
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
|
||||
// 获取完整的用户信息
|
||||
const userResponse = await getCurrentUser();
|
||||
|
||||
if (userResponse.success && userResponse.data) {
|
||||
// 构造完整的认证响应并保存
|
||||
const authResponse: AuthResponse = {
|
||||
token: token,
|
||||
user: userResponse.data
|
||||
};
|
||||
|
||||
saveAuthData(authResponse);
|
||||
|
||||
// 清除URL中的token参数
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('token');
|
||||
window.history.replaceState({}, document.title, url.toString());
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('第三方登录处理失败:', error);
|
||||
clearAuthData(); // 清除可能部分保存的数据
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getGitHubLoginUrl(): string {
|
||||
return `${BASE_URL}/auth/github/login`;
|
||||
}
|
||||
17
Web/src/api/backgroundTaskApi.ts
Normal file
17
Web/src/api/backgroundTaskApi.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { fetchApi } from './fetchClient';
|
||||
import type { BaseResult, PictureProcessingTask } from './types';
|
||||
|
||||
/**
|
||||
* 获取当前用户的所有处理任务
|
||||
*/
|
||||
export const getUserTasks = async (): Promise<BaseResult<PictureProcessingTask[]>> => {
|
||||
return fetchApi<PictureProcessingTask[]>('/background-tasks/user-tasks');
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取特定图片的处理状态
|
||||
* @param pictureId 图片ID
|
||||
*/
|
||||
export const getPictureProcessingStatus = async (pictureId: number): Promise<BaseResult<PictureProcessingTask>> => {
|
||||
return fetchApi<PictureProcessingTask>(`/background-tasks/picture-status/${pictureId}`);
|
||||
};
|
||||
74
Web/src/api/configApi.ts
Normal file
74
Web/src/api/configApi.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { UserRole, type BaseResult, type ConfigResponse, type SetConfigRequest } from './types';
|
||||
import { fetchApi } from './fetchClient';
|
||||
|
||||
// 获取所有配置
|
||||
export const getAllConfigs = async (): Promise<BaseResult<ConfigResponse[]>> => {
|
||||
try {
|
||||
return await fetchApi<ConfigResponse[]>('/config/get_configs');
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `获取配置失败: ${error.message}`,
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 获取单个配置
|
||||
export const getConfig = async (key: string): Promise<BaseResult<ConfigResponse>> => {
|
||||
try {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.append('key', key);
|
||||
|
||||
return await fetchApi<ConfigResponse>(`/config/get_config?${queryParams.toString()}`);
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `获取配置失败: ${error.message}`,
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 设置配置
|
||||
export const setConfig = async (config: SetConfigRequest): Promise<BaseResult<ConfigResponse>> => {
|
||||
try {
|
||||
return await fetchApi<ConfigResponse>('/config/set_config', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `设置配置失败: ${error.message}`,
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 删除配置
|
||||
export const deleteConfig = async (key: string): Promise<BaseResult<boolean>> => {
|
||||
try {
|
||||
return await fetchApi<boolean>('/config/delete_config', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(key),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
message: `删除配置失败: ${error.message}`,
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 角色权限检查
|
||||
export const hasRole = (userRole: string | undefined, requiredRole: UserRole): boolean => {
|
||||
if (!userRole) return false;
|
||||
|
||||
// 如果是管理员,拥有所有权限
|
||||
if (userRole === UserRole.Administrator) return true;
|
||||
|
||||
// 精确匹配角色
|
||||
return userRole === requiredRole;
|
||||
};
|
||||
31
Web/src/api/fetchClient.ts
Normal file
31
Web/src/api/fetchClient.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { BaseResult } from './types';
|
||||
export const BASE_URL = import.meta.env.PROD ? '/api' : 'http://localhost:5153/api';
|
||||
|
||||
export async function fetchApi<T = any>(
|
||||
url: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<BaseResult<T>> {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers as Record<string, string>,
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
const response = await fetch(`${BASE_URL}${url}`, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
const data = await response.json();
|
||||
return data as BaseResult<T>;
|
||||
} catch (error) {
|
||||
console.error('API请求错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '网络请求失败,请检查您的网络连接',
|
||||
code: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
56
Web/src/api/index.ts
Normal file
56
Web/src/api/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// 重新导出类型
|
||||
export * from './authApi';
|
||||
export * from './types';
|
||||
|
||||
// 导出fetch客户端
|
||||
export { fetchApi, BASE_URL } from './fetchClient';
|
||||
|
||||
// 导出Auth API
|
||||
export {
|
||||
register,
|
||||
login,
|
||||
getCurrentUser,
|
||||
updateUserInfo, // 添加导出更新用户信息函数
|
||||
saveAuthData,
|
||||
clearAuthData,
|
||||
isAuthenticated,
|
||||
getStoredUser
|
||||
} from './authApi';
|
||||
|
||||
// 导出Picture API
|
||||
export {
|
||||
getPictures,
|
||||
favoritePicture,
|
||||
unfavoritePicture,
|
||||
getUserFavorites,
|
||||
uploadPicture,
|
||||
deleteMultiplePictures, // 添加导出删除图片函数
|
||||
} from './pictureApi';
|
||||
|
||||
// 导出Album API
|
||||
export {
|
||||
getAlbums,
|
||||
getAlbumById,
|
||||
createAlbum,
|
||||
updateAlbum,
|
||||
deleteAlbum,
|
||||
addPictureToAlbum,
|
||||
addPicturesToAlbum,
|
||||
removePictureFromAlbum
|
||||
} from './albumApi';
|
||||
|
||||
// 导出BackgroundTask API
|
||||
export {
|
||||
getUserTasks,
|
||||
getPictureProcessingStatus,
|
||||
} from './backgroundTaskApi';
|
||||
|
||||
// 导出Config API
|
||||
export {
|
||||
getAllConfigs,
|
||||
getConfig,
|
||||
setConfig,
|
||||
deleteConfig,
|
||||
hasRole
|
||||
} from './configApi';
|
||||
|
||||
198
Web/src/api/pictureApi.ts
Normal file
198
Web/src/api/pictureApi.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import type { PaginatedResult, PictureResponse, FilteredPicturesRequest, BaseResult } from './types';
|
||||
import { fetchApi, BASE_URL } from './fetchClient';
|
||||
|
||||
// 获取图片列表
|
||||
export async function getPictures(params: FilteredPicturesRequest = {}): Promise<PaginatedResult<PictureResponse>> {
|
||||
// 添加调试日志
|
||||
console.log("Search API 请求参数:", params);
|
||||
|
||||
// 构建查询参数
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
// 添加所有非空参数
|
||||
if (params.page) queryParams.append('page', params.page.toString());
|
||||
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString());
|
||||
if (params.searchQuery) queryParams.append('searchQuery', params.searchQuery);
|
||||
if (params.tags) queryParams.append('tags', params.tags);
|
||||
if (params.startDate) queryParams.append('startDate', params.startDate);
|
||||
if (params.endDate) queryParams.append('endDate', params.endDate);
|
||||
if (params.userId) queryParams.append('userId', params.userId.toString());
|
||||
if (params.sortBy) queryParams.append('sortBy', params.sortBy);
|
||||
if (params.onlyWithGps !== undefined) queryParams.append('onlyWithGps', params.onlyWithGps.toString());
|
||||
if (params.useVectorSearch !== undefined) queryParams.append('useVectorSearch', params.useVectorSearch.toString());
|
||||
if (params.similarityThreshold) queryParams.append('similarityThreshold', params.similarityThreshold.toString());
|
||||
if (params.excludeAlbumId) queryParams.append('excludeAlbumId', params.excludeAlbumId.toString());
|
||||
if (params.albumId) queryParams.append('albumId', params.albumId.toString());
|
||||
if (params.onlyFavorites !== undefined) queryParams.append('onlyFavorites', params.onlyFavorites.toString());
|
||||
if (params.ownerId !== undefined) queryParams.append('ownerId', params.ownerId.toString());
|
||||
if (params.includeAllPublic !== undefined) queryParams.append('includeAllPublic', params.includeAllPublic.toString());
|
||||
|
||||
// 最终URL调试日志
|
||||
const url = `${BASE_URL}/picture/get_pictures?${queryParams.toString()}`;
|
||||
console.log("发送API请求:", url);
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
// 添加结果日志
|
||||
console.log("API 响应结果:", {
|
||||
success: data.success,
|
||||
totalCount: data.totalCount,
|
||||
resultsCount: data.data?.length || 0
|
||||
});
|
||||
|
||||
return data as PaginatedResult<PictureResponse>;
|
||||
} catch (error) {
|
||||
console.error('获取图片列表失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '网络请求失败,请检查您的网络连接',
|
||||
data: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
totalPages: 0,
|
||||
code: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 收藏图片
|
||||
export async function favoritePicture(pictureId: number): Promise<BaseResult<boolean>> {
|
||||
return fetchApi<boolean>('/picture/favorite', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ pictureId }),
|
||||
});
|
||||
}
|
||||
|
||||
// 取消收藏图片
|
||||
export async function unfavoritePicture(pictureId: number): Promise<BaseResult<boolean>> {
|
||||
return fetchApi<boolean>('/picture/unfavorite', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ pictureId }),
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户收藏的图片
|
||||
export async function getUserFavorites(page: number = 1, pageSize: number = 8): Promise<PaginatedResult<PictureResponse>> {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const url = `${BASE_URL}/picture/favorites?page=${page}&pageSize=${pageSize}`;
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
return data as PaginatedResult<PictureResponse>;
|
||||
} catch (error) {
|
||||
console.error('获取收藏图片失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '网络请求失败,请检查您的网络连接',
|
||||
data: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
totalPages: 0,
|
||||
code: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 上传图片
|
||||
export async function uploadPicture(
|
||||
file: File,
|
||||
data: {
|
||||
permission?: number;
|
||||
albumId?: number;
|
||||
onProgress?: (percent: number) => void
|
||||
} = {}
|
||||
): Promise<BaseResult<PictureResponse>> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
if (data.permission !== undefined) {
|
||||
formData.append('permission', data.permission.toString());
|
||||
}
|
||||
|
||||
if (data.albumId !== undefined) {
|
||||
formData.append('albumId', data.albumId.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
// 返回一个Promise
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.open('POST', `${BASE_URL}/picture/upload_picture`);
|
||||
|
||||
if (token) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable && data.onProgress) {
|
||||
const percent = Math.round((event.loaded / event.total) * 100);
|
||||
data.onProgress(percent);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
resolve(response);
|
||||
} else {
|
||||
reject({
|
||||
status: xhr.status,
|
||||
message: xhr.statusText || '上传失败',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
reject({
|
||||
status: xhr.status,
|
||||
message: '网络错误,上传失败',
|
||||
});
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('上传图片失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '上传图片失败',
|
||||
code: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 删除多张图片
|
||||
export async function deleteMultiplePictures(pictureIds: number[]): Promise<BaseResult<object>> {
|
||||
return fetchApi<object>('/picture/delete_pictures', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ pictureIds }),
|
||||
});
|
||||
}
|
||||
|
||||
111
Web/src/api/tagApi.ts
Normal file
111
Web/src/api/tagApi.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { BaseResult, PaginatedResult } from './types';
|
||||
import { fetchApi, BASE_URL } from './fetchClient';
|
||||
|
||||
// 标签响应类型
|
||||
export interface TagResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
pictureCount: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// 筛选标签请求参数
|
||||
export interface FilteredTagsRequest {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
searchQuery?: string;
|
||||
sortBy?: string;
|
||||
sortDirection?: string;
|
||||
}
|
||||
|
||||
// 创建标签请求
|
||||
export interface CreateTagRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 更新标签请求
|
||||
export interface UpdateTagRequest {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 获取所有标签
|
||||
export async function getAllTags(): Promise<BaseResult<TagResponse[]>> {
|
||||
return fetchApi<TagResponse[]>('/tag/all', {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 获取筛选后的标签
|
||||
export async function getFilteredTags(params: FilteredTagsRequest = {}): Promise<PaginatedResult<TagResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (params.page) queryParams.append('page', params.page.toString());
|
||||
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString());
|
||||
if (params.searchQuery) queryParams.append('searchQuery', params.searchQuery);
|
||||
if (params.sortBy) queryParams.append('sortBy', params.sortBy);
|
||||
if (params.sortDirection) queryParams.append('sortDirection', params.sortDirection);
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const url = `${BASE_URL}/tag/get_tags?${queryParams.toString()}`;
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
return data as PaginatedResult<TagResponse>;
|
||||
} catch (error) {
|
||||
console.error('获取标签列表失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '网络请求失败,请检查您的网络连接',
|
||||
data: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
totalPages: 0,
|
||||
code: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取标签详情
|
||||
export async function getTagById(id: number): Promise<BaseResult<TagResponse>> {
|
||||
return fetchApi<TagResponse>(`/tag/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 创建标签
|
||||
export async function createTag(request: CreateTagRequest): Promise<BaseResult<TagResponse>> {
|
||||
return fetchApi<TagResponse>('/tag/create_tag', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
// 更新标签
|
||||
export async function updateTag(request: UpdateTagRequest): Promise<BaseResult<TagResponse>> {
|
||||
return fetchApi<TagResponse>('/tag/update_tag', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
// 删除标签
|
||||
export async function deleteTag(id: number): Promise<BaseResult<boolean>> {
|
||||
return fetchApi<boolean>('/tag/delete_tag', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(id),
|
||||
});
|
||||
}
|
||||
207
Web/src/api/types.ts
Normal file
207
Web/src/api/types.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
// API响应的基础结构
|
||||
export interface BaseResult<T> {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data?: T;
|
||||
code: number;
|
||||
}
|
||||
|
||||
// 分页结果通用结构
|
||||
export interface PaginatedResult<T> {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: T[];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
code: number;
|
||||
}
|
||||
|
||||
// 登录请求参数
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// 注册请求参数
|
||||
export interface RegisterRequest {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
export interface UserProfile {
|
||||
id: number;
|
||||
userName: string;
|
||||
email: string;
|
||||
roleName: string;
|
||||
}
|
||||
|
||||
// 认证响应
|
||||
export interface AuthResponse {
|
||||
token: string;
|
||||
user: UserProfile;
|
||||
}
|
||||
|
||||
// 图片请求参数
|
||||
export interface FilteredPicturesRequest {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
searchQuery?: string;
|
||||
tags?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
userId?: number;
|
||||
sortBy?: string;
|
||||
onlyWithGps?: boolean;
|
||||
useVectorSearch?: boolean;
|
||||
similarityThreshold?: number;
|
||||
excludeAlbumId?: number;
|
||||
albumId?: number;
|
||||
onlyFavorites?: boolean;
|
||||
ownerId?: number;
|
||||
includeAllPublic?: boolean;
|
||||
}
|
||||
|
||||
// 图片响应数据
|
||||
export interface PictureResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
thumbnailPath: string;
|
||||
description: string;
|
||||
takenAt?: Date;
|
||||
createdAt: Date;
|
||||
exifInfo?: any;
|
||||
tags?: string[];
|
||||
userId: number;
|
||||
username?: string;
|
||||
isFavorited: boolean;
|
||||
favoriteCount: number;
|
||||
permission: number;
|
||||
albumId?: number;
|
||||
albumName?: string;
|
||||
processingStatus: ProcessingStatus;
|
||||
processingError?: string;
|
||||
processingProgress: number;
|
||||
}
|
||||
|
||||
// 收藏请求
|
||||
export interface FavoriteRequest {
|
||||
pictureId: number;
|
||||
}
|
||||
|
||||
// 上传队列中的文件项
|
||||
export interface UploadFile {
|
||||
id: string; // 本地ID,用于跟踪状态
|
||||
file: File; // 原始文件
|
||||
status: 'pending' | 'uploading' | 'success' | 'error'; // 上传状态
|
||||
percent: number; // 上传进度百分比 0-100
|
||||
error?: string; // 错误信息
|
||||
response?: PictureResponse; // 上传成功后的响应
|
||||
}
|
||||
|
||||
// 上传图片请求参数
|
||||
export interface UploadPictureParams {
|
||||
permission?: number; // 权限设置,默认为0(公开)
|
||||
albumId?: number; // 相册ID,可选
|
||||
}
|
||||
|
||||
// 相册响应数据
|
||||
export interface AlbumResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
coverImageUrl?: string;
|
||||
pictureCount: number;
|
||||
userId: number;
|
||||
username?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// 创建相册请求
|
||||
export interface CreateAlbumRequest {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// 更新相册请求
|
||||
export interface UpdateAlbumRequest {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// 相册图片操作请求
|
||||
export interface AlbumPictureRequest {
|
||||
albumId: number;
|
||||
pictureId: number;
|
||||
}
|
||||
|
||||
// 批量添加图片到相册请求
|
||||
export interface AlbumPicturesRequest {
|
||||
albumId: number;
|
||||
pictureIds: number[];
|
||||
}
|
||||
|
||||
// 删除多张图片请求
|
||||
export interface DeleteMultiplePicturesRequest {
|
||||
pictureIds: number[];
|
||||
}
|
||||
|
||||
// 将类型定义改为枚举,这样既可以作为类型也可以作为值使用
|
||||
export type ProcessingStatus = 'Pending' | 'Processing' | 'Completed' | 'Failed';
|
||||
|
||||
// 添加常量对象提供运行时值
|
||||
export const ProcessingStatus = {
|
||||
Pending: 'Pending' as ProcessingStatus,
|
||||
Processing: 'Processing' as ProcessingStatus,
|
||||
Completed: 'Completed' as ProcessingStatus,
|
||||
Failed: 'Failed' as ProcessingStatus
|
||||
};
|
||||
|
||||
// 图片处理任务
|
||||
export interface PictureProcessingTask {
|
||||
pictureId: number;
|
||||
taskId: string;
|
||||
pictureName: string;
|
||||
status: ProcessingStatus;
|
||||
progress: number; // 0-100
|
||||
error?: string;
|
||||
createdAt: Date;
|
||||
completedAt?: Date;
|
||||
}
|
||||
|
||||
// 配置响应数据
|
||||
export interface ConfigResponse {
|
||||
id: number;
|
||||
key: string;
|
||||
value: string;
|
||||
description: string;
|
||||
createdAt: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface SetConfigRequest {
|
||||
key: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type UserRole = "Administrator" | "User" | "";
|
||||
|
||||
export const UserRole = {
|
||||
Administrator: "Administrator" as UserRole,
|
||||
User: "User" as UserRole,
|
||||
Guest: "" as UserRole
|
||||
};
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
userName?: string;
|
||||
email?: string;
|
||||
currentPassword?: string;
|
||||
newPassword?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user