此文章,会从零开始结合Ant Design UI和PostgreSQL做一个简单的增删改
这里只是一个简单的demo,真实的开发中我们能可能还需要权限,日志,连接池等等。
参考官网:https://nextjs.org/learn/basics/getting-started
开发环境:
window10 x64
node v10.16.3
npm v6.13.4
1.项目初始化
参考‘Next.js与TypeScript从零起步学习笔记-1’,我们先创建一个空项目并添加TS引用:
npm init -y
npm install --save react react-dom next
mkdir pages
npm install --save-dev typescript @types/react @types/node
改一下生成命令,修改'package.json'文件代码:
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
然后我们在'pages'目录下,建一个'index.tsx',随便输入点代码,测试一下是否能跑去起来:
//pages/index.tsx
const Home = () => <h1>Hello world!</h1>;
export default Home;
运行项目:
npm run dev
应该可以看到效果:
这里我并打算用到严格模式(类型约束),因为后面文章引用到的类库,会出现诸多问题,为项目简单起见,我不建议用严格模式。
2.创建RESTful API
2.1 创建表
我们需要在数据库创建一张用户表,用来存放用户的数据,并对其进行简单的增删改查,我用的数据库是PostgreSQL。(PS:你需要有一点数据库相关的知识)
--table next_user
CREATE TABLE public.next_user (
id int4 NOT NULL GENERATED ALWAYS AS IDENTITY,
"name" varchar(40) NOT NULL,
age int2 NOT NULL,
created_date_time timestamp NOT NULL,
CONSTRAINT next_user_pk PRIMARY KEY (id)
);
2.2 创建RESTful API
我们在pages文件夹下创建一个api文件夹,主要用来存放我们api
//以项目根目录为准
cd pages
mkdir api
cd api
mkdir user
我们在'user'文件夹下,创建一个'[id].ts'的文件,这是我们的接口文件,并在文件敲入如下代码:
import { NextApiRequest, NextApiResponse } from 'next';
export default (req: NextApiRequest, res: NextApiResponse) => {
try{
switch (req.method.toUpperCase()) {
case "GET":
_get(req,res);
break;
case "POST":
_post(req,res);
break;
case "PUT":
_put(req,res);
break;
case "DELETE":
_delete(req,res);
break;
default:
res.status(404).send("");
break;
}
} catch (e){
//make some logs
console.debug("error");
console.debug(e);
res.status(500).send("");
}
};
function _get(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("GET");
}
function _post(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("POST");
}
function _put(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("PUT");
}
function _delete(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("DELETE");
}
以上,我们已经创建了我们最简单的RESTful API了,把项目跑起来,我们能看到效果
PS:以上是动态路由,即根据路径获取资源Id,如图3URL:http://localhost:3000/api/user/1,后面的'/1'表示的是访问这个id的资源,现实中只有编辑时候才会用到,新增或获取列表时候,并不适用,所以:
我们需要在'pages/api'下,新添一个路由文件'user.ts',主要作用是处理非指定资源(没有id):
//pages/api/user.ts
import { NextApiRequest, NextApiResponse } from 'next';
export default (req: NextApiRequest, res: NextApiResponse) => {
try{
switch (req.method.toUpperCase()) {
case "GET":
_get(req,res);
break;
case "POST":
_post(req,res);
break;
default:
res.status(404).send("");
break;
}
} catch (e){
//make some logs
console.debug("error");
console.debug(e);
res.status(500).send("");
}
};
function _get(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("GET");
}
function _post(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("POST");
}
我们再来修改'pages/api/user/[id].ts',把post去掉,因为post是新增,所以逻辑上在这个文件永远用不上
//pages/api/user/[id].ts
import { NextApiRequest, NextApiResponse } from 'next';
export default (req: NextApiRequest, res: NextApiResponse) => {
try{
switch (req.method.toUpperCase()) {
case "GET":
_get(req,res);
break;
case "PUT":
_put(req,res);
break;
case "DELETE":
_delete(req,res);
break;
default:
res.status(404).send("");
break;
}
} catch (e){
//make some logs
console.debug("error");
console.debug(e);
res.status(500).send("");
}
};
function _get(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("GET");
}
function _put(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("PUT");
}
function _delete(req: NextApiRequest, res: NextApiResponse){
res.status(200).send("DELETE");
}
这样建立文件,看上去有点繁琐,但目前我所知道,Next的路由就是这样。
能不能像.NET这样,用标签属性来确定呢?我目前没有找到好方法,知道的小伙伴请不吝赐教,私信告诉我
2.3 对数据库进行读写
我们连接数据库,第一步是先安装上驱动,我开始使用的是'ts-postgres',为什么用它?没啥的,就是因为这个东西是TS写的,但后面发现坑有点多,例如我要count多少行,总是返回null,我以为是函数问题,但换了now又正常,弄了很久,后面放弃了,只好换另一个驱动'node-postgres',这个东西问题就是用js写的,ts里用,你会发现一堆都是any。
安装node-postgres:
npm install pg
我们先来创建一个配置文件,这个配置文件用来保存一些系统的配置,例如数据库连接等等,在根目录下创建'config.json':
//config.json
{
"dbCofing":{
"host": "localhost",
"port":5432,
"user":"postgres",
"database":"next_learn",
"password":"123456"
}
}
然后我们创建一个文件,命名为'repositories',用来存放sql操作等逻辑:
//以项目根目录为准
cd pages
mkdir repositories
在'repositories'文件夹下,我们创建一个文件'user-repository.ts',这里主要编写用户表(见2.1)的读写逻辑。
//repositories/user-repository.ts
import { Client } from 'pg';
import config from "../config.json";
//这个接口应该单独弄出去,弄个文件夹夹叫utility之类放着,因为后续肯定不止这个地方用到。
export interface PageData<T> {
index?: number;
pageSize?: number;
totalCount?: number;
list?: Array<T>;
}
export interface NextUser {
id?: number;
name?: string;
age?: number;
createdDateTime?: Date;
}
export class UserRepository {
//分页获取所有用户
async getAllUser(index: number, pageSize: number): Promise<PageData<NextUser>> {
const client = new Client(config.dbCofing);
await client.connect();
try {
let pageData: PageData<NextUser> = {}
pageData.index = index;
pageData.pageSize = pageSize;
//以下加await的话,会同步等待结果返回
const totalCount = await client.query(
`SELECT count(*) as total_count from next_user`
);
pageData.totalCount = parseInt(totalCount.rows[0].total_count);
const result = await client.query(
`select id,name,age,created_date_time from next_user order by id desc limit ${pageSize} offset ${(pageSize * (index - 1))}; `
);
let nextUsers: NextUser[] = [];
for (const row of result.rows) {
let nextUser: NextUser = {
id: row.id as number,
name: row.name as string,
age: row.age as number,
createdDateTime: row.created_date_time as Date
};
nextUsers.push(nextUser);
}
pageData.list = nextUsers;
return pageData;
} catch(e){
console.log(e);
}
finally {
await client.end();
}
}
//根据id获取用户
async getUser(id: number): Promise<NextUser> {
const client = new Client(config.dbCofing);
await client.connect();
try {
const result = await client.query(
`select id,name,age,created_date_time from next_user where id = $1`, [id]
);
let nextUser: NextUser = null;
if(result.rows.length > 0){
nextUser = {
id: result.rows[0].id as number,
name: result.rows[0].name as string,
age: result.rows[0].age as number,
createdDateTime: result.rows[0].created_date_time as Date
};
}
return nextUser;
} finally {
await client.end();
}
}
//添加用户
async addUser(user: NextUser) {
const client = new Client(config.dbCofing);
await client.connect();
try {
await client.query(
`INSERT INTO public.next_user ("name", age, created_date_time) VALUES($1, $2, $3);`, [user.name, user.age, new Date()]
);
} finally {
await client.end();
}
}
//更新用户
async updateUser(user: NextUser) {
const client = new Client(config.dbCofing);
await client.connect();
try {
await client.query(
`UPDATE public.next_user SET "name"=$1, age=$2 WHERE id=$3;
`, [user.name, user.age,user.id]
);
} finally {
await client.end();
}
}
//删除用户
async deleteUser(id: number) {
const client = new Client(config.dbCofing);
await client.connect();
try {
await client.query(
`delete from next_user where id = $1`, [id]
);
} finally {
await client.end();
}
}
}
然后我们分别来修改一下'pages/api/user/[id].ts'和'pages/api/user.ts'文件,让他们访问数据库
PS:正常来说,为了逻辑复用,api不应该直接访问数据仓储层(repository),中间应该多一个service层什么的,这边只是一个演示demo,以简优先,所以省略很多
//pages/api/user/[id].ts
import { NextApiRequest, NextApiResponse } from 'next';
import {UserRepository,NextUser} from '../../../repositories/user-repository';
export default (req: NextApiRequest, res: NextApiResponse) => {
try{
switch (req.method.toUpperCase()) {
case "GET":
_get(req,res);
break;
case "PUT":
_put(req,res);
break;
case "DELETE":
_delete(req,res);
break;
default:
res.status(404).send("");
break;
}
} catch (e){
//make some logs
console.debug("error");
console.debug(e);
res.status(500).send("");
}
};
async function _get(req: NextApiRequest, res: NextApiResponse){
let userRepository = new UserRepository();
let id = parseInt(req.query.id.toString());
let user = await userRepository.getUser(id);
res.status(200).json({status:"ok",data:user});
}
async function _put(req: NextApiRequest, res: NextApiResponse){
let userRepository = new UserRepository();
let user:NextUser= req.body as NextUser;
user.id = parseInt(req.query.id.toString());
await userRepository.updateUser(user);
res.status(200).send({status:"ok"});
}1
async function _delete(req: NextApiRequest, res: NextApiResponse){
let userRepository = new UserRepository();
await userRepository.deleteUser(parseInt(req.query.id.toString()));
res.status(200).json({status:"ok"})
}
//pages/api/user.ts
import { NextApiRequest, NextApiResponse } from 'next';
import {UserRepository,NextUser} from '../../repositories/user-repository';
export default (req: NextApiRequest, res: NextApiResponse) => {
try{
switch (req.method.toUpperCase()) {
case "GET":
_get(req,res);
break;
case "POST":
_post(req,res);
break;
default:
res.status(404).send("");
break;
}
} catch (e){
//make some logs
console.debug("error");
console.debug(e);
res.status(500).send("");
}
};
async function _get(req: NextApiRequest, res: NextApiResponse){
let userRepository = new UserRepository();
let index = parseInt(req.query.index.toString());
let pageSize = parseInt(req.query.pageSize.toString());
let pageData = await userRepository.getAllUser(index,pageSize)
res.status(200).json({status:"ok",data:pageData});
}
async function _post(req: NextApiRequest, res: NextApiResponse){
let userRepository = new UserRepository();
let user:NextUser= req.body as NextUser;
await userRepository.addUser(user);
res.status(200).send({status:"ok"});
}
启动一下服务器,测试一下结果:
npm run dev
我们先post加一条数据:
然后看看接口查询返回:
其他诸如删除修改等,我就不一一截图了。
3.引入Ant Design UI
3.1配置Ant Design UI
我UI的功底十分差劲,所以引入第三方UI框架,实际开发上,有很多开源且优秀的框架以供你使用,你并不需要重复造轮子。阿里体系貌似全部推荐用yarn,可能大概是跟他们作者之间有什么关联吧?开始yarn有一个lock的优势,但npm后面也跟着更新了,所以我本人觉得yarn相对npm,优势不大。
引入Ant Design UI,在命令行输入:
npm install antd --save
这里有些按需加载的知识需要了解,实际上你应该按需加载。本篇为了简单,所以用全加载。
Any Design UI的使用,请参阅:https://ant.design/index-cn;
除了上述UI的使用文档,我们可能还需要参阅Pro Ant Design:https://pro.ant.design/docs/uset-typescript-cn,这里收集了一些TypeScript的问题。
Next在git上有一个demo,请参阅:https://github.com/zeit/next.js/tree/canary/examples/with-ant-design
Next加载全局CSS时候,需要配置,我这边习惯用less,所以sass不安装了。
安装less:
npm install less less-loader
安装完less之后,我们需要安装Next的全局css引入
npm install @zeit/next-css @zeit/next-less
然后我们在跟目录下,添加一个配置文件'next.config.js'。没错,这就是我们的webpack配置文件,在文件敲入:
const withCSS = require('@zeit/next-css')
const withLess = require('@zeit/next-less')
const isProd = process.env.NODE_ENV === 'production'
// fix: prevents error when .less files are required by node
if (typeof require !== 'undefined') {
require.extensions['.less'] = file => { }
}
module.exports = withLess(withCSS({
lessLoaderOptions: {
javascriptEnabled: true
}
}))
还有最后一步,就是要找一个地方,配置ant design的全局样式,我们建立一个在跟目录下,建一个文件夹'css',在css下建立一个'antd.less',敲入:
//css/antd.less
@import "~antd/dist/antd.less";
然后我们在首页,加点ant design的东西,看看成功没有。
修改我们'pages/index.tsx',代码如下:
//pages/index.tsx
import {Button} from 'antd'
import '../css/antd.less'
const Home = () =>
<div>
<Button type="primary">Primary</Button>
<Button>Default</Button>
<Button type="dashed">Dashed</Button>
<Button type="danger">Danger</Button>
<Button type="link">Link</Button>
</div>
export default Home;
启动项目,我们可以看到,ant design的东西出来:
目前整体目录结构如下(next-env.d.ts,tsconfig.json这个是系统运动时候生成的):
3.2 做一个简单的增删改页面
现在我们可以用它来做一个带分页的增删改页面了,页面我本地已经做好了,这里直接把它粘贴出来,也没什么好说的。。。。不过是无脑的套用UI框架而已,时间格式可能需要美化,自己写一个方法或者借助第三库如'@angular/common',这里用toString带过,修改我们'pages/index.tsx',代码如下:
//pages/index.tsx
import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { NextPage } from 'next';
import {
Form,
Input,
Tooltip,
Icon,
Cascader,
Select,
Row,
Col,
Checkbox,
AutoComplete,
Button,
Modal,
InputNumber,
Table,
Divider
} from 'antd';
import '../css/antd.less';
import '../css/style.less';
interface IProps extends FormComponentProps {
}
interface IState {
modalVisible?: boolean;
}
class Home extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
modalVisible: false
};
};
//注意这里要写成'handleAdd = () => {}',假如普通写'handleAdd() {}'会引起this为undefined
handleAdd = () => {
this.setState({ modalVisible: true });
};
closeModal = () => {
this.setState({ modalVisible: false });
}
doEdit = () => {
};
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 18 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<span>
<a>编辑</a>
<Divider type="vertical" />
<a>删除</a>
</span>
),
}
];
const dataSource = [
{
key: '1',
name: '吴彦祖',
age: 32
},
{
key: '2',
name: '彭于晏',
age: 42
},
];
return (
<div className="container">
<div style={{ paddingLeft: "50px" }}>
<Button type="primary" onClick={this.handleAdd}>新增</Button>
</div>
<div style={{ padding: "20px 50px" }}>
<Table dataSource={dataSource} columns={columns} />;
</div>
<Modal
title="Basic Modal"
visible={this.state.modalVisible}
onOk={this.closeModal}
onCancel={this.closeModal}
>
<Form {...formItemLayout} onSubmit={this.doEdit}>
<Form.Item label="姓名">
{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 label="年龄">
<InputNumber min={1} max={200} defaultValue={17} style={{ width: '100%', marginRight: '3%' }} />
</Form.Item>
</Form>
</Modal>
</div>
);
}
}
export default Form.create()(Home);
启动可以看到效果:
很简单的就能搭建一个页面,感谢这些开源企业的无私奉献,让我们的工作变得简单。
4.与API对接
现在到了开发的最后一步,我们需要利用之前的RESTful API(详见2)对数据库读写,并渲染页面。这里我使用第三库'axios'请求http接口,安装axios:
npm install axios
然后在页面'pages/index.tsx'的顶部引用进来:
//pages/index.tsx
import axios from 'axios';
4.1 获取所有数据
我们需要把现在的数据库的用户都显示在界面,在'pages/index.tsx'中,我们可以写一个方法这样请求接口:
//pages/index.tsx
//引用用户类型接口,主要为了API返回数据作类型转换
import { NextUser, PageData } from "../repositories/user-repository";
//引用Ant Design的table props,用于table的数据约束
import { ColumnProps } from 'antd/es/table';
//新建一个用户接口,这个接口用于table的数据显示
interface TableUser{
key?:number,
id?: number;
name?: string;
age?: number;
}
//修改state接口,加上用户属性特性
interface IState {
modalVisible?: boolean;
index?: number;
pageSize?: number;
tableData?: Array<TableUser>;
pagination?: any;
}
//这个是ant design的列
const columns: ColumnProps<TableUser>[] = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<span>
<a>编辑</a>
<Divider type="vertical" />
<a>删除</a>
</span>
),
}
];
//用我们新数据类型,从新封装table
class NextUserTable extends Table<TableUser> {}
//这个方法方法是获取数据
getData = async () => {
let { data } = await axios.get(`/api/user`, {
params: {
index: this.state.index,
pageSize: this.state.pageSize
}
});
if (data.status !== 'ok') return; //or error message
let pageData: PageData<NextUser> = data.data;
const pagination = { ...this.state.pagination };
pagination.total = pageData.totalCount;
pagination.pageSize = this.state.pageSize;
const users: Array<NextUser> = pageData.list;
const tableData: Array<TableUser> = [];
for (let user of users) {
tableData.push({
key: user.id,
id: user.id,
name: user.name,
age: user.age
});
}
this.setState({
index: this.state.index,
tableData: tableData,
pagination
});
};
//分页获取数据
handleTableChange = (pagination, filters, sorter) => {
this.setState({
index: pagination.current
});
this.getData();
};
然后我们的可以把ant design的table控件写成这样:
//pages/index.tsx
<NextUserTable
dataSource={this.state.tableData}
columns={columns}
pagination={this.state.pagination}
onChange = {this.handleTableChange}
/>
因为我目前数据库只有4条数据,我想弄个分页看看,所以设置pageSize:为了2,即1页2条,这样我们就能看到有2页面。
完整页面代码:
//pages/index.tsx
import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { ColumnProps } from 'antd/es/table';
//引用用户类型接口,主要为了API返回数据作类型转换
import { NextUser, PageData } from "../repositories/user-repository";
import axios from 'axios';
import {
Form,
Input,
Tooltip,
Icon,
Cascader,
Select,
Row,
Col,
Checkbox,
AutoComplete,
Button,
Modal,
InputNumber,
Table,
Divider
} from 'antd';
import '../css/antd.less';
import '../css/style.less';
interface IProps extends FormComponentProps {
}
interface IState {
modalVisible?: boolean;
index?: number;
pageSize?: number;
tableData?: Array<TableUser>;
pagination?: any;
}
//新建一个用户接口,这个接口为table的item格式约束
interface TableUser {
key?: number,
id?: number;
name?: string;
age?: number;
}
//这个是ant design的列
const columns: ColumnProps<TableUser>[] = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<span>
<a>编辑</a>
<Divider type="vertical" />
<a>删除</a>
</span>
),
}
];
const formItemLayout = {
labelCol: {
xs: { span: 18 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
//封装table
class NextUserTable extends Table<TableUser> { }
class Home extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
modalVisible: false,
index: 1,
pageSize: 2, //一页2条,是为了测试看到分页,我数据库不想做太多数据
tableData: []
};
};
componentDidMount?(): void {
this.getData();
}
getData = async () => {
let { data } = await axios.get(`/api/user`, {
params: {
index: this.state.index,
pageSize: this.state.pageSize
}
});
if (data.status !== 'ok') return; //or error message
let pageData: PageData<NextUser> = data.data;
const pagination = { ...this.state.pagination };
pagination.total = pageData.totalCount;
pagination.pageSize = this.state.pageSize;
const users: Array<NextUser> = pageData.list;
const tableData: Array<TableUser> = [];
for (let user of users) {
tableData.push({
key: user.id,
id: user.id,
name: user.name,
age: user.age
});
}
this.setState({
index: this.state.index,
tableData: tableData,
pagination
});
};
//分页获取数据
handleTableChange = (pagination, filters, sorter) => {
this.setState({
index: pagination.current
});
this.getData();
};
//注意这里要写成'handleAdd = () => {}',假如普通写'handleAdd() {}'会引起this为undefined
handleAdd = () => {
this.setState({ modalVisible: true });
};
closeModal = () => {
this.setState({ modalVisible: false });
}
doEdit = () => {
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div className="container">
<div style={{ paddingLeft: "50px" }}>
<Button type="primary" onClick={this.handleAdd}>新增</Button>
</div>
<div style={{ padding: "20px 50px" }}>
<NextUserTable
dataSource={this.state.tableData}
columns={columns}
pagination={this.state.pagination}
onChange={this.handleTableChange}
/>
</div>
<Modal
title="Basic Modal"
visible={this.state.modalVisible}
onOk={this.closeModal}
onCancel={this.closeModal}
>
<Form {...formItemLayout} onSubmit={this.doEdit}>
<Form.Item label="姓名">
{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 label="年龄">
<InputNumber min={1} max={200} defaultValue={17} style={{ width: '100%', marginRight: '3%' }} />
</Form.Item>
</Form>
</Modal>
</div>
);
}
}
export default Form.create()(Home);
启动我们浏览器,可以看到我们数据库的数据显示在页面上了:
至此,创建API,引入Ant Design,做数据请求以及TypeScript的数据约束,我们涉及到要用知识点,我们都已经基本用上了,这里用上,余下部门(修改,删除,新增),我这边只会直接把代码贴出来,原理与上述原理并无差异
4.2 余下部分:单个用户查询,新增,修改及删除用户
这里并无新的知识点,仅仅只是调用API,然后渲染页面(把页面弄得好看点),仅此,我这里把所有代码都塞一个文件里,其实这样并不方便维护,在实际开发时候,应该注意分离逻辑,增加复用,另外,上面获取全部数据时候分页有BUG,下面的代码也一并修复了,'pages/index.tsx'完整代码:
//pages/index.tsx
import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { ColumnProps } from 'antd/es/table';
//引用用户类型接口,主要为了API返回数据作类型转换
import { NextUser, PageData } from "../repositories/user-repository";
import axios from 'axios';
import {
Form,
Input,
Tooltip,
Icon,
Cascader,
Select,
Row,
Col,
Checkbox,
AutoComplete,
Button,
Modal,
InputNumber,
Table,
Divider,
message
} from 'antd';
import '../css/antd.less';
import '../css/style.less';
interface IProps extends FormComponentProps {
}
interface IState {
modalVisible?: boolean;
pageSize?: number;
tableData?: Array<TableUser>;
pagination?: any;
formData?: {
id?: number;
name?: string;
age?: number;
};
}
//新建一个用户接口,这个接口为table的item格式约束
interface TableUser {
key?: number,
id?: number;
name?: string;
age?: number;
}
const formItemLayout = {
labelCol: {
xs: { span: 18 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
//封装table
class NextUserTable extends Table<TableUser> { }
class Home extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
modalVisible: false,
pageSize: 2, //一页2条,是为了测试看到分页,我数据库不想做太多数据
tableData: [],
formData: {}
};
};
//这个是ant design的列
columns: ColumnProps<TableUser>[] = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<span>
<a onClick={() =>this.handleEdit(record.id)}>编辑</a>
<Divider type="vertical" />
<a onClick={() =>this.doDelete(record.id)}>删除</a>
</span>
),
}
];
componentDidMount?(): void {
this.getData(1);
}
getData = async (index) => {
let { data } = await axios.get(`/api/user`, {
params: {
index: index,
pageSize: this.state.pageSize
}
});
if (data.status !== 'ok') return; //or error message
let pageData: PageData<NextUser> = data.data;
const pagination = { ...this.state.pagination };
pagination.total = pageData.totalCount;
pagination.pageSize = this.state.pageSize;
const users: Array<NextUser> = pageData.list;
const tableData: Array<TableUser> = [];
for (let user of users) {
tableData.push({
key: user.id,
id: user.id,
name: user.name,
age: user.age
});
}
this.setState({
tableData: tableData,
pagination
});
};
//分页获取数据
handleTableChange = async (pagination, filters, sorter) => {
await this.getData(pagination.current);
};
//注意这里要写成'handleAdd = () => {}',假如普通写'handleAdd() {}'会引起this为undefined
handleAdd = () => {
this.setState({
modalVisible: true,
formData: {}
});
};
handleEdit = async id => {
let { data } = await axios.get(`/api/user/${id}`);
if(data.status !== "ok") return; //show some error
let user:NextUser = data.data;
this.setState({
modalVisible: true,
formData: {
id:id,
name:user.name,
age:user.age
}
});
};
closeModal = () => {
this.setState({ modalVisible: false });
}
doDelete = async id =>{
let { data } = await axios.delete(`/api/user/${id}`);
if (data.status === "ok") {
message.success('删除成功');
this.getData(1);
} else {
message.error('删除失败');
}
};
doEdit = async e => {
e.preventDefault();
this.props.form.validateFields(async (err, values) => {
if (err) return;
//Received values of form: {name: "1231212312", age: 123}
if (this.state.formData.id) {
let { data } = await axios.put(`/api/user/${this.state.formData.id}`, values);
if (data.status === "ok") {
message.success('修改成功');
this.setState({
modalVisible: false
});
this.getData(1);
} else {
message.error('修改失败');
}
} else {
let { data } = await axios.post(`/api/user`, values);
if (data.status === "ok") {
message.success('添加成功');
this.setState({ modalVisible: false });
} else {
message.error('添加失败');
}
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div className="container">
<div style={{ paddingLeft: "50px" }}>
<Button type="primary" onClick={this.handleAdd}>新增</Button>
</div>
<div style={{ padding: "20px 50px" }}>
<NextUserTable
dataSource={this.state.tableData}
columns={this.columns}
pagination={this.state.pagination}
onChange={this.handleTableChange}
/>
</div>
<Modal
title="Basic Modal"
visible={this.state.modalVisible}
onCancel={this.closeModal}
footer={[
<Button form="form" key="submit" htmlType="submit">
Submit
</Button>
]}
>
<Form id="form" {...formItemLayout} onSubmit={this.doEdit}>
<Form.Item label="姓名">
{getFieldDecorator('name', {
initialValue: this.state.formData.name,
rules: [{ required: true, message: 'Please input your name!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="name"
/>,
)}
</Form.Item>
<Form.Item label="年龄">
{getFieldDecorator('age', {
initialValue: this.state.formData.age,
rules: [{ required: true, message: 'Please input your age!' }],
})(
<InputNumber
min={1}
max={200}
style={{ width: '100%', marginRight: '3%' }}
placeholder="age"
/>
)}
</Form.Item>
</Form>
</Modal>
</div>
);
}
}
export default Form.create()(Home);
5.总结
总体来说,Next.js可以满足某些项目的全栈开发需求,当然,我这个demo用在生产环境上还远远不足,例如缺少权限验证(api接口不是谁都能调用,要授权),日志,连接池等等。
然后,Next是可以用服务端渲染,这个就有点像.NET的MVC和Java的JSP,但我估计现在很少人会这样用了吧?
另外,本人React方面新手一枚,有误导的地方,望留言指出。
我个人感觉这个东西在一些小项目上面尝试,其实还是不错(大项目不用是因为我目前这方面的知识欠缺,不敢乱来),anyway,每一样东西总有个起步吧?
最后祝大家新年快乐!
demo的git地址:https://github.com/JaqenHo/next_js_learn.git