此文章,會(huì)從零開始結(jié)合Ant Design UI和PostgreSQL做一個(gè)簡(jiǎn)單的增刪改
這里只是一個(gè)簡(jiǎn)單的demo蒜田,真實(shí)的開發(fā)中我們能可能還需要權(quán)限鸦做,日志,連接池等等负敏。
參考官網(wǎng):https://nextjs.org/learn/basics/getting-started
開發(fā)環(huán)境:
window10 x64
node v10.16.3
npm v6.13.4
1.項(xiàng)目初始化
參考‘Next.js與TypeScript從零起步學(xué)習(xí)筆記-1’壤躲,我們先創(chuàng)建一個(gè)空項(xiàng)目并添加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"
}
然后我們?cè)?pages'目錄下城菊,建一個(gè)'index.tsx',隨便輸入點(diǎn)代碼碉克,測(cè)試一下是否能跑去起來:
//pages/index.tsx
const Home = () => <h1>Hello world!</h1>;
export default Home;
運(yùn)行項(xiàng)目:
npm run dev
應(yīng)該可以看到效果:
這里我并打算用到嚴(yán)格模式(類型約束)凌唬,因?yàn)楹竺嫖恼乱玫降念悗?kù),會(huì)出現(xiàn)諸多問題漏麦,為項(xiàng)目簡(jiǎn)單起見客税,我不建議用嚴(yán)格模式。
2.創(chuàng)建RESTful API
2.1 創(chuàng)建表
我們需要在數(shù)據(jù)庫(kù)創(chuàng)建一張用戶表撕贞,用來存放用戶的數(shù)據(jù)更耻,并對(duì)其進(jìn)行簡(jiǎn)單的增刪改查,我用的數(shù)據(jù)庫(kù)是PostgreSQL捏膨。(PS:你需要有一點(diǎn)數(shù)據(jù)庫(kù)相關(guān)的知識(shí))
--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 創(chuàng)建RESTful API
我們?cè)趐ages文件夾下創(chuàng)建一個(gè)api文件夾秧均,主要用來存放我們api
//以項(xiàng)目根目錄為準(zhǔn)
cd pages
mkdir api
cd api
mkdir user
我們?cè)?user'文件夾下,創(chuàng)建一個(gè)'[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");
}
以上,我們已經(jīng)創(chuàng)建了我們最簡(jiǎn)單的RESTful API了链快,把項(xiàng)目跑起來誉己,我們能看到效果
PS:以上是動(dòng)態(tài)路由,即根據(jù)路徑獲取資源Id久又,如圖3URL:http://localhost:3000/api/user/1,后面的'/1'表示的是訪問這個(gè)id的資源,現(xiàn)實(shí)中只有編輯時(shí)候才會(huì)用到效五,新增或獲取列表時(shí)候地消,并不適用,所以:
我們需要在'pages/api'下畏妖,新添一個(gè)路由文件'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");
}
我們?cè)賮硇薷?pages/api/user/[id].ts',把post去掉戒劫,因?yàn)閜ost是新增半夷,所以邏輯上在這個(gè)文件永遠(yuǎn)用不上
//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");
}
這樣建立文件婆廊,看上去有點(diǎn)繁瑣,但目前我所知道巫橄,Next的路由就是這樣淘邻。
能不能像.NET這樣,用標(biāo)簽屬性來確定呢湘换?我目前沒有找到好方法,知道的小伙伴請(qǐng)不吝賜教,私信告訴我
2.3 對(duì)數(shù)據(jù)庫(kù)進(jìn)行讀寫
我們連接數(shù)據(jù)庫(kù)绩社,第一步是先安裝上驅(qū)動(dòng)卷要,我開始使用的是'ts-postgres',為什么用它帆离?沒啥的蔬蕊,就是因?yàn)檫@個(gè)東西是TS寫的,但后面發(fā)現(xiàn)坑有點(diǎn)多哥谷,例如我要count多少行岸夯,總是返回null,我以為是函數(shù)問題呼巷,但換了now又正常囱修,弄了很久,后面放棄了王悍,只好換另一個(gè)驅(qū)動(dòng)'node-postgres'破镰,這個(gè)東西問題就是用js寫的,ts里用压储,你會(huì)發(fā)現(xiàn)一堆都是any鲜漩。
安裝node-postgres:
npm install pg
我們先來創(chuàng)建一個(gè)配置文件,這個(gè)配置文件用來保存一些系統(tǒng)的配置集惋,例如數(shù)據(jù)庫(kù)連接等等孕似,在根目錄下創(chuàng)建'config.json':
//config.json
{
"dbCofing":{
"host": "localhost",
"port":5432,
"user":"postgres",
"database":"next_learn",
"password":"123456"
}
}
然后我們創(chuàng)建一個(gè)文件,命名為'repositories'刮刑,用來存放sql操作等邏輯:
//以項(xiàng)目根目錄為準(zhǔn)
cd pages
mkdir repositories
在'repositories'文件夾下喉祭,我們創(chuàng)建一個(gè)文件'user-repository.ts',這里主要編寫用戶表(見2.1)的讀寫邏輯雷绢。
//repositories/user-repository.ts
import { Client } from 'pg';
import config from "../config.json";
//這個(gè)接口應(yīng)該單獨(dú)弄出去泛烙,弄個(gè)文件夾夾叫utility之類放著,因?yàn)楹罄m(xù)肯定不止這個(gè)地方用到翘紊。
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 {
//分頁(yè)獲取所有用戶
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的話蔽氨,會(huì)同步等待結(jié)果返回
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();
}
}
//根據(jù)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'文件,讓他們?cè)L問數(shù)據(jù)庫(kù)
PS:正常來說,為了邏輯復(fù)用鹉究,api不應(yīng)該直接訪問數(shù)據(jù)倉(cāng)儲(chǔ)層(repository)宇立,中間應(yīng)該多一個(gè)service層什么的,這邊只是一個(gè)演示demo自赔,以簡(jiǎn)優(yōu)先妈嘹,所以省略很多
//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"});
}
啟動(dòng)一下服務(wù)器,測(cè)試一下結(jié)果:
npm run dev
我們先post加一條數(shù)據(jù):
然后看看接口查詢返回:
其他諸如刪除修改等匿级,我就不一一截圖了蟋滴。
3.引入Ant Design UI
3.1配置Ant Design UI
我UI的功底十分差勁,所以引入第三方UI框架痘绎,實(shí)際開發(fā)上津函,有很多開源且優(yōu)秀的框架以供你使用,你并不需要重復(fù)造輪子孤页。阿里體系貌似全部推薦用yarn,可能大概是跟他們作者之間有什么關(guān)聯(lián)吧尔苦?開始yarn有一個(gè)lock的優(yōu)勢(shì),但npm后面也跟著更新了行施,所以我本人覺得yarn相對(duì)npm,優(yōu)勢(shì)不大允坚。
引入Ant Design UI,在命令行輸入:
npm install antd --save
這里有些按需加載的知識(shí)需要了解,實(shí)際上你應(yīng)該按需加載蛾号。本篇為了簡(jiǎn)單稠项,所以用全加載。
Any Design UI的使用鲜结,請(qǐng)參閱:https://ant.design/index-cn;
除了上述UI的使用文檔展运,我們可能還需要參閱Pro Ant Design:https://pro.ant.design/docs/uset-typescript-cn,這里收集了一些TypeScript的問題。
Next在git上有一個(gè)demo精刷,請(qǐng)參閱:https://github.com/zeit/next.js/tree/canary/examples/with-ant-design
Next加載全局CSS時(shí)候拗胜,需要配置,我這邊習(xí)慣用less怒允,所以sass不安裝了埂软。
安裝less:
npm install less less-loader
安裝完less之后,我們需要安裝Next的全局css引入
npm install @zeit/next-css @zeit/next-less
然后我們?cè)诟夸浵氯沂拢砑右粋€(gè)配置文件'next.config.js'勘畔。沒錯(cuò),這就是我們的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
}
}))
還有最后一步炫七,就是要找一個(gè)地方,配置ant design的全局樣式蚊夫,我們建立一個(gè)在跟目錄下诉字,建一個(gè)文件夾'css',在css下建立一個(gè)'antd.less'懦尝,敲入:
//css/antd.less
@import "~antd/dist/antd.less";
然后我們?cè)谑醉?yè)知纷,加點(diǎn)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;
啟動(dòng)項(xiàng)目伍绳,我們可以看到,ant design的東西出來:
目前整體目錄結(jié)構(gòu)如下(next-env.d.ts乍桂,tsconfig.json這個(gè)是系統(tǒng)運(yùn)動(dòng)時(shí)候生成的):
3.2 做一個(gè)簡(jiǎn)單的增刪改頁(yè)面
現(xiàn)在我們可以用它來做一個(gè)帶分頁(yè)的增刪改頁(yè)面了冲杀,頁(yè)面我本地已經(jīng)做好了,這里直接把它粘貼出來睹酌,也沒什么好說的权谁。。憋沿。旺芽。不過是無腦的套用UI框架而已,時(shí)間格式可能需要美化,自己寫一個(gè)方法或者借助第三庫(kù)如'@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() {}'會(huì)引起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);
啟動(dòng)可以看到效果:
很簡(jiǎn)單的就能搭建一個(gè)頁(yè)面壶辜,感謝這些開源企業(yè)的無私奉獻(xiàn)悯舟,讓我們的工作變得簡(jiǎn)單。
4.與API對(duì)接
現(xiàn)在到了開發(fā)的最后一步砸民,我們需要利用之前的RESTful API(詳見2)對(duì)數(shù)據(jù)庫(kù)讀寫抵怎,并渲染頁(yè)面。這里我使用第三庫(kù)'axios'請(qǐng)求http接口,安裝axios:
npm install axios
然后在頁(yè)面'pages/index.tsx'的頂部引用進(jìn)來:
//pages/index.tsx
import axios from 'axios';
4.1 獲取所有數(shù)據(jù)
我們需要把現(xiàn)在的數(shù)據(jù)庫(kù)的用戶都顯示在界面阱洪,在'pages/index.tsx'中便贵,我們可以寫一個(gè)方法這樣請(qǐng)求接口:
//pages/index.tsx
//引用用戶類型接口,主要為了API返回?cái)?shù)據(jù)作類型轉(zhuǎn)換
import { NextUser, PageData } from "../repositories/user-repository";
//引用Ant Design的table props冗荸,用于table的數(shù)據(jù)約束
import { ColumnProps } from 'antd/es/table';
//新建一個(gè)用戶接口承璃,這個(gè)接口用于table的數(shù)據(jù)顯示
interface TableUser{
key?:number,
id?: number;
name?: string;
age?: number;
}
//修改state接口,加上用戶屬性特性
interface IState {
modalVisible?: boolean;
index?: number;
pageSize?: number;
tableData?: Array<TableUser>;
pagination?: any;
}
//這個(gè)是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>
),
}
];
//用我們新數(shù)據(jù)類型蚌本,從新封裝table
class NextUserTable extends Table<TableUser> {}
//這個(gè)方法方法是獲取數(shù)據(jù)
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
});
};
//分頁(yè)獲取數(shù)據(jù)
handleTableChange = (pagination, filters, sorter) => {
this.setState({
index: pagination.current
});
this.getData();
};
然后我們的可以把a(bǔ)nt design的table控件寫成這樣:
//pages/index.tsx
<NextUserTable
dataSource={this.state.tableData}
columns={columns}
pagination={this.state.pagination}
onChange = {this.handleTableChange}
/>
因?yàn)槲夷壳皵?shù)據(jù)庫(kù)只有4條數(shù)據(jù)盔粹,我想弄個(gè)分頁(yè)看看,所以設(shè)置pageSize:為了2程癌,即1頁(yè)2條舷嗡,這樣我們就能看到有2頁(yè)面。
完整頁(yè)面代碼:
//pages/index.tsx
import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { ColumnProps } from 'antd/es/table';
//引用用戶類型接口嵌莉,主要為了API返回?cái)?shù)據(jù)作類型轉(zhuǎn)換
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;
}
//新建一個(gè)用戶接口进萄,這個(gè)接口為table的item格式約束
interface TableUser {
key?: number,
id?: number;
name?: string;
age?: number;
}
//這個(gè)是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, //一頁(yè)2條,是為了測(cè)試看到分頁(yè),我數(shù)據(jù)庫(kù)不想做太多數(shù)據(jù)
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
});
};
//分頁(yè)獲取數(shù)據(jù)
handleTableChange = (pagination, filters, sorter) => {
this.setState({
index: pagination.current
});
this.getData();
};
//注意這里要寫成'handleAdd = () => {}',假如普通寫'handleAdd() {}'會(huì)引起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);
啟動(dòng)我們?yōu)g覽器中鼠,可以看到我們數(shù)據(jù)庫(kù)的數(shù)據(jù)顯示在頁(yè)面上了:
至此可婶,創(chuàng)建API,引入Ant Design援雇,做數(shù)據(jù)請(qǐng)求以及TypeScript的數(shù)據(jù)約束矛渴,我們涉及到要用知識(shí)點(diǎn),我們都已經(jīng)基本用上了惫搏,這里用上具温,余下部門(修改,刪除筐赔,新增)铣猩,我這邊只會(huì)直接把代碼貼出來,原理與上述原理并無差異
4.2 余下部分:?jiǎn)蝹€(gè)用戶查詢茴丰,新增剂习,修改及刪除用戶
這里并無新的知識(shí)點(diǎn),僅僅只是調(diào)用API较沪,然后渲染頁(yè)面(把頁(yè)面弄得好看點(diǎn))鳞绕,僅此,我這里把所有代碼都塞一個(gè)文件里尸曼,其實(shí)這樣并不方便維護(hù)们何,在實(shí)際開發(fā)時(shí)候,應(yīng)該注意分離邏輯控轿,增加復(fù)用冤竹,另外,上面獲取全部數(shù)據(jù)時(shí)候分頁(yè)有BUG茬射,下面的代碼也一并修復(fù)了鹦蠕,'pages/index.tsx'完整代碼:
//pages/index.tsx
import React from 'react'
import { FormComponentProps } from "antd/lib/form/Form";
import { ColumnProps } from 'antd/es/table';
//引用用戶類型接口,主要為了API返回?cái)?shù)據(jù)作類型轉(zhuǎn)換
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;
};
}
//新建一個(gè)用戶接口在抛,這個(gè)接口為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, //一頁(yè)2條钟病,是為了測(cè)試看到分頁(yè),我數(shù)據(jù)庫(kù)不想做太多數(shù)據(jù)
tableData: [],
formData: {}
};
};
//這個(gè)是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
});
};
//分頁(yè)獲取數(shù)據(jù)
handleTableChange = async (pagination, filters, sorter) => {
await this.getData(pagination.current);
};
//注意這里要寫成'handleAdd = () => {}',假如普通寫'handleAdd() {}'會(huì)引起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.總結(jié)
總體來說刚梭,Next.js可以滿足某些項(xiàng)目的全棧開發(fā)需求肠阱,當(dāng)然,我這個(gè)demo用在生產(chǎn)環(huán)境上還遠(yuǎn)遠(yuǎn)不足朴读,例如缺少權(quán)限驗(yàn)證(api接口不是誰(shuí)都能調(diào)用屹徘,要授權(quán)),日志衅金,連接池等等噪伊。
然后簿煌,Next是可以用服務(wù)端渲染,這個(gè)就有點(diǎn)像.NET的MVC和Java的JSP鉴吹,但我估計(jì)現(xiàn)在很少人會(huì)這樣用了吧啦吧?
另外,本人React方面新手一枚拙寡,有誤導(dǎo)的地方,望留言指出琳水。
我個(gè)人感覺這個(gè)東西在一些小項(xiàng)目上面嘗試肆糕,其實(shí)還是不錯(cuò)(大項(xiàng)目不用是因?yàn)槲夷壳斑@方面的知識(shí)欠缺,不敢亂來)在孝,anyway,每一樣?xùn)|西總有個(gè)起步吧诚啃?
最后祝大家新年快樂!
demo的git地址:https://github.com/JaqenHo/next_js_learn.git