Browse Source

next版本的pc端程序

master
科技小王子 3 months ago
parent
commit
c2fa484010
  1. 2
      next.config.ts
  2. 3
      src/api/bszx/bszxBranch/index.ts
  3. 145
      src/api/examples/tenant-api-usage.ts
  4. 12
      src/api/index.ts
  5. 19
      src/api/layout/index.ts
  6. 390
      src/api/modules/article.ts
  7. 246
      src/api/modules/auth.ts
  8. 284
      src/api/modules/user.ts
  9. 34
      src/api/passport/login/index.ts
  10. 1
      src/api/system/company/index.ts
  11. 288
      src/app/api-example/page.tsx
  12. 358
      src/app/articles/[id]/page.tsx
  13. 380
      src/app/articles/page.tsx
  14. 4
      src/app/globals.css
  15. 3
      src/app/layout.tsx
  16. 21
      src/app/page.tsx
  17. 145
      src/components/layout/Footer.tsx
  18. 120
      src/components/layout/Header.tsx
  19. 94
      src/components/sections/PartnersSection.tsx
  20. 5
      src/config/setting.ts
  21. 11
      src/utils/request.ts

2
next.config.ts

@ -62,7 +62,7 @@ const nextConfig: NextConfig = {
pathname: '/**',
},
],
},
}
};
export default nextConfig;

3
src/api/bszx/bszxBranch/index.ts

@ -79,8 +79,9 @@ export async function removeBszxBranch(id?: number) {
/**
* -
*/
export async function removeBatchBszxBranch(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/bszx/bszx-branch/batch',
{
data

145
src/api/examples/tenant-api-usage.ts

@ -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
*/

12
src/api/index.ts

@ -267,7 +267,7 @@ export class ApiClient {
(requestConfig.headers as Record<string, string>)['TenantId'] = APP_CONFIG.TENANT_ID;
// 添加请求体
if (data && ['POST', 'PUT', 'PATCH'].includes(method)) {
if (data && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
if (data instanceof FormData) {
// FormData 不需要设置 Content-Type
delete (requestConfig.headers as Record<string, string>)['Content-Type'];
@ -368,8 +368,10 @@ export class ApiClient {
/**
* DELETE
*/
async delete<T = unknown>(url: string, config?: RequestConfig): Promise<ApiResult<T>> {
return this.request<T>(url, 'DELETE', undefined, config);
async delete<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<ApiResult<T>> {
// 为对象数据添加租户ID
const mergedData = this.addTenantIdToData(data);
return this.request<T>(url, 'DELETE', mergedData, config);
}
/**
@ -402,8 +404,8 @@ export const request = {
put: <T = unknown>(url: string, data?: unknown, config?: RequestConfig) =>
apiClient.put<T>(url, data, config),
delete: <T = unknown>(url: string, config?: RequestConfig) =>
apiClient.delete<T>(url, config),
delete: <T = unknown>(url: string, data?: unknown, config?: RequestConfig) =>
apiClient.delete<T>(url, data, config),
patch: <T = unknown>(url: string, data?: unknown, config?: RequestConfig) =>
apiClient.patch<T>(url, data, config),

19
src/api/layout/index.ts

@ -16,7 +16,7 @@ export async function getTenantInfo(): Promise<Company> {
SERVER_API_URL + '/auth/tenant'
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
return res.data.data as Company;
}
return Promise.reject(new Error(res.data.message));
}
@ -34,7 +34,7 @@ export async function getSiteInfo(): Promise<CmsWebsite> {
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
return res.data.data as unknown as CmsWebsite;
}
return Promise.reject(new Error(res.data.message));
}
@ -45,7 +45,7 @@ export async function getSiteInfo(): Promise<CmsWebsite> {
export async function getUserInfo(): Promise<User> {
const res = await request.get<ApiResult<User>>(SERVER_API_URL + '/auth/user');
if (res.data.code === 0 && res.data.data) {
return res.data.data;
return res.data.data as unknown as User;
}
return Promise.reject(new Error(res.data.message));
}
@ -68,12 +68,12 @@ export async function updateLoginUser(data: User) {
* ()
* @return
*/
export async function getServerTime() {
const res = await request.get<ApiResult<any>>(
export async function getServerTime(): Promise<string> {
const res = await request.get<ApiResult<string>>(
MODULES_API_URL + '/cms/website/getServerTime'
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
return res.data.data as unknown as string;
}
return Promise.reject(new Error(res.data.message));
}
@ -82,13 +82,12 @@ export async function getServerTime() {
* 7
* @return
*/
export async function getNext7day() {
const res = await request.get<ApiResult<any>>(
export async function getNext7day(): Promise<string[]> {
const res = await request.get<ApiResult<string[]>>(
MODULES_API_URL + '/cms/website/getNext7day'
);
console.log('res.data.code: ', res.data.code);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
return res.data.data as unknown as string[];
}
return Promise.reject(new Error(res.data.message));
}

390
src/api/modules/article.ts

@ -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;

246
src/api/modules/auth.ts

@ -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;

284
src/api/modules/user.ts

@ -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;

34
src/api/passport/login/index.ts

@ -18,10 +18,15 @@ export async function login(data: LoginParam) {
SERVER_API_URL + '/loginByUserId',
data
);
if (res.data.code === 0) {
setToken(res.data.data?.access_token, data.remember);
if (res.data.data?.user) {
const user = res.data.data?.user;
if (res.data.code === 0 && res.data.data) {
// 确保 access_token 存在且为字符串类型
const loginResult = res.data.data as LoginResult;
if (loginResult.access_token) {
setToken(loginResult.access_token, data.remember);
}
if (loginResult.user) {
const user = loginResult.user;
localStorage.setItem('TenantId', String(user.tenantId));
localStorage.setItem('UserId', String(user.userId));
}
@ -49,10 +54,14 @@ export async function loginBySms(data: LoginParam) {
SERVER_API_URL + '/loginBySms',
data
);
if (res.data.code === 0) {
setToken(res.data.data?.access_token, data.remember);
if (res.data.data?.user) {
const user = res.data.data?.user;
if (res.data.code === 0 && res.data.data) {
const loginResult = res.data.data as LoginResult;
if (loginResult.access_token) {
setToken(loginResult.access_token, data.remember);
}
if (loginResult.user) {
const user = loginResult.user;
localStorage.setItem('TenantId', String(user.tenantId));
localStorage.setItem('Phone', String(user.phone));
localStorage.setItem('UserId', String(user.userId));
@ -87,8 +96,11 @@ export async function remoteLogin(data: LoginParam) {
'https://open.gxwebsoft.com/api/login',
data
);
if (res.data.code === 0) {
setToken(res.data.data?.access_token, data.remember);
if (res.data.code === 0 && res.data.data) {
const loginResult = res.data.data as LoginResult;
if (loginResult.access_token) {
setToken(loginResult.access_token, data.remember);
}
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
@ -97,7 +109,7 @@ export async function remoteLogin(data: LoginParam) {
/**
*
*/
export async function getWxWorkQrConnect(data) {
export async function getWxWorkQrConnect(data: unknown) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/wx-work',
data

1
src/api/system/company/index.ts

@ -28,7 +28,6 @@ export async function getCompanyAll(companyId: number) {
SERVER_API_URL + '/system/company/profileAll/' + companyId
);
if (res.data.code === 0 && res.data) {
console.log(res.data);
return res.data.data;
}
return Promise.reject(new Error(res.data.message));

288
src/app/api-example/page.tsx

@ -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>
);
}

358
src/app/articles/[id]/page.tsx

@ -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;

380
src/app/articles/page.tsx

@ -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;

4
src/app/globals.css

@ -1,6 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
/* 现代Web应用全局样式 */
:root {

3
src/app/layout.tsx

@ -22,10 +22,7 @@ export async function generateMetadata(): Promise<Metadata> {
};
try {
console.log('Attempting to fetch site info for metadata...');
const siteInfo = await getSiteInfo();
console.log('Site info fetched successfully:', siteInfo);
const title = siteInfo.websiteName || defaultMetadata.title as string;
const description = siteInfo.comments || defaultMetadata.description as string;
const keywords = siteInfo.keywords || defaultMetadata.keywords as string;

21
src/app/page.tsx

@ -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>
);
}

145
src/components/layout/Footer.tsx

@ -33,150 +33,7 @@ const Footer = () => {
return (
<footer className="bg-gray-900 text-white py-12">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-8">
{/* 公司信息 */}
<div className="lg:col-span-2">
<div className="mb-8 lg:mb-0">
<h3 className="text-lg font-semibold mb-4">WEBSOFT</h3>
<p className="text-gray-300 mb-6 max-w-md">
Web应用开发服务商
</p>
<div className="space-y-2 text-gray-300">
<div className="flex items-center">
<svg className="w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clipRule="evenodd" />
</svg>
<span></span>
</div>
<div className="flex items-center">
<svg className="w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
</svg>
<span>0591-88888888</span>
</div>
<div className="flex items-center">
<svg className="w-5 h-5 mr-3" fill="currentColor" viewBox="0 0 20 20">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg>
<span>info@websoft.top</span>
</div>
</div>
</div>
</div>
{/* 产品链接 */}
<div className="mb-8 lg:mb-0">
<h3 className="text-lg font-semibold mb-4"></h3>
<ul className="space-y-3">
{footerLinks.products.map((link) => (
<li key={link.name}>
<Link href={link.href} className="text-gray-300 hover:text-white transition-colors duration-300">
{link.name}
</Link>
</li>
))}
</ul>
</div>
{/* 解决方案链接 */}
<div className="mb-8 lg:mb-0">
<h3 className="text-lg font-semibold mb-4"></h3>
<ul className="space-y-3">
{footerLinks.solutions.map((link) => (
<li key={link.name}>
<Link href={link.href} className="text-gray-300 hover:text-white transition-colors duration-300">
{link.name}
</Link>
</li>
))}
</ul>
</div>
{/* 技术支持链接 */}
<div className="mb-8 lg:mb-0">
<h3 className="text-lg font-semibold mb-4"></h3>
<ul className="space-y-3">
{footerLinks.support.map((link) => (
<li key={link.name}>
<Link href={link.href} className="text-gray-300 hover:text-white transition-colors duration-300">
{link.name}
</Link>
</li>
))}
</ul>
</div>
{/* 公司链接和二维码 */}
<div className="mb-8 lg:mb-0">
<h3 className="text-lg font-semibold mb-4"></h3>
<ul className="space-y-3 mb-6">
{footerLinks.company.map((link) => (
<li key={link.name}>
<Link href={link.href} className="text-gray-300 hover:text-white transition-colors duration-300">
{link.name}
</Link>
</li>
))}
</ul>
{/* 二维码 */}
<div className="text-center">
<div className="w-20 h-20 bg-white rounded-lg flex items-center justify-center mb-2">
<div className="w-16 h-16 bg-gray-300 rounded flex items-center justify-center">
<svg className="w-8 h-8 text-gray-600" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M3 4a1 1 0 011-1h3a1 1 0 011 1v3a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm2 2V5h1v1H5zM3 13a1 1 0 011-1h3a1 1 0 011 1v3a1 1 0 01-1 1H4a1 1 0 01-1-1v-3zm2 2v-1h1v1H5zM13 3a1 1 0 00-1 1v3a1 1 0 001 1h3a1 1 0 001-1V4a1 1 0 00-1-1h-3zm1 2v1h1V5h-1z" clipRule="evenodd" />
</svg>
</div>
</div>
<p className="text-xs text-gray-400"></p>
</div>
</div>
</div>
{/* 底部版权信息 */}
<div className="border-t border-gray-800 mt-12 pt-8">
<div className="flex flex-col md:flex-row justify-between items-center">
<div className="text-gray-400 text-sm">
© {currentYear} . .
</div>
<div className="flex space-x-6 mt-4 md:mt-0">
<Link href="/privacy" className="text-gray-400 hover:text-white text-sm transition-colors duration-300">
</Link>
<Link href="/terms" className="text-gray-400 hover:text-white text-sm transition-colors duration-300">
</Link>
<Link href="/sitemap" className="text-gray-400 hover:text-white text-sm transition-colors duration-300">
</Link>
</div>
</div>
</div>
{/* 社交媒体链接 */}
<div className="flex justify-center space-x-6 mt-8">
<a href="#" className="text-gray-400 hover:text-white transition-colors duration-300">
<span className="sr-only"></span>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 01.213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 00.167-.054l1.903-1.114a.864.864 0 01.717-.098 10.16 10.16 0 002.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18 0 .659-.52 1.188-1.162 1.188-.642 0-1.162-.529-1.162-1.188 0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18 0 .659-.52 1.188-1.162 1.188-.642 0-1.162-.529-1.162-1.188 0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229 2.579 0 5.061-1.314 6.266-3.276a.545.545 0 00.034-.31.545.545 0 00-.169-.267c-.293-.24-.652-.379-1.019-.379-.642 0-1.162.529-1.162 1.188 0 .659.52 1.188 1.162 1.188.367 0 .726-.139 1.019-.379a.545.545 0 00.169-.267.545.545 0 00-.034-.31c-1.205-1.962-3.687-3.276-6.266-3.276-3.218 0-5.942 1.776-6.884 4.229-.907 2.5.06 4.792 1.78 6.22 1.534 1.274 3.483 1.838 5.28 1.786z"/>
</svg>
</a>
<a href="#" className="text-gray-400 hover:text-white transition-colors duration-300">
<span className="sr-only"></span>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M9.996 14.456c-1.747 0-3.166-1.418-3.166-3.166s1.419-3.166 3.166-3.166 3.166 1.418 3.166 3.166-1.419 3.166-3.166 3.166zm0-5.332c-1.197 0-2.166.969-2.166 2.166s.969 2.166 2.166 2.166 2.166-.969 2.166-2.166-.969-2.166-2.166-2.166z"/>
</svg>
</a>
<a href="#" className="text-gray-400 hover:text-white transition-colors duration-300">
<span className="sr-only">QQ</span>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z"/>
</svg>
</a>
</div>
</div>
Footer
</footer>
);
};

120
src/components/layout/Header.tsx

@ -29,7 +29,6 @@ const Header = () => {
const reload = () => {
getSiteInfo().then(data => {
console.log(data,'data')
if(data){
setConfig(data);
}
@ -41,123 +40,8 @@ const Header = () => {
}, []);
return (
<header className="bg-white shadow-sm border-b border-gray-100 sticky top-0 z-50">
<div className="container mx-auto px-4">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<div className="flex-shrink-0">
<Link href="/" className="text-xl font-bold text-gray-800">
= {config?.websiteName} =
</Link>
</div>
{/* Desktop Navigation */}
<nav className="hidden md:block">
<div className="flex items-center space-x-8">
{navigation.map((item) => (
<Link
key={item.name}
href={item.href}
className={`text-sm font-medium transition-colors duration-300 ${
isActive(item.href)
? 'text-blue-600'
: 'text-gray-700 hover:text-blue-600'
}`}
>
{item.name}
</Link>
))}
</div>
</nav>
{/* Language Switch & CTA */}
<div className="hidden md:flex items-center space-x-4">
<div className="flex items-center space-x-2 text-sm text-gray-600">
<span className="cursor-pointer hover:text-blue-600"></span>
<span>|</span>
<span className="cursor-pointer hover:text-blue-600">EN</span>
</div>
<Link href="/contact" className="btn btn-primary text-sm">
</Link>
</div>
{/* Mobile menu button */}
<div className="md:hidden">
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
aria-expanded="false"
>
<span className="sr-only"></span>
{!isMenuOpen ? (
<svg
className="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
) : (
<svg
className="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
)}
</button>
</div>
</div>
{/* Mobile Navigation */}
{isMenuOpen && (
<div className="md:hidden">
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-white border-t border-gray-200">
{navigation.map((item) => (
<Link
key={item.name}
href={item.href}
className={`block px-3 py-2 rounded-md text-base font-medium transition-colors duration-300 ${
isActive(item.href)
? 'text-blue-600 bg-blue-50'
: 'text-gray-700 hover:text-blue-600 hover:bg-gray-50'
}`}
onClick={() => setIsMenuOpen(false)}
>
{item.name}
</Link>
))}
<div className="px-3 py-2">
<Link
href="/contact"
className="btn btn-primary w-full"
onClick={() => setIsMenuOpen(false)}
>
</Link>
</div>
</div>
</div>
)}
</div>
<header className="bg-red-100 text-2xl shadow-sm border-b border-gray-100 sticky top-0 z-50">
Top
</header>
);
};

94
src/components/sections/PartnersSection.tsx

@ -1,35 +1,35 @@
'use client';
import Image from 'next/image';
// import Image from 'next/image';
const PartnersSection = () => {
const partners = [
{
name: '阿里云',
logo: '/partners/aliyun.png',
description: '云计算服务合作伙伴'
},
{
name: '腾讯云',
logo: '/partners/tencent.png',
description: '云服务技术合作'
},
{
name: '华为',
logo: '/partners/huawei.png',
description: '企业级解决方案合作'
},
{
name: '百度',
logo: '/partners/baidu.png',
description: 'AI技术合作伙伴'
},
{
name: '字节跳动',
logo: '/partners/bytedance.png',
description: '前端技术合作'
}
];
// const partners = [
// {
// name: '阿里云',
// logo: '/partners/aliyun.png',
// description: '云计算服务合作伙伴'
// },
// {
// name: '腾讯云',
// logo: '/partners/tencent.png',
// description: '云服务技术合作'
// },
// {
// name: '华为',
// logo: '/partners/huawei.png',
// description: '企业级解决方案合作'
// },
// {
// name: '百度',
// logo: '/partners/baidu.png',
// description: 'AI技术合作伙伴'
// },
// {
// name: '字节跳动',
// logo: '/partners/bytedance.png',
// description: '前端技术合作'
// }
// ];
return (
<section className="py-16 lg:py-24 bg-white">
@ -42,25 +42,25 @@ const PartnersSection = () => {
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-8 items-center">
{partners.map((partner, index) => (
<div key={index} className="flex flex-col items-center group">
<div className="w-24 h-24 bg-gray-100 rounded-lg flex items-center justify-center mb-4 group-hover:bg-gray-200 transition-colors duration-300">
<Image
src={partner.logo}
alt={partner.name}
width={80}
height={80}
className="object-contain filter grayscale group-hover:grayscale-0 transition-all duration-300"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0zNSAzMEg0NVY1MEgzNVYzMFoiIGZpbGw9IiM5Q0EzQUYiLz4KPHBhdGggZD0iTTMwIDQwSDUwVjQwSDMwWiIgZmlsbD0iIzlDQTNBRiIvPgo8L3N2Zz4K';
}}
/>
</div>
<h3 className="text-sm font-medium text-gray-900 mb-1">{partner.name}</h3>
<p className="text-xs text-gray-500 text-center">{partner.description}</p>
</div>
))}
{/*{partners.map((partner, index) => (*/}
{/* <div key={index} className="flex flex-col items-center group">*/}
{/* <div className="w-24 h-24 bg-gray-100 rounded-lg flex items-center justify-center mb-4 group-hover:bg-gray-200 transition-colors duration-300">*/}
{/* <Image*/}
{/* src={partner.logo}*/}
{/* alt={partner.name}*/}
{/* width={80}*/}
{/* height={80}*/}
{/* className="object-contain filter grayscale group-hover:grayscale-0 transition-all duration-300"*/}
{/* onError={(e) => {*/}
{/* const target = e.target as HTMLImageElement;*/}
{/* target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0zNSAzMEg0NVY1MEgzNVYzMFoiIGZpbGw9IiM5Q0EzQUYiLz4KPHBhdGggZD0iTTMwIDQwSDUwVjQwSDMwWiIgZmlsbD0iIzlDQTNBRiIvPgo8L3N2Zz4K';*/}
{/* }}*/}
{/* />*/}
{/* </div>*/}
{/* <h3 className="text-sm font-medium text-gray-900 mb-1">{partner.name}</h3>*/}
{/* <p className="text-xs text-gray-500 text-center">{partner.description}</p>*/}
{/* </div>*/}
{/*))}*/}
</div>
{/* 合作统计 */}

5
src/config/setting.ts

@ -20,6 +20,11 @@ export const MODULES_API_URL = 'https://cms-api.websoft.top/api';
*/
export const UPLOAD_API_URL = 'https://cms-api.websoft.top/api/upload';
/**
* ID
*/
export const TEMPLATE_ID = '10258';
// ==================== 环境配置 ====================
/**

11
src/utils/request.ts

@ -33,8 +33,15 @@ const request = {
/**
* DELETE
*/
delete: <T = unknown>(url: string, config?: RequestConfig): Promise<{ data: ApiResult<T> }> => {
return apiRequest.delete<T>(url, config).then(data => ({ data }));
delete: <T = unknown>(url: string, configOrData?: RequestConfig | { data?: unknown }): Promise<{ data: ApiResult<T> }> => {
// 检查第二个参数是否包含 data 属性
if (configOrData && 'data' in configOrData) {
const { data, ...config } = configOrData as { data?: unknown } & RequestConfig;
return apiRequest.delete<T>(url, data, config).then(data => ({ data }));
} else {
// 兼容原有的只传 config 的方式
return apiRequest.delete<T>(url, undefined, configOrData as RequestConfig).then(data => ({ data }));
}
},
/**

Loading…
Cancel
Save