refactor(api): 统一API路径配置并优化相关服务调用
All checks were successful
test/timeline-frontend/pipeline/head This commit looks good

feat: 添加API URL全局配置文件
fix: 修复SSR环境下的窗口对象检查
perf: 优化代理配置路径匹配顺序
style: 移除无用注释和未使用的类型声明
This commit is contained in:
2026-02-27 10:07:03 +08:00
parent 97e4a135e1
commit c1a88ea4da
29 changed files with 392 additions and 225 deletions

View File

@@ -160,56 +160,56 @@ export default defineConfig({
* @name Webpack configuration * @name Webpack configuration
* @description Custom webpack configuration for code splitting and optimization * @description Custom webpack configuration for code splitting and optimization
*/ */
chainWebpack: (config: any) => { // chainWebpack: (config: any) => {
// Ignore Windows system files to prevent Watchpack errors // // Ignore Windows system files to prevent Watchpack errors
config.watchOptions({ // config.watchOptions({
ignored: [ // ignored: [
'**/node_modules', // '**/node_modules',
'**/.git', // '**/.git',
'**/C:/DumpStack.log.tmp', // '**/C:/DumpStack.log.tmp',
'**/C:/pagefile.sys', // '**/C:/pagefile.sys',
'**/C:/swapfile.sys', // '**/C:/swapfile.sys',
'**/C:/System Volume Information', // '**/C:/System Volume Information',
'**/C:/$*', // Ignore all system files starting with $ // '**/C:/$*', // Ignore all system files starting with $
], // ],
}); // });
// Split large vendor libraries into separate chunks // // Split large vendor libraries into separate chunks
config.optimization.splitChunks({ // config.optimization.splitChunks({
chunks: 'all', // chunks: 'all',
cacheGroups: { // cacheGroups: {
// Ant Design and related libraries // // Ant Design and related libraries
antd: { // antd: {
name: 'antd', // name: 'antd',
test: /[\\/]node_modules[\\/](@ant-design|antd|@antv|rc-)/, // test: /[\\/]node_modules[\\/](@ant-design|antd|@antv|rc-)/,
priority: 20, // priority: 20,
}, // },
// React and related libraries // // React and related libraries
react: { // react: {
name: 'react', // name: 'react',
test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)/, // test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)/,
priority: 20, // priority: 20,
}, // },
// Other vendor libraries // // Other vendor libraries
vendors: { // vendors: {
name: 'vendors', // name: 'vendors',
test: /[\\/]node_modules[\\/]/, // test: /[\\/]node_modules[\\/]/,
priority: 10, // priority: 10,
}, // },
}, // },
}); // });
// Optimize images // // Optimize images
config.module // config.module
.rule('images') // .rule('images')
.test(/\.(png|jpe?g|gif|webp|svg)$/) // .test(/\.(png|jpe?g|gif|webp|svg)$/)
.use('url-loader') // .use('url-loader')
.loader('url-loader') // .loader('url-loader')
.options({ // .options({
limit: 8192, // Inline images smaller than 8KB // limit: 8192, // Inline images smaller than 8KB
name: 'static/images/[name].[hash:8].[ext]', // name: 'static/images/[name].[hash:8].[ext]',
}); // });
return config; // return config;
}, // },
}); });

View File

@@ -15,13 +15,22 @@ export default {
// 如果需要自定义本地开发服务器 请取消注释按需调整 // 如果需要自定义本地开发服务器 请取消注释按需调整
dev: { dev: {
// localhost:8000/api/** -> https://preview.pro.ant.design/api/** // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
// 注意:更具体的路径应该放在前面,避免被通用路径覆盖
'/api/story/': { '/api/story/': {
// 要代理的地址 // 要代理的地址
target: basePath, target: basePath,
// 配置了这个可以从 http 代理到 https // 配置了这个可以从 http 代理到 https
// 依赖 origin 的功能可能需要这个,比如 cookie // 依赖 origin 的功能可能需要这个,比如 cookie
changeOrigin: true, changeOrigin: true,
pathRewrite: { '^/api/story': '/api/story' }, pathRewrite: { '^/api/story': '/story' },
},
'/api/user/': {
// 要代理的地址
target: basePath,
// 配置了这个可以从 http 代理到 https
// 依赖 origin 的功能可能需要这个,比如 cookie
changeOrigin: true,
pathRewrite: { '^/api/user': '/user' },
}, },
'/file/': { '/file/': {
// 要代理的地址 // 要代理的地址
@@ -39,10 +48,11 @@ export default {
changeOrigin: true, changeOrigin: true,
pathRewrite: { '^/user-api': '/user' }, pathRewrite: { '^/user-api': '/user' },
}, },
// 通用 /api/ 代理放在最后,避免覆盖上面的具体配置
'/api/': { '/api/': {
target: 'https://proapi.azurewebsites.net', target: basePath,
changeOrigin: true, changeOrigin: true,
pathRewrite: { '^': '' }, pathRewrite: { '^/api': '/api' },
}, },
}, },

View File

@@ -8,7 +8,6 @@ import { history, Link, useModel } from '@umijs/max';
import { useIsMobile } from '@/hooks/useIsMobile'; import { useIsMobile } from '@/hooks/useIsMobile';
import defaultSettings from '../config/defaultSettings'; import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig'; import { errorConfig } from './requestErrorConfig';
import { currentUser as queryCurrentUser } from './services/ant-design-pro/api';
import { useNotifications } from '@/hooks/useNotifications'; import { useNotifications } from '@/hooks/useNotifications';
import BottomNav from '@/components/BottomNav'; import BottomNav from '@/components/BottomNav';
import SyncStatusIndicator from '@/components/SyncStatus/SyncStatusIndicator'; import SyncStatusIndicator from '@/components/SyncStatus/SyncStatusIndicator';
@@ -32,22 +31,23 @@ function LayoutChildrenWrapper({
setInitialState: any; setInitialState: any;
}) { }) {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { effectiveTheme, getCurrentColorScheme, fetchThemePreferences } = useModel('theme');
// 暂时注释掉 theme 相关代码,避免 useModel 调用导致问题
const { effectiveTheme, getCurrentColorScheme, fetchThemePreferences } = useModel('theme');
useEffect(() => { useEffect(() => {
fetchThemePreferences(); fetchThemePreferences();
}, [fetchThemePreferences]); }, [fetchThemePreferences]);
const colorScheme = getCurrentColorScheme(); const colorScheme = getCurrentColorScheme();
// 暂时注释掉 useNotifications避免 useModel 调用导致问题
useNotifications(); useNotifications();
return ( return (
<ConfigProvider <ConfigProvider
theme={{ theme={{
algorithm: effectiveTheme === 'dark' ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm, algorithm: antdTheme.defaultAlgorithm,
token: { token: {
colorPrimary: colorScheme.primaryColor, colorPrimary: '#1890ff',
}, },
}} }}
> >
@@ -77,34 +77,36 @@ export async function getInitialState(): Promise<{
currentUser?: API.CurrentUser; currentUser?: API.CurrentUser;
loading?: boolean; loading?: boolean;
}> { }> {
const fetchUserInfo = async () => { // 从 localStorage 获取用户信息
const getUserFromStorage = () => {
if (typeof window !== 'undefined') {
const userStr = localStorage.getItem('timeline_user');
if (userStr) {
try { try {
const msg = await queryCurrentUser({ skipErrorHandler: true }); return JSON.parse(userStr);
return msg.data; } catch (e) {
} catch (error) { return undefined;
// history.push(loginPath); }
}
} }
return undefined; return undefined;
}; };
if (history.location.pathname !== loginPath) { // 直接从 localStorage 读取用户信息避免API调用阻塞页面渲染
const currentUser = await fetchUserInfo(); const currentUser = getUserFromStorage();
return { return {
currentUser, currentUser,
settings: defaultSettings as Partial<LayoutSettings>, settings: defaultSettings as Partial<LayoutSettings>,
}; };
} }
return {
settings: defaultSettings as Partial<LayoutSettings>,
};
}
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => { export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
return { return {
actionsRender: () => [ actionsRender: () => [
<ClientOnly key="client-only-actions"> <ClientOnly key="client-only-actions">
<SyncStatusIndicator key="sync-status" />, <SyncStatusIndicator key="sync-status" />
<Question key="doc" />, <Question key="doc" />
<SelectLang key="SelectLang" /> <SelectLang key="SelectLang" />
</ClientOnly>, </ClientOnly>,
], ],

View File

@@ -48,6 +48,9 @@ const CommentList: React.FC<CommentListProps> = ({
}) => { }) => {
// Sort comments chronologically (oldest first) // Sort comments chronologically (oldest first)
const sortedComments = useMemo(() => { const sortedComments = useMemo(() => {
if (!comments || !Array.isArray(comments)) {
return [];
}
return [...comments].sort((a, b) => { return [...comments].sort((a, b) => {
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
}); });

View File

@@ -3,6 +3,7 @@ import { useEffect, useState, useCallback } from 'react';
import { useModel, request } from '@umijs/max'; import { useModel, request } from '@umijs/max';
import { notification as antdNotification } from 'antd'; import { notification as antdNotification } from 'antd';
import { Notification, NotificationType } from '@/types'; import { Notification, NotificationType } from '@/types';
import { AUTH_API, USER_API } from '@/services/config/apiUrls';
export const useNotifications = () => { export const useNotifications = () => {
const { initialState } = useModel('@@initialState'); const { initialState } = useModel('@@initialState');
@@ -13,7 +14,7 @@ export const useNotifications = () => {
const fetchUnreadNotifications = useCallback(async () => { const fetchUnreadNotifications = useCallback(async () => {
if (!initialState?.currentUser) return; if (!initialState?.currentUser) return;
try { try {
const res = await request<Notification[]>('/user-api/message/unread'); const res = await request<Notification[]>(`${AUTH_API.INFO}/message/unread`);
setNotifications(res); setNotifications(res);
setUnreadCount(res.length); setUnreadCount(res.length);
} catch (error) { } catch (error) {
@@ -53,7 +54,7 @@ export const useNotifications = () => {
const markAsRead = useCallback(async (ids: number[]) => { const markAsRead = useCallback(async (ids: number[]) => {
try { try {
await request('/api/user/notifications/read', { await request(USER_API.NOTIFICATIONS_READ, {
method: 'POST', method: 'POST',
data: ids, data: ids,
}); });
@@ -79,9 +80,14 @@ export const useNotifications = () => {
case NotificationType.SYSTEM: case NotificationType.SYSTEM:
return '系统通知'; return '系统通知';
default: default:
return '系统通知'; return '通知';
} }
}; };
return { notifications, unreadCount, markAsRead }; return {
notifications,
unreadCount,
markAsRead,
fetchUnreadNotifications,
};
}; };

View File

@@ -156,7 +156,7 @@ export function useReactions(
// Helpers // Helpers
hasReaction: !!userReaction, hasReaction: !!userReaction,
totalReactions: reactions totalReactions: reactions?.counts
? Object.values(reactions.counts).reduce((sum, count) => sum + count, 0) ? Object.values(reactions.counts).reduce((sum, count) => sum + count, 0)
: 0, : 0,
}; };

View File

@@ -15,7 +15,7 @@ export interface SyncState {
export default () => { export default () => {
const [syncState, setSyncState] = useState<SyncState>({ const [syncState, setSyncState] = useState<SyncState>({
isOnline: navigator.onLine, isOnline: typeof window !== 'undefined' ? navigator.onLine : true,
isSyncing: false, isSyncing: false,
pendingChanges: 0, pendingChanges: 0,
lastSyncTime: undefined, lastSyncTime: undefined,
@@ -24,6 +24,8 @@ export default () => {
// Update online status // Update online status
useEffect(() => { useEffect(() => {
if (typeof window === 'undefined') return;
const handleOnline = () => { const handleOnline = () => {
setSyncState((prev) => ({ ...prev, isOnline: true, syncError: undefined })); setSyncState((prev) => ({ ...prev, isOnline: true, syncError: undefined }));
// Automatically trigger sync when coming online // Automatically trigger sync when coming online

View File

@@ -1,19 +1,20 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import type { CurrentUser, FriendUser, HistoryMessage, ListItemDataType, MessageItem, UserInfo } from './data.d'; import type { CurrentUser, FriendUser, HistoryMessage, ListItemDataType, MessageItem, UserInfo } from './data.d';
import { CommonResponse } from '@/types/common'; import { CommonResponse } from '@/types/common';
import { AUTH_API, STORY_API } from '@/services/config/apiUrls';
export async function queryCurrent(): Promise<{ data: CurrentUser }> { export async function queryCurrent(): Promise<{ data: CurrentUser }> {
return request('/user-api/info'); return request(AUTH_API.INFO);
} }
export async function searchUsername(params: {username: string}) : Promise<{data: UserInfo[]}> { export async function searchUsername(params: {username: string}) : Promise<{data: UserInfo[]}> {
return request('/user-api/search', { return request(AUTH_API.FRIEND_SEARCH, {
params: params, params: params,
method: "GET" method: "GET"
}) })
} }
export async function addFriend(userId: string) : Promise<CommonResponse<String>> { export async function addFriend(userId: string) : Promise<CommonResponse<String>> {
return request('/user-api/friend/request', { return request(AUTH_API.FRIEND_REQUEST, {
data: { data: {
friendId: userId, friendId: userId,
}, },
@@ -21,29 +22,29 @@ export async function addFriend(userId: string) : Promise<CommonResponse<String>
}) })
} }
export async function queryFriendList(): Promise<CommonResponse<FriendUser[]>> { export async function queryFriendList(): Promise<CommonResponse<FriendUser[]>> {
return request('/user-api/friend/list'); return request(AUTH_API.FRIEND_LIST);
} }
export async function acceptFriendRequest(friendId: string): Promise<CommonResponse<string>> { export async function acceptFriendRequest(friendId: string): Promise<CommonResponse<string>> {
return request('/user-api/friend/accept', { return request(AUTH_API.FRIEND_ACCEPT, {
method: 'POST', method: 'POST',
data: { friendId }, data: { friendId },
}); });
} }
export async function rejectFriendRequest(friendId: string): Promise<CommonResponse<string>> { export async function rejectFriendRequest(friendId: string): Promise<CommonResponse<string>> {
return request('/user-api/friend/reject', { return request(AUTH_API.FRIEND_REJECT, {
method: 'POST', method: 'POST',
data: { friendId }, data: { friendId },
}); });
} }
export async function queryHistoryMessages(): Promise<CommonResponse<HistoryMessage[]>> { export async function queryHistoryMessages(): Promise<CommonResponse<HistoryMessage[]>> {
return request('/user-api/message/history/friend', { return request(AUTH_API.MESSAGE_HISTORY, {
method: 'GET', method: 'GET',
}); });
} }
export async function queryFriendDynamic(): Promise<CommonResponse<HistoryMessage[]>> { export async function queryFriendDynamic(): Promise<CommonResponse<HistoryMessage[]>> {
return request('/api/story/activity/authorized-items', { return request(STORY_API.ACTIVITY_AUTHORIZED_ITEMS, {
method: 'GET', method: 'GET',
}); });
} }

View File

@@ -1,24 +1,25 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import type { CurrentUser, GeographicItemType } from './data'; import type { CurrentUser, GeographicItemType } from './data';
import { CommonResponse } from '@/types/common'; import { CommonResponse } from '@/types/common';
import { AUTH_API, GEO_API, COMMON_API } from '@/services/config/apiUrls';
export async function queryCurrent(): Promise<{ data: CurrentUser }> { export async function queryCurrent(): Promise<{ data: CurrentUser }> {
return request('/user-api/info'); return request(AUTH_API.INFO);
} }
export async function updateCurrentUser(params: CurrentUser): Promise<{data: CommonResponse<string>}> { export async function updateCurrentUser(params: CurrentUser): Promise<{data: CommonResponse<string>}> {
return request('/user-api/info', { return request(AUTH_API.INFO, {
method: 'PUT', method: 'PUT',
data: params, data: params,
}) })
} }
export async function queryProvince(): Promise<{ data: GeographicItemType[] }> { export async function queryProvince(): Promise<{ data: GeographicItemType[] }> {
return request('/api/geographic/province'); return request(GEO_API.PROVINCE);
} }
export async function queryCity(province: string): Promise<{ data: GeographicItemType[] }> { export async function queryCity(province: string): Promise<{ data: GeographicItemType[] }> {
return request(`/api/geographic/city/${province}`); return request(GEO_API.CITY(province));
} }
export async function query() { export async function query() {
return request('/api/users'); return request(COMMON_API.USERS);
} }

View File

@@ -7,6 +7,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Button, message, Spin, Empty, Checkbox } from 'antd'; import { Button, message, Spin, Empty, Checkbox } from 'antd';
import { useModel, request } from '@umijs/max'; import { useModel, request } from '@umijs/max';
import { USER_API } from '@/services/config/apiUrls';
import styles from '../index.less'; import styles from '../index.less';
interface PhotoSelectorProps { interface PhotoSelectorProps {
@@ -42,7 +43,7 @@ const PhotoSelector: React.FC<PhotoSelectorProps> = ({
setLoading(true); setLoading(true);
try { try {
// Fetch user's photos from gallery // Fetch user's photos from gallery
const response = await request('/api/v1/gallery/photos', { const response = await request(USER_API.GALLERY_PHOTOS, {
method: 'GET', method: 'GET',
params: { pageSize: 100 }, params: { pageSize: 100 },
}); });

View File

@@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from 'react';
import { PlayCircleOutlined, PauseCircleOutlined, FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons'; import { PlayCircleOutlined, PauseCircleOutlined, FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons';
import { Spin } from 'antd'; import { Spin } from 'antd';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { FILE_API } from '@/services/config/apiUrls';
interface TimelineVideoProps { interface TimelineVideoProps {
videoInstanceId: string; videoInstanceId: string;
@@ -21,7 +22,7 @@ const TimelineVideo: React.FC<TimelineVideoProps> = ({ videoInstanceId, thumbnai
useEffect(() => { useEffect(() => {
const fetchVideoUrl = async () => { const fetchVideoUrl = async () => {
try { try {
const response = await request(`/file/get-video-url/${videoInstanceId}`, { method: 'GET' }); const response = await request(FILE_API.VIDEO_URL(videoInstanceId), { method: 'GET' });
if (response.code === 200 && response.data) { if (response.code === 200 && response.data) {
setVideoSrc(response.data); setVideoSrc(response.data);
} }

View File

@@ -1,6 +1,7 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import {StoryItem, StoryItemTimeQueryParams, StoryType} from './data.d'; import {StoryItem, StoryItemTimeQueryParams, StoryType} from './data.d';
import {CommonListResponse, CommonResponse} from "@/types/common"; import {CommonListResponse, CommonResponse} from "@/types/common";
import { STORY_API, FILE_API } from '@/services/config/apiUrls';
type ParamsType = { type ParamsType = {
count?: number; count?: number;
@@ -13,19 +14,19 @@ type ParamsType = {
export async function queryTimelineList( export async function queryTimelineList(
params: ParamsType, params: ParamsType,
): Promise<{ data: StoryType[] }> { ): Promise<{ data: StoryType[] }> {
return await request('/api/story/list', { return await request(STORY_API.LIST, {
params, params,
}); });
} }
export async function deleteStory(params: ParamsType): Promise<{ data: { list: StoryType[] } }> { export async function deleteStory(params: ParamsType): Promise<{ data: { list: StoryType[] } }> {
return request(`/api/story/${params.instanceId}`, { return request(STORY_API.DETAIL(params.instanceId!), {
method: 'DELETE', method: 'DELETE',
}); });
} }
export async function addStory(params: ParamsType): Promise<{ data: { list: StoryType[] } }> { export async function addStory(params: ParamsType): Promise<{ data: { list: StoryType[] } }> {
return request('/api/story/add', { return request(STORY_API.ADD, {
method: 'POST', method: 'POST',
data: { data: {
...params, ...params,
@@ -35,7 +36,7 @@ export async function addStory(params: ParamsType): Promise<{ data: { list: Stor
} }
export async function updateStory(params: ParamsType): Promise<{ data: { list: StoryType[] } }> { export async function updateStory(params: ParamsType): Promise<{ data: { list: StoryType[] } }> {
return await request(`/api/story/${params.instanceId}`, { return await request(STORY_API.DETAIL(params.instanceId!), {
method: 'PUT', method: 'PUT',
data: { data: {
...params, ...params,
@@ -44,12 +45,12 @@ export async function updateStory(params: ParamsType): Promise<{ data: { list: S
}); });
} }
export async function queryStoryDetail(itemId: string): Promise<{ data: StoryType }> { export async function queryStoryDetail(itemId: string): Promise<{ data: StoryType }> {
return request(`/api/story/${itemId}`, { return request(STORY_API.DETAIL(itemId), {
method: 'GET', method: 'GET',
}); });
} }
export async function addStoryItem(params: FormData): Promise<any> { export async function addStoryItem(params: FormData): Promise<any> {
return request(`/api/story/item`, { return request(STORY_API.ITEM, {
method: 'POST', method: 'POST',
data: params, data: params,
requestType: 'form', requestType: 'form',
@@ -57,7 +58,7 @@ export async function addStoryItem(params: FormData): Promise<any> {
}); });
} }
export async function updateStoryItem(params: FormData): Promise<any> { export async function updateStoryItem(params: FormData): Promise<any> {
return request(`/api/story/item`, { return request(STORY_API.ITEM, {
method: 'PUT', method: 'PUT',
data: params, data: params,
requestType: 'form', requestType: 'form',
@@ -66,43 +67,43 @@ export async function updateStoryItem(params: FormData): Promise<any> {
} }
export async function queryStoryItem(params: ParamsType): Promise<{ data: CommonListResponse<StoryItem> }> { export async function queryStoryItem(params: ParamsType): Promise<{ data: CommonListResponse<StoryItem> }> {
return request(`/api/story/item/list`, { return request(STORY_API.ITEM_LIST, {
method: 'GET', method: 'GET',
params: params, params: params,
}); });
} }
export async function queryStoryItemDetail(itemId: string): Promise<{ data: StoryItem }> { export async function queryStoryItemDetail(itemId: string): Promise<{ data: StoryItem }> {
return request(`/api/story/item/${itemId}`, { return request(STORY_API.ITEM_DETAIL(itemId), {
method: 'GET', method: 'GET',
}); });
} }
export async function countStoryItem(storyInstanceId: string): Promise<{ data: StoryItem }> { export async function countStoryItem(storyInstanceId: string): Promise<{ data: StoryItem }> {
return request(`/api/story/item/count/${storyInstanceId}`, { return request(STORY_API.ITEM_COUNT(storyInstanceId), {
method: 'GET', method: 'GET',
}); });
} }
export async function queryStoryItemImages(itemId: string): Promise<{ data: string[] }> { export async function queryStoryItemImages(itemId: string): Promise<{ data: string[] }> {
return request(`/api/story/item/images/${itemId}`, { return request(STORY_API.ITEM_IMAGES(itemId), {
method: 'GET', method: 'GET',
}); });
} }
export async function removeStoryItem(instanceId: string): Promise<CommonResponse<void>> { export async function removeStoryItem(instanceId: string): Promise<CommonResponse<void>> {
return request(`/api/story/item/${instanceId}`, { return request(STORY_API.ITEM_DETAIL(instanceId), {
method: 'DELETE', method: 'DELETE',
}); });
} }
export async function searchStoryItems(params: { keyword: string; pageNum: number; pageSize: number }) { export async function searchStoryItems(params: { keyword: string; pageNum: number; pageSize: number }) {
return request('/api/story/item/search', { return request(STORY_API.ITEM_SEARCH, {
method: 'GET', method: 'GET',
params, params,
}); });
} }
export async function fetchImage(imageInstanceId: string): Promise<any> { export async function fetchImage(imageInstanceId: string): Promise<any> {
return request(`/file/image/${imageInstanceId}`, { return request(FILE_API.IMAGE(imageInstanceId), {
method: 'GET', method: 'GET',
responseType: 'blob', responseType: 'blob',
getResponse: true, getResponse: true,
@@ -110,46 +111,46 @@ export async function fetchImage(imageInstanceId: string): Promise<any> {
} }
export async function authorizeStoryPermission(params: {userId: string, storyInstanceId: string, permissionType: number}) { export async function authorizeStoryPermission(params: {userId: string, storyInstanceId: string, permissionType: number}) {
return request('/api/story/permission/authorize', { return request(STORY_API.PERMISSION_AUTHORIZE, {
method: 'POST', method: 'POST',
data: params, data: params,
}); });
} }
export async function getStoryPermissions(storyId: string) { export async function getStoryPermissions(storyId: string) {
return request(`/api/story/permission/story/${storyId}`, { return request(STORY_API.PERMISSION_STORY(storyId), {
method: 'GET', method: 'GET',
}); });
} }
export async function inviteUser(params: {userId: string, storyInstanceId: string, permissionType: number}) { export async function inviteUser(params: {userId: string, storyInstanceId: string, permissionType: number}) {
return request('/api/story/permission/invite', { return request(STORY_API.PERMISSION_INVITE, {
method: 'POST', method: 'POST',
data: params, data: params,
}); });
} }
export async function acceptInvite(inviteId: string) { export async function acceptInvite(inviteId: string) {
return request(`/api/story/permission/invite/${inviteId}/accept`, { return request(STORY_API.PERMISSION_INVITE_ACCEPT(inviteId), {
method: 'PUT', method: 'PUT',
}); });
} }
export async function rejectInvite(inviteId: string) { export async function rejectInvite(inviteId: string) {
return request(`/api/story/permission/invite/${inviteId}/reject`, { return request(STORY_API.PERMISSION_INVITE_REJECT(inviteId), {
method: 'PUT', method: 'PUT',
}); });
} }
export async function updatePermission(params: {permissionId: string, permissionType: number}) { export async function updatePermission(params: {permissionId: string, permissionType: number}) {
return request('/api/story/permission', { return request(STORY_API.PERMISSION, {
method: 'PUT', method: 'PUT',
data: params data: params
}); });
} }
export async function removePermission(permissionId: string) { export async function removePermission(permissionId: string) {
return request(`/api/story/permission/${permissionId}`, { return request(`${STORY_API.PERMISSION}/${permissionId}`, {
method: 'DELETE' method: 'DELETE'
}); });
} }
@@ -173,7 +174,7 @@ export async function removePermission(permissionId: string) {
export async function updateStoryItemOrder( export async function updateStoryItemOrder(
orderData: Array<{ instanceId: string; sortOrder: number }> orderData: Array<{ instanceId: string; sortOrder: number }>
): Promise<CommonResponse<void>> { ): Promise<CommonResponse<void>> {
return request('/api/story/item/order', { return request(`${STORY_API.ITEM}/order`, {
method: 'PUT', method: 'PUT',
data: { items: orderData }, data: { items: orderData },
}); });
@@ -195,7 +196,7 @@ export async function updateStoryItemOrder(
export async function batchDeleteStoryItems( export async function batchDeleteStoryItems(
instanceIds: string[] instanceIds: string[]
): Promise<CommonResponse<void>> { ): Promise<CommonResponse<void>> {
return request('/api/story/item/batch-delete', { return request(`${STORY_API.ITEM}/batch-delete`, {
method: 'POST', method: 'POST',
data: { instanceIds }, data: { instanceIds },
}); });
@@ -215,7 +216,7 @@ export async function batchUpdateStoryItemTime(
instanceIds: string[], instanceIds: string[],
storyItemTime: string storyItemTime: string
): Promise<CommonResponse<void>> { ): Promise<CommonResponse<void>> {
return request('/api/story/item/batch-time', { return request(`${STORY_API.ITEM}/batch-time`, {
method: 'PUT', method: 'PUT',
data: { instanceIds, storyItemTime }, data: { instanceIds, storyItemTime },
}); });

View File

@@ -1,5 +1,6 @@
import { getFakeCaptcha } from '@/services/ant-design-pro/login'; import { getFakeCaptcha } from '@/services/ant-design-pro/login';
import { loginUser } from '@/services/user/api'; import { loginUser } from '@/services/user/api';
import type { UserLoginParams, UserLoginResult } from '@/services/user/typing';
import { CommonResponse } from '@/types/common'; import { CommonResponse } from '@/types/common';
import { import {
AlipayCircleOutlined, AlipayCircleOutlined,
@@ -133,9 +134,9 @@ const Login: React.FC = () => {
currentUser: response.data, currentUser: response.data,
})); }));
const urlParams = new URL(window.location.href).searchParams; const urlParams = new URL(window.location.href).searchParams;
// 修复:直接使用 redirect 参数,如果不存在则跳转到首页 // 修复:使用 history.push 进行跳转,避免页面刷新导致状态丢失
const redirect = urlParams.get('redirect'); const redirect = urlParams.get('redirect');
window.location.href = redirect || '/'; history.push(redirect || '/');
return; return;
} }
console.log(response.message); console.log(response.message);

View File

@@ -120,8 +120,13 @@ export const errorConfig: RequestConfig = {
return { ...config, headers }; return { ...config, headers };
} }
// 无 token 提示并引导登录 // 无 token 时,如果是获取当前用户信息的请求,不提示登录(让调用方处理)
if (!token) { if (!token) {
const skipAuthUrls = ['/api/currentUser', '/user-api/info'];
if (config?.url && skipAuthUrls.some((u) => config.url?.includes(u))) {
return config;
}
message.warning('请先登录'); message.warning('请先登录');
const currentUrl = window.location.href; const currentUrl = window.location.href;
const urlParams = new URL(currentUrl).searchParams; const urlParams = new URL(currentUrl).searchParams;

View File

@@ -4,14 +4,13 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { USER_API } from '@/services/config/apiUrls';
const BASE_URL = '/api/v1/albums';
/** /**
* Get all albums for the current user * Get all albums for the current user
*/ */
export async function getAlbums(): Promise<API.Album[]> { export async function getAlbums(): Promise<API.Album[]> {
return request(`${BASE_URL}`, { return request(USER_API.ALBUMS, {
method: 'GET', method: 'GET',
}); });
} }
@@ -20,7 +19,7 @@ export async function getAlbums(): Promise<API.Album[]> {
* Get album details by ID * Get album details by ID
*/ */
export async function getAlbumById(id: string): Promise<API.Album> { export async function getAlbumById(id: string): Promise<API.Album> {
return request(`${BASE_URL}/${id}`, { return request(USER_API.ALBUM_DETAIL(id), {
method: 'GET', method: 'GET',
}); });
} }
@@ -29,7 +28,7 @@ export async function getAlbumById(id: string): Promise<API.Album> {
* Create a new album * Create a new album
*/ */
export async function createAlbum(data: API.CreateAlbumDTO): Promise<API.Album> { export async function createAlbum(data: API.CreateAlbumDTO): Promise<API.Album> {
return request(`${BASE_URL}`, { return request(USER_API.ALBUMS, {
method: 'POST', method: 'POST',
data, data,
}); });
@@ -39,7 +38,7 @@ export async function createAlbum(data: API.CreateAlbumDTO): Promise<API.Album>
* Update an existing album * Update an existing album
*/ */
export async function updateAlbum(id: string, data: API.UpdateAlbumDTO): Promise<API.Album> { export async function updateAlbum(id: string, data: API.UpdateAlbumDTO): Promise<API.Album> {
return request(`${BASE_URL}/${id}`, { return request(USER_API.ALBUM_DETAIL(id), {
method: 'PUT', method: 'PUT',
data, data,
}); });
@@ -49,7 +48,7 @@ export async function updateAlbum(id: string, data: API.UpdateAlbumDTO): Promise
* Delete an album * Delete an album
*/ */
export async function deleteAlbum(id: string): Promise<void> { export async function deleteAlbum(id: string): Promise<void> {
return request(`${BASE_URL}/${id}`, { return request(USER_API.ALBUM_DETAIL(id), {
method: 'DELETE', method: 'DELETE',
}); });
} }
@@ -58,7 +57,7 @@ export async function deleteAlbum(id: string): Promise<void> {
* Add photos to an album * Add photos to an album
*/ */
export async function addPhotosToAlbum(id: string, photoIds: string[]): Promise<void> { export async function addPhotosToAlbum(id: string, photoIds: string[]): Promise<void> {
return request(`${BASE_URL}/${id}/photos`, { return request(USER_API.ALBUM_PHOTOS(id), {
method: 'POST', method: 'POST',
data: { photoIds }, data: { photoIds },
}); });
@@ -68,7 +67,7 @@ export async function addPhotosToAlbum(id: string, photoIds: string[]): Promise<
* Remove photos from an album * Remove photos from an album
*/ */
export async function removePhotosFromAlbum(id: string, photoIds: string[]): Promise<void> { export async function removePhotosFromAlbum(id: string, photoIds: string[]): Promise<void> {
return request(`${BASE_URL}/${id}/photos`, { return request(USER_API.ALBUM_PHOTOS(id), {
method: 'DELETE', method: 'DELETE',
data: { photoIds }, data: { photoIds },
}); });
@@ -78,7 +77,7 @@ export async function removePhotosFromAlbum(id: string, photoIds: string[]): Pro
* Reorder photos in an album * Reorder photos in an album
*/ */
export async function reorderPhotos(id: string, order: string[]): Promise<void> { export async function reorderPhotos(id: string, order: string[]): Promise<void> {
return request(`${BASE_URL}/${id}/photos/order`, { return request(USER_API.ALBUM_PHOTOS_ORDER(id), {
method: 'PUT', method: 'PUT',
data: { order }, data: { order },
}); });
@@ -88,7 +87,7 @@ export async function reorderPhotos(id: string, order: string[]): Promise<void>
* Set album cover photo * Set album cover photo
*/ */
export async function setAlbumCover(id: string, coverPhotoId: string): Promise<void> { export async function setAlbumCover(id: string, coverPhotoId: string): Promise<void> {
return request(`${BASE_URL}/${id}/cover`, { return request(USER_API.ALBUM_COVER(id), {
method: 'PUT', method: 'PUT',
data: { coverPhotoId }, data: { coverPhotoId },
}); });

View File

@@ -4,14 +4,13 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { USER_API } from '@/services/config/apiUrls';
const BASE_URL = '/api/v1/collections';
/** /**
* Get all smart collections for the current user * Get all smart collections for the current user
*/ */
export async function getSmartCollections(): Promise<API.SmartCollection[]> { export async function getSmartCollections(): Promise<API.SmartCollection[]> {
return request(`${BASE_URL}/smart`, { return request(USER_API.COLLECTIONS_SMART, {
method: 'GET', method: 'GET',
}); });
} }
@@ -23,7 +22,7 @@ export async function getCollectionContent(
collectionId: string, collectionId: string,
params: { page: number; pageSize: number }, params: { page: number; pageSize: number },
): Promise<API.CollectionContent> { ): Promise<API.CollectionContent> {
return request(`${BASE_URL}/smart/${collectionId}/content`, { return request(USER_API.COLLECTIONS_SMART_CONTENT(collectionId), {
method: 'GET', method: 'GET',
params, params,
}); });
@@ -33,7 +32,7 @@ export async function getCollectionContent(
* Force refresh smart collections * Force refresh smart collections
*/ */
export async function refreshSmartCollections(): Promise<void> { export async function refreshSmartCollections(): Promise<void> {
return request(`${BASE_URL}/smart/refresh`, { return request(USER_API.COLLECTIONS_SMART_REFRESH, {
method: 'POST', method: 'POST',
}); });
} }

View File

@@ -4,6 +4,7 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { SOCIAL_API } from '@/services/config/apiUrls';
/** /**
* Get comments for an entity * Get comments for an entity
@@ -12,7 +13,7 @@ export async function getComments(
entityType: API.SocialEntityType, entityType: API.SocialEntityType,
entityId: string, entityId: string,
): Promise<API.Comment[]> { ): Promise<API.Comment[]> {
return request(`/api/v1/comments/${entityType}/${entityId}`, { return request(SOCIAL_API.COMMENTS_BY_ENTITY(entityType, entityId), {
method: 'GET', method: 'GET',
}); });
} }
@@ -21,7 +22,7 @@ export async function getComments(
* Create a new comment * Create a new comment
*/ */
export async function createComment(data: API.CreateCommentDTO): Promise<API.Comment> { export async function createComment(data: API.CreateCommentDTO): Promise<API.Comment> {
return request('/api/v1/comments', { return request(SOCIAL_API.COMMENTS, {
method: 'POST', method: 'POST',
data, data,
}); });
@@ -34,7 +35,7 @@ export async function updateComment(
id: string, id: string,
data: API.UpdateCommentDTO, data: API.UpdateCommentDTO,
): Promise<API.Comment> { ): Promise<API.Comment> {
return request(`/api/v1/comments/${id}`, { return request(SOCIAL_API.COMMENTS_DETAIL(id), {
method: 'PUT', method: 'PUT',
data, data,
}); });
@@ -44,7 +45,7 @@ export async function updateComment(
* Delete a comment * Delete a comment
*/ */
export async function deleteComment(id: string): Promise<void> { export async function deleteComment(id: string): Promise<void> {
return request(`/api/v1/comments/${id}`, { return request(SOCIAL_API.COMMENTS_DETAIL(id), {
method: 'DELETE', method: 'DELETE',
}); });
} }

View File

@@ -0,0 +1,131 @@
/**
* API URL 全局配置
* 所有后端接口地址统一在此配置,便于管理和维护
*/
// 基础 API 前缀
const API_PREFIX = '/api';
const USER_API_PREFIX = `${API_PREFIX}/user/api/v1`;
const USER_AUTH_PREFIX = '/user-api';
// Story 相关接口
export const STORY_API = {
BASE: `${API_PREFIX}/story`,
LIST: `${API_PREFIX}/story/list`,
ADD: `${API_PREFIX}/story/add`,
DETAIL: (id: string) => `${API_PREFIX}/story/${id}`,
// Story Item 相关
ITEM: `${API_PREFIX}/story/item`,
ITEM_LIST: `${API_PREFIX}/story/item/list`,
ITEM_DETAIL: (id: string) => `${API_PREFIX}/story/item/${id}`,
ITEM_COUNT: (storyInstanceId: string) => `${API_PREFIX}/story/item/count/${storyInstanceId}`,
ITEM_IMAGES: (itemId: string) => `${API_PREFIX}/story/item/images/${itemId}`,
ITEM_SEARCH: `${API_PREFIX}/story/item/search`,
// 权限相关
PERMISSION: `${API_PREFIX}/story/permission`,
PERMISSION_AUTHORIZE: `${API_PREFIX}/story/permission/authorize`,
PERMISSION_STORY: (storyId: string) => `${API_PREFIX}/story/permission/story/${storyId}`,
PERMISSION_INVITE: `${API_PREFIX}/story/permission/invite`,
PERMISSION_INVITE_ACCEPT: (inviteId: string) => `${API_PREFIX}/story/permission/invite/${inviteId}/accept`,
PERMISSION_INVITE_REJECT: (inviteId: string) => `${API_PREFIX}/story/permission/invite/${inviteId}/reject`,
// 活动相关
ACTIVITY_AUTHORIZED_ITEMS: `${API_PREFIX}/story/activity/authorized-items`,
} as const;
// 文件相关接口
export const FILE_API = {
IMAGE: (imageInstanceId: string) => `${API_PREFIX}/file/image/${imageInstanceId}`,
IMAGE_LOW_RES: (imageInstanceId: string) => `${API_PREFIX}/file/image-low-res/${imageInstanceId}`,
IMAGE_LIST: `${API_PREFIX}/file/image/list`,
VIDEO_URL: (videoInstanceId: string) => `${API_PREFIX}/file/get-video-url/${videoInstanceId}`,
UPLOAD_IMAGE: `${API_PREFIX}/file/upload-image`,
GET_UPLOAD_URL: (fileName: string) => `${API_PREFIX}/file/get-upload-url/${fileName}`,
UPLOADED: `${API_PREFIX}/file/uploaded`,
BATCH_INFO: `${API_PREFIX}/file/batch-info`,
} as const;
// 用户相关接口
export const USER_API = {
PROFILE: `${USER_API_PREFIX}/profile`,
PROFILE_COVER: `${USER_API_PREFIX}/profile/cover`,
PROFILE_CUSTOM_FIELDS: `${USER_API_PREFIX}/profile/custom-fields`,
PREFERENCES: `${USER_API_PREFIX}/preferences`,
PREFERENCES_THEME: `${USER_API_PREFIX}/preferences/theme`,
PREFERENCES_LAYOUT: `${USER_API_PREFIX}/preferences/layout`,
PREFERENCES_TIMELINE: `${USER_API_PREFIX}/preferences/timeline`,
ALBUMS: `${USER_API_PREFIX}/albums`,
ALBUM_DETAIL: (id: string) => `${USER_API_PREFIX}/albums/${id}`,
ALBUM_PHOTOS: (id: string) => `${USER_API_PREFIX}/albums/${id}/photos`,
ALBUM_PHOTOS_ORDER: (id: string) => `${USER_API_PREFIX}/albums/${id}/photos/order`,
ALBUM_COVER: (id: string) => `${USER_API_PREFIX}/albums/${id}/cover`,
COLLECTIONS: `${USER_API_PREFIX}/collections`,
COLLECTIONS_SMART: `${USER_API_PREFIX}/collections/smart`,
COLLECTIONS_SMART_CONTENT: (collectionId: string) => `${USER_API_PREFIX}/collections/smart/${collectionId}/content`,
COLLECTIONS_SMART_REFRESH: `${USER_API_PREFIX}/collections/smart/refresh`,
// Gallery 相关
GALLERY_PHOTOS: `${API_PREFIX}/v1/gallery/photos`,
// 通知相关
NOTIFICATIONS_READ: `${API_PREFIX}/user/notifications/read`,
} as const;
// 用户认证相关接口
export const AUTH_API = {
INFO: `${USER_AUTH_PREFIX}/info`,
REGISTER: `${USER_AUTH_PREFIX}/auth/register`,
LOGIN: `${USER_AUTH_PREFIX}/auth/login`,
LOGOUT: `${USER_AUTH_PREFIX}/auth/logout`,
// 好友相关
FRIEND_SEARCH: `${USER_AUTH_PREFIX}/search`,
FRIEND_REQUEST: `${USER_AUTH_PREFIX}/friend/request`,
FRIEND_LIST: `${USER_AUTH_PREFIX}/friend/list`,
FRIEND_ACCEPT: `${USER_AUTH_PREFIX}/friend/accept`,
FRIEND_REJECT: `${USER_AUTH_PREFIX}/friend/reject`,
MESSAGE_HISTORY: `${USER_AUTH_PREFIX}/message/history/friend`,
} as const;
// 社交功能接口
export const SOCIAL_API = {
COMMENTS: `${API_PREFIX}/v1/comments`,
COMMENTS_DETAIL: (id: string) => `${API_PREFIX}/v1/comments/${id}`,
COMMENTS_BY_ENTITY: (entityType: string, entityId: string) => `${API_PREFIX}/v1/comments/${entityType}/${entityId}`,
REACTIONS: `${API_PREFIX}/v1/reactions`,
REACTIONS_BY_ENTITY: (entityType: string, entityId: string) => `${API_PREFIX}/v1/reactions/${entityType}/${entityId}`,
} as const;
// 同步相关接口
export const SYNC_API = {
BASE: `${API_PREFIX}/v1/sync`,
CHANGES: `${API_PREFIX}/v1/sync/changes`,
STATUS: `${API_PREFIX}/v1/sync/status`,
RESOLVE_CONFLICT: `${API_PREFIX}/v1/sync/resolve-conflict`,
} as const;
// 统计相关接口
export const STATISTICS_API = {
BASE: `${API_PREFIX}/v1/statistics`,
OVERVIEW: `${API_PREFIX}/v1/statistics/overview`,
UPLOADS: `${API_PREFIX}/v1/statistics/uploads`,
STORAGE: `${API_PREFIX}/v1/statistics/storage`,
REFRESH: `${API_PREFIX}/v1/statistics/refresh`,
} as const;
// 地理信息接口
export const GEO_API = {
PROVINCE: `${API_PREFIX}/geographic/province`,
CITY: (province: string) => `${API_PREFIX}/geographic/city/${province}`,
} as const;
// 其他通用接口
export const COMMON_API = {
USERS: `${API_PREFIX}/users`,
} as const;

View File

@@ -1,22 +1,24 @@
import {request} from "@@/exports"; import {request} from "@@/exports";
import {CommonListResponse, CommonResponse} from "@/types/common"; import {CommonListResponse, CommonResponse} from "@/types/common";
import {ImageItem} from "@/pages/gallery/typings"; import {ImageItem} from "@/pages/gallery/typings";
import { STORY_API, FILE_API } from '@/services/config/apiUrls';
// 查询storyItem图片列表 // 查询storyItem图片列表
export async function queryStoryItemImages(itemId: string): Promise<{ data: string[] }> { export async function queryStoryItemImages(itemId: string): Promise<{ data: string[] }> {
return request(`/api/story/item/images/${itemId}`, { return request(STORY_API.ITEM_IMAGES(itemId), {
method: 'GET', method: 'GET',
}); });
} }
// 获取图片 // 获取图片
export async function fetchImage(imageInstanceId: string): Promise<any> { export async function fetchImage(imageInstanceId: string): Promise<any> {
return request(`/file/image/${imageInstanceId}`, { return request(FILE_API.IMAGE(imageInstanceId), {
method: 'GET', method: 'GET',
responseType: 'blob', responseType: 'blob',
getResponse: true, getResponse: true,
}); });
} }
export async function fetchImageLowRes(imageInstanceId: string): Promise<any> { export async function fetchImageLowRes(imageInstanceId: string): Promise<any> {
return request(`/file/image-low-res/${imageInstanceId}`, { return request(FILE_API.IMAGE_LOW_RES(imageInstanceId), {
method: 'GET', method: 'GET',
responseType: 'blob', responseType: 'blob',
getResponse: true, getResponse: true,
@@ -32,7 +34,7 @@ export async function getImagesList(
}, },
options?: { [key: string]: any }, options?: { [key: string]: any },
) { ) {
return request<CommonResponse<CommonListResponse<ImageItem>>>('/file/image/list', { return request<CommonResponse<CommonListResponse<ImageItem>>>(FILE_API.IMAGE_LIST, {
method: 'GET', method: 'GET',
params: { params: {
...params, ...params,
@@ -42,7 +44,7 @@ export async function getImagesList(
} }
export async function deleteImage(params: { instanceId: string }) { export async function deleteImage(params: { instanceId: string }) {
return request<CommonResponse<any>>(`/file/image/${params.instanceId}`, { return request<CommonResponse<any>>(FILE_API.IMAGE(params.instanceId), {
method: 'DELETE', method: 'DELETE',
params: { params: {
...params, ...params,
@@ -50,33 +52,33 @@ export async function deleteImage(params: { instanceId: string }) {
}); });
} }
export async function uploadImage(params: FormData) { export async function uploadImage(params: FormData) {
return request<CommonResponse<any>>('/file/upload-image', { return request<CommonResponse<any>>(FILE_API.UPLOAD_IMAGE, {
method: 'POST', method: 'POST',
data: params, data: params,
}); });
} }
export async function getUploadUrl(fileName: string) { export async function getUploadUrl(fileName: string) {
return request<CommonResponse<string>>(`/file/get-upload-url/${fileName}`, { return request<CommonResponse<string>>(FILE_API.GET_UPLOAD_URL(fileName), {
method: 'GET', method: 'GET',
}); });
} }
export async function getVideoUrl(instanceId: string): Promise<CommonResponse<string>> { export async function getVideoUrl(instanceId: string): Promise<CommonResponse<string>> {
return request<CommonResponse<string>>(`/file/get-video-url/${instanceId}`, { return request<CommonResponse<string>>(FILE_API.VIDEO_URL(instanceId), {
method: 'GET', method: 'GET',
}); });
} }
export async function saveFileMetadata(data: any) { export async function saveFileMetadata(data: any) {
return request<CommonResponse<string>>('/file/uploaded', { return request<CommonResponse<string>>(FILE_API.UPLOADED, {
method: 'POST', method: 'POST',
data, data,
}); });
} }
export async function batchGetFileInfo(instanceIds: string[]): Promise<CommonResponse<any[]>> { export async function batchGetFileInfo(instanceIds: string[]): Promise<CommonResponse<any[]>> {
return request<CommonResponse<any[]>>('/file/batch-info', { return request<CommonResponse<any[]>>(FILE_API.BATCH_INFO, {
method: 'POST', method: 'POST',
data: instanceIds, data: instanceIds,
}); });

View File

@@ -4,14 +4,13 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { USER_API } from '@/services/config/apiUrls';
const BASE_URL = '/api/v1/preferences';
/** /**
* Get user preferences * Get user preferences
*/ */
export async function getPreferences(): Promise<API.UserPreferences> { export async function getPreferences(): Promise<API.UserPreferences> {
return request(`${BASE_URL}`, { return request(USER_API.PREFERENCES, {
method: 'GET', method: 'GET',
}); });
} }
@@ -20,7 +19,7 @@ export async function getPreferences(): Promise<API.UserPreferences> {
* Update theme preferences * Update theme preferences
*/ */
export async function updateThemePreferences(data: API.UpdateThemeDTO): Promise<void> { export async function updateThemePreferences(data: API.UpdateThemeDTO): Promise<void> {
return request(`${BASE_URL}/theme`, { return request(USER_API.PREFERENCES_THEME, {
method: 'PUT', method: 'PUT',
data, data,
}); });
@@ -30,7 +29,7 @@ export async function updateThemePreferences(data: API.UpdateThemeDTO): Promise<
* Update layout preferences * Update layout preferences
*/ */
export async function updateLayoutPreferences(data: API.UpdateLayoutDTO): Promise<void> { export async function updateLayoutPreferences(data: API.UpdateLayoutDTO): Promise<void> {
return request(`${BASE_URL}/layout`, { return request(USER_API.PREFERENCES_LAYOUT, {
method: 'PUT', method: 'PUT',
data, data,
}); });
@@ -40,7 +39,7 @@ export async function updateLayoutPreferences(data: API.UpdateLayoutDTO): Promis
* Update timeline preferences * Update timeline preferences
*/ */
export async function updateTimelinePreferences(data: API.UpdateTimelineDTO): Promise<void> { export async function updateTimelinePreferences(data: API.UpdateTimelineDTO): Promise<void> {
return request(`${BASE_URL}/timeline`, { return request(USER_API.PREFERENCES_TIMELINE, {
method: 'PUT', method: 'PUT',
data, data,
}); });

View File

@@ -4,8 +4,7 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { USER_API } from '@/services/config/apiUrls';
const BASE_URL = '/api/v1/profile';
export interface UserProfile { export interface UserProfile {
userId: string; userId: string;
@@ -32,7 +31,7 @@ export interface ProfileData {
* Get user profile * Get user profile
*/ */
export async function getProfile(): Promise<ProfileData> { export async function getProfile(): Promise<ProfileData> {
return request(`${BASE_URL}`, { return request(USER_API.PROFILE, {
method: 'GET', method: 'GET',
}); });
} }
@@ -41,7 +40,7 @@ export async function getProfile(): Promise<ProfileData> {
* Update profile bio * Update profile bio
*/ */
export async function updateProfile(data: { bio?: string }): Promise<void> { export async function updateProfile(data: { bio?: string }): Promise<void> {
return request(`${BASE_URL}`, { return request(USER_API.PROFILE, {
method: 'PUT', method: 'PUT',
data, data,
}); });
@@ -54,7 +53,7 @@ export async function uploadCoverPhoto(file: File): Promise<string> {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
return request(`${BASE_URL}/cover`, { return request(USER_API.PROFILE_COVER, {
method: 'POST', method: 'POST',
data: formData, data: formData,
}); });
@@ -64,7 +63,7 @@ export async function uploadCoverPhoto(file: File): Promise<string> {
* Update custom fields * Update custom fields
*/ */
export async function updateCustomFields(fields: CustomField[]): Promise<void> { export async function updateCustomFields(fields: CustomField[]): Promise<void> {
return request(`${BASE_URL}/custom-fields`, { return request(USER_API.PROFILE_CUSTOM_FIELDS, {
method: 'PUT', method: 'PUT',
data: { fields }, data: { fields },
}); });

View File

@@ -4,8 +4,7 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { SOCIAL_API } from '@/services/config/apiUrls';
const BASE_URL = '/api/v1/reactions';
/** /**
* Get reactions for an entity * Get reactions for an entity
@@ -14,7 +13,7 @@ export async function getReactions(
entityType: API.SocialEntityType, entityType: API.SocialEntityType,
entityId: string, entityId: string,
): Promise<API.ReactionSummary> { ): Promise<API.ReactionSummary> {
return request(`${BASE_URL}/${entityType}/${entityId}`, { return request(SOCIAL_API.REACTIONS_BY_ENTITY(entityType, entityId), {
method: 'GET', method: 'GET',
}); });
} }
@@ -23,7 +22,7 @@ export async function getReactions(
* Add or update a reaction * Add or update a reaction
*/ */
export async function addReaction(data: API.AddReactionDTO): Promise<void> { export async function addReaction(data: API.AddReactionDTO): Promise<void> {
return request(`${BASE_URL}`, { return request(SOCIAL_API.REACTIONS, {
method: 'POST', method: 'POST',
data, data,
}); });
@@ -36,7 +35,7 @@ export async function removeReaction(
entityType: API.SocialEntityType, entityType: API.SocialEntityType,
entityId: string, entityId: string,
): Promise<void> { ): Promise<void> {
return request(`${BASE_URL}/${entityType}/${entityId}`, { return request(SOCIAL_API.REACTIONS_BY_ENTITY(entityType, entityId), {
method: 'DELETE', method: 'DELETE',
}); });
} }

View File

@@ -4,14 +4,13 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { STATISTICS_API } from '@/services/config/apiUrls';
const BASE_URL = '/api/v1/statistics';
/** /**
* Get user statistics overview * Get user statistics overview
*/ */
export async function getStatisticsOverview(): Promise<API.UserStatistics> { export async function getStatisticsOverview(): Promise<API.UserStatistics> {
return request(`${BASE_URL}/overview`, { return request(STATISTICS_API.OVERVIEW, {
method: 'GET', method: 'GET',
}); });
} }
@@ -20,7 +19,7 @@ export async function getStatisticsOverview(): Promise<API.UserStatistics> {
* Get upload trends * Get upload trends
*/ */
export async function getUploadTrends(): Promise<API.UploadTrend[]> { export async function getUploadTrends(): Promise<API.UploadTrend[]> {
return request(`${BASE_URL}/uploads`, { return request(STATISTICS_API.UPLOADS, {
method: 'GET', method: 'GET',
}); });
} }
@@ -29,7 +28,7 @@ export async function getUploadTrends(): Promise<API.UploadTrend[]> {
* Get storage breakdown * Get storage breakdown
*/ */
export async function getStorageBreakdown(): Promise<API.StorageBreakdown> { export async function getStorageBreakdown(): Promise<API.StorageBreakdown> {
return request(`${BASE_URL}/storage`, { return request(STATISTICS_API.STORAGE, {
method: 'GET', method: 'GET',
}); });
} }
@@ -38,7 +37,7 @@ export async function getStorageBreakdown(): Promise<API.StorageBreakdown> {
* Force refresh statistics * Force refresh statistics
*/ */
export async function refreshStatistics(): Promise<void> { export async function refreshStatistics(): Promise<void> {
return request(`${BASE_URL}/refresh`, { return request(STATISTICS_API.REFRESH, {
method: 'POST', method: 'POST',
}); });
} }

View File

@@ -4,14 +4,13 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { SYNC_API } from '@/services/config/apiUrls';
const BASE_URL = '/api/v1/sync';
/** /**
* Upload offline changes batch * Upload offline changes batch
*/ */
export async function uploadChanges(changes: API.ChangeRecord[]): Promise<API.SyncResult> { export async function uploadChanges(changes: API.ChangeRecord[]): Promise<API.SyncResult> {
return request(`${BASE_URL}/changes`, { return request(SYNC_API.CHANGES, {
method: 'POST', method: 'POST',
data: { changes }, data: { changes },
}); });
@@ -21,7 +20,7 @@ export async function uploadChanges(changes: API.ChangeRecord[]): Promise<API.Sy
* Get sync status * Get sync status
*/ */
export async function getSyncStatus(): Promise<{ status: API.SyncStatus; pendingCount: number }> { export async function getSyncStatus(): Promise<{ status: API.SyncStatus; pendingCount: number }> {
return request(`${BASE_URL}/status`, { return request(SYNC_API.STATUS, {
method: 'GET', method: 'GET',
}); });
} }
@@ -30,7 +29,7 @@ export async function getSyncStatus(): Promise<{ status: API.SyncStatus; pending
* Resolve sync conflict * Resolve sync conflict
*/ */
export async function resolveConflict(resolution: API.Resolution): Promise<void> { export async function resolveConflict(resolution: API.Resolution): Promise<void> {
return request(`${BASE_URL}/resolve-conflict`, { return request(SYNC_API.RESOLVE_CONFLICT, {
method: 'POST', method: 'POST',
data: resolution, data: resolution,
}); });

View File

@@ -1,23 +1,24 @@
import { CommonResponse } from "@/types/common"; import { CommonResponse } from "@/types/common";
import {request} from "@@/exports"; import {request} from "@@/exports";
import { AUTH_API } from '@/services/config/apiUrls';
export async function registerUser(params: UserRegisterParams, options?: { skipErrorHandler?: boolean }): Promise<CommonResponse<string>> { export async function registerUser(params: UserRegisterParams, options?: { skipErrorHandler?: boolean }): Promise<CommonResponse<string>> {
return request('/user-api/auth/register', { return request(AUTH_API.REGISTER, {
method: 'POST', method: 'POST',
data: params, data: params,
getResponse: true, getResponse: true,
}); });
} }
export async function loginUser(params: UserLoginParams): Promise<CommonResponse<UserLoginResult>> { export async function loginUser(params: UserLoginParams): Promise<CommonResponse<UserLoginResult>> {
return request('/user-api/auth/login', { return request(AUTH_API.LOGIN, {
method: 'POST', method: 'POST',
data: params, data: params,
// getResponse: true, // getResponse: true,
}); });
} }
export async function logoutUser() { export async function logoutUser() {
return request('/user-api/auth/logout', { return request(AUTH_API.LOGOUT, {
method: 'POST', method: 'POST',
}); });
} }

View File

@@ -1,5 +1,5 @@
// 注册用户参数 // 注册用户参数
interface UserRegisterParams { export interface UserRegisterParams {
username: string; username: string;
nickname: string; nickname: string;
password: string; password: string;
@@ -7,25 +7,25 @@ interface UserRegisterParams {
phone: string; phone: string;
} }
// 登录用户参数 // 登录用户参数
interface UserLoginParams { export interface UserLoginParams {
username?: string; username?: string;
password?: string; password?: string;
loginType?: string; loginType?: string;
} }
// 退出登录参数 // 退出登录参数
interface UserLogoutParams { export interface UserLogoutParams {
username: string; username: string;
password: string; password: string;
} }
// 登录返回参数 // 登录返回参数
interface UserLoginResult { export interface UserLoginResult {
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
accessTokenExpiresInSeconds: number; accessTokenExpiresInSeconds: number;
refreshTokenExpiresInSeconds: number; refreshTokenExpiresInSeconds: number;
} }
// 用户信息 // 用户信息
interface UserInfo { export interface UserInfo {
username: string, username: string,
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;

26
src/typings.d.ts vendored
View File

@@ -19,17 +19,17 @@ declare module 'react-masonry-css';
declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false; declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
// Extend Namespaces type to include 'theme' // Extend Namespaces type to include 'theme'
declare module '@umijs/max' { // declare module '@umijs/max' {
export type Namespaces = // export type Namespaces =
| 'menu' // | 'menu'
| 'settings' // | 'settings'
| 'pages' // | 'pages'
| 'component' // | 'component'
| 'globalHeader' // | 'globalHeader'
| 'theme' // | 'theme'
| 'sync' // | 'sync'
| 'albums' // | 'albums'
| 'collections' // | 'collections'
| 'layout'; // | 'layout';
} // }

View File

@@ -20,8 +20,8 @@ export class OfflineDatabase extends Dexie {
super('TimelineOffline'); super('TimelineOffline');
// Define database schema // Define database schema
// Version 1: Initial schema // Version 2: Added synced index to changes table
this.version(1).stores({ this.version(2).stores({
// Stories table with indexes // Stories table with indexes
// Primary key: id // Primary key: id
// Indexes: userId, createdAt, syncStatus for efficient querying // Indexes: userId, createdAt, syncStatus for efficient querying
@@ -34,9 +34,8 @@ export class OfflineDatabase extends Dexie {
// Changes table with auto-incrementing primary key // Changes table with auto-incrementing primary key
// Primary key: ++id (auto-increment) // Primary key: ++id (auto-increment)
// Indexes: entityType, entityId, operation, timestamp // Indexes: entityType, entityId, operation, timestamp, synced
// Note: synced is stored as 0/1 for IndexedDB compatibility but not indexed changes: '++id, entityType, entityId, operation, timestamp, synced',
changes: '++id, entityType, entityId, operation, timestamp',
}); });
// Map tables to TypeScript types // Map tables to TypeScript types

View File

@@ -18,19 +18,25 @@ import { message } from 'antd';
export class SyncManager { export class SyncManager {
private queue: API.ChangeRecord[] = []; private queue: API.ChangeRecord[] = [];
private syncing: boolean = false; private syncing: boolean = false;
private online: boolean = navigator.onLine; private online: boolean = typeof window !== 'undefined' ? navigator.onLine : true;
private syncListeners: Array<(status: API.SyncStatus) => void> = []; private syncListeners: Array<(status: API.SyncStatus) => void> = [];
private autoSyncEnabled: boolean = true; private autoSyncEnabled: boolean = true;
private initialized: boolean = false;
constructor() { constructor() {
// Defer initialization to avoid SSR issues
if (typeof window !== 'undefined') {
this.initializeEventListeners(); this.initializeEventListeners();
this.loadQueueFromDB(); this.loadQueueFromDB();
this.initialized = true;
}
} }
/** /**
* Initialize online/offline event listeners * Initialize online/offline event listeners
*/ */
private initializeEventListeners(): void { private initializeEventListeners(): void {
if (typeof window === 'undefined') return;
window.addEventListener('online', () => this.onOnline()); window.addEventListener('online', () => this.onOnline());
window.addEventListener('offline', () => this.onOffline()); window.addEventListener('offline', () => this.onOffline());
} }