摘要:本項(xiàng)目中采用了進(jìn)行狀態(tài)管理,的主要作用是允許狀態(tài)在不同分支的組件中進(jìn)行傳遞,從而避免了使用原始方法如導(dǎo)致的不同分支組件之間數(shù)據(jù)無法傳遞子組件無法修改父組件狀態(tài)等問題。
項(xiàng)目功能
最近在做一個(gè)舊書交易網(wǎng)站,本屬于B/S體系結(jié)構(gòu)的課程作業(yè),但由于采用了新的框架所以躍躍欲試想都記錄下來。
技術(shù)選型 數(shù)據(jù)庫實(shí)現(xiàn)一個(gè)舊書交易網(wǎng)站,基本功能如下:
實(shí)現(xiàn)用戶注冊(cè)、登錄功能,用戶注冊(cè)時(shí)需要填寫必要的信息并驗(yàn)證,如用戶名、密碼要求在6字節(jié)以上,email的格式驗(yàn)證,并保證用戶名和email在系統(tǒng)中唯一。
用戶登錄后可以發(fā)布要交易的書籍,需要編輯相關(guān)信息,包括書名、原價(jià)、出售價(jià)、類別和內(nèi)容介紹等信息、外觀照片等,可以通過ISBN和書名鏈接到外部系統(tǒng)(如Amazon/京東/當(dāng)當(dāng)?shù)染W(wǎng)站)的詳細(xì)介紹頁面。
根據(jù)用戶發(fā)布的書籍聚合生成首頁,可以分類檢索。
用戶可以設(shè)置交易模式為寄送還是線下交易,生成訂單時(shí)錄入不同內(nèi)容。
集成一個(gè)消息系統(tǒng),買家和賣家之間可以通信。
提供求購模塊,用戶可以發(fā)布自己想要的書籍。
界面樣式需要適配PC和手機(jī)的瀏覽器。
實(shí)現(xiàn)一個(gè)Android或iphone客戶端軟件,功能同網(wǎng)站,額外支持定位功能,發(fā)布時(shí)記錄位置,可以根據(jù)用戶的位置匹配最近的待售書籍。消息和訂單支持推送。
數(shù)據(jù)庫使用MySQL進(jìn)行開發(fā),因?yàn)榄h(huán)境之前都已經(jīng)配好了( ̄▽ ̄)"
后端經(jīng)過Express和Koa比對(duì),最終選擇Koa作為基于Node.js的Web開發(fā)框架。Koa是一個(gè)新的web框架,由Express幕后原班人馬打造,語法上也使用了ES6新的語法(例如丟棄了回調(diào)函數(shù)而使用async解決異步調(diào)用問題),看起來十分優(yōu)雅o( ̄▽ ̄)o
前端采用React+Semantic UI,由于之前對(duì)React有足夠多的實(shí)踐,因此本次重點(diǎn)還是放在后端開發(fā)及前后端連接上……
開發(fā)過程 參考教程Vue+Koa全棧開發(fā)
Koa框架教程 - 阮一峰
Koa框架搭建 初始化
命令行輸入
npm init -y npm i koa koa-json npm i -D nodemon
更改package.json內(nèi)容,將scripts中的內(nèi)容更改為"start":"nodemon app.js"
根目錄下新建app.js
const Koa = require("koa"); const json = require("koa-json"); const logger = require("koa-logger"); const KoaRouter = require("koa-router"); const parser = require("koa-bodyparser"); const app = new Koa(); const router = new KoaRouter(); // Json Prettier Middleware app.use(json()); app.use(parser()); app.use(logger()); // Simple Middleware Example // app.use(async ctx => (ctx.body = { msg: "Hello world" })); app.listen(4113, () => console.log("----------Server Started----------")); module.exports = app;
命令行輸入node app.js,瀏覽器打開localhost:3000查看返回?cái)?shù)據(jù)
sequelize連接數(shù)據(jù)庫
安裝包
npm install sequelize-auto -g npm install tedious -g npm install mysql -g
進(jìn)入src目錄,輸入sequelize-auto -o "./schema" -d bookiezilla -h 127.0.0.1 -u root -p 3306 -x XXXXX -e mysql,(其中 -o 參數(shù)后面的是輸出的文件夾目錄, -d 參數(shù)后面的是數(shù)據(jù)庫名, -h 參數(shù)后面是數(shù)據(jù)庫地址, -u 參數(shù)后面是數(shù)據(jù)庫用戶名, -p 參數(shù)后面是端口號(hào), -x 參數(shù)后面是數(shù)據(jù)庫密碼 -e 參數(shù)后面指定數(shù)據(jù)庫為mysql)
此時(shí)schema文件夾下會(huì)自動(dòng)生成三個(gè)表的文件,例如:
/* jshint indent: 2 */ module.exports = function(sequelize, DataTypes) { return sequelize.define( "book", { BookID: { type: DataTypes.INTEGER(11), allowNull: false, primaryKey: true }, BookName: { type: DataTypes.STRING(45), allowNull: true }, BookCostPrice: { type: "DOUBLE", allowNull: true }, BookSalePrice: { type: "DOUBLE", allowNull: true }, BookCategory: { type: DataTypes.STRING(45), allowNull: true }, BookPhoto: { type: DataTypes.STRING(45), allowNull: true }, BookContent: { type: DataTypes.STRING(45), allowNull: true }, BookISBN: { type: DataTypes.STRING(45), allowNull: true } }, { tableName: "book" } ); };
在serversrcconfig下新建文件database.js,用于初始化Sequelize和數(shù)據(jù)庫的連接。
const Sequelize = require("sequelize"); // 使用url連接的形式進(jìn)行連接,注意將root: 后面的XXXX改成自己數(shù)據(jù)庫的密碼 const BookieZilla = new Sequelize( "mysql://root:XXXXX@localhost/bookiezilla", { define: { timestamps: false// 取消Sequelzie自動(dòng)給數(shù)據(jù)表加入時(shí)間戳(createdAt以及updatedAt),否則進(jìn)行增刪改查操作時(shí)可能會(huì)報(bào)錯(cuò) } } ); module.exports = { BookieZilla // 將BookieZilla暴露出接口方便Model調(diào)用 };
為方便之后根據(jù)用戶id查詢信息,可先在數(shù)據(jù)庫中隨意增加一條數(shù)據(jù)。
在serversrcmodels下新建文件userModel.js,數(shù)據(jù)庫和表結(jié)構(gòu)文件連接起來。
const db = require("../config/database.js"); const userModel = "../schema/user.js";// 引入user的表結(jié)構(gòu) const BookieZilla = db.BookieZilla;// 引入數(shù)據(jù)庫 const User = BookieZilla.import(userModel);// 用sequelize的import方法引入表結(jié)構(gòu),實(shí)例化了User。 const getUserById = async function(id) { const userInfo = await User.findOne({ where: { UserID: id } }); return userInfo; }; module.exports = { getUserById, getUserByEmail };
在serversrccontrollers下新建文件userController.js,來執(zhí)行這個(gè)方法,并返回結(jié)果。
Koa 提供一個(gè) Context 對(duì)象,表示一次對(duì)話的上下文(包括 HTTP 請(qǐng)求和 HTTP 回復(fù))。通過加工這個(gè)對(duì)象,就可以控制返回給用戶的內(nèi)容。
const user = require("../models/userModel.js"); const getUserInfo = async function(ctx) { const id = ctx.params.id;// 獲取url里傳過來的參數(shù)里的id const result = await user.getUserById(id); ctx.body = result;// 將請(qǐng)求的結(jié)果放到response的body里返回 }; module.exports = { getUserInfo, vertifyUserLogin };
在serversrc outes下新建文件auth.js,用于規(guī)劃auth下的路由規(guī)則。
const auth = require("../controllers/userController.js"); const router = require("koa-router")(); router.get("/user/:id", auth.getUserInfo); module.exports = router;
回到根目錄下的app.js,將這個(gè)路由規(guī)則“掛載”到Koa上去。
const Koa = require("koa"); const json = require("koa-json"); const logger = require("koa-logger"); const KoaRouter = require("koa-router"); const parser = require("koa-bodyparser"); const auth = require("./src/routes/auth.js");// 引入auth const app = new Koa(); const router = new KoaRouter(); // Json Prettier Middleware app.use(json()); app.use(parser()); app.use(logger()); // Simple Middleware Example // app.use(async ctx => (ctx.body = { msg: "Hello world" })); // Router Middleware router.use("/auth", auth.routes());// 掛載到koa-router上,同時(shí)會(huì)讓所有的auth的請(qǐng)求路徑前面加上"/auth"的請(qǐng)求路徑。 app.use(router.routes()).use(router.allowedMethods());// 將路由規(guī)則掛載到Koa上。 app.listen(4113, () => console.log("----------Server Started----------")); module.exports = app;
API Test
SUCCESS!!!
前后端數(shù)據(jù)傳遞由于本項(xiàng)目采用的是前后端分離的架構(gòu),因此需要通過json來傳遞數(shù)據(jù),以實(shí)現(xiàn)登錄功能為例來闡述實(shí)現(xiàn)的具體步驟。
后端驗(yàn)證登錄
serversrcmodelsuserModel.js增加方法,用于通過郵箱查找用戶。
// ... const getUserByEmail = async function(email) { const userInfo = await User.findOne({ where: { UserEmail: email } }); return userInfo; }; module.exports = { getUserById, getUserByEmail };
serversrccontrolleruserController.js增加方法,用于驗(yàn)證登錄信息并將結(jié)果以json形式返回給前端。
注意此處實(shí)際上應(yīng)用了JSON-WEB-TOKEN實(shí)現(xiàn)無狀態(tài)請(qǐng)求,關(guān)于jwt的原理和實(shí)現(xiàn)方法請(qǐng)參考這篇文章和這篇文章。
簡單來說,運(yùn)用了JSON-WEB-TOKEN的登錄系統(tǒng)應(yīng)該是這樣的:
用戶在登錄頁輸入賬號(hào)密碼,將賬號(hào)密碼(密碼進(jìn)行md5加密)發(fā)送請(qǐng)求給后端
后端驗(yàn)證一下用戶的賬號(hào)和密碼的信息,如果符合,就下發(fā)一個(gè)TOKEN返回給客戶端。如果不符合就不發(fā)送TOKEN回去,返回驗(yàn)證錯(cuò)誤信息。
如果登錄成功,客戶端將TOKEN用某種方式存下來(SessionStorage、LocalStorage),之后要請(qǐng)求其他資源的時(shí)候,在請(qǐng)求頭(Header)里帶上這個(gè)TOKEN進(jìn)行請(qǐng)求。
后端收到請(qǐng)求信息,先驗(yàn)證一下TOKEN是否有效,有效則下發(fā)請(qǐng)求的資源,無效則返回驗(yàn)證錯(cuò)誤。
使用前需要安裝相應(yīng)庫:
npm i koa-jwt jsonwebtoken util -s
此外,為保證安全性,后端數(shù)據(jù)庫的密碼不能采用明文保存,此處使用bcrypt的加密方式。
npm i bcryptjs -s
const user = require("../models/userModel.js"); const jwt = require("jsonwebtoken"); const bcrypt = require("bcryptjs"); const getUserInfo = async function(ctx) { const id = ctx.params.id; const result = await user.getUserById(id); ctx.body = result; }; const vertifyUserLogin = async function(ctx) { const data = ctx.request.body; // post過來的數(shù)據(jù)存在request.body里 const userInfo = await user.getUserByEmail(data.email); if (userInfo != null) { // 如果查無此用戶會(huì)返回null if (!bcrypt.compareSync(data.psw, userInfo.UserPsw) { ctx.body = { status: false, msg: "Wrong password" }; } else { // 如果密碼正確 const userToken = { id: userInfo.UserID, email: userInfo.UserEmail }; const secret = "react-koa-bookiezilla"; // 指定密鑰,這是之后用來判斷token合法性的標(biāo)志 const token = jwt.sign(userToken, secret); // 簽發(fā)token ctx.body = { status: true, token: token // 返回token }; } } else { ctx.body = { status: false, msg: "User doesn"t exist" }; } }; module.exports = { getUserInfo, vertifyUserLogin };
更新serversrc outesauth.js中的路由規(guī)則。
const auth = require("../controllers/userController.js"); const router = require("koa-router")(); router.get("/user/:id", auth.getUserInfo); router.post("/login", auth.vertifyUserLogin); module.exports = router;前端校驗(yàn)數(shù)據(jù)并發(fā)送請(qǐng)求
前端主要使用了react-router進(jìn)行路由跳轉(zhuǎn),使用semantic-ui作為UI組件庫,使用axios發(fā)送請(qǐng)求,Login.js代碼如下:
import React, { Component } from "react"; import { Button, Form, Grid, Header, Image, Message, Segment, Loader } from "semantic-ui-react"; import { NavLink, withRouter } from "react-router-dom"; import axios from "axios"; import Logo from "../images/logo.png"; class Login extends Component { state = { email: "", psw: "", alert: false, load: false }; vertifyFormat = () => { var pattern = /^([A-Za-z0-9_-.])+@([A-Za-z0-9_-.])+.([A-Za-z]{2,4})$/; return pattern.test(this.state.email) && this.state.psw.length >= 6; }; sendLoginRequest = () => { if (this.vertifyFormat()) { this.setState({ alert: false, load: true }); axios .post("/auth/login", { email: this.state.email, psw: this.state.psw }) .then(res => { console.log(res); }) .catch(err => { console.log(err); }); } else { this.setState({ alert: true }); } }; render() { var alert = this.state.alert === false ? ( ) : (React配置代理); var load = this.state.load === false ? : ; return ( ); } } export default withRouter(Login); Log-in to your B::kzilla New to us? Sign Up
安裝http-proxy-middleware中間件。
npm install http-proxy-middleware -s
create-react-app初始化的項(xiàng)目需要eject,使基本配置暴露出來。
npm run eject
clientsrc下新建文件setupProxy.js,配置代理轉(zhuǎn)發(fā)信息。
const proxy = require("http-proxy-middleware"); module.exports = function(app) { app.use( proxy("/api", { target: "http://localhost:4113", changeOrigin: true }) ); app.use( proxy("/auth", { target: "http://localhost:4113", changeOrigin: true }) ); };
clientscriptsstart.js中進(jìn)行配置,在const devServer = new WebpackDevServer(compiler, serverConfig);后添加語句require("../src/setupProxy")(devServer);
發(fā)送請(qǐng)求格式如下:
axios .post("/auth/login", { email: this.state.email, psw: this.state.psw }) .then(res => { console.log(res); }) .catch(err => { console.log(err); });
喜聞樂見的測試環(huán)節(jié)!
設(shè)計(jì)原理 數(shù)據(jù)庫 User*UserID | UserName | UserPsw | *UserEmail |
---|---|---|---|
INT | VARCHAR(45) | VARCHAR(45) | VARCHAR(45) |
CREATE TABLE `bookiezilla`.`user` ( `UserID` INT NOT NULL, `UserName` VARCHAR(45) NULL, `UserPsw` VARCHAR(45) NULL, `UserEmail` VARCHAR(45) NOT NULL, PRIMARY KEY (`UserID`, `UserEmail`));Book
*BookID | BookName | BookCostPrice | BookSalePrice | BookCategory | BookPhoto | BookContent | BookISBN | BookRefs |
---|---|---|---|---|---|---|---|---|
INT | VARCHAR(45) | DOUBLE | DOUBLE | VARCHAR(45) | VARCHAR(45) | VARCHAR(45) | VARCHAR(45) | VARCHAR(45) |
CREATE TABLE `bookiezilla`.`book` ( `BookID` INT NOT NULL, `BookName` VARCHAR(45) NULL, `BookCostPrice` DOUBLE NULL, `BookSalePrice` DOUBLE NULL, `BookCategory` VARCHAR(45) NULL, `BookPhoto` VARCHAR(45) NULL, `BookContent` VARCHAR(45) NULL, `BookISBN` VARCHAR(45) NULL, PRIMARY KEY (`BookID`));Order
*OrderID | *UserID | *BookID | TradeMethod | TradeStatus | TradeParty | TraderID |
---|---|---|---|---|---|---|
INT | INT | INT | VARCHAR(45) | VARCHAR(45) | VARCHAR(45) | INT |
CREATE TABLE `bookiezilla`.`order` ( `OrderID` INT NOT NULL, `UserID` INT NOT NULL, `BookID` INT NOT NULL, `TradeMethod` VARCHAR(45) NULL, `TradeStatus` VARCHAR(45) NULL, `TraderID` INT NULL, PRIMARY KEY (`OrderID`));前端 目錄結(jié)構(gòu)
. │ .gitignore │ package-lock.json │ package.json │ README.md │ yarn.lock │ ├─config // 基本配置文件 │ │ env.js │ │ modules.js │ │ paths.js │ │ pnpTs.js │ │ webpack.config.js │ │ webpackDevServer.config.js │ │ │ └─jest │ cssTransform.js │ fileTransform.js │ ├─public │ favicon.ico │ index.html │ manifest.json │ ├─scripts // eject后生成的文件配置 │ build.js │ start.js │ test.js │ └─src // 主要頁面及組件部分 │ App.css │ App.js │ index.css │ index.js │ serviceWorker.js │ setupProxy.js // 設(shè)置代理轉(zhuǎn)發(fā),解決跨域問題 │ ├─actions // react-redux需要定義的actions │ UpdateActions.js │ ├─components // 頁面的組件部分 │ BookList.jsx │ BookMarket.jsx │ FeedBack.jsx │ OrderInfo.jsx │ PublishForm.jsx │ SearchBar.jsx │ SideMenu.jsx │ StatisticData.jsx │ StepFlow.jsx │ ├─images // 項(xiàng)目中使用的圖片資源 │ logo.png │ matthew.png │ ├─pages // 頁面部分 │ Home.jsx │ Login.jsx │ Market.jsx │ Message.jsx │ Publish.jsx │ Signup.jsx │ └─reducers // react-redux需要定義的reducers rootReducer.js實(shí)現(xiàn)細(xì)節(jié)
項(xiàng)目中使用了react-router來控制路由,基本原理如下:
在App.js中引入路由對(duì)應(yīng)的頁面或組件,并引入react-router-dom中的BrowserRouter、Route、Switch組件進(jìn)行定義。
// App.jsx import React, { Component } from "react"; import { BrowserRouter, Route, Switch } from "react-router-dom"; import SideMenu from "./components/SideMenu"; import Login from "./pages/Login"; import Signup from "./pages/Signup"; import Home from "./pages/Home"; import Market from "./pages/Market"; import Publish from "./pages/Publish"; import Message from "./pages/Message"; import OrderInfo from "./components/OrderInfo"; class App extends Component { render() { return (); } } export default App; {/* Only match one */}
當(dāng)項(xiàng)目頁面中需要進(jìn)行頁面跳轉(zhuǎn)時(shí),可使用react-router-dom中的withRouter將組件包裹起來,再使用NavLink進(jìn)行跳轉(zhuǎn)。
// Login.jsx import { NavLink, withRouter } from "react-router-dom"; class Login extends Component { ..... sendLoginRequest = () => { ...... this.props.history.push("/home"); render(){ ...... } }; export default withRouter(Login);
本項(xiàng)目中采用了react-redux進(jìn)行狀態(tài)管理,redux的主要作用是允許狀態(tài)在不同分支的組件中進(jìn)行傳遞,從而避免了使用原始方法(如this.props)導(dǎo)致的不同分支組件之間數(shù)據(jù)無法傳遞、子組件無法修改父組件狀態(tài)等問題。具體使用方法如下:
在src educers下新建文件rootReducer.js用于更新中心狀態(tài)樹中的信息。
// rootReducer.js const initState = { id: null, token: null }; const rootReducer = (state = initState, action) => { if (action.type === "UPDATE_ID") { return { ...state, id: action.id }; } if (action.type === "UPDATE_TOKEN") { return { ...state, token: action.token }; } return state; }; export default rootReducer;
在srcactions中新建文件UpdateActions.js用于定義行為。
// UpdateActions.js export const updateId = id => { return { type: "UPDATE_ID", id: id }; }; export const updateToken = token => { return { type: "UPDATE_TOKEN", token: token }; };
在srcindex.js中使用react-redux中的組件對(duì)項(xiàng)目入口文件進(jìn)行包裹,并在全局范圍內(nèi)建立狀態(tài)樹。
// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; import "semantic-ui-css/semantic.min.css"; import { createStore } from "redux"; import { Provider } from "react-redux"; import rootReducer from "./reducers/rootReducer"; const store = createStore(rootReducer); ReactDOM.render(, document.getElementById("root") ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister(); ,
當(dāng)需要更新狀態(tài)樹中的信息時(shí),使用引入的action作為函數(shù)進(jìn)行更新。
// Login.jsx import { connect } from "react-redux"; import { updateId, updateToken } from "../actions/UpdateActions"; class Login extends Component { ...... sendLoginRequest = () => { ...... this.props.updateId(res.data.id); this.props.updateToken(res.data.token); ...... }; } const mapStateToProps = state => { return {}; }; const mapDispatchToProps = dispatch => { return { updateToken: token => { dispatch(updateToken(token)); }, updateId: id => { dispatch(updateId(id)); } }; }; export default connect( mapStateToProps, mapDispatchToProps )(withRouter(Login));
當(dāng)需要使用狀態(tài)樹中的信息時(shí),先調(diào)用react-redux中的connect包裹組件,再使用this.props直接調(diào)用即可。
// PublishForm.jsx import { connect } from "react-redux"; class PublishForm extends Component { ...... var UserID = this.props.id; var UserToken = this.props.token; ...... } const mapStateToProps = state => { return { id: state.id, token: state.token }; }; export default connect(mapStateToProps)(PublishForm);后端 目錄結(jié)構(gòu)
. │ app.js │ package-lock.json │ package.json │ └─src ├─config // 數(shù)據(jù)庫配置 │ database.js │ ├─controllers // 控制器,獲取請(qǐng)求數(shù)據(jù)并調(diào)用models中的方法進(jìn)行處理并返回結(jié)果 │ apiController.js │ msgController.js │ userController.js │ ├─models // 實(shí)例模型,主要使用Sequelize定義的方法對(duì)數(shù)據(jù)庫進(jìn)行增刪改查 │ bookModel.js │ CommentModel.js │ orderModel.js │ userModel.js │ ├─routes // 路由,不同文件對(duì)應(yīng)不同類型的api接口,分別與授權(quán)、功能實(shí)現(xiàn)、信息傳遞有關(guān) │ api.js │ auth.js │ msg.js │ └─schema // 數(shù)據(jù)庫表結(jié)構(gòu),可使用Sequelize自動(dòng)生成 book.js comment.js order.js user.js實(shí)現(xiàn)細(xì)節(jié)
當(dāng)Koa后端監(jiān)聽的端口接收到請(qǐng)求時(shí),會(huì)根據(jù)app.js中的路由規(guī)則進(jìn)行處理,我們將不同類型的接口定義在不同文件中,再通過router.use()進(jìn)行調(diào)用,避免發(fā)生接口冗亂復(fù)雜的情況。
// app.js const Koa = require("koa"); const json = require("koa-json"); const logger = require("koa-logger"); const KoaRouter = require("koa-router"); const parser = require("koa-bodyparser"); const auth = require("./src/routes/auth.js"); const api = require("./src/routes/api.js"); const msg = require("./src/routes/msg.js"); const app = new Koa(); const router = new KoaRouter(); // Json Prettier Middleware app.use(json()); app.use(parser()); app.use(logger()); // Simple Middleware Example // app.use(async ctx => (ctx.body = { msg: "Hello world" })); // Router Middleware router.use("/auth", auth.routes()); router.use("/msg", msg.routes()); router.use("/api", api.routes()); app.use(router.routes()).use(router.allowedMethods()); app.listen(4113, () => console.log("----------Server Started----------")); module.exports = app;
// auth.js const auth = require("../controllers/userController.js"); const router = require("koa-router")(); router.get("/user/:id", auth.getUserInfo); router.post("/login", auth.vertifyUserLogin); router.post("/signup", auth.signupNewUser); module.exports = router;
// api.js const api = require("../controllers/apiController.js"); const router = require("koa-router")(); router.get("/getbooks", api.getAllBooks); router.get("/getorder/:id", api.getOrderInfo); router.post("/searchbooks", api.searchBooks); router.post("/publish", api.publishNewBook); router.post("/confirmorder", api.updateOrderOfTrade); module.exports = router;
// msg.js const msg = require("../controllers/msgController.js"); const router = require("koa-router")(); router.get("/getcomments", msg.getAllComments); router.post("/newcomment", msg.publishNewComment); module.exports = router;項(xiàng)目成果 登錄注冊(cè)
Bookizilla能夠?qū)崿F(xiàn)用戶注冊(cè)、用戶登錄功能,其中對(duì)用戶注冊(cè)時(shí)需要的數(shù)據(jù)做了格式處理(如驗(yàn)證Email格式、保證兩次密碼輸入數(shù)據(jù)相符且不小于6字節(jié)等)。如果用戶在注冊(cè)過程中出現(xiàn)錯(cuò)誤,則會(huì)出現(xiàn)相應(yīng)提示以指導(dǎo)用戶進(jìn)行正確輸入。
Login.jsx
Signup.jsx
個(gè)人主頁Bookiezilla的主頁呈現(xiàn)的是與該用戶有關(guān)的信息數(shù)據(jù)(如FAVES、VIEWS等,但由于目前后端并未儲(chǔ)存相關(guān)數(shù)據(jù)所以暫用了mocks)及該用戶所發(fā)布的所有書籍。
Home.jsx
書籍市場Bookiezilla的書籍市場呈現(xiàn)了所有用戶發(fā)布的所有書籍,用戶可以使用上方的搜索框輸入關(guān)鍵詞(如書名、標(biāo)簽 、ISBN等)。用戶還可點(diǎn)擊圖書下方按鈕以查看具體信息,進(jìn)而決定是否達(dá)成交易,也可點(diǎn)擊鏈接在Amazon中查看書籍的詳細(xì)介紹。
Market.jsx
書籍發(fā)布Bookiezilla允許用戶發(fā)布書籍,并設(shè)置訂單的關(guān)鍵信息(如書籍基本信息、交易模式、尋求買家或賣家等)。需要注意的是,由于書籍發(fā)布和書籍求購很大一部分內(nèi)容是重合的,所以此處將二者合并并且給出TradeParty選項(xiàng)來使用戶選擇是想要發(fā)布書籍還是求購書籍。
Publish.jsx
信息發(fā)布Bookiezilla設(shè)置了信息發(fā)布面板,用于用戶之間的溝通交流、信息發(fā)布等。用戶可直接發(fā)布評(píng)論或回復(fù)他人的評(píng)論,從而進(jìn)行持續(xù)性的交流。
Message.jsx
https://github.com/Sylvie-Hsu...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/106745.html
Github上的腳手架實(shí)在太多,可能大多數(shù)都是只專注在前端的web開發(fā),例如流行的React生態(tài)中的create-react-app和Vue生態(tài)中的Vue-cli, 但是可能作為像我一樣的全棧開發(fā),一個(gè)只關(guān)注在前端開發(fā)的腳手架滿足不了所有的需求,我們可能需要開發(fā)更復(fù)雜的全棧JS的項(xiàng)目,所以這里介紹又一個(gè)基于NodeJS的全棧開發(fā)框架 koa-web-kit,不一定適合所有人,但至少又多了個(gè)選擇?。...
稍微整理了一下自己平時(shí)看到的前端學(xué)習(xí)資源,分享給大家。 html MDN:Mozilla開發(fā)者網(wǎng)絡(luò) SEO:前端開發(fā)中的SEO css 張鑫旭:張鑫旭的博客 css精靈圖:css精靈圖實(shí)踐 柵格系統(tǒng):詳解CSS中的柵格系統(tǒng) 媒體查詢:css媒體查詢用法 rem布局:手機(jī)端頁面自適應(yīng)布局 移動(dòng)前端開發(fā)之viewport的深入理解:深入理解viewport 淘寶前端布局:手機(jī)淘寶移動(dòng)端布局 fl...
摘要:是一個(gè)基于和的服務(wù)器端和瀏覽器端的的前后端全棧應(yīng)用框架。是的組件,并且會(huì)進(jìn)行數(shù)據(jù)初始化不但可以支持的數(shù)據(jù)初始化,還可以合并和的,使用同一個(gè),和的無縫結(jié)合。 koa-cola是一個(gè)基于koa和react的服務(wù)器端SSR(server side render)和瀏覽器端的SPA(single page application)的web前后端全棧應(yīng)用框架。 koa-cola使用typescr...
摘要:序列文章從項(xiàng)目中由淺入深的學(xué)習(xí)微信小程序和快應(yīng)用從項(xiàng)目中由淺入深的學(xué)習(xí)從項(xiàng)目中由淺入深的學(xué)習(xí)前言的出現(xiàn)前端已經(jīng)可以用一把梭從前端寫到后臺(tái)。 showImg(https://segmentfault.com/img/bVbrRI5?w=1920&h=1080); 序列文章 從項(xiàng)目中由淺入深的學(xué)習(xí)vue,微信小程序和快應(yīng)用 (1)從項(xiàng)目中由淺入深的學(xué)習(xí)react (2)從項(xiàng)目中由淺入深的學(xué)...
摘要:搭建的博客曾經(jīng)用的寫的博客,現(xiàn)在看來已經(jīng)很了,所以用目前最火的框架重構(gòu)一下。后端重構(gòu)博客嘛,以前用寫的后臺(tái),所以略懂一些,作為一個(gè)前端開發(fā),目標(biāo)就是全棧嘛,選用了最為流行的也用了目前最為流行的作為后端配合。 React-Node搭建的博客 曾經(jīng)用的php+mysql+js寫的博客,現(xiàn)在看來已經(jīng)很low了,所以用目前最火的react+koa框架重構(gòu)一下。先上地址吧:目前線上版本http:...
閱讀 2779·2021-10-14 09:42
閱讀 834·2021-10-11 10:57
閱讀 780·2019-08-30 15:54
閱讀 1922·2019-08-30 13:50
閱讀 1691·2019-08-30 11:19
閱讀 938·2019-08-29 12:38
閱讀 1429·2019-08-26 11:51
閱讀 1398·2019-08-26 10:48