Files
nl-blogs/client/src/services/api.ts
2026-06-25 16:57:06 +08:00

1889 lines
49 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useAuth } from '../composables/useAuth'
export const API_BASE = '/api'
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)
}
// 通用请求头配置
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[]
links: { live?: string; github?: string; demo?: string }
next: string
}
// 分类相关类型
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
}
// 文章相关类型
export interface Post {
id: number
title: string
categoryId: number
categoryName?: string // Display name
categorySlug?: string
category?: Category // Optional full object
columnId?: number | null
columnName?: string
tags?: Tag[]
date: string
excerpt?: string
content?: string
isPublished?: number
readCount?: number
snippets?: SnippetBrief[]
}
export interface PostHistory {
id: number
postId: number
version: number
title: string
categoryId: number
categoryName?: string
columnId?: number
columnName?: string
tagIds?: number[]
tagNames?: string[]
excerpt?: string
content?: string
isPublished: number
modifiedBy: number
modifiedByName?: string
modifiedAt: string
createdAt: string
}
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
}
// 代码类型
export interface CodeType {
id: number
name: string
category: number // 0前端 1后端 2其他
}
export const CODE_TYPE_CATEGORY_LABELS: Record<number, string> = {
0: '前端',
1: '后端',
2: '其他'
}
export const getCodeTypes = async (): Promise<CodeType[]> => {
const response = await fetch(`${API_BASE}/code-types`)
return await parseApiResponse(response)
}
export const getAdminCodeTypes = async (): Promise<CodeType[]> => {
const response = await authFetch(`${API_BASE}/admin/code-types`, { headers: getAuthHeaders() })
return await parseApiResponse(response)
}
export const createCodeType = async (data: Omit<CodeType, 'id'>): Promise<void> => {
const response = await authFetch(`${API_BASE}/admin/code-types`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
}
export const updateCodeType = async (id: number, data: Omit<CodeType, 'id'>): Promise<void> => {
const response = await authFetch(`${API_BASE}/admin/code-types/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
}
export const deleteCodeType = async (id: number): Promise<void> => {
const response = await authFetch(`${API_BASE}/admin/code-types/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
}
// 文章/代码片段简要类型
export interface PostBrief {
id: number
title: string
}
export interface SnippetBrief {
id: number
title: string
type: string
codeType?: CodeType
}
// 代码片段相关类型
export interface Snippet {
id: number
title: string
code: string
type: string
codeTypeId?: number
codeType?: CodeType
description?: string
viewCount?: number
posts?: PostBrief[]
}
// 系统配置相关类型
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
region?: string
path: string
method: string
action?: string
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',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
})
const result = await parseApiResponse<{ token: string; user: User; expire: number }>(response, {
skipSessionExpired: true,
})
return {
token: result.token,
user: result.user,
expire: result.expire,
}
} catch (error) {
console.error('Login error:', error)
throw error
}
}
// 用户管理API
export interface GetUsersParams {
page?: number
pageSize?: number
keyword?: string
}
export const getUsersPaginated = async (params: GetUsersParams = {}): Promise<PaginationResponse<User>> => {
const page = params.page ?? 1
const pageSize = params.pageSize ?? 10
const qs = new URLSearchParams({ page: String(page), pageSize: String(pageSize) })
if (params.keyword) qs.set('keyword', params.keyword)
const response = await authFetch(`${API_BASE}/admin/users?${qs}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
}
export const getUsers = async (): Promise<User[]> => {
try {
const data = await getUsersPaginated({ page: 1, pageSize: 1000 })
return data.list
} catch (error) {
console.error('Get users error:', error)
throw error
}
}
export const fetchUser = async (id: number): Promise<User> => {
try {
const response = await authFetch(`${API_BASE}/admin/users/${id}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
} catch (error) {
console.error(`Error fetching user ${id}:`, error)
throw error
}
}
export const createUser = async (userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/users`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(userData)
})
await parseApiResponse(response)
} 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 {
const response = await authFetch(`${API_BASE}/admin/users/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(userData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update user error:', error)
throw error
}
}
export const deleteUser = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/users/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete user error:', error)
throw error
}
}
// 角色管理API
export const getRoles = async (): Promise<Role[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/roles`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
console.error('Get roles error:', error)
throw error
}
}
export const createRole = async (roleData: Omit<Role, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/roles`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(roleData)
})
await parseApiResponse(response)
} 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 {
const response = await authFetch(`${API_BASE}/admin/roles/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(roleData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update role error:', error)
throw error
}
}
export const deleteRole = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/roles/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete role error:', error)
throw error
}
}
export const fetchRole = async (id: number): Promise<Role> => {
try {
const response = await authFetch(`${API_BASE}/admin/roles/${id}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
} catch (error) {
console.error(`Error fetching role ${id}:`, error)
throw error
}
}
// 作品管理API
export const getAdminWorks = async (): Promise<Work[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/works`, {
headers: getAuthHeaders()
})
const data = await parseApiResponse<PaginationResponse<Work>>(response)
const list = data?.list
return (list && Array.isArray(list)) ? list : []
} catch (error) {
console.error('Get admin works error:', error)
throw error
}
}
export const fetchWorks = async (): Promise<Work[]> => {
try {
const result = await fetchJson<Work[] | null>(`${API_BASE}/works`)
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 : [],
links: work.links || {},
next: work.next || ''
})) as Work[]
} catch (error) {
console.error('Error fetching works:', error)
return []
}
}
export const fetchWork = async (id: string): Promise<Work> => {
try {
const result = await fetchJson<Work>(`${API_BASE}/works/${id}`)
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
} catch (error) {
console.error(`Error fetching work ${id}:`, error)
return {
id: 'default',
title: '默认作品',
category: '默认分类',
year: '2024',
heroImg: '',
desc: '',
techStack: [],
gallery: [],
links: {},
next: ''
}
}
}
export const createWork = async (workData: Omit<Work, 'id' | 'next'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/works`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(workData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Create work error:', error)
throw error
}
}
export const updateWork = async (id: string, workData: Omit<Work, 'id' | 'next'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/works/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(workData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update work error:', error)
throw error
}
}
export const deleteWork = async (id: string): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/works/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete work error:', error)
throw error
}
}
// 文章管理API
export const getAdminPosts = async (
page: number = 1,
pageSize: number = 20,
keyword?: string
): Promise<PaginationResponse<Post>> => {
try {
const params = new URLSearchParams()
params.append('page', page.toString())
params.append('pageSize', pageSize.toString())
if (keyword) {
params.append('keyword', keyword)
}
const response = await authFetch(`${API_BASE}/admin/posts?${params.toString()}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
} catch (error) {
console.error('Get admin posts error:', error)
throw error
}
}
export const fetchPosts = async (
query?: string,
categoryId?: number,
tagId?: number,
columnId?: number,
page?: number,
pageSize?: number
): Promise<PaginationResponse<Post>> => {
try {
let url = `${API_BASE}/posts`
const params = new URLSearchParams()
if (query) params.append('q', query)
if (categoryId) params.append('category', categoryId.toString())
if (tagId) params.append('tag', tagId.toString())
if (columnId) params.append('column', columnId.toString())
if (page) params.append('page', page.toString())
if (pageSize) params.append('pageSize', pageSize.toString())
if (params.toString()) {
url += `?${params.toString()}`
}
const data = await fetchJson<PaginationResponse<Post> | Post[]>(url)
// 返回分页格式的响应
if (data && typeof data === 'object' && 'list' in data) {
return data as PaginationResponse<Post>
}
// 兼容旧格式(直接返回数组)
const list = Array.isArray(data) ? data : []
return {
list,
total: list.length,
page: page || 1,
size: pageSize || list.length
}
} catch (error) {
console.error('Error fetching posts:', error)
return {
list: [],
total: 0,
page: page || 1,
size: pageSize || 10
}
}
}
export const fetchPost = async (id: number | string): Promise<Post> => {
try {
const response = await fetch(`${API_BASE}/posts/${id}`)
return await parseApiResponse(response)
} catch (error) {
console.error(`Error fetching post ${id}:`, error)
// Return empty fallback
return {
id: Number(id),
title: '未知文章',
categoryId: 0,
date: '2024-01-01'
}
}
}
// 获取推荐文章基于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}`)
return await parseApiResponse(response) || []
} catch (error) {
console.error(`Error fetching recommended posts for ${postId}:`, error)
return []
}
}
export const createPost = async (postData: Omit<Post, 'id'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/posts`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(postData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Create post error:', error)
throw error
}
}
export const updatePost = async (id: number | string, postData: Omit<Post, 'id'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/posts/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(postData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update post error:', error)
throw error
}
}
// 更新文章关联关系(只更新分类、专栏、标签,不更新内容)
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
}
const response = await authFetch(`${API_BASE}/admin/posts/${id}/relations`, {
method: 'PATCH',
headers: getAuthHeaders(),
body: JSON.stringify(payload)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update post relations error:', error)
throw error
}
}
export const togglePostStatus = async (id: number, isPublished: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/posts/${id}/status`, {
method: 'PATCH',
headers: getAuthHeaders(),
body: JSON.stringify({ isPublished })
})
await parseApiResponse(response)
} catch (error) {
console.error('Toggle post status error:', error)
throw error
}
}
export const deletePost = async (id: number | string): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/posts/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete post error:', error)
throw error
}
}
// 分类管理API
export const fetchCategories = async (): Promise<Category[]> => {
try {
const response = await fetch(`${API_BASE}/categories`)
return await parseApiResponse(response) || []
} catch (error) {
console.error('Fetch categories error:', error)
return []
}
}
export const getAdminCategories = async (): Promise<Category[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/categories`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
console.error('Get admin categories error:', error)
throw error
}
}
export const createCategory = async (data: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/categories`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const updateCategory = async (id: number, data: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/categories/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const deleteCategory = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/categories/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
// 专栏管理API
export const fetchColumns = async (): Promise<Column[]> => {
try {
const response = await fetch(`${API_BASE}/columns`)
return await parseApiResponse(response) || []
} catch (error) {
return []
}
}
export const fetchColumn = async (id: number): Promise<Column> => {
try {
const response = await fetch(`${API_BASE}/columns/${id}`)
return await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const getAdminColumns = async (): Promise<Column[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/columns`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
throw error
}
}
export const createColumn = async (data: Omit<Column, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/columns`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const updateColumn = async (id: number, data: Omit<Column, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/columns/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const deleteColumn = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/columns/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const fetchColumnPosts = async (id: number): Promise<Post[]> => {
try {
const response = await fetch(`${API_BASE}/columns/${id}/posts`)
return await parseApiResponse(response) || []
} catch (error) {
console.error('Fetch column posts error:', error)
return []
}
}
export const addPostToColumn = async (columnId: number, postId: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/columns/${columnId}/posts`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ postId })
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const removePostFromColumn = async (columnId: number, postId: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/columns/${columnId}/posts/${postId}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
// 标签管理API (复用现有 Tag 类型)
export const fetchTags = async (): Promise<Tag[]> => {
try {
const response = await fetch(`${API_BASE}/tags`)
return await parseApiResponse(response) || []
} catch (error) {
return []
}
}
export const adminGetTags = async (): Promise<Tag[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/tags`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
throw error
}
}
export const adminGetTag = async (id: number): Promise<Tag> => {
try {
const response = await authFetch(`${API_BASE}/admin/tags/${id}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) // Adapt to standard response
} catch (error) {
throw error
}
}
export const createTag = async (data: { name: string; slug: string }): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/tags`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const updateTag = async (id: number, data: { name: string; slug: string }): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/tags/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
export const deleteTag = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/tags/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
throw error
}
}
// 文章历史记录API
export const getPostHistory = async (postId: number | string): Promise<PostHistory[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/posts/${postId}/history`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
console.error('Get post history error:', error)
throw error
}
}
export const getPostHistoryByVersion = async (postId: number | string, version: number): Promise<PostHistory> => {
try {
const response = await authFetch(`${API_BASE}/admin/posts/${postId}/history/${version}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
} catch (error) {
console.error('Get post history by version error:', error)
throw error
}
}
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}`)) || []
}
// 代码片段管理API
export const getAdminSnippets = async (): Promise<Snippet[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/snippets`, {
headers: getAuthHeaders()
})
const data = await parseApiResponse<any>(response)
return (data?.list || []) as Snippet[]
} 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`)
return await parseApiResponse(response) || []
} catch (error) {
console.error('Error fetching snippets:', error)
return []
}
}
export const fetchSnippet = async (id: number | string): Promise<Snippet> => {
try {
const response = await fetch(`${API_BASE}/snippets/${id}`)
return await parseApiResponse(response)
} catch (error) {
console.error(`Error fetching snippet ${id}:`, error)
return {
id: typeof id === 'number' ? id : 0,
title: '默认代码片段',
code: 'console.log("Hello World");',
type: 'js'
}
}
}
export const createSnippet = async (snippetData: Omit<Snippet, 'id' | 'viewCount' | 'codeType' | 'type' | 'posts'> & { type?: string; postIds?: number[] }): Promise<{ id: number }> => {
try {
const response = await authFetch(`${API_BASE}/admin/snippets`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(snippetData)
})
const data = await parseApiResponse<{ id: number }>(response)
return data || { id: 0 }
} catch (error) {
console.error('Create snippet error:', error)
throw error
}
}
export const updateSnippet = async (id: number | string, snippetData: Omit<Snippet, 'id' | 'viewCount' | 'codeType' | 'type' | 'posts'> & { type?: string; postIds?: number[] }): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/snippets/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(snippetData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update snippet error:', error)
throw error
}
}
export const deleteSnippet = async (id: number | string): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/snippets/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete snippet error:', error)
throw error
}
}
// 系统配置API
export const getSettings = async (): Promise<Setting[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/settings`, {
headers: getAuthHeaders()
})
const data = await parseApiResponse<Setting[]>(response)
if (!data) {
return []
}
return Array.isArray(data) ? data : []
} catch (error) {
console.error('Get settings error:', error)
throw error
}
}
export const createSetting = async (settingData: Omit<Setting, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/settings`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(settingData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Create setting error:', error)
throw error
}
}
export const updateSetting = async (settingData: Omit<Setting, 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/settings`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(settingData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update setting error:', error)
throw error
}
}
export const deleteSetting = async (keyName: string): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/settings/${keyName}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete setting error:', error)
throw error
}
}
// 公开设置API前端使用无需认证
export interface PublicSettings {
site_title?: string
site_description?: string
site_author?: string
site_keywords?: string
visible_menus?: string
posts_per_page?: string
}
export const getPublicSettings = async (): Promise<PublicSettings> => {
try {
const response = await fetch(`${API_BASE}/settings`)
return await parseApiResponse(response) || {}
} catch (error) {
console.error('Get public settings error:', error)
// 返回空对象,前端使用默认值
return {}
}
}
// 操作日志筛选参数
export interface OperationLogFilters {
action?: string
method?: string
status?: number
userId?: number
startDate?: string
endDate?: string
}
// 操作日志API
export const getOperationLogs = async (
page: number = 1,
pageSize: number = 10,
filters: OperationLogFilters = {}
): Promise<PaginationResponse<OperationLog>> => {
try {
const qs = new URLSearchParams({ page: String(page), pageSize: String(pageSize) })
if (filters.action) qs.set('action', filters.action)
if (filters.method) qs.set('method', filters.method)
if (filters.status) qs.set('status', String(filters.status))
if (filters.userId) qs.set('userId', String(filters.userId))
if (filters.startDate) qs.set('startDate', filters.startDate)
if (filters.endDate) qs.set('endDate', filters.endDate)
const response = await authFetch(`${API_BASE}/admin/operation-logs?${qs}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
} catch (error) {
console.error('Get operation logs error:', error)
throw error
}
}
// 访问日志类型
export interface AccessLog {
id: number
ip: string
userAgent: string
path: string
method: string
statusCode: number
responseTime: number
region: string
createdAt: string
}
// 访问日志筛选参数
export interface AccessLogFilters {
path?: string
region?: string
startDate?: string
endDate?: string
}
// 访问日志API
export const getAccessLogs = async (
page: number = 1,
pageSize: number = 10,
filters: AccessLogFilters = {}
): Promise<PaginationResponse<AccessLog>> => {
try {
const qs = new URLSearchParams({ page: String(page), pageSize: String(pageSize) })
if (filters.path) qs.set('path', filters.path)
if (filters.region) qs.set('region', filters.region)
if (filters.startDate) qs.set('startDate', filters.startDate)
if (filters.endDate) qs.set('endDate', filters.endDate)
const response = await authFetch(`${API_BASE}/admin/access-logs?${qs}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
} catch (error) {
console.error('Get access logs error:', error)
throw error
}
}
// 文章访问记录类型(来自 user_access_logs 表)
export interface PostAccessLog {
id: number
userId: number
ip: string
region: string
articleId: number
createdAt: string
}
// 获取指定文章的访问记录
export const getPostAccessLogs = async (postId: number, page: number = 1, pageSize: number = 20): Promise<PaginationResponse<PostAccessLog>> => {
try {
const response = await authFetch(`${API_BASE}/admin/posts/${postId}/access-logs?page=${page}&pageSize=${pageSize}`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response)
} catch (error) {
console.error('Get post access logs error:', error)
throw error
}
}
// 仪表盘数据类型
export interface DashboardStats {
users: number
posts: number
works: number
snippets: number
}
// 最近活动类型
export interface RecentActivity {
id: number
icon: string
text: string
time: string
}
// 仪表盘API
export const getDashboardStats = async (startDate?: string, endDate?: string): Promise<DashboardStats> => {
try {
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()}`
return await authFetchJson<DashboardStats>(url, {
headers: getAuthHeaders()
})
} catch (error) {
console.error('Get dashboard stats error:', error)
throw error
}
}
// 获取最近活动
export const getRecentActivities = async (): Promise<RecentActivity[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/dashboard/activities`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
console.error('Get recent activities error:', error)
throw error
}
}
// 关于页面相关类型
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 {
const result = await fetchJson<AboutProfile>(`${API_BASE}/about`)
if (!result) {
throw new Error('个人资料数据为空')
}
// 确保 techStack 和 experiences 始终是数组
if (!Array.isArray(result.techStack)) {
result.techStack = []
}
if (!Array.isArray(result.experiences)) {
result.experiences = []
}
return result
} catch (error) {
console.error('Fetch about profile error:', error)
throw error
}
}
export const getAdminAboutProfiles = async (): Promise<AboutProfile[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/about`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} 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 {
const response = await authFetch(`${API_BASE}/admin/about`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(profileData)
})
await parseApiResponse(response)
} 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 {
const response = await authFetch(`${API_BASE}/admin/about/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(profileData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update about profile error:', error)
throw error
}
}
export const deleteAboutProfile = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/about/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete about profile error:', error)
throw error
}
}
// 客户评价相关类型
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`)
return await parseApiResponse(response) || []
} catch (error) {
console.error('Fetch testimonials error:', error)
throw error
}
}
export const createTestimonial = async (data: Omit<Testimonial, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/testimonials`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} 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 {
const response = await authFetch(`${API_BASE}/admin/testimonials/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update testimonial error:', error)
throw error
}
}
export const deleteTestimonial = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/testimonials/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} 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`)
return await parseApiResponse(response) || []
} catch (error) {
console.error('Fetch partners error:', error)
throw error
}
}
export const createPartner = async (data: Omit<Partner, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/partners`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} 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 {
const response = await authFetch(`${API_BASE}/admin/partners/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update partner error:', error)
throw error
}
}
export const deletePartner = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/partners/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete partner error:', error)
throw error
}
}
// 咨询相关类型
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
}
// OSS配置相关类型
export interface OSSConfig {
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 {
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
}
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 {
await fetchJson(`${API_BASE}/inquiries`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
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`)
return await parseApiResponse(response) || []
} catch (error) {
console.error('Fetch email suffixes error:', error)
throw error
}
}
export const fetchInquiries = async (): Promise<Inquiry[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/inquiries`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
console.error('Fetch inquiries error:', error)
throw error
}
}
// OSS配置相关API
export const getOSSConfigs = async (): Promise<OSSConfig[]> => {
try {
const response = await authFetch(`${API_BASE}/admin/oss-configs`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
console.error('Get OSS configs error:', error)
throw error
}
}
export const createOSSConfig = async (configData: Omit<OSSConfig, 'id' | 'createdAt' | 'updatedAt'>): Promise<OSSConfig> => {
try {
const response = await authFetch(`${API_BASE}/admin/oss-configs`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(configData)
})
return await parseApiResponse(response)
} 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 {
const response = await authFetch(`${API_BASE}/admin/oss-configs/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(configData)
})
return await parseApiResponse(response)
} catch (error) {
console.error('Update OSS config error:', error)
throw error
}
}
export const deleteOSSConfig = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/oss-configs/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete OSS config error:', error)
throw error
}
}
// 附件相关类型
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
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() : ''}`
const data = await authFetchJson<PaginationResponse<Attachment>>(url, {
headers: getAuthHeaders()
})
return {
list: data?.list || [],
total: data?.total || 0,
page: data?.page || 1,
size: data?.size || 20
}
} catch (error) {
console.error('Get admin attachments error:', error)
throw error
}
}
export const updateAttachment = async (id: number, data: { categoryId?: number | null }): Promise<Attachment> => {
try {
return await authFetchJson<Attachment>(`${API_BASE}/admin/attachments/${id}`, {
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 {
const response = await authFetch(`${API_BASE}/admin/attachment-categories`, {
headers: getAuthHeaders()
})
return await parseApiResponse(response) || []
} catch (error) {
console.error('Get attachment categories error:', error)
throw error
}
}
export const createAttachmentCategory = async (categoryData: Omit<AttachmentCategory, 'id' | 'createdAt' | 'updatedAt'>): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/attachment-categories`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(categoryData)
})
await parseApiResponse(response)
} 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 {
const response = await authFetch(`${API_BASE}/admin/attachment-categories/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(categoryData)
})
await parseApiResponse(response)
} catch (error) {
console.error('Update attachment category error:', error)
throw error
}
}
export const deleteAttachmentCategory = async (id: number): Promise<void> => {
try {
const response = await authFetch(`${API_BASE}/admin/attachment-categories/${id}`, {
method: 'DELETE',
headers: getAuthHeaders()
})
await parseApiResponse(response)
} catch (error) {
console.error('Delete attachment category error:', error)
throw error
}
}