2026-06-24 16:50:04 +08:00
|
|
|
|
import { useAuth } from '../composables/useAuth'
|
|
|
|
|
|
|
2026-01-20 10:36:27 +08:00
|
|
|
|
export const API_BASE = '/api'
|
2026-01-15 13:51:44 +08:00
|
|
|
|
|
2026-06-24 16:50:04 +08:00
|
|
|
|
export interface ApiResponse<T = unknown> {
|
|
|
|
|
|
code: number
|
|
|
|
|
|
message: string
|
|
|
|
|
|
result: T
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ParseApiOptions {
|
|
|
|
|
|
skipSessionExpired?: boolean
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function parseApiResponse<T>(response: Response, options?: ParseApiOptions): Promise<T> {
|
|
|
|
|
|
let data: ApiResponse<T>
|
|
|
|
|
|
try {
|
|
|
|
|
|
data = await response.json()
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
throw new Error('响应解析失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (data.code === 401 && !options?.skipSessionExpired) {
|
|
|
|
|
|
const { handleSessionExpired } = useAuth()
|
|
|
|
|
|
handleSessionExpired()
|
|
|
|
|
|
throw new Error(data.message || '登录已过期,请重新登录')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (data.code !== 200) {
|
|
|
|
|
|
throw new Error(data.message || '请求失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return data.result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 带认证的 fetch,仅附加 Authorization,业务状态由 parseApiResponse 解析
|
|
|
|
|
|
export async function authFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {
|
|
|
|
|
|
const { getToken } = useAuth()
|
|
|
|
|
|
const headers = new Headers(init.headers)
|
|
|
|
|
|
|
|
|
|
|
|
const token = getToken()
|
|
|
|
|
|
if (token && !headers.has('Authorization')) {
|
|
|
|
|
|
headers.set('Authorization', `Bearer ${token}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return fetch(input, { ...init, headers })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function authFetchJson<T>(
|
|
|
|
|
|
input: RequestInfo | URL,
|
|
|
|
|
|
init: RequestInit = {},
|
|
|
|
|
|
options?: ParseApiOptions
|
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
|
const response = await authFetch(input, init)
|
|
|
|
|
|
return parseApiResponse<T>(response, options)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function fetchJson<T>(
|
|
|
|
|
|
input: RequestInfo | URL,
|
|
|
|
|
|
init: RequestInit = {},
|
|
|
|
|
|
options?: ParseApiOptions
|
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
|
const response = await fetch(input, init)
|
|
|
|
|
|
return parseApiResponse<T>(response, options)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
// 通用请求头配置
|
|
|
|
|
|
export const getAuthHeaders = () => {
|
|
|
|
|
|
const token = localStorage.getItem('token')
|
|
|
|
|
|
return {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 认证相关类型
|
|
|
|
|
|
export interface User {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
username: string
|
|
|
|
|
|
email: string
|
|
|
|
|
|
role: string
|
|
|
|
|
|
isActive: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoginRequest {
|
|
|
|
|
|
username: string
|
|
|
|
|
|
password: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface LoginResponse {
|
|
|
|
|
|
token: string
|
|
|
|
|
|
user: User
|
|
|
|
|
|
expire: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 角色相关类型
|
|
|
|
|
|
export interface Role {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 作品相关类型
|
|
|
|
|
|
export interface Work {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
title: string
|
|
|
|
|
|
category: string
|
|
|
|
|
|
year: string
|
|
|
|
|
|
heroImg: string
|
|
|
|
|
|
desc: string
|
|
|
|
|
|
techStack: { category: string; items: string[] }[]
|
|
|
|
|
|
gallery: string[]
|
2026-01-19 21:19:35 +08:00
|
|
|
|
links: { live?: string; github?: string; demo?: string }
|
2026-01-15 13:51:44 +08:00
|
|
|
|
next: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 17:03:34 +08:00
|
|
|
|
// 分类相关类型
|
|
|
|
|
|
export interface Category {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
slug: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
sortOrder: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 专栏相关类型
|
|
|
|
|
|
export interface Column {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
cover: string
|
|
|
|
|
|
isActive: number
|
|
|
|
|
|
sortOrder: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标签相关类型
|
|
|
|
|
|
export interface Tag {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
slug: string
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
// 文章相关类型
|
|
|
|
|
|
export interface Post {
|
2026-01-15 17:03:59 +08:00
|
|
|
|
id: number
|
2026-01-15 13:51:44 +08:00
|
|
|
|
title: string
|
2026-01-16 17:03:34 +08:00
|
|
|
|
categoryId: number
|
|
|
|
|
|
categoryName?: string // Display name
|
|
|
|
|
|
categorySlug?: string
|
|
|
|
|
|
category?: Category // Optional full object
|
2026-01-19 16:14:08 +08:00
|
|
|
|
columnId?: number | null
|
|
|
|
|
|
columnName?: string
|
2026-01-16 17:03:34 +08:00
|
|
|
|
tags?: Tag[]
|
2026-01-15 13:51:44 +08:00
|
|
|
|
date: string
|
|
|
|
|
|
excerpt?: string
|
|
|
|
|
|
content?: string
|
|
|
|
|
|
isPublished?: number
|
2026-01-16 17:03:34 +08:00
|
|
|
|
readCount?: number
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PostHistory {
|
|
|
|
|
|
id: number
|
2026-01-15 17:03:59 +08:00
|
|
|
|
postId: number
|
2026-01-15 13:51:44 +08:00
|
|
|
|
version: number
|
|
|
|
|
|
title: string
|
2026-01-16 17:03:34 +08:00
|
|
|
|
categoryId: number
|
2026-06-24 16:50:04 +08:00
|
|
|
|
columnId?: number
|
|
|
|
|
|
tagIds?: number[]
|
2026-01-15 13:51:44 +08:00
|
|
|
|
excerpt?: string
|
2026-06-24 16:50:04 +08:00
|
|
|
|
content?: string
|
2026-01-15 13:51:44 +08:00
|
|
|
|
isPublished: number
|
|
|
|
|
|
modifiedBy: number
|
|
|
|
|
|
modifiedAt: string
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 16:50:04 +08:00
|
|
|
|
export interface PostHistoryFieldDiff {
|
|
|
|
|
|
from: string
|
|
|
|
|
|
to: string
|
|
|
|
|
|
changed: boolean
|
|
|
|
|
|
diff?: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PostHistoryDiff {
|
|
|
|
|
|
fromVersion: number
|
|
|
|
|
|
toVersion: number
|
|
|
|
|
|
fields: Record<string, PostHistoryFieldDiff>
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HotSearchKeyword {
|
|
|
|
|
|
keyword: string
|
|
|
|
|
|
count: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
// 代码片段相关类型
|
|
|
|
|
|
export interface Snippet {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
title: string
|
|
|
|
|
|
code: string
|
|
|
|
|
|
type: string
|
|
|
|
|
|
description?: string
|
|
|
|
|
|
viewCount?: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 系统配置相关类型
|
|
|
|
|
|
export interface Setting {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
keyName: string
|
|
|
|
|
|
value: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 操作日志相关类型
|
|
|
|
|
|
export interface OperationLog {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
userId: number
|
|
|
|
|
|
username: string
|
|
|
|
|
|
ip: string
|
2026-01-20 14:31:39 +08:00
|
|
|
|
region?: string
|
2026-01-15 13:51:44 +08:00
|
|
|
|
path: string
|
|
|
|
|
|
method: string
|
2026-01-20 15:23:37 +08:00
|
|
|
|
action?: string
|
2026-01-15 13:51:44 +08:00
|
|
|
|
params: string
|
|
|
|
|
|
status: number
|
|
|
|
|
|
duration: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 分页响应类型
|
|
|
|
|
|
export interface PaginationResponse<T> {
|
|
|
|
|
|
list: T[]
|
|
|
|
|
|
total: number
|
|
|
|
|
|
page: number
|
|
|
|
|
|
size: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 登录API
|
|
|
|
|
|
export const login = async (credentials: LoginRequest): Promise<LoginResponse> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/admin/login`, {
|
|
|
|
|
|
method: 'POST',
|
2026-06-24 16:50:04 +08:00
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
2026-01-15 13:51:44 +08:00
|
|
|
|
body: JSON.stringify(credentials),
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const result = await parseApiResponse<{ token: string; user: User; expire: number }>(response, {
|
|
|
|
|
|
skipSessionExpired: true,
|
|
|
|
|
|
})
|
2026-01-16 17:03:34 +08:00
|
|
|
|
return {
|
|
|
|
|
|
token: result.token,
|
|
|
|
|
|
user: result.user,
|
2026-06-24 16:50:04 +08:00
|
|
|
|
expire: result.expire,
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Login error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 用户管理API
|
|
|
|
|
|
export const getUsers = async (): Promise<User[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/users`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const data = await parseApiResponse<any>(response)
|
|
|
|
|
|
return (data?.list || []) as User[]
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get users error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchUser = async (id: number): Promise<User> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/users/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error fetching user ${id}:`, error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createUser = async (userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/users`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(userData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create user error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateUser = async (id: number, userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/users/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(userData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update user error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteUser = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/users/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete user error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 角色管理API
|
|
|
|
|
|
export const getRoles = async (): Promise<Role[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/roles`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get roles error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createRole = async (roleData: Omit<Role, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/roles`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(roleData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create role error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateRole = async (id: number, roleData: Omit<Role, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/roles/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(roleData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update role error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteRole = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/roles/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete role error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchRole = async (id: number): Promise<Role> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/roles/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error fetching role ${id}:`, error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 作品管理API
|
|
|
|
|
|
export const getAdminWorks = async (): Promise<Work[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/works`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const data = await parseApiResponse<PaginationResponse<Work>>(response)
|
|
|
|
|
|
const list = data?.list
|
2026-01-16 17:03:34 +08:00
|
|
|
|
return (list && Array.isArray(list)) ? list : []
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get admin works error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchWorks = async (): Promise<Work[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const result = await fetchJson<Work[] | null>(`${API_BASE}/works`)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
if (!result) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(result)) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
// 确保每个 work 对象的字段格式正确
|
|
|
|
|
|
return result.map((work: any) => ({
|
|
|
|
|
|
id: work.id || '',
|
|
|
|
|
|
title: work.title || '',
|
|
|
|
|
|
category: work.category || '',
|
|
|
|
|
|
year: work.year || '',
|
|
|
|
|
|
heroImg: work.heroImg || '',
|
|
|
|
|
|
desc: work.desc || '',
|
|
|
|
|
|
techStack: Array.isArray(work.techStack) ? work.techStack : [],
|
|
|
|
|
|
gallery: Array.isArray(work.gallery) ? work.gallery : [],
|
2026-01-19 21:19:35 +08:00
|
|
|
|
links: work.links || {},
|
2026-01-16 17:03:34 +08:00
|
|
|
|
next: work.next || ''
|
|
|
|
|
|
})) as Work[]
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error fetching works:', error)
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchWork = async (id: string): Promise<Work> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const result = await fetchJson<Work>(`${API_BASE}/works/${id}`)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
if (!result) {
|
|
|
|
|
|
throw new Error('Work not found')
|
|
|
|
|
|
}
|
|
|
|
|
|
// 确保 techStack 和 gallery 始终是数组
|
|
|
|
|
|
if (!Array.isArray(result.techStack)) {
|
|
|
|
|
|
result.techStack = []
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(result.gallery)) {
|
|
|
|
|
|
result.gallery = []
|
|
|
|
|
|
}
|
|
|
|
|
|
return result
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error fetching work ${id}:`, error)
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: 'default',
|
|
|
|
|
|
title: '默认作品',
|
|
|
|
|
|
category: '默认分类',
|
|
|
|
|
|
year: '2024',
|
|
|
|
|
|
heroImg: '',
|
|
|
|
|
|
desc: '',
|
|
|
|
|
|
techStack: [],
|
|
|
|
|
|
gallery: [],
|
2026-01-19 21:19:35 +08:00
|
|
|
|
links: {},
|
2026-01-15 13:51:44 +08:00
|
|
|
|
next: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createWork = async (workData: Omit<Work, 'id' | 'next'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/works`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(workData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create work error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateWork = async (id: string, workData: Omit<Work, 'id' | 'next'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/works/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(workData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update work error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteWork = async (id: string): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/works/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete work error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 文章管理API
|
2026-01-23 16:05:23 +08:00
|
|
|
|
export const getAdminPosts = async (
|
|
|
|
|
|
page: number = 1,
|
|
|
|
|
|
pageSize: number = 20,
|
|
|
|
|
|
keyword?: string
|
|
|
|
|
|
): Promise<PaginationResponse<Post>> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
2026-01-23 16:05:23 +08:00
|
|
|
|
const params = new URLSearchParams()
|
|
|
|
|
|
params.append('page', page.toString())
|
|
|
|
|
|
params.append('pageSize', pageSize.toString())
|
|
|
|
|
|
if (keyword) {
|
|
|
|
|
|
params.append('keyword', keyword)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts?${params.toString()}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get admin posts error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:05:23 +08:00
|
|
|
|
export const fetchPosts = async (
|
|
|
|
|
|
query?: string,
|
|
|
|
|
|
categoryId?: number,
|
|
|
|
|
|
tagId?: number,
|
|
|
|
|
|
columnId?: number,
|
|
|
|
|
|
page?: number,
|
|
|
|
|
|
pageSize?: number
|
|
|
|
|
|
): Promise<PaginationResponse<Post>> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
2026-01-15 17:03:59 +08:00
|
|
|
|
let url = `${API_BASE}/posts`
|
2026-01-16 17:03:34 +08:00
|
|
|
|
const params = new URLSearchParams()
|
|
|
|
|
|
if (query) params.append('q', query)
|
|
|
|
|
|
if (categoryId) params.append('category', categoryId.toString())
|
|
|
|
|
|
if (tagId) params.append('tag', tagId.toString())
|
2026-01-19 16:14:08 +08:00
|
|
|
|
if (columnId) params.append('column', columnId.toString())
|
2026-01-23 16:05:23 +08:00
|
|
|
|
if (page) params.append('page', page.toString())
|
|
|
|
|
|
if (pageSize) params.append('pageSize', pageSize.toString())
|
2026-01-16 17:03:34 +08:00
|
|
|
|
|
|
|
|
|
|
if (params.toString()) {
|
|
|
|
|
|
url += `?${params.toString()}`
|
2026-01-15 17:03:59 +08:00
|
|
|
|
}
|
2026-01-16 17:03:34 +08:00
|
|
|
|
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const data = await fetchJson<PaginationResponse<Post> | Post[]>(url)
|
2026-01-23 16:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 返回分页格式的响应
|
2026-06-24 16:50:04 +08:00
|
|
|
|
if (data && typeof data === 'object' && 'list' in data) {
|
|
|
|
|
|
return data as PaginationResponse<Post>
|
2026-01-23 16:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容旧格式(直接返回数组)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const list = Array.isArray(data) ? data : []
|
2026-01-23 16:05:23 +08:00
|
|
|
|
return {
|
|
|
|
|
|
list,
|
|
|
|
|
|
total: list.length,
|
|
|
|
|
|
page: page || 1,
|
|
|
|
|
|
size: pageSize || list.length
|
|
|
|
|
|
}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error fetching posts:', error)
|
2026-01-23 16:05:23 +08:00
|
|
|
|
return {
|
|
|
|
|
|
list: [],
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
page: page || 1,
|
|
|
|
|
|
size: pageSize || 10
|
|
|
|
|
|
}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:03:59 +08:00
|
|
|
|
export const fetchPost = async (id: number | string): Promise<Post> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/posts/${id}`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error fetching post ${id}:`, error)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
// Return empty fallback
|
2026-01-15 13:51:44 +08:00
|
|
|
|
return {
|
2026-01-15 17:03:59 +08:00
|
|
|
|
id: Number(id),
|
2026-01-16 17:03:34 +08:00
|
|
|
|
title: '未知文章',
|
|
|
|
|
|
categoryId: 0,
|
2026-01-15 13:51:44 +08:00
|
|
|
|
date: '2024-01-01'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 15:43:17 +08:00
|
|
|
|
// 获取推荐文章(基于IP的协同过滤)
|
|
|
|
|
|
export const getRecommendedPosts = async (postId: number | string, limit: number = 3): Promise<Post[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/posts/${postId}/recommendations?limit=${limit}`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-20 15:43:17 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error fetching recommended posts for ${postId}:`, error)
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
export const createPost = async (postData: Omit<Post, 'id'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(postData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create post error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:03:59 +08:00
|
|
|
|
export const updatePost = async (id: number | string, postData: Omit<Post, 'id'>): Promise<void> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(postData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update post error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:05:23 +08:00
|
|
|
|
// 更新文章关联关系(只更新分类、专栏、标签,不更新内容)
|
|
|
|
|
|
export const updatePostRelations = async (
|
|
|
|
|
|
id: number | string,
|
|
|
|
|
|
relations: { categoryId: number; columnId: number | null; tagIds: number[] }
|
|
|
|
|
|
): Promise<void> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload: any = {
|
|
|
|
|
|
categoryId: relations.categoryId,
|
|
|
|
|
|
tagIds: relations.tagIds
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (relations.columnId !== null && relations.columnId > 0) {
|
|
|
|
|
|
payload.columnId = relations.columnId
|
|
|
|
|
|
} else {
|
|
|
|
|
|
payload.columnId = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${id}/relations`, {
|
2026-01-23 16:05:23 +08:00
|
|
|
|
method: 'PATCH',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(payload)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-23 16:05:23 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update post relations error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 17:03:34 +08:00
|
|
|
|
export const togglePostStatus = async (id: number, isPublished: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${id}/status`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'PATCH',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify({ isPublished })
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Toggle post status error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:03:59 +08:00
|
|
|
|
export const deletePost = async (id: number | string): Promise<void> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete post error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 17:03:34 +08:00
|
|
|
|
// 分类管理API
|
|
|
|
|
|
export const fetchCategories = async (): Promise<Category[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/categories`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch categories error:', error)
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const getAdminCategories = async (): Promise<Category[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/categories`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get admin categories error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createCategory = async (data: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/categories`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateCategory = async (id: number, data: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/categories/${id}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteCategory = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/categories/${id}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 专栏管理API
|
|
|
|
|
|
export const fetchColumns = async (): Promise<Column[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/columns`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchColumn = async (id: number): Promise<Column> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/columns/${id}`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const getAdminColumns = async (): Promise<Column[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/columns`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createColumn = async (data: Omit<Column, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/columns`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateColumn = async (id: number, data: Omit<Column, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/columns/${id}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteColumn = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/columns/${id}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchColumnPosts = async (id: number): Promise<Post[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/columns/${id}/posts`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch column posts error:', error)
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const addPostToColumn = async (columnId: number, postId: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/columns/${columnId}/posts`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify({ postId })
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const removePostFromColumn = async (columnId: number, postId: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/columns/${columnId}/posts/${postId}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标签管理API (复用现有 Tag 类型)
|
|
|
|
|
|
export const fetchTags = async (): Promise<Tag[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/tags`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const adminGetTags = async (): Promise<Tag[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/tags`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const adminGetTag = async (id: number): Promise<Tag> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/tags/${id}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) // Adapt to standard response
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createTag = async (data: { name: string; slug: string }): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/tags`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateTag = async (id: number, data: { name: string; slug: string }): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/tags/${id}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteTag = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/tags/${id}`, {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 17:03:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
// 文章历史记录API
|
2026-01-15 17:03:59 +08:00
|
|
|
|
export const getPostHistory = async (postId: number | string): Promise<PostHistory[]> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${postId}/history`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get post history error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:03:59 +08:00
|
|
|
|
export const getPostHistoryByVersion = async (postId: number | string, version: number): Promise<PostHistory> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${postId}/history/${version}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get post history by version error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 16:50:04 +08:00
|
|
|
|
export const getPostHistoryDiff = async (
|
|
|
|
|
|
postId: number | string,
|
|
|
|
|
|
fromVersion: number,
|
|
|
|
|
|
toVersion: number
|
|
|
|
|
|
): Promise<PostHistoryDiff> => {
|
|
|
|
|
|
const response = await authFetch(
|
|
|
|
|
|
`${API_BASE}/admin/posts/${postId}/history/diff?from=${fromVersion}&to=${toVersion}`,
|
|
|
|
|
|
{ headers: getAuthHeaders() }
|
|
|
|
|
|
)
|
|
|
|
|
|
return await parseApiResponse(response)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const restorePostHistory = async (postId: number | string, version: number): Promise<Post> => {
|
|
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${postId}/history/${version}/restore`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
|
|
|
|
|
return await parseApiResponse(response)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const getHotSearches = async (limit = 10, days = 30): Promise<HotSearchKeyword[]> => {
|
|
|
|
|
|
return (await fetchJson<HotSearchKeyword[]>(`${API_BASE}/search/hot?limit=${limit}&days=${days}`)) || []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
// 代码片段管理API
|
|
|
|
|
|
export const getAdminSnippets = async (): Promise<Snippet[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/snippets`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const data = await parseApiResponse<any>(response)
|
|
|
|
|
|
return (data?.list || []) as Snippet[]
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get admin snippets error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchSnippets = async (): Promise<Snippet[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/snippets`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error fetching snippets:', error)
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchSnippet = async (id: string): Promise<Snippet> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/snippets/${id}`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error fetching snippet ${id}:`, error)
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: id,
|
|
|
|
|
|
title: '默认代码片段',
|
|
|
|
|
|
code: 'console.log("Hello World");',
|
|
|
|
|
|
type: 'js'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createSnippet = async (snippetData: Omit<Snippet, 'id' | 'viewCount'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/snippets`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(snippetData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create snippet error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateSnippet = async (id: string, snippetData: Omit<Snippet, 'id' | 'viewCount'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/snippets/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(snippetData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update snippet error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteSnippet = async (id: string): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/snippets/${id}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete snippet error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 系统配置API
|
|
|
|
|
|
export const getSettings = async (): Promise<Setting[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/settings`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const data = await parseApiResponse<Setting[]>(response)
|
|
|
|
|
|
if (!data) {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
return []
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return Array.isArray(data) ? data : []
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get settings error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createSetting = async (settingData: Omit<Setting, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/settings`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(settingData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create setting error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateSetting = async (settingData: Omit<Setting, 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/settings`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(settingData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update setting error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteSetting = async (keyName: string): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/settings/${keyName}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete setting error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 11:12:01 +08:00
|
|
|
|
// 公开设置API(前端使用,无需认证)
|
|
|
|
|
|
export interface PublicSettings {
|
|
|
|
|
|
site_title?: string
|
|
|
|
|
|
site_description?: string
|
|
|
|
|
|
site_author?: string
|
|
|
|
|
|
site_keywords?: string
|
2026-01-20 12:02:01 +08:00
|
|
|
|
visible_menus?: string
|
2026-01-23 16:05:23 +08:00
|
|
|
|
posts_per_page?: string
|
2026-01-20 11:12:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const getPublicSettings = async (): Promise<PublicSettings> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/settings`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || {}
|
2026-01-20 11:12:01 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get public settings error:', error)
|
|
|
|
|
|
// 返回空对象,前端使用默认值
|
|
|
|
|
|
return {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
// 操作日志API
|
|
|
|
|
|
export const getOperationLogs = async (page: number = 1, pageSize: number = 10): Promise<PaginationResponse<OperationLog>> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/operation-logs?page=${page}&pageSize=${pageSize}`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get operation logs error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 11:00:42 +08:00
|
|
|
|
// 访问日志类型
|
|
|
|
|
|
export interface AccessLog {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
ip: string
|
|
|
|
|
|
userAgent: string
|
|
|
|
|
|
path: string
|
|
|
|
|
|
method: string
|
|
|
|
|
|
statusCode: number
|
|
|
|
|
|
responseTime: number
|
|
|
|
|
|
region: string
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 访问日志API
|
|
|
|
|
|
export const getAccessLogs = async (page: number = 1, pageSize: number = 10): Promise<PaginationResponse<AccessLog>> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/access-logs?page=${page}&pageSize=${pageSize}`, {
|
2026-01-20 11:00:42 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-20 11:00:42 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get access logs error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 15:23:37 +08:00
|
|
|
|
// 文章访问记录类型(来自 user_access_logs 表)
|
|
|
|
|
|
export interface PostAccessLog {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
userId: number
|
|
|
|
|
|
ip: string
|
|
|
|
|
|
region: string
|
|
|
|
|
|
articleId: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 11:00:42 +08:00
|
|
|
|
// 获取指定文章的访问记录
|
2026-01-20 15:23:37 +08:00
|
|
|
|
export const getPostAccessLogs = async (postId: number, page: number = 1, pageSize: number = 20): Promise<PaginationResponse<PostAccessLog>> => {
|
2026-01-20 11:00:42 +08:00
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/posts/${postId}/access-logs?page=${page}&pageSize=${pageSize}`, {
|
2026-01-20 11:00:42 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-20 11:00:42 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get post access logs error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
// 仪表盘数据类型
|
|
|
|
|
|
export interface DashboardStats {
|
|
|
|
|
|
users: number
|
|
|
|
|
|
posts: number
|
|
|
|
|
|
works: number
|
|
|
|
|
|
snippets: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 最近活动类型
|
|
|
|
|
|
export interface RecentActivity {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
icon: string
|
|
|
|
|
|
text: string
|
|
|
|
|
|
time: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 仪表盘API
|
2026-01-16 10:19:30 +08:00
|
|
|
|
export const getDashboardStats = async (startDate?: string, endDate?: string): Promise<DashboardStats> => {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
try {
|
2026-01-16 10:19:30 +08:00
|
|
|
|
let url = `${API_BASE}/admin/dashboard/stats`
|
|
|
|
|
|
const params = new URLSearchParams()
|
|
|
|
|
|
if (startDate) params.append('startDate', startDate)
|
|
|
|
|
|
if (endDate) params.append('endDate', endDate)
|
|
|
|
|
|
if (params.toString()) url += `?${params.toString()}`
|
|
|
|
|
|
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await authFetchJson<DashboardStats>(url, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get dashboard stats error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取最近活动
|
|
|
|
|
|
export const getRecentActivities = async (): Promise<RecentActivity[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/dashboard/activities`, {
|
2026-01-15 13:51:44 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get recent activities error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 15:37:34 +08:00
|
|
|
|
// 关于页面相关类型
|
|
|
|
|
|
export interface Experience {
|
|
|
|
|
|
year: string
|
|
|
|
|
|
role: string
|
|
|
|
|
|
company: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AboutProfile {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
avatar: string
|
|
|
|
|
|
location: string
|
|
|
|
|
|
bio: string
|
|
|
|
|
|
email: string
|
|
|
|
|
|
wechat: string
|
|
|
|
|
|
techStack: string[]
|
|
|
|
|
|
experiences: Experience[]
|
|
|
|
|
|
isPrimary: boolean
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 关于页面API
|
|
|
|
|
|
export const fetchAboutProfile = async (): Promise<AboutProfile> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const result = await fetchJson<AboutProfile>(`${API_BASE}/about`)
|
|
|
|
|
|
if (!result) {
|
2026-01-16 17:03:34 +08:00
|
|
|
|
throw new Error('个人资料数据为空')
|
|
|
|
|
|
}
|
|
|
|
|
|
// 确保 techStack 和 experiences 始终是数组
|
|
|
|
|
|
if (!Array.isArray(result.techStack)) {
|
|
|
|
|
|
result.techStack = []
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(result.experiences)) {
|
|
|
|
|
|
result.experiences = []
|
2026-01-15 15:37:34 +08:00
|
|
|
|
}
|
2026-01-16 17:03:34 +08:00
|
|
|
|
return result
|
2026-01-15 15:37:34 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch about profile error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 15:42:20 +08:00
|
|
|
|
|
|
|
|
|
|
export const getAdminAboutProfiles = async (): Promise<AboutProfile[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/about`, {
|
2026-01-15 15:42:20 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-15 15:42:20 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get admin about profiles error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createAboutProfile = async (profileData: Omit<AboutProfile, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/about`, {
|
2026-01-15 15:42:20 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(profileData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 15:42:20 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create about profile error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateAboutProfile = async (id: number, profileData: Omit<AboutProfile, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/about/${id}`, {
|
2026-01-15 15:42:20 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(profileData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 15:42:20 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update about profile error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteAboutProfile = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/about/${id}`, {
|
2026-01-15 15:42:20 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-15 15:42:20 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete about profile error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-16 08:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 客户评价相关类型
|
|
|
|
|
|
export interface Testimonial {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
content: string
|
|
|
|
|
|
author: string
|
|
|
|
|
|
role: string
|
|
|
|
|
|
avatar: string
|
|
|
|
|
|
rating: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合作伙伴相关类型
|
|
|
|
|
|
export interface Partner {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
logo: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
website: string
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 客户评价API
|
|
|
|
|
|
export const fetchTestimonials = async (): Promise<Testimonial[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/testimonials`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch testimonials error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createTestimonial = async (data: Omit<Testimonial, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/testimonials`, {
|
2026-01-16 08:29:57 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create testimonial error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateTestimonial = async (id: number, data: Omit<Testimonial, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/testimonials/${id}`, {
|
2026-01-16 08:29:57 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update testimonial error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteTestimonial = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/testimonials/${id}`, {
|
2026-01-16 08:29:57 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete testimonial error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合作伙伴API
|
|
|
|
|
|
export const fetchPartners = async (): Promise<Partner[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/partners`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch partners error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createPartner = async (data: Omit<Partner, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/partners`, {
|
2026-01-16 08:29:57 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create partner error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updatePartner = async (id: number, data: Omit<Partner, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/partners/${id}`, {
|
2026-01-16 08:29:57 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update partner error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deletePartner = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/partners/${id}`, {
|
2026-01-16 08:29:57 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-16 08:29:57 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete partner error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-16 09:08:07 +08:00
|
|
|
|
|
|
|
|
|
|
// 咨询相关类型
|
|
|
|
|
|
export interface Inquiry {
|
|
|
|
|
|
id?: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
company: string
|
|
|
|
|
|
contactMethod: string
|
|
|
|
|
|
contactValue: string
|
|
|
|
|
|
budget: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
status?: number // 0-Unread, 1-Read, 2-Contacted
|
|
|
|
|
|
createdAt?: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-19 16:14:08 +08:00
|
|
|
|
// OSS配置相关类型
|
|
|
|
|
|
export interface OSSConfig {
|
2026-01-19 20:21:09 +08:00
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
storageType: string
|
|
|
|
|
|
// 通用字段(向后兼容)
|
|
|
|
|
|
accessKey?: string
|
|
|
|
|
|
secretKey?: string
|
|
|
|
|
|
bucket?: string
|
|
|
|
|
|
region?: string
|
|
|
|
|
|
domain?: string
|
|
|
|
|
|
// 阿里云OSS专用字段
|
|
|
|
|
|
ossAccessKeyId?: string
|
|
|
|
|
|
ossAccessKeySecret?: string
|
|
|
|
|
|
ossEndpoint?: string
|
|
|
|
|
|
ossBucket?: string
|
|
|
|
|
|
ossDomain?: string
|
|
|
|
|
|
// 腾讯云COS专用字段
|
|
|
|
|
|
qcloudSecretId?: string
|
|
|
|
|
|
qcloudSecretKey?: string
|
|
|
|
|
|
qcloudRegion?: string
|
|
|
|
|
|
qcloudBucket?: string
|
|
|
|
|
|
qcloudDomain?: string
|
|
|
|
|
|
// 七牛云专用字段
|
|
|
|
|
|
qiniuAccessKey?: string
|
|
|
|
|
|
qiniuSecretKey?: string
|
|
|
|
|
|
qiniuBucket?: string
|
|
|
|
|
|
qiniuRegion?: string
|
|
|
|
|
|
qiniuDomain?: string
|
|
|
|
|
|
isActive: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 向后兼容的旧接口定义(已废弃,保留用于类型兼容)
|
|
|
|
|
|
export interface OSSConfigOld {
|
2026-01-19 16:14:08 +08:00
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
storageType: string // local/qcloud/aliyun/qiniu
|
|
|
|
|
|
accessKey: string // 加密存储,返回时显示为 ***
|
|
|
|
|
|
secretKey: string // 加密存储,返回时显示为 ***
|
|
|
|
|
|
bucket: string
|
|
|
|
|
|
region: string
|
|
|
|
|
|
domain: string
|
|
|
|
|
|
isActive: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 09:08:07 +08:00
|
|
|
|
export interface EmailSuffix {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
suffix: string
|
|
|
|
|
|
isActive: boolean
|
|
|
|
|
|
sortOrder: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 咨询相关API
|
|
|
|
|
|
export const submitInquiry = async (data: Inquiry): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await fetchJson(`${API_BASE}/inquiries`, {
|
2026-01-16 09:08:07 +08:00
|
|
|
|
method: 'POST',
|
2026-06-24 16:50:04 +08:00
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
2026-01-16 09:08:07 +08:00
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Submit inquiry error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchEmailSuffixes = async (): Promise<EmailSuffix[]> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/email-suffixes`)
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 09:08:07 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch email suffixes error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const fetchInquiries = async (): Promise<Inquiry[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/inquiries`, {
|
2026-01-16 09:08:07 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-16 09:08:07 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Fetch inquiries error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-19 16:14:08 +08:00
|
|
|
|
|
|
|
|
|
|
// OSS配置相关API
|
|
|
|
|
|
export const getOSSConfigs = async (): Promise<OSSConfig[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/oss-configs`, {
|
2026-01-19 16:14:08 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-19 16:14:08 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get OSS configs error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createOSSConfig = async (configData: Omit<OSSConfig, 'id' | 'createdAt' | 'updatedAt'>): Promise<OSSConfig> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/oss-configs`, {
|
2026-01-19 16:14:08 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(configData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-19 16:14:08 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create OSS config error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateOSSConfig = async (id: number, configData: Partial<Omit<OSSConfig, 'id' | 'createdAt' | 'updatedAt'>>): Promise<OSSConfig> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/oss-configs/${id}`, {
|
2026-01-19 16:14:08 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(configData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response)
|
2026-01-19 16:14:08 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update OSS config error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteOSSConfig = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/oss-configs/${id}`, {
|
2026-01-19 16:14:08 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-19 16:14:08 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete OSS config error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-19 20:21:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 附件相关类型
|
|
|
|
|
|
export interface Attachment {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
categoryId?: number
|
|
|
|
|
|
category?: {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
}
|
|
|
|
|
|
originalName: string
|
|
|
|
|
|
storedName: string
|
|
|
|
|
|
filePath: string
|
|
|
|
|
|
fileUrl: string
|
|
|
|
|
|
fileSize: number
|
|
|
|
|
|
fileType: string
|
|
|
|
|
|
mimeType: string
|
|
|
|
|
|
storageType: string
|
|
|
|
|
|
ossConfigId?: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 附件分类相关类型
|
|
|
|
|
|
export interface AttachmentCategory {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
sortOrder: number
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 附件管理API
|
2026-01-19 21:19:35 +08:00
|
|
|
|
export const getAdminAttachments = async (params?: {
|
|
|
|
|
|
page?: number
|
|
|
|
|
|
pageSize?: number
|
|
|
|
|
|
categoryId?: number
|
|
|
|
|
|
fileType?: string
|
|
|
|
|
|
keyword?: string
|
|
|
|
|
|
}): Promise<{ list: Attachment[], total: number, page: number, size: number }> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const queryParams = new URLSearchParams()
|
|
|
|
|
|
if (params?.page) queryParams.append('page', params.page.toString())
|
|
|
|
|
|
if (params?.pageSize) queryParams.append('pageSize', params.pageSize.toString())
|
|
|
|
|
|
if (params?.categoryId) queryParams.append('categoryId', params.categoryId.toString())
|
|
|
|
|
|
if (params?.fileType) queryParams.append('fileType', params.fileType)
|
|
|
|
|
|
if (params?.keyword) queryParams.append('keyword', params.keyword)
|
|
|
|
|
|
|
|
|
|
|
|
const url = `${API_BASE}/admin/attachments${queryParams.toString() ? '?' + queryParams.toString() : ''}`
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const data = await authFetchJson<PaginationResponse<Attachment>>(url, {
|
2026-01-19 21:19:35 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
|
|
|
|
|
return {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
list: data?.list || [],
|
|
|
|
|
|
total: data?.total || 0,
|
|
|
|
|
|
page: data?.page || 1,
|
|
|
|
|
|
size: data?.size || 20
|
2026-01-19 21:19:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get admin attachments error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-19 20:21:09 +08:00
|
|
|
|
export const updateAttachment = async (id: number, data: { categoryId?: number | null }): Promise<Attachment> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await authFetchJson<Attachment>(`${API_BASE}/admin/attachments/${id}`, {
|
2026-01-19 20:21:09 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(data)
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update attachment error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 附件分类管理API
|
|
|
|
|
|
export const getAttachmentCategories = async (): Promise<AttachmentCategory[]> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/attachment-categories`, {
|
2026-01-19 20:21:09 +08:00
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
return await parseApiResponse(response) || []
|
2026-01-19 20:21:09 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Get attachment categories error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const createAttachmentCategory = async (categoryData: Omit<AttachmentCategory, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/attachment-categories`, {
|
2026-01-19 20:21:09 +08:00
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(categoryData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-19 20:21:09 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Create attachment category error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const updateAttachmentCategory = async (id: number, categoryData: Partial<Omit<AttachmentCategory, 'id' | 'createdAt' | 'updatedAt'>>): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/attachment-categories/${id}`, {
|
2026-01-19 20:21:09 +08:00
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
|
|
body: JSON.stringify(categoryData)
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-19 20:21:09 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Update attachment category error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteAttachmentCategory = async (id: number): Promise<void> => {
|
|
|
|
|
|
try {
|
2026-06-24 16:50:04 +08:00
|
|
|
|
const response = await authFetch(`${API_BASE}/admin/attachment-categories/${id}`, {
|
2026-01-19 20:21:09 +08:00
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
headers: getAuthHeaders()
|
|
|
|
|
|
})
|
2026-06-24 16:50:04 +08:00
|
|
|
|
await parseApiResponse(response)
|
2026-01-19 20:21:09 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Delete attachment category error:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|