21 changed files with 118 additions and 2447 deletions
@ -1,145 +0,0 @@ |
|||
/** |
|||
* 租户 API 使用示例 |
|||
* 展示如何使用带租户 ID 的 API 请求 |
|||
*/ |
|||
|
|||
import { request } from '@/api'; |
|||
import type { ApiResult, PageResult, PageParam } from '@/api'; |
|||
|
|||
// ==================== 示例数据类型 ====================
|
|||
|
|||
interface User { |
|||
id: number; |
|||
name: string; |
|||
email: string; |
|||
tenantId: string; |
|||
createTime: string; |
|||
} |
|||
|
|||
interface CreateUserData { |
|||
name: string; |
|||
email: string; |
|||
password: string; |
|||
} |
|||
|
|||
interface UpdateUserData { |
|||
name?: string; |
|||
email?: string; |
|||
} |
|||
|
|||
// ==================== API 使用示例 ====================
|
|||
|
|||
/** |
|||
* 获取用户列表 - GET 请求示例 |
|||
* 租户 ID 会自动添加到查询参数中 |
|||
*/ |
|||
export const getUserList = async (params?: PageParam): Promise<ApiResult<PageResult<User>>> => { |
|||
return request.get<PageResult<User>>('/users', params); |
|||
}; |
|||
|
|||
/** |
|||
* 获取用户详情 - GET 请求示例 |
|||
* 租户 ID 会自动添加到查询参数中 |
|||
*/ |
|||
export const getUserById = async (id: number): Promise<ApiResult<User>> => { |
|||
return request.get<User>(`/users/${id}`); |
|||
}; |
|||
|
|||
/** |
|||
* 创建用户 - POST 请求示例 |
|||
* 租户 ID 会自动添加到请求体中 |
|||
*/ |
|||
export const createUser = async (data: CreateUserData): Promise<ApiResult<User>> => { |
|||
return request.post<User>('/users', data); |
|||
}; |
|||
|
|||
/** |
|||
* 更新用户 - PUT 请求示例 |
|||
* 租户 ID 会自动添加到请求体中 |
|||
*/ |
|||
export const updateUser = async (id: number, data: UpdateUserData): Promise<ApiResult<User>> => { |
|||
return request.put<User>(`/users/${id}`, data); |
|||
}; |
|||
|
|||
/** |
|||
* 部分更新用户 - PATCH 请求示例 |
|||
* 租户 ID 会自动添加到请求体中 |
|||
*/ |
|||
export const patchUser = async (id: number, data: Partial<UpdateUserData>): Promise<ApiResult<User>> => { |
|||
return request.patch<User>(`/users/${id}`, data); |
|||
}; |
|||
|
|||
/** |
|||
* 删除用户 - DELETE 请求示例 |
|||
* 租户 ID 会自动添加到请求头中 |
|||
*/ |
|||
export const deleteUser = async (id: number): Promise<ApiResult<void>> => { |
|||
return request.delete<void>(`/users/${id}`); |
|||
}; |
|||
|
|||
// ==================== 使用示例 ====================
|
|||
|
|||
/** |
|||
* 组件中使用示例 |
|||
*/ |
|||
export const exampleUsage = async () => { |
|||
try { |
|||
// 获取用户列表
|
|||
const userListResult = await getUserList({ |
|||
page: 1, |
|||
limit: 10, |
|||
keywords: '张三' |
|||
}); |
|||
|
|||
if (userListResult.code === 0) { |
|||
console.log('用户列表:', userListResult.data?.list); |
|||
} |
|||
|
|||
// 创建用户
|
|||
const createResult = await createUser({ |
|||
name: '新用户', |
|||
email: 'newuser@example.com', |
|||
password: '123456' |
|||
}); |
|||
|
|||
if (createResult.code === 0) { |
|||
console.log('创建成功:', createResult.data); |
|||
} |
|||
|
|||
// 更新用户
|
|||
if (createResult.data?.id) { |
|||
const updateResult = await updateUser(createResult.data.id, { |
|||
name: '更新后的用户名' |
|||
}); |
|||
|
|||
if (updateResult.code === 0) { |
|||
console.log('更新成功:', updateResult.data); |
|||
} |
|||
} |
|||
|
|||
} catch (error) { |
|||
console.error('API 请求失败:', error); |
|||
} |
|||
}; |
|||
|
|||
// ==================== 注意事项 ====================
|
|||
|
|||
/** |
|||
* 租户 ID 自动添加说明: |
|||
* |
|||
* 1. GET 请求:租户 ID 会自动添加到查询参数中 |
|||
* 例如:/users?page=1&limit=10&tenantId=1001 |
|||
* |
|||
* 2. POST/PUT/PATCH 请求:租户 ID 会自动添加到请求体中 |
|||
* 例如:{ name: '用户名', email: 'email@example.com', tenantId: '10001' } |
|||
* |
|||
* 3. 所有请求:租户 ID 会自动添加到请求头中 |
|||
* 例如:TenantId: 1001 |
|||
* |
|||
* 4. 租户 ID 从配置文件中读取: |
|||
* - 环境变量:NEXT_PUBLIC_TENANT_ID |
|||
* - 配置文件:APP_CONFIG.TENANT_ID |
|||
* - 默认值:'10001' |
|||
* |
|||
* 5. 如果需要覆盖租户 ID,可以在请求参数或数据中显式指定 |
|||
*/ |
@ -1,390 +0,0 @@ |
|||
/** |
|||
* 文章相关 API |
|||
*/ |
|||
|
|||
import { request, type ApiResult, type PageResult, type PageParam } from '@/api'; |
|||
|
|||
// ==================== 类型定义 ====================
|
|||
|
|||
/** |
|||
* 文章信息 |
|||
*/ |
|||
export interface Article { |
|||
id: number; |
|||
title: string; |
|||
content: string; |
|||
excerpt?: string; |
|||
thumbnail?: string; |
|||
author: string; |
|||
authorId: number; |
|||
categoryId?: number; |
|||
categoryName?: string; |
|||
tags?: string[]; |
|||
status: 'draft' | 'published' | 'archived'; |
|||
viewCount: number; |
|||
likeCount: number; |
|||
commentCount: number; |
|||
isTop: boolean; |
|||
isRecommend: boolean; |
|||
publishTime?: string; |
|||
createTime: string; |
|||
updateTime: string; |
|||
seoTitle?: string; |
|||
seoKeywords?: string; |
|||
seoDescription?: string; |
|||
} |
|||
|
|||
/** |
|||
* 文章分类 |
|||
*/ |
|||
export interface ArticleCategory { |
|||
id: number; |
|||
name: string; |
|||
slug: string; |
|||
description?: string; |
|||
parentId?: number; |
|||
sortOrder: number; |
|||
articleCount: number; |
|||
status: number; |
|||
createTime: string; |
|||
children?: ArticleCategory[]; |
|||
} |
|||
|
|||
/** |
|||
* 文章标签 |
|||
*/ |
|||
export interface ArticleTag { |
|||
id: number; |
|||
name: string; |
|||
slug: string; |
|||
color?: string; |
|||
articleCount: number; |
|||
createTime: string; |
|||
} |
|||
|
|||
/** |
|||
* 文章查询参数 |
|||
*/ |
|||
export interface ArticleSearchParams extends PageParam { |
|||
title?: string; |
|||
categoryId?: number; |
|||
authorId?: number; |
|||
status?: 'draft' | 'published' | 'archived'; |
|||
isTop?: boolean; |
|||
isRecommend?: boolean; |
|||
tags?: string[]; |
|||
startTime?: string; |
|||
endTime?: string; |
|||
} |
|||
|
|||
/** |
|||
* 文章创建参数 |
|||
*/ |
|||
export interface CreateArticleParams { |
|||
title: string; |
|||
content: string; |
|||
excerpt?: string; |
|||
thumbnail?: string; |
|||
categoryId?: number; |
|||
tags?: string[]; |
|||
status?: 'draft' | 'published'; |
|||
isTop?: boolean; |
|||
isRecommend?: boolean; |
|||
publishTime?: string; |
|||
seoTitle?: string; |
|||
seoKeywords?: string; |
|||
seoDescription?: string; |
|||
} |
|||
|
|||
/** |
|||
* 文章更新参数 |
|||
*/ |
|||
export interface UpdateArticleParams extends CreateArticleParams { |
|||
id: number; |
|||
} |
|||
|
|||
// ==================== API 方法 ====================
|
|||
|
|||
/** |
|||
* 文章 API 类 |
|||
*/ |
|||
export class ArticleApi { |
|||
/** |
|||
* 分页查询文章列表 |
|||
*/ |
|||
static async getArticles(params?: ArticleSearchParams): Promise<ApiResult<PageResult<Article>>> { |
|||
return request.get<PageResult<Article>>('/cms/cms-article/page', params); |
|||
} |
|||
|
|||
/** |
|||
* 获取推荐文章列表 |
|||
*/ |
|||
static async getRecommendArticles(limit = 6): Promise<ApiResult<Article[]>> { |
|||
return request.get<Article[]>('/cms/cms-article/recommend', { limit }); |
|||
} |
|||
|
|||
/** |
|||
* 获取热门文章列表 |
|||
*/ |
|||
static async getHotArticles(limit = 10): Promise<ApiResult<Article[]>> { |
|||
return request.get<Article[]>('/cms/cms-article/hot', { limit }); |
|||
} |
|||
|
|||
/** |
|||
* 获取最新文章列表 |
|||
*/ |
|||
static async getLatestArticles(limit = 10): Promise<ApiResult<Article[]>> { |
|||
return request.get<Article[]>('/cms/cms-article', { limit }); |
|||
} |
|||
|
|||
/** |
|||
* 根据 ID 获取文章详情 |
|||
*/ |
|||
static async getArticleById(id: number): Promise<ApiResult<Article>> { |
|||
return request.get<Article>(`/cms/cms-article/${id}`); |
|||
} |
|||
|
|||
/** |
|||
* 根据分类获取文章列表 |
|||
*/ |
|||
static async getArticlesByCategory( |
|||
categoryId: number, |
|||
params?: Omit<ArticleSearchParams, 'categoryId'> |
|||
): Promise<ApiResult<PageResult<Article>>> { |
|||
return request.get<PageResult<Article>>('/cms/cms-article/page', { |
|||
...params, |
|||
categoryId, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 根据标签获取文章列表 |
|||
*/ |
|||
static async getArticlesByTag( |
|||
tag: string, |
|||
params?: Omit<ArticleSearchParams, 'tags'> |
|||
): Promise<ApiResult<PageResult<Article>>> { |
|||
return request.get<PageResult<Article>>('/cms/cms-article/page', { |
|||
...params, |
|||
tags: [tag], |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 搜索文章 |
|||
*/ |
|||
static async searchArticles( |
|||
keyword: string, |
|||
params?: ArticleSearchParams |
|||
): Promise<ApiResult<PageResult<Article>>> { |
|||
return request.get<PageResult<Article>>('/cms/cms-article/search', { |
|||
...params, |
|||
keywords: keyword, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 获取相关文章 |
|||
*/ |
|||
static async getRelatedArticles(id: number, limit = 5): Promise<ApiResult<Article[]>> { |
|||
return request.get<Article[]>(`/cms/cms-article/${id}/related`, { limit }); |
|||
} |
|||
|
|||
/** |
|||
* 增加文章浏览量 |
|||
*/ |
|||
static async increaseViewCount(id: number): Promise<ApiResult<void>> { |
|||
return request.post<void>(`/cms/cms-article/${id}/view`); |
|||
} |
|||
|
|||
/** |
|||
* 点赞文章 |
|||
*/ |
|||
static async likeArticle(id: number): Promise<ApiResult<void>> { |
|||
return request.post<void>(`/cms/cms-article/${id}/like`); |
|||
} |
|||
|
|||
/** |
|||
* 取消点赞文章 |
|||
*/ |
|||
static async unlikeArticle(id: number): Promise<ApiResult<void>> { |
|||
return request.delete<void>(`/cms/cms-article/${id}/like`); |
|||
} |
|||
|
|||
/** |
|||
* 创建文章(管理员功能) |
|||
*/ |
|||
static async createArticle(params: CreateArticleParams): Promise<ApiResult<string>> { |
|||
return request.post<string>('/cms/cms-article', params); |
|||
} |
|||
|
|||
/** |
|||
* 更新文章(管理员功能) |
|||
*/ |
|||
static async updateArticle(params: UpdateArticleParams): Promise<ApiResult<string>> { |
|||
return request.put<string>('/cms/cms-article', params); |
|||
} |
|||
|
|||
/** |
|||
* 删除文章(管理员功能) |
|||
*/ |
|||
static async deleteArticle(id: number): Promise<ApiResult<string>> { |
|||
return request.delete<string>(`/cms/cms-article/${id}`); |
|||
} |
|||
|
|||
/** |
|||
* 批量删除文章(管理员功能) |
|||
*/ |
|||
static async batchDeleteArticles(ids: number[]): Promise<ApiResult<string>> { |
|||
return request.post<string>('/cms/cms-article/batch-delete', ids); |
|||
} |
|||
|
|||
/** |
|||
* 发布文章(管理员功能) |
|||
*/ |
|||
static async publishArticle(id: number): Promise<ApiResult<string>> { |
|||
return request.post<string>(`/cms/cms-article/${id}/publish`); |
|||
} |
|||
|
|||
/** |
|||
* 取消发布文章(管理员功能) |
|||
*/ |
|||
static async unpublishArticle(id: number): Promise<ApiResult<string>> { |
|||
return request.post<string>(`/cms/cms-article/${id}/unpublish`); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 文章分类 API 类 |
|||
*/ |
|||
export class ArticleCategoryApi { |
|||
/** |
|||
* 获取所有分类列表 |
|||
*/ |
|||
static async getCategories(): Promise<ApiResult<ArticleCategory[]>> { |
|||
return request.get<ArticleCategory[]>('/cms/cms-article-category'); |
|||
} |
|||
|
|||
/** |
|||
* 获取分类树 |
|||
*/ |
|||
static async getCategoryTree(): Promise<ApiResult<ArticleCategory[]>> { |
|||
return request.get<ArticleCategory[]>('/cms/cms-article-category/tree'); |
|||
} |
|||
|
|||
/** |
|||
* 根据 ID 获取分类详情 |
|||
*/ |
|||
static async getCategoryById(id: number): Promise<ApiResult<ArticleCategory>> { |
|||
return request.get<ArticleCategory>(`/cms/cms-article-category/${id}`); |
|||
} |
|||
|
|||
/** |
|||
* 创建分类(管理员功能) |
|||
*/ |
|||
static async createCategory(params: { |
|||
name: string; |
|||
slug?: string; |
|||
description?: string; |
|||
parentId?: number; |
|||
sortOrder?: number; |
|||
}): Promise<ApiResult<string>> { |
|||
return request.post<string>('/cms/cms-article-category', params); |
|||
} |
|||
|
|||
/** |
|||
* 更新分类(管理员功能) |
|||
*/ |
|||
static async updateCategory(params: { |
|||
id: number; |
|||
name?: string; |
|||
slug?: string; |
|||
description?: string; |
|||
parentId?: number; |
|||
sortOrder?: number; |
|||
}): Promise<ApiResult<string>> { |
|||
return request.put<string>('/cms/cms-article-category', params); |
|||
} |
|||
|
|||
/** |
|||
* 删除分类(管理员功能) |
|||
*/ |
|||
static async deleteCategory(id: number): Promise<ApiResult<string>> { |
|||
return request.delete<string>(`/cms/cms-article-category/${id}`); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 文章标签 API 类 |
|||
*/ |
|||
export class ArticleTagApi { |
|||
/** |
|||
* 获取所有标签列表 |
|||
*/ |
|||
static async getTags(): Promise<ApiResult<ArticleTag[]>> { |
|||
return request.get<ArticleTag[]>('/cms/cms-article-tag'); |
|||
} |
|||
|
|||
/** |
|||
* 获取热门标签 |
|||
*/ |
|||
static async getHotTags(limit = 20): Promise<ApiResult<ArticleTag[]>> { |
|||
return request.get<ArticleTag[]>('/cms/cms-article-tag/hot', { limit }); |
|||
} |
|||
|
|||
/** |
|||
* 根据 ID 获取标签详情 |
|||
*/ |
|||
static async getTagById(id: number): Promise<ApiResult<ArticleTag>> { |
|||
return request.get<ArticleTag>(`/cms/cms-article-tag/${id}`); |
|||
} |
|||
|
|||
/** |
|||
* 搜索标签 |
|||
*/ |
|||
static async searchTags(keyword: string): Promise<ApiResult<ArticleTag[]>> { |
|||
return request.get<ArticleTag[]>('/cms/cms-article-tag/search', { keyword }); |
|||
} |
|||
} |
|||
|
|||
// ==================== 便捷方法导出 ====================
|
|||
|
|||
export const { |
|||
getArticles, |
|||
getRecommendArticles, |
|||
getHotArticles, |
|||
getLatestArticles, |
|||
getArticleById, |
|||
getArticlesByCategory, |
|||
getArticlesByTag, |
|||
searchArticles, |
|||
getRelatedArticles, |
|||
increaseViewCount, |
|||
likeArticle, |
|||
unlikeArticle, |
|||
createArticle, |
|||
updateArticle, |
|||
deleteArticle, |
|||
batchDeleteArticles, |
|||
publishArticle, |
|||
unpublishArticle, |
|||
} = ArticleApi; |
|||
|
|||
export const { |
|||
getCategories, |
|||
getCategoryTree, |
|||
getCategoryById, |
|||
createCategory, |
|||
updateCategory, |
|||
deleteCategory, |
|||
} = ArticleCategoryApi; |
|||
|
|||
export const { |
|||
getTags, |
|||
getHotTags, |
|||
getTagById, |
|||
searchTags, |
|||
} = ArticleTagApi; |
|||
|
|||
// 默认导出
|
|||
export default ArticleApi; |
@ -1,246 +0,0 @@ |
|||
/** |
|||
* 认证相关 API - 现代化封装示例 |
|||
*/ |
|||
|
|||
import { request, type ApiResult, type PageResult } from '@/api'; |
|||
|
|||
// ==================== 类型定义 ====================
|
|||
|
|||
/** |
|||
* 登录参数 |
|||
*/ |
|||
export interface LoginParams { |
|||
username: string; |
|||
password: string; |
|||
tenantId?: number; |
|||
remember?: boolean; |
|||
phone?: string; |
|||
code?: string; |
|||
} |
|||
|
|||
/** |
|||
* 登录结果 |
|||
*/ |
|||
export interface LoginResult { |
|||
access_token: string; |
|||
refresh_token?: string; |
|||
user: User; |
|||
expiresIn?: number; |
|||
} |
|||
|
|||
/** |
|||
* 用户信息 |
|||
*/ |
|||
export interface User { |
|||
userId: number; |
|||
username: string; |
|||
email?: string; |
|||
phone?: string; |
|||
avatar?: string; |
|||
nickname?: string; |
|||
realName?: string; |
|||
status: number; |
|||
tenantId: number; |
|||
companyId?: number; |
|||
merchantId?: number; |
|||
merchantName?: string; |
|||
roles?: Role[]; |
|||
permissions?: string[]; |
|||
createTime?: string; |
|||
updateTime?: string; |
|||
} |
|||
|
|||
/** |
|||
* 角色信息 |
|||
*/ |
|||
export interface Role { |
|||
roleId: number; |
|||
roleName: string; |
|||
roleCode: string; |
|||
description?: string; |
|||
status: number; |
|||
} |
|||
|
|||
/** |
|||
* 验证码结果 |
|||
*/ |
|||
export interface CaptchaResult { |
|||
base64: string; |
|||
text: string; |
|||
} |
|||
|
|||
// ==================== API 方法 ====================
|
|||
|
|||
/** |
|||
* 认证 API 类 |
|||
*/ |
|||
export class AuthApi { |
|||
/** |
|||
* 用户登录 |
|||
*/ |
|||
static async login(params: LoginParams): Promise<ApiResult<LoginResult>> { |
|||
return request.post<LoginResult>('/loginByUserId', params); |
|||
} |
|||
|
|||
/** |
|||
* 短信登录 |
|||
*/ |
|||
static async loginBySms(params: LoginParams): Promise<ApiResult<LoginResult>> { |
|||
return request.post<LoginResult>('/loginBySms', params); |
|||
} |
|||
|
|||
/** |
|||
* 用户注册 |
|||
*/ |
|||
static async register(params: Partial<User>): Promise<ApiResult<string>> { |
|||
return request.post<string>('/register', params); |
|||
} |
|||
|
|||
/** |
|||
* 用户登出 |
|||
*/ |
|||
static async logout(): Promise<ApiResult<string>> { |
|||
return request.post<string>('/logout'); |
|||
} |
|||
|
|||
/** |
|||
* 获取验证码 |
|||
*/ |
|||
static async getCaptcha(): Promise<ApiResult<CaptchaResult>> { |
|||
return request.get<CaptchaResult>('/captcha'); |
|||
} |
|||
|
|||
/** |
|||
* 发送短信验证码 |
|||
*/ |
|||
static async sendSmsCaptcha(params: { phone: string }): Promise<ApiResult<string>> { |
|||
return request.post<string>('/sendSmsCaptcha', params); |
|||
} |
|||
|
|||
/** |
|||
* 获取当前用户信息 |
|||
*/ |
|||
static async getCurrentUser(): Promise<ApiResult<User>> { |
|||
return request.get<User>('/auth/user'); |
|||
} |
|||
|
|||
/** |
|||
* 更新用户信息 |
|||
*/ |
|||
static async updateProfile(params: Partial<User>): Promise<ApiResult<User>> { |
|||
return request.put<User>('/auth/user', params); |
|||
} |
|||
|
|||
/** |
|||
* 修改密码 |
|||
*/ |
|||
static async changePassword(params: { |
|||
oldPassword: string; |
|||
newPassword: string; |
|||
confirmPassword: string; |
|||
}): Promise<ApiResult<string>> { |
|||
return request.post<string>('/auth/change-password', params); |
|||
} |
|||
|
|||
/** |
|||
* 忘记密码 |
|||
*/ |
|||
static async forgotPassword(params: { email: string }): Promise<ApiResult<string>> { |
|||
return request.post<string>('/auth/forgot-password', params); |
|||
} |
|||
|
|||
/** |
|||
* 重置密码 |
|||
*/ |
|||
static async resetPassword(params: { |
|||
token: string; |
|||
password: string; |
|||
confirmPassword: string; |
|||
}): Promise<ApiResult<string>> { |
|||
return request.post<string>('/auth/reset-password', params); |
|||
} |
|||
|
|||
/** |
|||
* 刷新 token |
|||
*/ |
|||
static async refreshToken(refreshToken: string): Promise<ApiResult<LoginResult>> { |
|||
return request.post<LoginResult>('/auth/refresh', { refreshToken }); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户菜单 |
|||
*/ |
|||
static async getUserMenus(): Promise<ApiResult<Menu[]>> { |
|||
return request.get<Menu[]>('/auth/menus'); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户权限 |
|||
*/ |
|||
static async getUserPermissions(): Promise<ApiResult<string[]>> { |
|||
return request.get<string[]>('/auth/permissions'); |
|||
} |
|||
|
|||
/** |
|||
* 检查用户名是否可用 |
|||
*/ |
|||
static async checkUsername(username: string): Promise<ApiResult<boolean>> { |
|||
return request.get<boolean>('/auth/check-username', { username }); |
|||
} |
|||
|
|||
/** |
|||
* 检查邮箱是否可用 |
|||
*/ |
|||
static async checkEmail(email: string): Promise<ApiResult<boolean>> { |
|||
return request.get<boolean>('/auth/check-email', { email }); |
|||
} |
|||
|
|||
/** |
|||
* 检查手机号是否可用 |
|||
*/ |
|||
static async checkPhone(phone: string): Promise<ApiResult<boolean>> { |
|||
return request.get<boolean>('/auth/check-phone', { phone }); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 菜单信息 |
|||
*/ |
|||
export interface Menu { |
|||
menuId: number; |
|||
menuName: string; |
|||
menuCode: string; |
|||
parentId?: number; |
|||
path?: string; |
|||
component?: string; |
|||
icon?: string; |
|||
sortNumber: number; |
|||
menuType: number; |
|||
status: number; |
|||
children?: Menu[]; |
|||
} |
|||
|
|||
// ==================== 便捷方法导出 ====================
|
|||
|
|||
export const { |
|||
login, |
|||
loginBySms, |
|||
register, |
|||
logout, |
|||
getCaptcha, |
|||
sendSmsCaptcha, |
|||
getCurrentUser, |
|||
updateProfile, |
|||
changePassword, |
|||
forgotPassword, |
|||
resetPassword, |
|||
refreshToken, |
|||
getUserMenus, |
|||
getUserPermissions, |
|||
checkUsername, |
|||
checkEmail, |
|||
checkPhone, |
|||
} = AuthApi; |
|||
|
|||
// 默认导出
|
|||
export default AuthApi; |
@ -1,284 +0,0 @@ |
|||
/** |
|||
* 用户管理相关 API - 现代化封装示例 |
|||
*/ |
|||
|
|||
import { request, type ApiResult, type PageResult, type PageParam } from '@/api'; |
|||
import type { User } from './auth'; |
|||
|
|||
// ==================== 类型定义 ====================
|
|||
|
|||
/** |
|||
* 用户查询参数 |
|||
*/ |
|||
export interface UserSearchParams extends PageParam { |
|||
username?: string; |
|||
email?: string; |
|||
phone?: string; |
|||
status?: number; |
|||
roleId?: number; |
|||
departmentId?: number; |
|||
createTimeStart?: string; |
|||
createTimeEnd?: string; |
|||
} |
|||
|
|||
/** |
|||
* 用户创建参数 |
|||
*/ |
|||
export interface CreateUserParams { |
|||
username: string; |
|||
password: string; |
|||
email?: string; |
|||
phone?: string; |
|||
nickname?: string; |
|||
realName?: string; |
|||
avatar?: string; |
|||
status?: number; |
|||
roleIds?: number[]; |
|||
departmentId?: number; |
|||
remark?: string; |
|||
} |
|||
|
|||
/** |
|||
* 用户更新参数 |
|||
*/ |
|||
export interface UpdateUserParams { |
|||
userId: number; |
|||
username?: string; |
|||
email?: string; |
|||
phone?: string; |
|||
nickname?: string; |
|||
realName?: string; |
|||
avatar?: string; |
|||
status?: number; |
|||
roleIds?: number[]; |
|||
departmentId?: number; |
|||
remark?: string; |
|||
} |
|||
|
|||
/** |
|||
* 用户统计信息 |
|||
*/ |
|||
export interface UserStats { |
|||
total: number; |
|||
active: number; |
|||
inactive: number; |
|||
newToday: number; |
|||
newThisWeek: number; |
|||
newThisMonth: number; |
|||
} |
|||
|
|||
/** |
|||
* 用户余额信息 |
|||
*/ |
|||
export interface UserBalance { |
|||
userId: number; |
|||
balance: number; |
|||
frozenBalance: number; |
|||
totalRecharge: number; |
|||
totalConsume: number; |
|||
} |
|||
|
|||
// ==================== API 方法 ====================
|
|||
|
|||
/** |
|||
* 用户管理 API 类 |
|||
*/ |
|||
export class UserApi { |
|||
/** |
|||
* 分页查询用户列表 |
|||
*/ |
|||
static async getUsers(params?: UserSearchParams): Promise<ApiResult<PageResult<User>>> { |
|||
return request.get<PageResult<User>>('/system/user/page', params); |
|||
} |
|||
|
|||
/** |
|||
* 获取所有用户列表(不分页) |
|||
*/ |
|||
static async getAllUsers(params?: Omit<UserSearchParams, 'page' | 'limit'>): Promise<ApiResult<User[]>> { |
|||
return request.get<User[]>('/system/user', params); |
|||
} |
|||
|
|||
/** |
|||
* 根据 ID 获取用户详情 |
|||
*/ |
|||
static async getUserById(userId: number): Promise<ApiResult<User>> { |
|||
return request.get<User>(`/system/user/${userId}`); |
|||
} |
|||
|
|||
/** |
|||
* 创建用户 |
|||
*/ |
|||
static async createUser(params: CreateUserParams): Promise<ApiResult<string>> { |
|||
return request.post<string>('/system/user', params); |
|||
} |
|||
|
|||
/** |
|||
* 更新用户信息 |
|||
*/ |
|||
static async updateUser(params: UpdateUserParams): Promise<ApiResult<string>> { |
|||
return request.put<string>('/system/user', params); |
|||
} |
|||
|
|||
/** |
|||
* 删除用户 |
|||
*/ |
|||
static async deleteUser(userId: number): Promise<ApiResult<string>> { |
|||
return request.delete<string>(`/system/user/${userId}`); |
|||
} |
|||
|
|||
/** |
|||
* 批量删除用户 |
|||
*/ |
|||
static async batchDeleteUsers(userIds: number[]): Promise<ApiResult<string>> { |
|||
return request.delete<string>('/system/user/batch', { data: userIds }); |
|||
} |
|||
|
|||
/** |
|||
* 更新用户状态 |
|||
*/ |
|||
static async updateUserStatus(userId: number, status: number): Promise<ApiResult<string>> { |
|||
return request.put<string>('/system/user/status', { userId, status }); |
|||
} |
|||
|
|||
/** |
|||
* 重置用户密码 |
|||
*/ |
|||
static async resetUserPassword(userId: number, password = '123456'): Promise<ApiResult<string>> { |
|||
return request.put<string>('/system/user/password', { userId, password }); |
|||
} |
|||
|
|||
/** |
|||
* 导入用户 |
|||
*/ |
|||
static async importUsers(file: File): Promise<ApiResult<string>> { |
|||
const formData = new FormData(); |
|||
formData.append('file', file); |
|||
return request.post<string>('/system/user/import', formData); |
|||
} |
|||
|
|||
/** |
|||
* 导出用户 |
|||
*/ |
|||
static async exportUsers(params?: UserSearchParams): Promise<ApiResult<{ downloadUrl: string }>> { |
|||
return request.post<{ downloadUrl: string }>('/system/user/export', params); |
|||
} |
|||
|
|||
/** |
|||
* 检查用户是否存在 |
|||
*/ |
|||
static async checkUserExists(field: string, value: string, userId?: number): Promise<ApiResult<boolean>> { |
|||
return request.get<boolean>('/system/user/existence', { field, value, id: userId }); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户统计信息 |
|||
*/ |
|||
static async getUserStats(): Promise<ApiResult<UserStats>> { |
|||
return request.get<UserStats>('/system/user/stats'); |
|||
} |
|||
|
|||
/** |
|||
* 统计用户余额 |
|||
*/ |
|||
static async getUserBalance(params?: UserSearchParams): Promise<ApiResult<UserBalance[]>> { |
|||
return request.get<UserBalance[]>('/system/user/countUserBalance', params); |
|||
} |
|||
|
|||
/** |
|||
* 获取员工列表 |
|||
*/ |
|||
static async getStaffs(params?: UserSearchParams): Promise<ApiResult<User[]>> { |
|||
return request.get<User[]>('/system/user/staffs', params); |
|||
} |
|||
|
|||
/** |
|||
* 获取管理员列表 |
|||
*/ |
|||
static async getAdmins(params?: UserSearchParams): Promise<ApiResult<User[]>> { |
|||
return request.get<User[]>('/system/user/listAdminsByPhoneAll', params); |
|||
} |
|||
|
|||
/** |
|||
* 更新用户推荐状态 |
|||
*/ |
|||
static async updateUserRecommend(params: { |
|||
userId: number; |
|||
isRecommend: boolean; |
|||
}): Promise<ApiResult<string>> { |
|||
return request.put<string>('/system/user/recommend', params); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户登录日志 |
|||
*/ |
|||
static async getUserLoginLogs(params: { |
|||
userId?: number; |
|||
page?: number; |
|||
limit?: number; |
|||
startTime?: string; |
|||
endTime?: string; |
|||
}): Promise<ApiResult<PageResult<{ |
|||
id: number; |
|||
userId: number; |
|||
username: string; |
|||
ip: string; |
|||
location: string; |
|||
userAgent: string; |
|||
loginTime: string; |
|||
status: number; |
|||
}>>> { |
|||
return request.get('/system/user/login-logs', params); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户操作日志 |
|||
*/ |
|||
static async getUserOperationLogs(params: { |
|||
userId?: number; |
|||
page?: number; |
|||
limit?: number; |
|||
startTime?: string; |
|||
endTime?: string; |
|||
}): Promise<ApiResult<PageResult<{ |
|||
id: number; |
|||
userId: number; |
|||
username: string; |
|||
operation: string; |
|||
method: string; |
|||
url: string; |
|||
ip: string; |
|||
location: string; |
|||
userAgent: string; |
|||
operationTime: string; |
|||
status: number; |
|||
}>>> { |
|||
return request.get('/system/user/operation-logs', params); |
|||
} |
|||
} |
|||
|
|||
// ==================== 便捷方法导出 ====================
|
|||
|
|||
export const { |
|||
getUsers, |
|||
getAllUsers, |
|||
getUserById, |
|||
createUser, |
|||
updateUser, |
|||
deleteUser, |
|||
batchDeleteUsers, |
|||
updateUserStatus, |
|||
resetUserPassword, |
|||
importUsers, |
|||
exportUsers, |
|||
checkUserExists, |
|||
getUserStats, |
|||
getUserBalance, |
|||
getStaffs, |
|||
getAdmins, |
|||
updateUserRecommend, |
|||
getUserLoginLogs, |
|||
getUserOperationLogs, |
|||
} = UserApi; |
|||
|
|||
// 默认导出
|
|||
export default UserApi; |
@ -1,288 +0,0 @@ |
|||
'use client'; |
|||
|
|||
import { useState } from 'react'; |
|||
import { AuthApi } from '@/api/modules/auth'; |
|||
import { UserApi } from '@/api/modules/user'; |
|||
import { request } from '@/api'; |
|||
import type { User, LoginResult } from '@/api/modules/auth'; |
|||
|
|||
export default function ApiExamplePage() { |
|||
const [loading, setLoading] = useState(false); |
|||
const [result, setResult] = useState<string>(''); |
|||
const [users, setUsers] = useState<User[]>([]); |
|||
const [loginResult, setLoginResult] = useState<LoginResult | null>(null); |
|||
|
|||
// 测试登录
|
|||
const testLogin = async () => { |
|||
setLoading(true); |
|||
setResult(''); |
|||
try { |
|||
const response = await AuthApi.login({ |
|||
username: 'demo', |
|||
password: 'demo123', |
|||
}); |
|||
|
|||
setLoginResult(response.data || null); |
|||
setResult(`登录成功: ${JSON.stringify(response, null, 2)}`); |
|||
} catch (error: any) { |
|||
setResult(`登录失败: ${error.message}`); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
// 测试获取用户列表
|
|||
const testGetUsers = async () => { |
|||
setLoading(true); |
|||
setResult(''); |
|||
try { |
|||
const response = await UserApi.getUsers({ |
|||
page: 1, |
|||
limit: 10, |
|||
}); |
|||
|
|||
setUsers(response.data?.list || []); |
|||
setResult(`获取用户列表成功: ${JSON.stringify(response, null, 2)}`); |
|||
} catch (error: any) { |
|||
setResult(`获取用户列表失败: ${error.message}`); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
// 测试原始请求方法
|
|||
const testRawRequest = async () => { |
|||
setLoading(true); |
|||
setResult(''); |
|||
try { |
|||
const response = await request.get('/system/user/page', { |
|||
page: 1, |
|||
limit: 5, |
|||
}); |
|||
|
|||
setResult(`原始请求成功: ${JSON.stringify(response, null, 2)}`); |
|||
} catch (error: any) { |
|||
setResult(`原始请求失败: ${error.message}`); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
// 测试获取验证码
|
|||
const testGetCaptcha = async () => { |
|||
setLoading(true); |
|||
setResult(''); |
|||
try { |
|||
const response = await AuthApi.getCaptcha(); |
|||
setResult(`获取验证码成功: ${JSON.stringify(response, null, 2)}`); |
|||
} catch (error: any) { |
|||
setResult(`获取验证码失败: ${error.message}`); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
// 测试检查用户名
|
|||
const testCheckUsername = async () => { |
|||
setLoading(true); |
|||
setResult(''); |
|||
try { |
|||
const response = await AuthApi.checkUsername('admin'); |
|||
setResult(`检查用户名成功: ${JSON.stringify(response, null, 2)}`); |
|||
} catch (error: any) { |
|||
setResult(`检查用户名失败: ${error.message}`); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
return ( |
|||
<div className="container mx-auto p-6 max-w-6xl"> |
|||
<h1 className="text-3xl font-bold mb-8">API 封装系统示例</h1> |
|||
|
|||
{/* 功能按钮 */} |
|||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-8"> |
|||
<button |
|||
onClick={testLogin} |
|||
disabled={loading} |
|||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50" |
|||
> |
|||
测试登录 |
|||
</button> |
|||
|
|||
<button |
|||
onClick={testGetUsers} |
|||
disabled={loading} |
|||
className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50" |
|||
> |
|||
获取用户列表 |
|||
</button> |
|||
|
|||
<button |
|||
onClick={testRawRequest} |
|||
disabled={loading} |
|||
className="bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50" |
|||
> |
|||
原始请求 |
|||
</button> |
|||
|
|||
<button |
|||
onClick={testGetCaptcha} |
|||
disabled={loading} |
|||
className="bg-orange-500 hover:bg-orange-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50" |
|||
> |
|||
获取验证码 |
|||
</button> |
|||
|
|||
<button |
|||
onClick={testCheckUsername} |
|||
disabled={loading} |
|||
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50" |
|||
> |
|||
检查用户名 |
|||
</button> |
|||
</div> |
|||
|
|||
{/* 加载状态 */} |
|||
{loading && ( |
|||
<div className="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded mb-6"> |
|||
正在请求中... |
|||
</div> |
|||
)} |
|||
|
|||
{/* 登录结果显示 */} |
|||
{loginResult && ( |
|||
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-6"> |
|||
<h3 className="font-bold mb-2">登录信息:</h3> |
|||
<p><strong>用户名:</strong> {loginResult.user?.username}</p> |
|||
<p><strong>用户ID:</strong> {loginResult.user?.userId}</p> |
|||
<p><strong>Token:</strong> {loginResult.access_token?.substring(0, 50)}...</p> |
|||
</div> |
|||
)} |
|||
|
|||
{/* 用户列表显示 */} |
|||
{users.length > 0 && ( |
|||
<div className="bg-gray-100 border border-gray-400 text-gray-700 px-4 py-3 rounded mb-6"> |
|||
<h3 className="font-bold mb-2">用户列表:</h3> |
|||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> |
|||
{users.map((user) => ( |
|||
<div key={user.userId} className="bg-white p-3 rounded shadow"> |
|||
<p><strong>ID:</strong> {user.userId}</p> |
|||
<p><strong>用户名:</strong> {user.username}</p> |
|||
<p><strong>邮箱:</strong> {user.email || '未设置'}</p> |
|||
<p><strong>状态:</strong> {user.status === 1 ? '正常' : '禁用'}</p> |
|||
</div> |
|||
))} |
|||
</div> |
|||
</div> |
|||
)} |
|||
|
|||
{/* 结果显示 */} |
|||
{result && ( |
|||
<div className="bg-gray-100 border border-gray-400 text-gray-700 px-4 py-3 rounded mb-6"> |
|||
<h3 className="font-bold mb-2">请求结果:</h3> |
|||
<pre className="text-sm overflow-x-auto whitespace-pre-wrap">{result}</pre> |
|||
</div> |
|||
)} |
|||
|
|||
{/* API 使用说明 */} |
|||
<div className="bg-white p-6 rounded-lg shadow-md border"> |
|||
<h2 className="text-xl font-semibold mb-4">API 使用说明</h2> |
|||
|
|||
<div className="space-y-6"> |
|||
<div> |
|||
<h3 className="font-semibold text-blue-600">1. 现代化 API 模块</h3> |
|||
<pre className="bg-gray-100 p-3 rounded mt-2 text-sm overflow-x-auto"> |
|||
{`import { AuthApi, UserApi } from '@/api/modules/auth';
|
|||
|
|||
// 用户登录
|
|||
const result = await AuthApi.login({ |
|||
username: 'admin', |
|||
password: '123456' |
|||
}); |
|||
|
|||
// 获取用户列表
|
|||
const users = await UserApi.getUsers({ |
|||
page: 1, |
|||
limit: 10 |
|||
});`}
|
|||
</pre> |
|||
</div> |
|||
|
|||
<div> |
|||
<h3 className="font-semibold text-green-600">2. 原始请求方法</h3> |
|||
<pre className="bg-gray-100 p-3 rounded mt-2 text-sm overflow-x-auto"> |
|||
{`import { request } from '@/api';
|
|||
|
|||
// GET 请求
|
|||
const response = await request.get('/api/data', { page: 1 }); |
|||
|
|||
// POST 请求
|
|||
const response = await request.post('/api/data', { name: 'test' });`}
|
|||
</pre> |
|||
</div> |
|||
|
|||
<div> |
|||
<h3 className="font-semibold text-purple-600">3. 兼容现有代码</h3> |
|||
<pre className="bg-gray-100 p-3 rounded mt-2 text-sm overflow-x-auto"> |
|||
{`import request from '@/utils/request';
|
|||
|
|||
const res = await request.get('/api/data', { params: { page: 1 } }); |
|||
if (res.data.code === 0) { |
|||
console.log(res.data.data); |
|||
}`}
|
|||
</pre> |
|||
</div> |
|||
|
|||
<div> |
|||
<h3 className="font-semibold text-orange-600">4. Token 管理</h3> |
|||
<pre className="bg-gray-100 p-3 rounded mt-2 text-sm overflow-x-auto"> |
|||
{`import { setToken, getToken, clearToken } from '@/api';
|
|||
|
|||
// 设置 token
|
|||
setToken('your-access-token', true); |
|||
|
|||
// 获取 token
|
|||
const token = getToken(); |
|||
|
|||
// 清除 token
|
|||
clearToken();`}
|
|||
</pre> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
{/* 特性说明 */} |
|||
<div className="mt-8 bg-white p-6 rounded-lg shadow-md border"> |
|||
<h2 className="text-xl font-semibold mb-4">系统特性</h2> |
|||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|||
<div> |
|||
<h3 className="font-semibold text-blue-600 mb-2">✅ 已实现的特性</h3> |
|||
<ul className="text-sm space-y-1"> |
|||
<li>• TypeScript 类型安全</li> |
|||
<li>• 自动 Token 管理</li> |
|||
<li>• 统一错误处理</li> |
|||
<li>• 请求/响应拦截</li> |
|||
<li>• 兼容现有代码</li> |
|||
<li>• Next.js SSR 支持</li> |
|||
<li>• 文件上传支持</li> |
|||
<li>• 自动认证失效处理</li> |
|||
</ul> |
|||
</div> |
|||
|
|||
<div> |
|||
<h3 className="font-semibold text-green-600 mb-2">🚀 API 端点</h3> |
|||
<ul className="text-sm space-y-1"> |
|||
<li>• 认证相关: 登录、注册、登出</li> |
|||
<li>• 用户管理: CRUD、状态管理</li> |
|||
<li>• 文件上传: 图片、文档上传</li> |
|||
<li>• 系统管理: 配置、日志</li> |
|||
<li>• 内容管理: 文章、分类</li> |
|||
<li>• 权限管理: 角色、菜单</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
@ -1,358 +0,0 @@ |
|||
'use client'; |
|||
|
|||
import { useState, useEffect } from 'react'; |
|||
import { useParams } from 'next/navigation'; |
|||
import Link from 'next/link'; |
|||
import Image from 'next/image'; |
|||
import { ArticleApi, type Article } from '@/api/modules/article'; |
|||
|
|||
const ArticleDetailPage = () => { |
|||
const params = useParams(); |
|||
const articleId = parseInt(params.id as string); |
|||
|
|||
const [article, setArticle] = useState<Article | null>(null); |
|||
const [relatedArticles, setRelatedArticles] = useState<Article[]>([]); |
|||
const [loading, setLoading] = useState(true); |
|||
const [error, setError] = useState<string | null>(null); |
|||
const [liked, setLiked] = useState(false); |
|||
|
|||
useEffect(() => { |
|||
if (articleId) { |
|||
fetchArticle(); |
|||
fetchRelatedArticles(); |
|||
} |
|||
}, [articleId]); |
|||
|
|||
const fetchArticle = async () => { |
|||
try { |
|||
setLoading(true); |
|||
setError(null); |
|||
|
|||
// 增加浏览量
|
|||
await ArticleApi.increaseViewCount(articleId); |
|||
|
|||
// 获取文章详情
|
|||
const response = await ArticleApi.getArticleById(articleId); |
|||
|
|||
if (response.code === 0 && response.data) { |
|||
setArticle(response.data); |
|||
} else { |
|||
setError(response.message || '文章不存在'); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取文章失败:', error); |
|||
setError('网络错误,请稍后重试'); |
|||
|
|||
// 设置模拟文章数据
|
|||
setArticle({ |
|||
id: articleId, |
|||
title: '企业数字化转型的关键要素与实施策略', |
|||
content: ` |
|||
<h2>引言</h2> |
|||
<p>在当今快速发展的数字化时代,企业数字化转型已经不再是一个选择题,而是一个必答题。随着云计算、大数据、人工智能、物联网等新兴技术的快速发展,传统企业面临着前所未有的挑战和机遇。</p> |
|||
|
|||
<h2>数字化转型的核心要素</h2> |
|||
<h3>1. 战略规划</h3> |
|||
<p>数字化转型首先需要明确的战略规划。企业需要从顶层设计出发,制定符合自身发展阶段和行业特点的数字化转型战略。这包括:</p> |
|||
<ul> |
|||
<li>明确转型目标和愿景</li> |
|||
<li>制定分阶段实施计划</li> |
|||
<li>建立评估和监控机制</li> |
|||
<li>确保资源投入和组织保障</li> |
|||
</ul> |
|||
|
|||
<h3>2. 技术基础设施</h3> |
|||
<p>强大的技术基础设施是数字化转型的基石。企业需要构建灵活、可扩展、安全的IT架构,包括:</p> |
|||
<ul> |
|||
<li>云计算平台建设</li> |
|||
<li>数据中心现代化</li> |
|||
<li>网络安全体系完善</li> |
|||
<li>移动办公环境搭建</li> |
|||
</ul> |
|||
|
|||
<h3>3. 数据治理</h3> |
|||
<p>数据是数字化转型的核心资产。企业需要建立完善的数据治理体系:</p> |
|||
<ul> |
|||
<li>数据标准化和规范化</li> |
|||
<li>数据质量管理</li> |
|||
<li>数据安全和隐私保护</li> |
|||
<li>数据分析和应用</li> |
|||
</ul> |
|||
|
|||
<h2>实施策略</h2> |
|||
<h3>分步骤实施</h3> |
|||
<p>数字化转型是一个长期的过程,需要分步骤、有计划地推进。建议采用以下策略:</p> |
|||
<ol> |
|||
<li><strong>评估现状</strong>:全面评估企业当前的数字化水平</li> |
|||
<li><strong>制定规划</strong>:基于评估结果制定详细的转型规划</li> |
|||
<li><strong>试点先行</strong>:选择关键业务领域进行试点</li> |
|||
<li><strong>逐步推广</strong>:在试点成功的基础上逐步推广</li> |
|||
<li><strong>持续优化</strong>:根据实施效果持续优化和改进</li> |
|||
</ol> |
|||
|
|||
<h2>成功案例分析</h2> |
|||
<p>许多企业在数字化转型方面取得了显著成效。例如,某制造企业通过实施智能制造系统,生产效率提升了30%,产品质量得到显著改善。某零售企业通过数字化营销平台,客户转化率提升了50%。</p> |
|||
|
|||
<h2>结论</h2> |
|||
<p>数字化转型是企业适应数字时代、保持竞争优势的必由之路。成功的数字化转型需要企业在战略、技术、人才、文化等多个维度进行系统性变革。只有坚持以客户为中心,以数据为驱动,以技术为支撑,企业才能在数字化转型的道路上取得成功。</p> |
|||
`,
|
|||
excerpt: '在数字化时代,企业需要从战略、技术、人才等多个维度进行全面转型,以适应快速变化的市场环境。本文深入分析了企业数字化转型的关键要素和实施策略。', |
|||
thumbnail: '/article-detail.jpg', |
|||
author: '张三', |
|||
authorId: 1, |
|||
categoryId: 1, |
|||
categoryName: '行业洞察', |
|||
tags: ['数字化转型', '企业管理', '技术创新'], |
|||
status: 'published', |
|||
viewCount: 1250, |
|||
likeCount: 89, |
|||
commentCount: 23, |
|||
isTop: false, |
|||
isRecommend: true, |
|||
publishTime: '2024-01-15T10:30:00Z', |
|||
createTime: '2024-01-15T10:30:00Z', |
|||
updateTime: '2024-01-15T10:30:00Z', |
|||
seoTitle: '企业数字化转型的关键要素与实施策略 - 专业指南', |
|||
seoKeywords: '数字化转型,企业管理,技术创新,战略规划', |
|||
seoDescription: '深入解析企业数字化转型的核心要素,提供实用的实施策略和成功案例分析,助力企业在数字时代取得成功。' |
|||
}); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
const fetchRelatedArticles = async () => { |
|||
try { |
|||
const response = await ArticleApi.getRelatedArticles(articleId, 4); |
|||
if (response.code === 0 && response.data) { |
|||
setRelatedArticles(response.data); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取相关文章失败:', error); |
|||
// 设置模拟相关文章数据
|
|||
setRelatedArticles([]); |
|||
} |
|||
}; |
|||
|
|||
const handleLike = async () => { |
|||
try { |
|||
if (liked) { |
|||
await ArticleApi.unlikeArticle(articleId); |
|||
setLiked(false); |
|||
if (article) { |
|||
setArticle({ ...article, likeCount: article.likeCount - 1 }); |
|||
} |
|||
} else { |
|||
await ArticleApi.likeArticle(articleId); |
|||
setLiked(true); |
|||
if (article) { |
|||
setArticle({ ...article, likeCount: article.likeCount + 1 }); |
|||
} |
|||
} |
|||
} catch (error) { |
|||
console.error('点赞操作失败:', error); |
|||
} |
|||
}; |
|||
|
|||
const formatDate = (dateString: string) => { |
|||
const date = new Date(dateString); |
|||
return date.toLocaleDateString('zh-CN', { |
|||
year: 'numeric', |
|||
month: 'long', |
|||
day: 'numeric', |
|||
hour: '2-digit', |
|||
minute: '2-digit' |
|||
}); |
|||
}; |
|||
|
|||
const formatNumber = (num: number) => { |
|||
if (num >= 1000) { |
|||
return (num / 1000).toFixed(1) + 'k'; |
|||
} |
|||
return num.toString(); |
|||
}; |
|||
|
|||
if (loading) { |
|||
return ( |
|||
<div className="min-h-screen bg-gray-50"> |
|||
<div className="container py-12"> |
|||
<div className="max-w-4xl mx-auto"> |
|||
<div className="animate-pulse"> |
|||
<div className="h-8 bg-gray-300 rounded mb-4"></div> |
|||
<div className="h-6 bg-gray-300 rounded mb-8 w-3/4"></div> |
|||
<div className="h-64 bg-gray-300 rounded mb-8"></div> |
|||
<div className="space-y-4"> |
|||
<div className="h-4 bg-gray-300 rounded"></div> |
|||
<div className="h-4 bg-gray-300 rounded"></div> |
|||
<div className="h-4 bg-gray-300 rounded w-5/6"></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
if (error || !article) { |
|||
return ( |
|||
<div className="min-h-screen bg-gray-50 flex items-center justify-center"> |
|||
<div className="text-center"> |
|||
<div className="text-6xl text-gray-400 mb-4">404</div> |
|||
<h1 className="text-2xl font-bold text-gray-900 mb-4">文章不存在</h1> |
|||
<p className="text-gray-600 mb-8">{error || '您访问的文章可能已被删除或不存在'}</p> |
|||
<Link href="/articles" className="btn btn-primary"> |
|||
返回文章列表 |
|||
</Link> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
return ( |
|||
<div className="min-h-screen bg-gray-50"> |
|||
{/* 面包屑导航 */} |
|||
<div className="bg-white border-b border-gray-200"> |
|||
<div className="container py-4"> |
|||
<nav className="flex items-center space-x-2 text-sm text-gray-600"> |
|||
<Link href="/" className="hover:text-blue-600">首页</Link> |
|||
<span>/</span> |
|||
<Link href="/articles" className="hover:text-blue-600">新闻资讯</Link> |
|||
<span>/</span> |
|||
<span className="text-gray-900">{article.title}</span> |
|||
</nav> |
|||
</div> |
|||
</div> |
|||
|
|||
<div className="container py-12"> |
|||
<div className="max-w-4xl mx-auto"> |
|||
{/* 文章头部 */} |
|||
<header className="bg-white rounded-lg shadow-sm p-8 mb-8"> |
|||
{/* 分类标签 */} |
|||
{article.categoryName && ( |
|||
<div className="mb-4"> |
|||
<span className="inline-block px-3 py-1 text-sm font-medium bg-blue-100 text-blue-800 rounded-full"> |
|||
{article.categoryName} |
|||
</span> |
|||
</div> |
|||
)} |
|||
|
|||
{/* 文章标题 */} |
|||
<h1 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-6"> |
|||
{article.title} |
|||
</h1> |
|||
|
|||
{/* 文章元信息 */} |
|||
<div className="flex flex-wrap items-center justify-between text-gray-600 mb-6"> |
|||
<div className="flex items-center space-x-6"> |
|||
<span className="flex items-center"> |
|||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> |
|||
</svg> |
|||
{article.author} |
|||
</span> |
|||
<span className="flex items-center"> |
|||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
|||
</svg> |
|||
{formatDate(article.createTime)} |
|||
</span> |
|||
</div> |
|||
|
|||
<div className="flex items-center space-x-4"> |
|||
<span className="flex items-center"> |
|||
<svg className="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> |
|||
</svg> |
|||
{formatNumber(article.viewCount)} |
|||
</span> |
|||
<button |
|||
onClick={handleLike} |
|||
className={`flex items-center transition-colors duration-300 ${ |
|||
liked ? 'text-red-500' : 'text-gray-600 hover:text-red-500' |
|||
}`}
|
|||
> |
|||
<svg className="w-5 h-5 mr-1" fill={liked ? 'currentColor' : 'none'} stroke="currentColor" viewBox="0 0 24 24"> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /> |
|||
</svg> |
|||
{formatNumber(article.likeCount)} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
{/* 文章摘要 */} |
|||
{article.excerpt && ( |
|||
<div className="bg-blue-50 border-l-4 border-blue-400 p-4 mb-6"> |
|||
<p className="text-blue-800 font-medium">{article.excerpt}</p> |
|||
</div> |
|||
)} |
|||
|
|||
{/* 标签 */} |
|||
{article.tags && article.tags.length > 0 && ( |
|||
<div className="flex flex-wrap gap-2"> |
|||
{article.tags.map((tag, index) => ( |
|||
<span |
|||
key={index} |
|||
className="inline-block px-3 py-1 text-sm bg-gray-100 text-gray-700 rounded-full" |
|||
> |
|||
#{tag} |
|||
</span> |
|||
))} |
|||
</div> |
|||
)} |
|||
</header> |
|||
|
|||
{/* 文章内容 */} |
|||
<article className="bg-white rounded-lg shadow-sm p-8 mb-8"> |
|||
<div |
|||
className="prose prose-lg max-w-none" |
|||
dangerouslySetInnerHTML={{ __html: article.content }} |
|||
/> |
|||
</article> |
|||
|
|||
{/* 相关文章 */} |
|||
{relatedArticles.length > 0 && ( |
|||
<section className="bg-white rounded-lg shadow-sm p-8"> |
|||
<h3 className="text-2xl font-bold mb-6">相关文章</h3> |
|||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|||
{relatedArticles.map((relatedArticle) => ( |
|||
<Link |
|||
key={relatedArticle.id} |
|||
href={`/articles/${relatedArticle.id}`} |
|||
className="flex space-x-4 p-4 rounded-lg hover:bg-gray-50 transition-colors duration-300" |
|||
> |
|||
<Image |
|||
src={relatedArticle.thumbnail || '/placeholder-article.jpg'} |
|||
alt={relatedArticle.title} |
|||
width={120} |
|||
height={80} |
|||
className="rounded-lg object-cover flex-shrink-0" |
|||
onError={(e) => { |
|||
const target = e.target as HTMLImageElement; |
|||
target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjgwIiB2aWV3Qm94PSIwIDAgMTIwIDgwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB3aWR0aD0iMTIwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik01MCAzMEg3MFY1MEg1MFYzMFoiIGZpbGw9IiM5Q0EzQUYiLz4KPHA+CjxwYXRoIGQ9Ik00MCA0MEg4MFY0MEg0MFoiIGZpbGw9IiM5Q0EzQUYiLz4KPC9zdmc+Cg=='; |
|||
}} |
|||
/> |
|||
<div className="flex-1"> |
|||
<h4 className="font-semibold text-gray-900 mb-2 line-clamp-2"> |
|||
{relatedArticle.title} |
|||
</h4> |
|||
<p className="text-sm text-gray-600 mb-2 line-clamp-2"> |
|||
{relatedArticle.excerpt} |
|||
</p> |
|||
<div className="flex items-center justify-between text-xs text-gray-500"> |
|||
<span>{relatedArticle.categoryName}</span> |
|||
<span>{formatDate(relatedArticle.createTime)}</span> |
|||
</div> |
|||
</div> |
|||
</Link> |
|||
))} |
|||
</div> |
|||
</section> |
|||
)} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default ArticleDetailPage; |
@ -1,380 +0,0 @@ |
|||
'use client'; |
|||
|
|||
import { useState, useEffect } from 'react'; |
|||
import Link from 'next/link'; |
|||
import Image from 'next/image'; |
|||
import { ArticleApi, ArticleCategoryApi, type Article, type ArticleCategory } from '@/api/modules/article'; |
|||
|
|||
const ArticlesPage = () => { |
|||
const [articles, setArticles] = useState<Article[]>([]); |
|||
const [categories, setCategories] = useState<ArticleCategory[]>([]); |
|||
const [loading, setLoading] = useState(true); |
|||
const [error, setError] = useState<string | null>(null); |
|||
const [currentPage, setCurrentPage] = useState(1); |
|||
const [totalPages, setTotalPages] = useState(1); |
|||
const [selectedCategory, setSelectedCategory] = useState<number | null>(null); |
|||
const [searchKeyword, setSearchKeyword] = useState(''); |
|||
|
|||
const pageSize = 12; |
|||
|
|||
useEffect(() => { |
|||
fetchCategories(); |
|||
}, []); |
|||
|
|||
useEffect(() => { |
|||
fetchArticles(); |
|||
}, [currentPage, selectedCategory, searchKeyword]); |
|||
|
|||
const fetchCategories = async () => { |
|||
try { |
|||
const response = await ArticleCategoryApi.getCategories(); |
|||
if (response.code === 0 && response.data) { |
|||
setCategories(response.data); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取分类失败:', error); |
|||
// 设置模拟分类数据
|
|||
setCategories([ |
|||
{ id: 1, name: '行业洞察', slug: 'industry', description: '', parentId: 0, sortOrder: 1, articleCount: 15, status: 1, createTime: '2024-01-01T00:00:00Z' }, |
|||
{ id: 2, name: '技术分享', slug: 'tech', description: '', parentId: 0, sortOrder: 2, articleCount: 23, status: 1, createTime: '2024-01-01T00:00:00Z' }, |
|||
{ id: 3, name: '前沿科技', slug: 'innovation', description: '', parentId: 0, sortOrder: 3, articleCount: 18, status: 1, createTime: '2024-01-01T00:00:00Z' }, |
|||
{ id: 4, name: '企业动态', slug: 'news', description: '', parentId: 0, sortOrder: 4, articleCount: 12, status: 1, createTime: '2024-01-01T00:00:00Z' }, |
|||
]); |
|||
} |
|||
}; |
|||
|
|||
const fetchArticles = async () => { |
|||
try { |
|||
setLoading(true); |
|||
setError(null); |
|||
|
|||
let response; |
|||
if (searchKeyword) { |
|||
response = await ArticleApi.searchArticles(searchKeyword, { |
|||
page: currentPage, |
|||
limit: pageSize, |
|||
categoryId: selectedCategory || undefined, |
|||
status: 'published', |
|||
}); |
|||
} else { |
|||
response = await ArticleApi.getArticles(); |
|||
} |
|||
|
|||
if (response.code === 0 && response.data) { |
|||
setArticles(response.data.list); |
|||
setTotalPages(Math.ceil(response.data.count / pageSize)); |
|||
} else { |
|||
setError(response.message || '获取文章失败'); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取文章失败:', error); |
|||
setError('网络错误,请稍后重试'); |
|||
|
|||
// 设置模拟文章数据
|
|||
const mockArticles: Article[] = Array.from({ length: pageSize }, (_, index) => ({ |
|||
id: (currentPage - 1) * pageSize + index + 1, |
|||
title: `文章标题 ${(currentPage - 1) * pageSize + index + 1}`, |
|||
content: '这是文章的详细内容...', |
|||
excerpt: '这是文章的摘要内容,介绍了文章的主要观点和核心内容...', |
|||
thumbnail: `/article-${(index % 3) + 1}.jpg`, |
|||
author: ['张三', '李四', '王五'][index % 3], |
|||
authorId: (index % 3) + 1, |
|||
categoryId: selectedCategory || ((index % 4) + 1), |
|||
categoryName: categories.find(c => c.id === (selectedCategory || ((index % 4) + 1)))?.name || '默认分类', |
|||
tags: ['标签1', '标签2'], |
|||
status: 'published', |
|||
viewCount: Math.floor(Math.random() * 2000) + 100, |
|||
likeCount: Math.floor(Math.random() * 200) + 10, |
|||
commentCount: Math.floor(Math.random() * 50) + 1, |
|||
isTop: index === 0, |
|||
isRecommend: index < 3, |
|||
createTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(), |
|||
updateTime: new Date().toISOString(), |
|||
})); |
|||
|
|||
setArticles(mockArticles); |
|||
setTotalPages(5); // 模拟总页数
|
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}; |
|||
|
|||
const handleSearch = (e: React.FormEvent) => { |
|||
e.preventDefault(); |
|||
setCurrentPage(1); |
|||
fetchArticles(); |
|||
}; |
|||
|
|||
const handleCategoryChange = (categoryId: number | null) => { |
|||
setSelectedCategory(categoryId); |
|||
setCurrentPage(1); |
|||
}; |
|||
|
|||
const formatDate = (dateString: string) => { |
|||
const date = new Date(dateString); |
|||
return date.toLocaleDateString('zh-CN', { |
|||
year: 'numeric', |
|||
month: 'long', |
|||
day: 'numeric' |
|||
}); |
|||
}; |
|||
|
|||
const formatNumber = (num: number) => { |
|||
if (num >= 1000) { |
|||
return (num / 1000).toFixed(1) + 'k'; |
|||
} |
|||
return num.toString(); |
|||
}; |
|||
|
|||
return ( |
|||
<div className="min-h-screen bg-gray-50"> |
|||
{/* 页面头部 */} |
|||
<div className="bg-white border-b border-gray-200"> |
|||
<div className="container py-12"> |
|||
<div className="text-center"> |
|||
<h1 className="text-4xl font-bold text-gray-900 mb-4">新闻资讯</h1> |
|||
<p className="text-xl text-gray-600 max-w-2xl mx-auto"> |
|||
了解行业动态,掌握前沿技术,获取专业见解 |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div className="container py-12"> |
|||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8"> |
|||
{/* 侧边栏 */} |
|||
<div className="lg:col-span-1"> |
|||
{/* 搜索框 */} |
|||
<div className="bg-white rounded-lg shadow-sm p-6 mb-8"> |
|||
<h3 className="text-lg font-semibold mb-4">搜索文章</h3> |
|||
<form onSubmit={handleSearch}> |
|||
<div className="flex"> |
|||
<input |
|||
type="text" |
|||
value={searchKeyword} |
|||
onChange={(e) => setSearchKeyword(e.target.value)} |
|||
placeholder="输入关键词..." |
|||
className="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" |
|||
/> |
|||
<button |
|||
type="submit" |
|||
className="px-4 py-2 bg-blue-600 text-white rounded-r-lg hover:bg-blue-700 transition-colors duration-300" |
|||
> |
|||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> |
|||
</svg> |
|||
</button> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
|
|||
{/* 分类筛选 */} |
|||
<div className="bg-white rounded-lg shadow-sm p-6"> |
|||
<h3 className="text-lg font-semibold mb-4">文章分类</h3> |
|||
<ul className="space-y-2"> |
|||
<li> |
|||
<button |
|||
onClick={() => handleCategoryChange(null)} |
|||
className={`w-full text-left px-3 py-2 rounded-lg transition-colors duration-300 ${ |
|||
selectedCategory === null |
|||
? 'bg-blue-100 text-blue-600' |
|||
: 'text-gray-700 hover:bg-gray-100' |
|||
}`}
|
|||
> |
|||
全部文章 |
|||
</button> |
|||
</li> |
|||
{categories.map((category) => ( |
|||
<li key={category.id}> |
|||
<button |
|||
onClick={() => handleCategoryChange(category.id)} |
|||
className={`w-full text-left px-3 py-2 rounded-lg transition-colors duration-300 flex justify-between items-center ${ |
|||
selectedCategory === category.id |
|||
? 'bg-blue-100 text-blue-600' |
|||
: 'text-gray-700 hover:bg-gray-100' |
|||
}`}
|
|||
> |
|||
<span>{category.name}</span> |
|||
<span className="text-sm text-gray-500">({category.articleCount})</span> |
|||
</button> |
|||
</li> |
|||
))} |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
|
|||
{/* 主内容区 */} |
|||
<div className="lg:col-span-3"> |
|||
{/* 筛选信息 */} |
|||
<div className="flex justify-between items-center mb-8"> |
|||
<div className="text-gray-600"> |
|||
{selectedCategory && ( |
|||
<span> |
|||
分类:<span className="text-blue-600 font-medium"> |
|||
{categories.find(c => c.id === selectedCategory)?.name} |
|||
</span> |
|||
</span> |
|||
)} |
|||
{searchKeyword && ( |
|||
<span className="ml-4"> |
|||
搜索:<span className="text-blue-600 font-medium">"{searchKeyword}"</span> |
|||
</span> |
|||
)} |
|||
</div> |
|||
<div className="text-gray-600"> |
|||
共 {articles.length} 篇文章 |
|||
</div> |
|||
</div> |
|||
|
|||
{/* 错误提示 */} |
|||
{error && ( |
|||
<div className="bg-yellow-50 border border-yellow-200 text-yellow-800 px-4 py-3 rounded-lg mb-8"> |
|||
<p>{error}</p> |
|||
<p className="text-sm mt-2">以下为示例内容</p> |
|||
</div> |
|||
)} |
|||
|
|||
{/* 文章列表 */} |
|||
{loading ? ( |
|||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8"> |
|||
{[1, 2, 3, 4, 5, 6].map((i) => ( |
|||
<div key={i} className="article-card animate-pulse"> |
|||
<div className="w-full h-48 bg-gray-300"></div> |
|||
<div className="article-content"> |
|||
<div className="h-4 bg-gray-300 rounded mb-3"></div> |
|||
<div className="h-6 bg-gray-300 rounded mb-3"></div> |
|||
<div className="h-4 bg-gray-300 rounded mb-4"></div> |
|||
<div className="h-4 bg-gray-300 rounded"></div> |
|||
</div> |
|||
</div> |
|||
))} |
|||
</div> |
|||
) : ( |
|||
<> |
|||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8"> |
|||
{articles.map((article, index) => ( |
|||
<article |
|||
key={article.id} |
|||
className="article-card fade-in-up" |
|||
style={{ animationDelay: `${index * 0.1}s` }} |
|||
> |
|||
{/* 文章图片 */} |
|||
<div className="relative overflow-hidden"> |
|||
<Image |
|||
src={article.thumbnail || '/placeholder-article.jpg'} |
|||
alt={article.title} |
|||
width={400} |
|||
height={200} |
|||
className="article-image hover:scale-105 transition-transform duration-300" |
|||
onError={(e) => { |
|||
const target = e.target as HTMLImageElement; |
|||
target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDQwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0xNzUgNzVIMjI1VjEyNUgxNzVWNzVaIiBmaWxsPSIjOUNBM0FGIi8+CjxwYXRoIGQ9Ik0xNTAgMTAwSDI1MFYxMDBIMTUwWiIgZmlsbD0iIzlDQTNBRiIvPgo8L3N2Zz4K'; |
|||
}} |
|||
/> |
|||
|
|||
{/* 置顶标签 */} |
|||
{article.isTop && ( |
|||
<div className="absolute top-4 left-4 bg-red-500 text-white px-2 py-1 text-xs rounded"> |
|||
置顶 |
|||
</div> |
|||
)} |
|||
|
|||
{/* 推荐标签 */} |
|||
{article.isRecommend && ( |
|||
<div className="absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 text-xs rounded"> |
|||
推荐 |
|||
</div> |
|||
)} |
|||
</div> |
|||
|
|||
{/* 文章内容 */} |
|||
<div className="article-content"> |
|||
{/* 分类标签 */} |
|||
{article.categoryName && ( |
|||
<span className="article-category"> |
|||
{article.categoryName} |
|||
</span> |
|||
)} |
|||
|
|||
{/* 文章标题 */} |
|||
<h3 className="article-title"> |
|||
<Link href={`/articles/${article.id}`}> |
|||
{article.title} |
|||
</Link> |
|||
</h3> |
|||
|
|||
{/* 文章摘要 */} |
|||
<p className="article-excerpt"> |
|||
{article.excerpt || article.content?.substring(0, 100) + '...'} |
|||
</p> |
|||
|
|||
{/* 文章元信息 */} |
|||
<div className="article-meta"> |
|||
<div className="flex items-center space-x-4"> |
|||
<span className="flex items-center"> |
|||
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> |
|||
</svg> |
|||
{formatNumber(article.viewCount)} |
|||
</span> |
|||
<span className="flex items-center"> |
|||
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /> |
|||
</svg> |
|||
{formatNumber(article.likeCount)} |
|||
</span> |
|||
</div> |
|||
<span>{formatDate(article.createTime)}</span> |
|||
</div> |
|||
</div> |
|||
</article> |
|||
))} |
|||
</div> |
|||
|
|||
{/* 分页 */} |
|||
{totalPages > 1 && ( |
|||
<div className="flex justify-center mt-12"> |
|||
<nav className="flex items-center space-x-2"> |
|||
<button |
|||
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))} |
|||
disabled={currentPage === 1} |
|||
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" |
|||
> |
|||
上一页 |
|||
</button> |
|||
|
|||
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( |
|||
<button |
|||
key={page} |
|||
onClick={() => setCurrentPage(page)} |
|||
className={`px-3 py-2 text-sm font-medium rounded-md ${ |
|||
currentPage === page |
|||
? 'text-blue-600 bg-blue-50 border border-blue-300' |
|||
: 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50' |
|||
}`}
|
|||
> |
|||
{page} |
|||
</button> |
|||
))} |
|||
|
|||
<button |
|||
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))} |
|||
disabled={currentPage === totalPages} |
|||
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" |
|||
> |
|||
下一页 |
|||
</button> |
|||
</nav> |
|||
</div> |
|||
)} |
|||
</> |
|||
)} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default ArticlesPage; |
@ -1,26 +1,27 @@ |
|||
import HeroSection from '@/components/sections/HeroSection'; |
|||
import FeaturesSection from '@/components/sections/FeaturesSection'; |
|||
import CasesSection from '@/components/sections/CasesSection'; |
|||
import PartnersSection from '@/components/sections/PartnersSection'; |
|||
import ContactSection from '@/components/sections/ContactSection'; |
|||
// import HeroSection from '@/components/sections/HeroSection';
|
|||
// import FeaturesSection from '@/components/sections/FeaturesSection';
|
|||
// import CasesSection from '@/components/sections/CasesSection';
|
|||
// import PartnersSection from '@/components/sections/PartnersSection';
|
|||
// import ContactSection from '@/components/sections/ContactSection';
|
|||
|
|||
export default function Home() { |
|||
return ( |
|||
<main> |
|||
<div className={'bg-gray-200'}>Main</div> |
|||
{/* 英雄区域 */} |
|||
<HeroSection /> |
|||
{/*<HeroSection />*/} |
|||
|
|||
{/* 产品服务 */} |
|||
<FeaturesSection /> |
|||
{/*<FeaturesSection />*/} |
|||
|
|||
{/* 客户案例 */} |
|||
<CasesSection /> |
|||
{/*<CasesSection />*/} |
|||
|
|||
{/* 合作伙伴 */} |
|||
<PartnersSection /> |
|||
{/*<PartnersSection />*/} |
|||
|
|||
{/* 联系我们 */} |
|||
<ContactSection /> |
|||
{/*<ContactSection />*/} |
|||
</main> |
|||
); |
|||
} |
|||
|
Loading…
Reference in new issue