Koa
或者其它Node.js
服务器进行集成mkdir zhufengnextjs
cd zhufengnextjs
npm init -y
yarn add --dev typescript react @types/react react-dom @types/node next axios
mkdir pages
package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
https://github.com/zeit/next.js/blob/canary/.gitignore
public
静态文件服务映射pages
组件代码自动分割index.tsx
export default function () {
return (
<div>Home</div>
)
}
curl http://localhost:3000/
styled-jsx
来生成独立作用域的 CSS
css
antd
并实现按需加载yarn add @zeit/next-css antd babel-plugin-import
.babelrc
{
"presets": [
"next/babel"
],
"plugins": [
[
"import",
{
"libraryName": "antd"
}
]
]
}
next.config.js
const withCSS = require('@zeit/next-css');
if (typeof require !== 'undefined') {
require.extensions['.css'] = file => { }
}
module.exports = withCSS({})
App
组件来初始化页面,你可以重写它来控制页面初始化pages_app.tsx
import App, { Container } from 'next/app';
import Link from 'next/link';
import { Layout, Menu, Icon } from 'antd';
import 'antd/dist/antd.css';
import { withRouter } from 'next/router';
const { Header, Footer } = Layout;
class LayoutApp extends App<any> {
render() {
let { Component } = this.props as any;
let pathname = this.props.router.pathname;
pathname = '/' + (pathname.split('/')[1]);
return (
<Container>
<style jsx>
{`a{display:inline-block!important;}`}
</style>
<Layout>
<Header className="header">
<img src="/images/jglogo.png" style={{ width: 120, height: 31, margin: '16px 24px 16px 0px', float: 'left' }} />
<Menu theme="dark" mode="horizontal" style={{ lineHeight: '64px', display: 'inline-block' }}
selectedKeys={[pathname]}
defaultSelectedKeys={[pathname]}>
<Menu.Item key="/">
<Icon type="home" /><Link href="/"><a>首页</a></Link>
</Menu.Item>
<Menu.Item key="/user">
<Icon type="/user" /> <Link href="/user" ><a>用户管理</a></Link>
</Menu.Item>
<Menu.Item key="/profile">
<Icon type="profile" /><Link href="/profile"><a>个人中心</a></Link>
</Menu.Item>
<Menu.Item key="/login">
<Icon type="login" /><Link href="/login"><a>登录</a></Link>
</Menu.Item>
</Menu>
</Header>
<Component />
<Footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</Footer>
</Layout>
</Container>
)
}
}
export default withRouter(LayoutApp);
pages\index.tsx
import router from 'next/router'
import { Button, Layout } from 'antd';
const { Content } = Layout;
export default function () {
return (
<Content style={{ margin: '20px auto' }}>
<div>Home</div>
<Button onClick={() => router.push('/user')}>/user</Button>
</Content>
)
}
pages\user.tsx
import router from 'next/router';
import { Button, Layout } from 'antd';
const { Content } = Layout;
export default function () {
return (
<Content style={{ margin: '20px auto' }}>
<div>User</div>
<Button onClick={() => router.push('/profile')}>/profile</Button>
</Content>
)
}
pages\profile.tsx
import router from 'next/router';
import { Button, Layout } from 'antd';
const { Content } = Layout;
export default function () {
return (
<Content style={{ margin: '20px auto' }}>
<div>Profile</div>
<Button onClick={() => router.back()}>返回</Button>
</Content>
)
}
getInitialProps
获取数据pages_app.tsx
import App from 'next/app';
import Link from 'next/link';
import { Layout, Menu, Icon } from 'antd';
import 'antd/dist/antd.css';
import { withRouter } from 'next/router';
const { Header, Footer } = Layout;
class LayoutApp extends App<any> {
+ static async getInitialProps(params) {
+ if (params.ctx.req) params.ctx.req = 'req';
+ if (params.ctx.res) params.ctx.res = 'res';
+ console.log('1.LayoutApp.getInitialProps', params);
+ let { Component, ctx } = params;
+ let pageProps = {};
+ if (Component.getInitialProps)
+ pageProps = await Component.getInitialProps(ctx);
+ return { pageProps };
+ }
render() {
+ console.log('3.LayoutApp.render', this.props);
let { Component } = this.props as any;
let pathname = this.props.router.pathname;
pathname = '/' + (pathname.split('/')[1]);
return (
<>
<style jsx>
+ {`a{display:inline-block!important;}`}
</style>
<Layout>
<Header className="header">
+ <img src="/images/jglogo.png" style={{ width: 120, height: 31, margin: '16px 24px 16px 0px', float: 'left' }} />
+ <Menu theme="dark" mode="horizontal" style={{ lineHeight: '64px', display: 'inline-block' }}
selectedKeys={[pathname]}
defaultSelectedKeys={[pathname]}>
<Menu.Item key="/">
<Icon type="home" /><Link href="/"><a>首页</a></Link>
</Menu.Item>
<Menu.Item key="/user">
<Icon type="/user" /> <Link href="/user" ><a>用户管理</a></Link>
</Menu.Item>
<Menu.Item key="/profile">
<Icon type="profile" /><Link href="/profile"><a>个人中心</a></Link>
</Menu.Item>
</Menu>
</Header>
+ <Component {...this.props.pageProps} />
<Footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</Footer>
</Layout>
</>
)
}
}
export default withRouter(LayoutApp);
pages\user\index.tsx
import { withRouter } from 'next/router';
import Link from 'next/link';
import { Layout, Menu, Icon } from 'antd';
const { Sider, Content } = Layout;
function UserLayout(props) {
return (
<>
<style jsx>
{`a{display:inline-block!important;}`}
</style>
<Layout>
<Sider>
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={[props.router.pathname]}
>
<Menu.Item key="/user/list">
<Icon type="user" /><Link href="/user/list"><a>用户列表</a></Link>
</Menu.Item>
<Menu.Item key="/user/add">
<Icon type="plus" /><Link href="/user/add"><a>添加用户</a></Link>
</Menu.Item>
</Menu>
</Sider>
<Content style={{ padding: 8 }}>
{props.children}
</Content>
</Layout>
</>
)
}
export default withRouter(UserLayout);
pages\user\list.tsx
import UserLayout from './index';
import { List } from 'antd';
import Link from 'next/link';
function UseList(props) {
console.log('4.UseList.render', props);
return (
<UserLayout>
<List
header={<div>用户列表</div>}
footer={<div>共计多少{props.list.length}个用户</div>}
bordered
dataSource={props.list}
renderItem={(item: any) => (
<List.Item key={item._id}>
<Link as={`/user/detail/${item._id}`} href={{ pathname: `/user/detail`, query: { id: item._id } }}><a>{item.username}</a></Link>
</List.Item>
)}
/>
</UserLayout>
)
}
UseList.getInitialProps = async (ctx) => {
if (ctx.req) { ctx.req = 'req'; }
if (ctx.res) { ctx.res = 'res'; }
console.log('2.UseList.getInitialProps ctx', ctx);
let list = [{ _id: 1, username: 'zhangsan', password: '1' }, { _id: 2, username: 'lisi', password: '2' }];
return { list };
}
export default UseList;
pages\user\add.tsx
import UserLayout from './index';
import router from 'next/router';
import { Form, Input, Button, Icon } from 'antd';
function UserAdd(props) {
const { getFieldDecorator } = props.form;
return (
<UserLayout>
<Form className="login-form" style={{ maxWidth: '300px' }}>
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
添加用户
</Button>
</Form.Item>
</Form>
</UserLayout>
)
}
export default Form.create({ name: 'UserAdd' })(UserAdd);
koa
koa
使用next
作中间件yarn add koa koa-router
client\index.js
let Koa = require('koa');
let Router = require('koa-router');
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev: true });
const handler = app.getRequestHandler();
app.prepare().then(() => {
const server = new Koa();
let router = new Router();
server.use(router.routes());
server.use(async (ctx, next) => {
await handler(ctx.req, ctx.res);
ctx.response = false;
});
server.listen(3000, () => console.log('server started at port 3000'));
});
package.json
"scripts": {
+ "client": "nodemon client",
"build": "next build",
"start": "next start"
},
getInitialProps
将会把数据序列化,就像JSON.stringify
getInitialProps
返回的是一个普通 JS
对象,而不是Date, Map 或 Set类型getInitialProps
只会加载在服务端。只有当路由跳转(Link组件跳转或 API 方法跳转)时,客户端才会执行getInitialProps
getInitialProps
将不能使用在子组件中。只能使用在pages
页面中pathname
,query
或 asPath
到你组件中,你可以使用withRouter
utils\axios.tsx
import axios from 'axios';
axios.defaults.withCredentials = true
const instance = axios.create({
baseURL: 'http://localhost:4000'
})
export default instance;
pages\user\add.tsx
import UserLayout from './index';
import router from 'next/router';
+import { Form, Input, Button, Icon, message } from 'antd';
+import axios from '../../utils/axios';
function UserAdd(props) {
+ async function handleSubmit(event) {
+ event.preventDefault();
+ let values = props.form.getFieldsValue();
+ let response = await axios.post('/api/register', values);
+ if (response.data.code === 0) {
+ router.push('/user/list');
+ } else {
+ message.error('添加用户失败');
+ }
+ }
const { getFieldDecorator } = props.form;
return (
<UserLayout>
+ <Form onSubmit={handleSubmit} className="login-form" style={{ maxWidth: '300px' }}>
{props.currentUser && <span>创建者:{props.currentUser.username}</span>}
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
添加用户
</Button>
</Form.Item>
</Form>
</UserLayout>
)
}
export default Form.create({ name: 'UserAdd' })(UserAdd);
pages\user\list.tsx
import UserLayout from './index';
import { List } from 'antd';
import Link from 'next/link';
+import axios from '../../utils/axios';
function UseList(props) {
console.log('4.UseList.render', props);
return (
<UserLayout>
<List
header={<div>用户列表</div>}
footer={<div>共计多少{props.list.length}个用户</div>}
bordered
dataSource={props.list}
renderItem={(item: any) => (
<List.Item key={item._id}>
<Link as={`/user/detail/${item._id}`} href={{ pathname: `/user/detail`, query: { id: item._id } }}><a>{item.username}</a></Link>
</List.Item>
)}
/>
</UserLayout>
)
}
UseList.getInitialProps = async (ctx) => {
if (ctx.req) { ctx.req = 'req'; }
if (ctx.res) { ctx.res = 'res'; }
console.log('2.UseList.getInitialProps ctx', ctx);
+ let response = await axios({ url: '/api/users', method: 'GET' });
+ return { list: response.data.data };
}
export default UseList;
pages\user\detail.tsx
import React, { useState } from 'react';
import UserLayout from './';
import { withRouter } from 'next/router';
import { Button } from 'antd';
import axios from '../../utils/axios';
import dynamic from 'next/dynamic';
const UserInfo = dynamic(import('../../components/UserInfo'))
function UserDetail(props) {
let [show, setShow] = useState(false);
return (
<UserLayout>
<p>ID: {props.router.query.id}</p>
<Button onClick={() => setShow(!show)}>显示/隐藏</Button>
{
show && <UserInfo user={props.user} />
}
</UserLayout>
)
}
UserDetail.getInitialProps = async (ctx) => {
let response = await axios({ url: `/api/users/${ctx.query.id}`, method: 'GET' });
return { user: response.data.data };
}
export default withRouter(UserDetail);
components\UserInfo.tsx
import { useState } from 'react';
import { Button } from 'antd';
function UserInfo(props) {
let [created, setCreated] = useState(props.user.created);
async function changeFormat() {
let moment = await import('moment');
setCreated(moment.default(props.user.created).fromNow());
}
return (
<>
<p>用户名:{props.user.username}</p>
<p>密码:{props.user.password}</p>
<p>创建时间:{created}<Button onClick={changeFormat}>切换为相对时间</Button></p>
</>
)
}
export default UserInfo;
client\index.js
let Koa = require('koa');
let Router = require('koa-router');
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev: true });
const handler = app.getRequestHandler();
app.prepare().then(() => {
const server = new Koa();
let router = new Router();
+ router.get('/user/detail/:id', async (ctx, next) => {
+ const id = ctx.params.id;
+ await handler(ctx.req, ctx.res, {
+ pathname: '/user/detail',
+ query: { id }
+ });
+ ctx.response = false;
+ });
server.use(router.routes());
server.use(async (ctx, next) => {
await handler(ctx.req, ctx.res);
ctx.response = false;
});
server.listen(3000, () => console.log('server started at port 3000'));
});
import router from 'next/router';
import { Form, Input, Button, Icon, message } from 'antd';
import axios from '../utils/axios';
function Login(props) {
const { getFieldDecorator } = props.form;
async function handleSubmit(event) {
event.preventDefault();
let values = props.form.getFieldsValue();
let response = await axios.post('/api/login', values);
if (response.data.code === 0) {
router.push('/');
} else {
message.error('登录失败');
}
}
return (
<Form onSubmit={handleSubmit} className="login-form" style={{ maxWidth: '300px',margin:'20px auto' }}>
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
登录
</Button>
</Form.Item>
</Form>
)
}
const WrappedLogin = Form.create({ name: 'Login' })(Login);
export default WrappedLogin;
yarn add redux react-redux
pages_app.tsx
import App from 'next/app';
import Link from 'next/link';
+import { Layout, Menu, Icon, Avatar } from 'antd';
import 'antd/dist/antd.css';
import { withRouter } from 'next/router';
+import axios from '../utils/axios';
+import initStore from '../store';
+import { Provider } from 'react-redux';
+import * as TYEPS from '../store/action-types';
+const { Header, Footer, Content } = Layout;
+declare global {
+ interface Window {
+ _redux_store_: any
+ }
+}
+function getStore(initialState) {
+ if (typeof window == 'undefined') {
+ return initStore(initialState);
+ } else {
+ if (!window._redux_store_) {
+ window._redux_store_ = initStore(initialState);
+ }
+ return window._redux_store_;
+ }
+}
class LayoutApp extends App<any> {
+ store: any
+ constructor(props) {
+ super(props);
+ this.store = getStore(props.initialState);
+ }
+ static async getInitialProps({ Component, ctx }) {
+ let store = getStore({});
+ let currentUser;
+ let options: any = { url: '/api/currentUser' };
+ if (ctx.req&&ctx.req.headers.cookie) {
+ (options.headers = options.headers || {}).cookie = ctx.req.headers.cookie;
+ }
+ let response = await axios(options);
+ if (response.data.code == 0) {
+ currentUser = response.data.data;
+ store.dispatch({ type: TYEPS.SET_USER_INFO, payload: currentUser });
+ }
let pageProps = {};
if (Component.getInitialProps)
pageProps = await Component.getInitialProps(ctx);
+ return { pageProps, currentUser, initialState: store.getState() };
}
render() {
let { Component } = this.props as any;
let pathname = this.props.router.pathname;
pathname = '/' + (pathname.split('/')[1]);
return (
+ <Provider store={this.store}>
<style jsx>
{`a{display:inline-block!important;}`}
</style>
<Layout>
<Header className="header">
<img src="/images/jglogo.png" style={{ width: 120, height: 31, margin: '16px 24px 16px 0px', float: 'left' }} />
<Menu theme="dark" mode="horizontal" style={{ lineHeight: '64px', display: 'inline-block' }}
selectedKeys={[pathname]}
defaultSelectedKeys={[pathname]}>
<Menu.Item key="/">
<Icon type="home" /><Link href="/"><a>首页</a></Link>
</Menu.Item>
<Menu.Item key="/user">
<Icon type="/user" /> <Link href="/user" ><a>用户管理</a></Link>
</Menu.Item>
<Menu.Item key="/profile">
<Icon type="profile" /><Link href="/profile"><a>个人中心</a></Link>
</Menu.Item>
<Menu.Item key="/login">
<Icon type="login" /><Link href="/login"><a>登录</a></Link>
</Menu.Item>
</Menu>
+ {
+ this.props.currentUser && <div style={{ display: 'inline-block', float: 'right', color: 'red' }}>
+ <Avatar style={{ color: '#F00', backgroundColor: '#CCC' }}>{this.props.currentUser.username}</Avatar>
+ </div>
+ }
</Header>
<Component {...this.props.pageProps} />
<Footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</Footer>
</Layout>
+ </Provider>
)
}
}
export default withRouter(LayoutApp);
pages\user\add.tsx
import UserLayout from './index';
import router from 'next/router';
import { Form, Input, Button, Icon, message } from 'antd';
import axios from '../../utils/axios';
+import { connect } from 'react-redux';
function UserAdd(props) {
async function handleSubmit(event) {
event.preventDefault();
let values = props.form.getFieldsValue();
let response = await axios.post('/api/register', values);
if (response.data.code === 0) {
router.push('/user/list');
} else {
message.error('添加用户失败');
}
}
const { getFieldDecorator } = props.form;
return (
<UserLayout>
<Form onSubmit={handleSubmit} className="login-form" style={{ maxWidth: '300px' }}>
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
添加用户
</Button>
</Form.Item>
</Form>
</UserLayout>
)
}
+const WrappedUserAdd = Form.create({ name: 'UserAdd' })(UserAdd);
+export default WrappedUserAdd;
store\action-types.tsx
export const SET_USER_INFO = 'SET_USER_INFO';//设置用户信息
store\index.tsx
import { createStore } from 'redux';
import * as TYPES from './action-types';
let initState = {
currentUser: null
}
const reducer = (state = initState, action) => {
switch (action.type) {
case TYPES.SET_USER_INFO:
return { currentUser: action.payload }
default:
return state;
}
}
export default function initStore(initialState) {
const store = createStore(reducer, initialState);
return store;
}
事件 | 触发时机 |
---|---|
routeChangeStart(url) | 路由开始切换时触发 |
routeChangeComplete(url) | 完成路由切换时触发 |
routeChangeError(err, url) | 路由切换报错时触发 |
beforeHistoryChange(url) | 浏览器 history 模式开始切换时触发 |
hashChangeStart(url) | 开始切换 hash 值但是没有切换页面路由时触发 |
hashChangeComplete(url) | 完成切换 hash 值但是没有切换页面路由时触发 |
import App from 'next/app';
import Link from 'next/link';
import { Layout, Menu, Icon, Avatar } from 'antd';
import 'antd/dist/antd.css';
import { withRouter } from 'next/router';
import axios from '../utils/axios';
import initStore from '../store';
import { Provider } from 'react-redux';
import * as TYEPS from '../store/action-types';
+import router from 'next/router';
+import { Spin } from 'antd';
const { Header, Footer, Content } = Layout;
declare global {
interface Window {
_redux_store_: any
}
}
function getStore(initialState) {
if (typeof window == 'undefined') {
return initStore(initialState);
} else {
if (!window._redux_store_) {
window._redux_store_ = initStore(initialState);
}
return window._redux_store_;
}
}
class LayoutApp extends App<any> {
store: any
+ state = { loading: false }
+ routeChangeStart: any
+ routeChangeComplete: any
constructor(props) {
super(props);
this.store = getStore(props.initialState);
}
static async getInitialProps({ Component, ctx }) {
let store = getStore({});
let currentUser;
let options: any = { url: '/api/currentUser' };
if (ctx.req) {
(options.headers = options.headers || {}).cookie = ctx.req.headers.cookie;
}
let response = await axios(options);
if (response.data.code == 0) {
currentUser = response.data.data;
store.dispatch({ type: TYEPS.SET_USER_INFO, payload: currentUser });
}
let pageProps = {};
if (Component.getInitialProps)
pageProps = await Component.getInitialProps(ctx);
return { pageProps, currentUser, initialState: store.getState() };
}
+ componentDidMount() {
+ this.routeChangeStart = (url) => {
+ this.setState({ loading: true });
+ };
+ router.events.on('routeChangeStart', this.routeChangeStart);
+ this.routeChangeComplete = (url) => {
+ this.setState({ loading: false });
+ };
+ router.events.on('routeChangeComplete', this.routeChangeComplete);
+ }
componentWillUnmount() {
router.events.off('routeChangeStart', this.routeChangeStart)
router.events.off('routeChangeStart', this.routeChangeComplete)
}
render() {
let { Component, pageProps, currentUser } = this.props as any;
let pathname = this.props.router.pathname;
pathname = '/' + (pathname.split('/')[1]);
return (
<Provider store={this.store}>
<style jsx>
{`a{display:inline-block!important;}`}
</style>
<Layout>
<Header className="header">
<img src="/images/jglogo.png" style={{ width: 120, height: 31, margin: '16px 24px 16px 0px', float: 'left' }} />
<Menu theme="dark" mode="horizontal" style={{ lineHeight: '64px', display: 'inline-block' }}
selectedKeys={[pathname]}
defaultSelectedKeys={[pathname]}>
<Menu.Item key="/">
<Icon type="home" /><Link href="/"><a>首页</a></Link>
</Menu.Item>
<Menu.Item key="/user">
<Icon type="/user" /> <Link href="/user" ><a>用户管理</a></Link>
</Menu.Item>
<Menu.Item key="/profile">
<Icon type="profile" /><Link href="/profile"><a>个人中心</a></Link>
</Menu.Item>
<Menu.Item key="/login">
<Icon type="login" /><Link href="/login"><a>登录</a></Link>
</Menu.Item>
</Menu>
+ {
+ this.props.currentUser && <div style={{ display: 'inline-block', float: 'right', color: 'red' }}>
+ <Avatar style={{ color: '#F00', backgroundColor: '#CCC' }}>{this.props.currentUser.username}</Avatar>
+ </div>
}
</Header>
{
this.state.loading ? <Spin style={{ margin: '50px auto' }} /> : <Component {...pageProps} currentUser={currentUser} />
}
<Footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</Footer>
</Layout>
</Provider>
)
}
}
export default withRouter(LayoutApp);
pages\profile.tsx
import router from 'next/router';
import { Button, Layout } from 'antd';
+import axios from '../utils/axios';
const { Content } = Layout;
+function Profile(props) {
return (
<Content style={{ margin: '20px auto' }}>
+ <div>当前登录用户:{props.currentUser.username}</div>
<Button onClick={() => router.back()}>返回</Button>
</Content>
)
}
+Profile.getInitialProps = async function (ctx) {
+ let options: any = { url: '/api/currentUser' };
+ if (ctx.req&&ctx.req.headers.cookie) {
+ (options.headers = options.headers || {}).cookie = ctx.req.headers.cookie;
+ }
+ let response = await axios(options);
+ if (response.data.code == 0) {
+ return {};
+ } else {
+ if (ctx.req) {
+ ctx.res.writeHead(303, { Location: '/login' })
+ ctx.res.end()
+ } else {
+ router.push('/login');
+ }
+ return {};
+ }
+}
+
+const WrappedProfile = connect(
+ state => state
+)(Profile);
+export default WrappedProfile;
pages_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class CustomDocument extends Document {
static async getInitialProps(ctx) {
const props = await Document.getInitialProps(ctx);
return { ...props };
}
render() {
return (
<Html>
<Head>
<style>
{
`
*{
padding:0;
margin:0;
}
`
}
</style>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default CustomDocument;
pages\index.tsx
import router from 'next/router'
import { Button, Layout } from 'antd';
import Head from 'next/head'
const { Content } = Layout;
export default function () {
return (
<>
<Head>
<title>首页</title>
<meta name="description" content="这是首页" />
</Head>
<Content style={{ margin: '20px auto' }}>
<div>Home</div>
<Button onClick={() => router.push('/user')}>/user</Button>
</Content>
</>
)
}
yarn add express body-parser cors express-session connect-mongo mongoose
let express = require("express");
let bodyParser = require("body-parser");
let cors = require("cors");
let Models = require('./db');
let session = require("express-session");
let config = require('./config');
let MongoStore = require('connect-mongo')(session);
let app = express();
app.use(
cors({
origin: ['http://localhost:3000'],
credentials: true,
allowedHeaders: "Content-Type,Authorization",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS"
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(
session({
secret: config.secret,
resave: false,
saveUninitialized: true,
store: new MongoStore({
url: config.dbUrl,
mongoOptions: {
useNewUrlParser: true,
useUnifiedTopology: true
}
})
})
);
app.get('/api/users', async (req, res) => {
let users = await Models.UserModel.find();
res.send({ code: 0, data: users });
});
app.get('/api/users/:id', async (req, res) => {
let user = await Models.UserModel.findById(req.params.id);
res.send({ code: 0, data: user });
});
app.post('/api/register', async (req, res) => {
let user = req.body;
user = await Models.UserModel.create(user);
res.send({ code: 0, data: user });
});
app.post('/api/login', async (req, res) => {
let user = req.body;
let dbUser = await Models.UserModel.findOne(user);
if (dbUser) {
req.session.currentUser = dbUser;
res.send({ code: 0, data: dbUser });
} else {
res.send({ code: 1, error: '登录失败' });
}
});
app.get('/api/currentUser', async (req, res) => {
let currentUser = req.session.currentUser;
if (currentUser) {
res.send({ code: 0, data: currentUser });
} else {
res.send({ code: 1, error: '当前用户未登录' });
}
});
app.listen(4000, () => {
console.log('服务器在4000端口启动!');
});
api\config.js
module.exports = {
secret: 'zhufeng',
dbUrl: "mongodb://127.0.0.1/zhufengnext2"
}
api\db.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;
const config = require('./config');
const conn = mongoose.createConnection(config.dbUrl, { useNewUrlParser: true, useUnifiedTopology: true });
const UserModel = conn.model('User', new Schema({
username: { type: String },
password: { type: String }
}, { timestamps: { createdAt: 'created', updatedAt: 'updated' } }));
module.exports = {
UserModel
}