时间:2025-03-14 来源:525游
Strapi 后端:
已安装并运行 Strapi(http://localhost:1337)。
已创建 articles 内容类型,包含字段:title(Text)、content(Rich Text)、slug(Text,唯一,选填,用于 SEO 优化)。
已配置好权限:Articles 的 find 和 findOne 权限设置为公开,create 权限需要认证。
示例数据:id: 4, documentId: "nbedxv11g6sdbxvnsdjxs48n", title: "hello world,test2", content: [...]。
Next.js 前端:
已创建 Next.js 项目(位于 C:Nodetest-examplepackageswebsite)。
已安装必要的依赖:axios。
下面我们来操作具体的执行步骤
1. 安装 axios
cd C:Nodetest-examplepackageswebsite
安装 axios npm install axios
安装后,检查 node_modules/axios 是否存在。
确认 package.json 中的 dependencies 包含:
"dependencies": {
"axios": "^1.8.3",
"next": "15.2.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
2、在website 目录下创建以下文件夹和文件:
win10创建文件夹命令符:
# 创建目录
mkdir apparticles apparticles[id] applogin appregister appcreate-article components context utils
# 创建文件
echo. > apparticles[id]page.js
echo. > apploginpage.js
echo. > appregisterpage.js
echo. > appcreate-articlepage.js
echo. > componentsAuthWrapper.js
echo. > componentsNavbar.js
echo. > contextAuthContext.js
echo. > utilsapi.js
echo. > next.config.mjs
运行上述命令后,目录结构应与第 1 步描述一致。app/layout.js 和 app/page.js 由 Next.js 自动生成,其他文件已手动创建。
现在整个目录,应该是下面这样的
test-example/packages/website/
├── app/
│ ├── articles/
│ │ ├── [id]/
│ │ │ └── page.js # 文章详情页面,动态路由,显示具体文章内容
│ ├── login/
│ │ └── page.js # 登录页面,处理用户登录
│ ├── register/
│ │ └── page.js # 注册页面,处理用户注册
│ ├── create-article/
│ │ └── page.js # 创建文章页面,处理文章发布
│ ├── layout.js # 根布局文件,包含全局导航
│ └── page.js # 首页,显示文章列表
├── components/ # 组件目录
│ ├── AuthWrapper.js # 认证包装组件,提供 AuthContext
│ └── Navbar.js # 导航组件,动态显示导航链接
├── context/ # 上下文目录(新建)
│ └── AuthContext.js # 认证上下文,管理用户状态
├── utils/ # 工具函数目录
│ └── api.js # API 请求工具,封装与 Strapi 的交互
├── public/ # 静态资源目录(Next.js 默认生成)
│ ├── favicon.ico
│ └── ...
├── styles/ # 样式目录(Next.js 默认生成)
│ ├── globals.css # 全局样式
│ └── Home.module.css # 模块化样式(可选)
├── package.json # 项目依赖和脚本
├── next.config.mjs # Next.js 配置文件(新建)
└── README.md # 项目说明
文件说明
app/page.js:首页,显示文章列表,调用 getArticles 获取数据。
app/articles/[id]/page.js:文章详情页,动态路由,根据 documentId 获取具体文章。
app/login/page.js:登录页面,处理用户登录。
app/register/page.js:注册页面,处理用户注册。
app/create-article/page.js:创建文章页面,允许登录用户发布文章。
app/layout.js:根布局文件,包含全局导航,使用 AuthWrapper 和 Navbar 组件。
components/AuthWrapper.js:认证包装组件,提供 AuthContext。
components/Navbar.js(新建):导航组件,动态渲染导航链接。
context/AuthContext.js(新建):认证上下文,管理用户登录状态和 token。
utils/api.js:封装 API 请求,与 Strapi 后端交互。
styles/globals.css:全局样式,简单美化页面。
next.config.mjs(新建):Next.js 配置文件,调整配置以支持项目运行。
3. 具体新建文件内容、作用和功能说明
3.1 app/layout.js
作用:根布局文件,定义全局布局,包含导航。
import AuthWrapper from '../components/AuthWrapper';
import Navbar from '../components/Navbar';
import '../styles/globals.css';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<AuthWrapper>
<Navbar />
{children}
</AuthWrapper>
</body>
</html>
);
}
功能说明:
使用 AuthWrapper 组件提供认证上下文。使用 Navbar 组件渲染导航栏。引入全局样式 globals.css。
3.2 app/page.js
作用:首页,显示文章列表
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { getArticles } from '../utils/api';
export default function Home() {
const [articles, setArticles] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchArticles = async () => {
try {
const response = await getArticles();
console.log('Full API Response:', response);
console.log('Response Data:', response.data);
const articlesData = response.data?.data || response.data; // 处理嵌套
if (Array.isArray(articlesData)) {
setArticles(articlesData);
} else {
setArticles([]);
setError('No articles data found in API response');
}
} catch (err) {
console.error('Fetch Articles Error:', err);
setError('Failed to fetch articles');
setArticles([]);
} finally {
setIsLoading(false);
}
};
fetchArticles();
}, []);
if (isLoading) {
return <div className="container">Loading...</div>;
}
if (error) {
return <div className="container">Error: {error}</div>;
}
return (
<div className="container">
<h1>Articles</h1>
<div className="article-list">
{articles.length === 0 ? (
<p>No articles found.</p>
) : (
articles.map((article) => {
console.log('Article Object:', article);
return (
<div key={article.id} className="article-card">
<Link href={`/articles/${article.documentId}`}>
<h2>{article.title || 'Untitled'}</h2>
</Link>
<p>
{article.content && article.content[0]?.children && article.content[0].children[0]?.text
? article.content[0].children[0].text.slice(0, 100) + '...'
: 'No content available'}
</p>
</div>
);
})
)}
</div>
</div>
);
}
功能说明:
调用 getArticles 获取文章列表。使用 useEffect 和 useState 管理数据加载状态。
渲染文章列表,支持点击跳转到文章详情页(使用 documentId 作为参数)。
3.3 app/articles/[id]/page.js
作用:文章详情页,动态路由,显示具体文章内容。
'use client';
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import { getArticle } from '../../../utils/api';
export default function ArticleDetail() {
const params = useParams();
const documentId = params?.id;
const [article, setArticle] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchArticle = async () => {
try {
const response = await getArticle(documentId);
console.log('Full Article Response:', response);
console.log('Response Data:', response.data);
const articleData = response.data?.data || response.data; // 处理嵌套
if (articleData) {
setArticle(articleData);
} else {
setArticle(null);
}
} catch (err) {
console.error('Fetch Article Error:', err);
} finally {
setIsLoading(false);
}
};
if (documentId) fetchArticle();
}, [documentId]);
if (isLoading) {
return <div className="container">Loading...</div>;
}
if (!article) {
return <div className="container">Article not found.</div>;
}
return (
<div className="container">
<h1>{article.title || 'Untitled'}</h1>
<div className="article-content">
{article.content && Array.isArray(article.content) ? (
article.content.map((block, index) => (
<p key={index}>
{block.children.map((child, childIndex) => (
<span key={childIndex}>{child.text}</span>
))}
</p>
))
) : (
<p>No content available.</p>
)}
</div>
</div>
);
}
功能说明:
使用 useParams 获取动态路由参数 id(documentId)。调用 getArticle 获取文章详情。渲染文章标题和内容。
3.4 app/login/page.js(新建)
作用:登录页面,处理用户登录。
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { login } from '../../utils/api';
import { useAuth } from '../../context/AuthContext';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const { login: authLogin } = useAuth();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const { data } = await login(email, password);
authLogin(data.user, data.jwt);
router.push('/');
} catch (err) {
setError('Login failed');
}
};
return (
<div className="container">
<h1>Login</h1>
<form onSubmit={handleSubmit} className="form">
<div className="form-group">
<label>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" className="btn">Login</button>
</form>
{error && <p className="error">{error}</p>}
</div>
);
}
功能说明:
提供登录表单,输入邮箱和密码。调用 login API,向 Strapi 发送登录请求。
成功后将 JWT 和用户信息存储到 localStorage,并跳转到首页。
3.5 app/register/page.js(新建)
作用:注册页面,处理用户注册。
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { register } from '../../utils/api';
export default function Register() {
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const { data } = await register(username, email, password);
router.push('/login');
} catch (err) {
setError('Registration failed');
}
};
return (
<div className="container">
<h1>Register</h1>
<form onSubmit={handleSubmit} className="form">
<div className="form-group">
<label>Username</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" className="btn">Register</button>
</form>
{error && <p className="error">{error}</p>}
</div>
);
}
功能说明:
提供注册表单,输入用户名、邮箱和密码。调用 register API,向 Strapi 发送注册请求。
成功后存储 JWT 和用户信息,跳转到首页。
3.6 app/create-article/page.js(新建)
作用:创建文章页面,允许登录用户发布文章。
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { createArticle } from '../../utils/api';
import { useAuth } from '../../context/AuthContext';
export default function CreateArticle() {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [error, setError] = useState('');
const { token } = useAuth();
const router = useRouter();
const handleSubmit = async (e) => {
e.preventDefault();
if (!token) {
setError('Please login to create an article');
return;
}
try {
const articleData = {
title,
content: [
{
type: 'paragraph',
children: [
{
type: 'text',
text: content || 'Default content',
},
],
},
],
publishedAt: new Date().toISOString(),
};
await createArticle(token, articleData);
router.push('/');
} catch (err) {
setError('Failed to create article');
}
};
return (
<div className="container">
<h1>Create Article</h1>
<form onSubmit={handleSubmit} className="form">
<div className="form-group">
<label>Title</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Content</label>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
required
rows="5"
/>
</div>
<button type="submit" className="btn">Publish</button>
</form>
{error && <p className="error">{error}</p>}
</div>
);
}
功能说明:
提供文章发布表单,输入标题和内容。检查用户是否登录(通过 token)。
调用 createArticle API,向 Strapi 发送文章创建请求。成功后跳转到首页。
3.7 components/AuthWrapper.js(新建)
作用:认证包装组件,提供 AuthContext。
'use client';
import { AuthProvider } from '../context/AuthContext';
export default function AuthWrapper({ children }) {
return <AuthProvider>{children}</AuthProvider>;
}
功能说明:
使用 AuthProvider 提供认证上下文,包装所有页面内容。
确保子组件可以通过 useAuth 访问用户状态和 token。
3.8 components/Navbar.js(新建)
作用:导航组件,动态渲染导航链接。
'use client';
import Link from 'next/link';
import { useAuth } from '../context/AuthContext';
export default function Navbar() {
const { user, logout } = useAuth();
return (
<nav className="navbar">
<Link href="/">Home</Link>
{user ? (
<>
<Link href="/create-article">Create Article</Link>
<button onClick={logout} className="btn">Logout</button>
<span>Welcome, {user.username}</span>
</>
) : (
<>
<Link href="/login">Login</Link>
<Link href="/register">Register</Link>
</>
)}
</nav>
);
}
功能说明:
使用 useAuth 获取用户状态和 logout 方法。
动态渲染导航:未登录显示“登录”和“注册”,已登录显示“创建文章”、“登出”和欢迎信息。
3.9 context/AuthContext.js(新建)
作用:认证上下文,管理用户登录状态和 token。
'use client';
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [token, setToken] = useState(null);
useEffect(() => {
if (typeof window !== 'undefined') {
const storedToken = localStorage.getItem('token');
const storedUser = localStorage.getItem('user');
if (storedToken && storedUser) {
setToken(storedToken);
setUser(JSON.parse(storedUser));
}
}
}, []);
const login = (userData, jwt) => {
setUser(userData);
setToken(jwt);
if (typeof window !== 'undefined') {
localStorage.setItem('token', jwt);
localStorage.setItem('user', JSON.stringify(userData));
}
};
const logout = () => {
setUser(null);
setToken(null);
if (typeof window !== 'undefined') {
localStorage.removeItem('token');
localStorage.removeItem('user');
}
};
return (
<AuthContext.Provider value={{ user, token, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);
功能说明:
创建 AuthContext,管理用户状态(user 和 token)。
useEffect:在客户端加载时检查 localStorage,恢复用户状态。
login:保存用户数据和 token 到状态和 localStorage。
logout:清除用户状态和 localStorage。
useAuth:提供钩子,供其他组件访问上下文。
3.10 utils/api.js
作用:封装 API 请求,与 Strapi 后端交互。
import axios from 'axios';
const API_URL = 'http://localhost:1337/api';
export const register = async (username, email, password) => {
return axios.post(`${API_URL}/auth/local/register`, {
username,
email,
password,
});
};
export const login = async (email, password) => {
return axios.post(`${API_URL}/auth/local`, {
identifier: email,
password,
});
};
export const getArticles = async () => {
return axios.get(`${API_URL}/articles?populate=*`);
};
export const getArticle = async (documentId) => {
return axios.get(`${API_URL}/articles/${documentId}?populate=*`);
};
export const createArticle = async (token, articleData) => {
return axios.post(
`${API_URL}/articles`,
{ data: articleData },
{ headers: { Authorization: `Bearer ${token}` } }
);
};
功能说明:
register:向 Strapi 发送注册请求。login:向 Strapi 发送登录请求。
getArticles:获取文章列表。getArticle:获取单篇文章(使用 documentId)。
createArticle:创建新文章(需要认证)。
3.11 styles/globals.css
作用:全局样式,简单美化页面。
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.navbar {
display: flex;
gap: 20px;
padding: 10px 20px;
background-color: #0070f3;
color: white;
}
.navbar a {
color: white;
text-decoration: none;
}
.navbar button {
background: none;
border: none;
color: white;
cursor: pointer;
}
.form {
display: flex;
flex-direction: column;
gap: 15px;
max-width: 400px;
margin: 0 auto;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
margin-bottom: 5px;
font-weight: bold;
}
.form-group input,
.form-group textarea {
padding: 8px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
.btn {
padding: 10px;
background-color: #0070f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:hover {
background-color: #005bb5;
}
.error {
color: red;
margin-top: 10px;
}
.article-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.article-card {
padding: 15px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.article-card h2 {
margin: 0 0 10px;
color: #0070f3;
}
.article-content p {
margin: 10px 0;
}
功能说明:
提供全局样式,美化页面布局、导航、表单和文章展示。
3.12 next.config.mjs(新建)
作用:Next.js 配置文件,调整项目配置。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
export default nextConfig;
功能说明:
启用 reactStrictMode,确保 React 严格模式运行,帮助发现潜在问题。
可根据需要添加其他配置(如图像优化、环境变量等)。
4.配置完毕以后,npm run dev 启动strapi 与next.js 然后测试下面的!
注册:访问 http://localhost:3000/register,输入用户名、邮箱和密码,注册用户。
登录:访问 http://localhost:3000/login,输入邮箱和密码,登录。
文章列表:访问 http://localhost:3000/,查看文章列表。
文章详情:点击文章标题,访问 http://localhost:3000/articles/nbedxv11g6sdbxvnsdjxs48n,查看文章详情。
发布文章:登录后访问 http://localhost:3000/create-article,创建新文章。
好的。下面已经打包备份了。strapi cms 和next.js website 文件中修改的资料;可以查找对比。
下地地址 https://www.525you.com/d/file/2025/202503/f588af66fa52f6c2e80446d0fdfbreb1.rar