添加基础项目结构和路由配置
- 添加 main.tsx 应用入口 - 添加 App.tsx 根组件 - 配置路由系统(React Router v7) - 添加状态管理(Zustand) - 定义 TypeScript 类型
This commit is contained in:
parent
7d32dee944
commit
13e2bc9637
16
src/App.tsx
Normal file
16
src/App.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { RouterProvider } from 'react-router-dom';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
import zhCN from 'antd/locale/zh_CN';
|
||||||
|
import { router } from './routes';
|
||||||
|
import { theme } from './theme';
|
||||||
|
import './global.css';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<ConfigProvider theme={theme} locale={zhCN}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
9
src/main.tsx
Normal file
9
src/main.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
55
src/routes/index.tsx
Normal file
55
src/routes/index.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { createBrowserRouter, Navigate } from 'react-router-dom';
|
||||||
|
import { MainLayout } from '../layouts/MainLayout';
|
||||||
|
import { Dashboard } from '../pages/Dashboard';
|
||||||
|
import { Upload } from '../pages/Upload';
|
||||||
|
import { Gallery } from '../pages/Gallery';
|
||||||
|
import { Links } from '../pages/Links';
|
||||||
|
import { Tools } from '../pages/Tools';
|
||||||
|
import { Storage } from '../pages/Storage';
|
||||||
|
import { Analytics } from '../pages/Analytics';
|
||||||
|
import { Settings } from '../pages/Settings';
|
||||||
|
|
||||||
|
export const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <MainLayout />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
element: <Navigate to="/dashboard" replace />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dashboard',
|
||||||
|
element: <Dashboard />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'upload',
|
||||||
|
element: <Upload />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'gallery',
|
||||||
|
element: <Gallery />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'links',
|
||||||
|
element: <Links />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tools',
|
||||||
|
element: <Tools />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'storage',
|
||||||
|
element: <Storage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'analytics',
|
||||||
|
element: <Analytics />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'settings',
|
||||||
|
element: <Settings />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
93
src/stores/useGalleryStore.ts
Normal file
93
src/stores/useGalleryStore.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import type { ImageItem, Album, GalleryFilters } from '../types';
|
||||||
|
|
||||||
|
interface GalleryState {
|
||||||
|
images: ImageItem[];
|
||||||
|
albums: Album[];
|
||||||
|
selectedImages: string[];
|
||||||
|
viewMode: 'grid' | 'list';
|
||||||
|
filters: GalleryFilters;
|
||||||
|
loading: boolean;
|
||||||
|
|
||||||
|
setImages: (images: ImageItem[]) => void;
|
||||||
|
setAlbums: (albums: Album[]) => void;
|
||||||
|
addImage: (image: ImageItem) => void;
|
||||||
|
deleteImages: (ids: string[]) => void;
|
||||||
|
toggleImageSelection: (id: string) => void;
|
||||||
|
clearSelection: () => void;
|
||||||
|
selectAll: () => void;
|
||||||
|
setViewMode: (mode: 'grid' | 'list') => void;
|
||||||
|
updateFilters: (filters: Partial<GalleryFilters>) => void;
|
||||||
|
resetFilters: () => void;
|
||||||
|
moveToAlbum: (imageIds: string[], albumId: string) => void;
|
||||||
|
toggleFavorite: (imageId: string) => void;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFilters: GalleryFilters = {
|
||||||
|
sortBy: 'date',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGalleryStore = create<GalleryState>((set) => ({
|
||||||
|
images: [],
|
||||||
|
albums: [],
|
||||||
|
selectedImages: [],
|
||||||
|
viewMode: 'grid',
|
||||||
|
filters: defaultFilters,
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
setImages: (images) => set({ images }),
|
||||||
|
|
||||||
|
setAlbums: (albums) => set({ albums }),
|
||||||
|
|
||||||
|
addImage: (image) =>
|
||||||
|
set((state) => ({
|
||||||
|
images: [image, ...state.images],
|
||||||
|
})),
|
||||||
|
|
||||||
|
deleteImages: (ids) =>
|
||||||
|
set((state) => ({
|
||||||
|
images: state.images.filter((img) => !ids.includes(img.id)),
|
||||||
|
selectedImages: state.selectedImages.filter((id) => !ids.includes(id)),
|
||||||
|
})),
|
||||||
|
|
||||||
|
toggleImageSelection: (id) =>
|
||||||
|
set((state) => ({
|
||||||
|
selectedImages: state.selectedImages.includes(id)
|
||||||
|
? state.selectedImages.filter((imgId) => imgId !== id)
|
||||||
|
: [...state.selectedImages, id],
|
||||||
|
})),
|
||||||
|
|
||||||
|
clearSelection: () => set({ selectedImages: [] }),
|
||||||
|
|
||||||
|
selectAll: () =>
|
||||||
|
set((state) => ({
|
||||||
|
selectedImages: state.images.map((img) => img.id),
|
||||||
|
})),
|
||||||
|
|
||||||
|
setViewMode: (mode) => set({ viewMode: mode }),
|
||||||
|
|
||||||
|
updateFilters: (filters) =>
|
||||||
|
set((state) => ({
|
||||||
|
filters: { ...state.filters, ...filters },
|
||||||
|
})),
|
||||||
|
|
||||||
|
resetFilters: () => set({ filters: defaultFilters }),
|
||||||
|
|
||||||
|
moveToAlbum: (imageIds, albumId) =>
|
||||||
|
set((state) => ({
|
||||||
|
images: state.images.map((img) =>
|
||||||
|
imageIds.includes(img.id) ? { ...img, albumId } : img
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
|
||||||
|
toggleFavorite: (imageId) =>
|
||||||
|
set((state) => ({
|
||||||
|
images: state.images.map((img) =>
|
||||||
|
img.id === imageId ? { ...img, isFavorite: !img.isFavorite } : img
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
|
||||||
|
setLoading: (loading) => set({ loading }),
|
||||||
|
}));
|
||||||
34
src/stores/useSettingsStore.ts
Normal file
34
src/stores/useSettingsStore.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
import type { UserSettings } from '../types';
|
||||||
|
|
||||||
|
interface SettingsState extends UserSettings {
|
||||||
|
updateSettings: (settings: Partial<UserSettings>) => void;
|
||||||
|
resetSettings: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSettings: UserSettings = {
|
||||||
|
theme: 'light',
|
||||||
|
language: 'zh-CN',
|
||||||
|
autoCompress: true,
|
||||||
|
uploadQuality: 80,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSettingsStore = create<SettingsState>()(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
...defaultSettings,
|
||||||
|
|
||||||
|
updateSettings: (settings) =>
|
||||||
|
set((state) => ({
|
||||||
|
...state,
|
||||||
|
...settings,
|
||||||
|
})),
|
||||||
|
|
||||||
|
resetSettings: () => set(defaultSettings),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'picstack-settings',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
107
src/stores/useStorageStore.ts
Normal file
107
src/stores/useStorageStore.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
import type { StorageSource, StorageStats } from '../types';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
interface StorageState {
|
||||||
|
sources: StorageSource[];
|
||||||
|
activeSourceId: string | null;
|
||||||
|
storageStats: StorageStats | null;
|
||||||
|
|
||||||
|
addSource: (source: Omit<StorageSource, 'id' | 'createdAt' | 'status'>) => void;
|
||||||
|
removeSource: (id: string) => void;
|
||||||
|
updateSource: (id: string, updates: Partial<StorageSource>) => void;
|
||||||
|
setActiveSource: (id: string) => void;
|
||||||
|
updateStats: (stats: StorageStats) => void;
|
||||||
|
testConnection: (id: string) => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStorageStore = create<StorageState>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
id: 'local-default',
|
||||||
|
name: '本地存储',
|
||||||
|
type: 'local',
|
||||||
|
config: {
|
||||||
|
basePath: '/uploads',
|
||||||
|
},
|
||||||
|
isActive: true,
|
||||||
|
status: 'connected',
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeSourceId: 'local-default',
|
||||||
|
storageStats: {
|
||||||
|
totalSpace: 1024 * 1024 * 1024 * 100, // 100GB
|
||||||
|
usedSpace: 1024 * 1024 * 1024 * 5, // 5GB
|
||||||
|
fileCount: 0,
|
||||||
|
traffic: {
|
||||||
|
upload: 0,
|
||||||
|
download: 0,
|
||||||
|
},
|
||||||
|
cost: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
addSource: (source) => {
|
||||||
|
const newSource: StorageSource = {
|
||||||
|
...source,
|
||||||
|
id: nanoid(),
|
||||||
|
status: 'disconnected',
|
||||||
|
createdAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
sources: [...state.sources, newSource],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
removeSource: (id) =>
|
||||||
|
set((state) => ({
|
||||||
|
sources: state.sources.filter((s) => s.id !== id),
|
||||||
|
activeSourceId: state.activeSourceId === id ? null : state.activeSourceId,
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateSource: (id, updates) =>
|
||||||
|
set((state) => ({
|
||||||
|
sources: state.sources.map((s) => (s.id === id ? { ...s, ...updates } : s)),
|
||||||
|
})),
|
||||||
|
|
||||||
|
setActiveSource: (id) =>
|
||||||
|
set((state) => ({
|
||||||
|
sources: state.sources.map((s) => ({
|
||||||
|
...s,
|
||||||
|
isActive: s.id === id,
|
||||||
|
})),
|
||||||
|
activeSourceId: id,
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateStats: (stats) => set({ storageStats: stats }),
|
||||||
|
|
||||||
|
testConnection: async (id) => {
|
||||||
|
const source = get().sources.find((s) => s.id === id);
|
||||||
|
if (!source) return false;
|
||||||
|
|
||||||
|
// 这里应该实现实际的连接测试逻辑
|
||||||
|
// 暂时返回模拟结果
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
sources: state.sources.map((s) =>
|
||||||
|
s.id === id ? { ...s, status: 'connected' as const } : s
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'picstack-storage',
|
||||||
|
partialize: (state) => ({
|
||||||
|
sources: state.sources,
|
||||||
|
activeSourceId: state.activeSourceId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
116
src/stores/useUploadStore.ts
Normal file
116
src/stores/useUploadStore.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import type { UploadTask } from '../types';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
interface UploadState {
|
||||||
|
uploadQueue: UploadTask[];
|
||||||
|
currentUploading: string[];
|
||||||
|
completedFiles: UploadTask[];
|
||||||
|
|
||||||
|
addToQueue: (files: File[]) => void;
|
||||||
|
startUpload: (taskId: string) => Promise<void>;
|
||||||
|
updateProgress: (taskId: string, progress: number) => void;
|
||||||
|
completeUpload: (taskId: string, uploadedUrl: string, thumbnail: string) => void;
|
||||||
|
failUpload: (taskId: string, error: string) => void;
|
||||||
|
pauseUpload: (taskId: string) => void;
|
||||||
|
cancelUpload: (taskId: string) => void;
|
||||||
|
clearCompleted: () => void;
|
||||||
|
removeFromQueue: (taskId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUploadStore = create<UploadState>((set) => ({
|
||||||
|
uploadQueue: [],
|
||||||
|
currentUploading: [],
|
||||||
|
completedFiles: [],
|
||||||
|
|
||||||
|
addToQueue: (files: File[]) => {
|
||||||
|
const newTasks: UploadTask[] = files.map((file) => ({
|
||||||
|
id: nanoid(),
|
||||||
|
file,
|
||||||
|
status: 'pending',
|
||||||
|
progress: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
uploadQueue: [...state.uploadQueue, ...newTasks],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
startUpload: async (taskId: string) => {
|
||||||
|
set((state) => ({
|
||||||
|
currentUploading: [...state.currentUploading, taskId],
|
||||||
|
uploadQueue: state.uploadQueue.map((task) =>
|
||||||
|
task.id === taskId ? { ...task, status: 'uploading' as const } : task
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 这里应该调用实际的上传服务
|
||||||
|
// 暂时用模拟数据
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateProgress: (taskId: string, progress: number) => {
|
||||||
|
set((state) => ({
|
||||||
|
uploadQueue: state.uploadQueue.map((task) =>
|
||||||
|
task.id === taskId ? { ...task, progress } : task
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
completeUpload: (taskId: string, uploadedUrl: string, thumbnail: string) => {
|
||||||
|
set((state) => {
|
||||||
|
const completedTask = state.uploadQueue.find((task) => task.id === taskId);
|
||||||
|
if (!completedTask) return state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
uploadQueue: state.uploadQueue.filter((task) => task.id !== taskId),
|
||||||
|
currentUploading: state.currentUploading.filter((id) => id !== taskId),
|
||||||
|
completedFiles: [
|
||||||
|
...state.completedFiles,
|
||||||
|
{
|
||||||
|
...completedTask,
|
||||||
|
status: 'success' as const,
|
||||||
|
progress: 100,
|
||||||
|
uploadedUrl,
|
||||||
|
thumbnail,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
failUpload: (taskId: string, error: string) => {
|
||||||
|
set((state) => ({
|
||||||
|
uploadQueue: state.uploadQueue.map((task) =>
|
||||||
|
task.id === taskId ? { ...task, status: 'error' as const, error } : task
|
||||||
|
),
|
||||||
|
currentUploading: state.currentUploading.filter((id) => id !== taskId),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
pauseUpload: (taskId: string) => {
|
||||||
|
set((state) => ({
|
||||||
|
uploadQueue: state.uploadQueue.map((task) =>
|
||||||
|
task.id === taskId ? { ...task, status: 'paused' as const } : task
|
||||||
|
),
|
||||||
|
currentUploading: state.currentUploading.filter((id) => id !== taskId),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelUpload: (taskId: string) => {
|
||||||
|
set((state) => ({
|
||||||
|
uploadQueue: state.uploadQueue.filter((task) => task.id !== taskId),
|
||||||
|
currentUploading: state.currentUploading.filter((id) => id !== taskId),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
clearCompleted: () => {
|
||||||
|
set({ completedFiles: [] });
|
||||||
|
},
|
||||||
|
|
||||||
|
removeFromQueue: (taskId: string) => {
|
||||||
|
set((state) => ({
|
||||||
|
uploadQueue: state.uploadQueue.filter((task) => task.id !== taskId),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}));
|
||||||
142
src/types/index.ts
Normal file
142
src/types/index.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// 图片相关类型
|
||||||
|
export interface ImageItem {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
thumbnail: string;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
format: string;
|
||||||
|
uploadedAt: Date;
|
||||||
|
albumId?: string;
|
||||||
|
tags: string[];
|
||||||
|
isFavorite: boolean;
|
||||||
|
storageSource: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相册类型
|
||||||
|
export interface Album {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
coverImage?: string;
|
||||||
|
imageCount: number;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传任务类型
|
||||||
|
export interface UploadTask {
|
||||||
|
id: string;
|
||||||
|
file: File;
|
||||||
|
status: 'pending' | 'uploading' | 'success' | 'error' | 'paused';
|
||||||
|
progress: number;
|
||||||
|
error?: string;
|
||||||
|
uploadedUrl?: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储源配置类型
|
||||||
|
export interface StorageSource {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: 'local' | 'minio' | 'aliyun-oss' | 'tencent-cos';
|
||||||
|
config: StorageConfig;
|
||||||
|
isActive: boolean;
|
||||||
|
status: 'connected' | 'disconnected' | 'error';
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageConfig {
|
||||||
|
endpoint?: string;
|
||||||
|
accessKeyId?: string;
|
||||||
|
accessKeySecret?: string;
|
||||||
|
bucket?: string;
|
||||||
|
region?: string;
|
||||||
|
basePath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储统计类型
|
||||||
|
export interface StorageStats {
|
||||||
|
totalSpace: number;
|
||||||
|
usedSpace: number;
|
||||||
|
fileCount: number;
|
||||||
|
traffic: {
|
||||||
|
upload: number;
|
||||||
|
download: number;
|
||||||
|
};
|
||||||
|
cost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链接生成配置
|
||||||
|
export interface LinkConfig {
|
||||||
|
format: 'markdown' | 'html' | 'url' | 'custom';
|
||||||
|
customTemplate?: string;
|
||||||
|
domain?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片处理配置
|
||||||
|
export interface ImageProcessConfig {
|
||||||
|
compress?: {
|
||||||
|
quality: number;
|
||||||
|
maxWidth?: number;
|
||||||
|
maxHeight?: number;
|
||||||
|
};
|
||||||
|
watermark?: {
|
||||||
|
type: 'text' | 'image';
|
||||||
|
content: string;
|
||||||
|
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center';
|
||||||
|
opacity: number;
|
||||||
|
fontSize?: number;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
resize?: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
keepAspectRatio: boolean;
|
||||||
|
};
|
||||||
|
convert?: {
|
||||||
|
format: 'jpg' | 'png' | 'webp' | 'avif';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户设置类型
|
||||||
|
export interface UserSettings {
|
||||||
|
theme: 'light' | 'dark';
|
||||||
|
language: 'zh-CN' | 'en-US';
|
||||||
|
autoCompress: boolean;
|
||||||
|
defaultWatermark?: ImageProcessConfig['watermark'];
|
||||||
|
defaultAlbum?: string;
|
||||||
|
uploadQuality: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选器类型
|
||||||
|
export interface GalleryFilters {
|
||||||
|
search?: string;
|
||||||
|
albumId?: string;
|
||||||
|
tags?: string[];
|
||||||
|
dateRange?: [Date, Date];
|
||||||
|
format?: string[];
|
||||||
|
sortBy: 'date' | 'name' | 'size';
|
||||||
|
sortOrder: 'asc' | 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分析数据类型
|
||||||
|
export interface AnalyticsData {
|
||||||
|
uploadTrend: {
|
||||||
|
date: string;
|
||||||
|
count: number;
|
||||||
|
size: number;
|
||||||
|
}[];
|
||||||
|
trafficTrend: {
|
||||||
|
date: string;
|
||||||
|
upload: number;
|
||||||
|
download: number;
|
||||||
|
}[];
|
||||||
|
storageDistribution: {
|
||||||
|
type: string;
|
||||||
|
value: number;
|
||||||
|
}[];
|
||||||
|
popularImages: ImageItem[];
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user