如何使用 HTML、CSS 和 JS 创建电子商务网站(第 2 部分)
视频教程
代码
NPM 初始化
服务器
注册页面
大家好,希望你一切顺利。今天我们将进行全栈电商网站系列的第二部分。在这一部分中,你将创建一个 Node 服务器,在本地主机上运行网站,然后学习如何进行表单验证以及如何将用户数据存储在 Firestore 中。总的来说,在本视频中,我们将制作注册页面/登录页面、注销功能和卖家控制面板。
如果你还没看过上一集,现在就观看
如果您想观看演示或完整的编码教程视频以更好地理解,您可以观看下面的教程。
视频教程
如果您能订阅我的 YouTube 频道来支持我,我将不胜感激。
代码
您可以在下方看到我们项目的文件夹结构。与上一部分相比,我们添加了一些新文件。
那么让我们开始编码吧。
NPM 初始化
从服务器启动,在终端或 cmd 提示符中打开之前的代码文件夹。然后运行npm init
。这会将 NPM 初始化到项目中。之后,运行此命令安装一些软件包。
npm i express.js nodemon firebase-admin bcrypt
express.js
- 是创建一个服务器nodemon
- 是持续运行服务器。firebase-admin
- 是从后端访问firebase。bcrypt
- 在将用户密码存储到我们的数据库之前对其进行加密。
安装完成后,您将package.json
在目录中看到。打开文件并查看scripts
对象的变化。
"scripts": {
"start": "nodemon server.js"
}
这将创建一个用于 NPM 的启动命令。如果你还没有创建server.js
文件,那就创建一个。然后我们来创建服务器。
服务器
打开server.js
文件。然后导入我们刚刚安装的包。
// importing packages
const express = require('express');
const admin = require('firebase-admin');
const bcrypt = require('bcrypt');
const path = require('path');
我们没有安装
path
包/库,因为它是 nodeJS 附带的默认包。path
允许访问路径或执行与路径相关的操作。
// declare static path
let staticPath = path.join(__dirname, "public");
将公共文件夹的路径设为静态路径。什么是静态路径?静态路径只是告诉服务器在哪里查找文件的路径。
//intializing express.js
const app = express();
//middlewares
app.use(express.static(staticPath));
app.listen(3000, () => {
console.log('listening on port 3000.......');
})
在上面的代码中,我创建了一个快速服务器并监听端口 3000 上的请求。
制定/
、/404
路线。
//routes
//home route
app.get("/", (req, res) => {
res.sendFile(path.join(staticPath, "index.html"));
})
现在通过npm start
在终端上运行来启动你的服务器。localhost:3000
在你的 Chrome 浏览器上打开它来查看页面。如果服务器正常运行,你就能看到这个index.html
页面。
对于404
路由,我们将使用中间件。请确保将此中间件添加到服务器的最底层。否则,404
即使您位于某个定义的路由上,也会显示页面。
// 404 route
app.get('/404', (req, res) => {
res.sendFile(path.join(staticPath, "404.html"));
})
app.use((req, res) => {
res.redirect('/404');
})
你可能注意到了,我创建了一个单独的404
页面,并将用户请求重定向到任何未知的路由。为什么我这样做呢?因为如果我404
通过中间件传递页面,我肯定会得到这个页面,但如果我们采用嵌套路由,我得到的页面就没有样式了。参见下图。
所以我们现在几乎完成了我们的服务器,只需创建一条/signup
路线来传送注册页面。
//signup route
app.get('/signup', (req, res) => {
res.sendFile(path.join(staticPath, "signup.html"));
})
在路由前添加所有路由
404
。
注册页面
打开你的signup.html
文件。从 HTML5 模板开始。输入合适的标题并链接form.css
文件。
首先为页面制作一个加载器。
<img src="img/loader.gif" class="loader" alt="">
表单.css
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
width: 100%;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #f5f5f5;
font-family: 'roboto', sans-serif;
}
.loader{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100px;
}
输出
现在制作表格。
<div class="container">
<img src="img/dark-logo.png" class="logo" alt="">
<div>
<input type="text" autocomplete="off" id="name" placeholder="name">
<input type="email" autocomplete="off" id="email" placeholder="email">
<input type="password" autocomplete="off" id="password" placeholder="password">
<input type="text" autocomplete="off" id="number" placeholder="number">
<input type="checkbox" checked class="checkbox" id="terms-and-cond">
<label for="terms-and-cond">agree to our <a href="">terms and conditions</a></label>
<br>
<input type="checkbox" class="checkbox" id="notification">
<label for="notification">recieve upcoming offers and events mails</a></label>
<button class="submit-btn">create account</button>
</div>
<a href="/login" class="link">already have an account? Log in here</a>
</div>
如果你注意到上面的代码,就会发现我使用的div
是表单而不是form
标签。为什么?因为使用 HTML,form
你可以POST
向服务器发送请求,但无法获取响应,而我们希望获取服务器的响应来验证是否成功。
表单.css
.logo{
height: 80px;
display: block;
margin: 0 auto 50px;
}
input[type="text"],
input[type="password"],
input[type="email"],
textarea{
display: block;
width: 300px;
height: 40px;
padding: 20px;
border-radius: 5px;
background: #fff;
border: none;
outline: none;
margin: 20px 0;
text-transform: capitalize;
color: #383838;
font-size: 14px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.01);
font-family: 'roboto', sans-serif;
}
::placeholder{
color: #383838;
}
.submit-btn{
width: 300px;
height: 40px;
text-align: center;
line-height: 40px;
background: #383838;
color: #fff;
border-radius: 2px;
text-transform: capitalize;
border: none;
cursor: pointer;
display: block;
margin: 30px 0;
}
/* checkbox styles */
.checkbox{
-webkit-appearance: none;
position: relative;
width: 15px;
height: 15px;
border-radius: 2px;
background: #fff;
border: 1px solid #383838;
cursor: pointer;
}
.checkbox:checked{
background: #383838;
}
.checkbox::after{
content: '';
position: absolute;
top: 60%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 100%;
pointer-events: none;
background-image: url(../img/check.png);
background-size: contain;
background-repeat: no-repeat;
display: none;
}
.checkbox:checked::after{
display: block;
}
label{
text-transform: capitalize;
display: inline-block;
margin-bottom: 10px;
font-size: 14px;
color: #383838;
}
label a{
color: #383838;
}
.link{
color: #383838;
text-transform: capitalize;
text-align: center;
display: block;
}
上面有很多 CSS 代码,不是吗?好吧,如果你不了解上述任何 CSS 属性,欢迎在评论区问我。
输出
现在,制作一个警告框。
<div class="alert-box">
<img src="img/error.png" class="alert-img" alt="">
<p class="alert-msg">Error message</p>
</div>
/* alert */
.alert-box{
width: 300px;
min-height: 150px;
background: #fff;
border-radius: 10px;
box-shadow: 0 5px 100px rgba(0, 0, 0, 0.05);
position: absolute;
top: 60%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
opacity: 0;
pointer-events: none;
transition: 1s;
}
.alert-box.show{
opacity: 1;
pointer-events: all;
top: 50%;
}
.alert-img{
display: block;
margin: 10px auto 20px;
height: 60px;
}
.alert-msg{
color: #e24c4b;
font-size: 20px;
text-transform: capitalize;
text-align: center;
line-height: 30px;
font-weight: 500;
}
输出
啥时候
alert-box
有show
课。
太棒了!注册页面完成了。现在开始让它功能化吧。添加form.js
到signup.html
页面。
<script src="js/form.js"></script>
Form.js
选择我们需要的所有元素。
const loader = document.querySelector('.loader');
// select inputs
const submitBtn = document.querySelector('.submit-btn');
const name = document.querySelector('#name');
const email = document.querySelector('#email');
const password = document.querySelector('#password');
const number = document.querySelector('#number');
const tac = document.querySelector('#terms-and-cond');
const notification = document.querySelector('#notification');
选择完所有元素后,使用 向验证表单中添加click
事件。submitBtn
if else
submitBtn.addEventListener('click', () => {
if(name.value.length < 3){
showAlert('name must be 3 letters long');
} else if(!email.value.length){
showAlert('enter your email');
} else if(password.value.length < 8){
showAlert('password should be 8 letters long');
} else if(!number.value.length){
showAlert('enter your phone number');
} else if(!Number(number.value) || number.value.length < 10){
showAlert('invalid number, please enter valid one');
} else if(!tac.checked){
showAlert('you must agree to our terms and conditions');
} else{
// submit form
}
})
在上面的代码中,我们如何进行验证?嗯,我使用的if else
验证方式是,如果为真,则运行以下代码;如果为假,则运行上述else
代码。
让我们了解一下名称验证。
if(name.value.length < 3){
showAlert('name must be 3 letters long');
}
if
正在检查条件,该条件写在( condition )
.中name
,是我们name
在文件顶部声明的元素。-value
因为name
是一个输入字段。它必须有一个值。当然,它可以为空。因此,name.value
它只是返回输入字段的值。length
用于计算字符串中有多少个字母,或者数组中有多少个元素。所以基本上,通过使用 ,name.value.length
我们检查名称的值的长度,当然,长度是一个整数。
一旦我们得到了长度(一个数字),就检查它是否小于 3。
因此,如果条件为真,则 JS 将运行if
块内编写的代码,即
showAlert('name must be 3 letters long');
这也是我们对其他领域进行验证的方式。
showAlert(msg)
所以我们现在必须创建函数。
// alert function
const showAlert = (msg) => {
let alertBox = document.querySelector('.alert-box');
let alertMsg = document.querySelector('.alert-msg');
alertMsg.innerHTML = msg;
alertBox.classList.add('show');
setTimeout(() => {
alertBox.classList.remove('show');
}, 3000);
}
在上面的函数中,首先我选择与警告框相关的元素。然后,我将msg
参数设置为innerHTML
,alertMsg
也就是p
的元素alert-box
。然后show
为 添加了类。并在 3000 毫秒(即 3 秒)后alertBox
使用setTimeout
删除该类。show
好了,注册验证已经完成,现在该提交表单了。为了提交表单,我们需要创建另一个函数,该函数接受path
和data
作为参数。为什么要创建一个单独的函数呢?因为我们可以在注册页面和登录页面都使用该函数。
// send data function
const sendData = (path, data) => {
fetch(path, {
method: 'post',
headers: new Headers({'Content-Type': 'application/json'}),
body: JSON.stringify(data)
}).then((res) => res.json())
.then(response => {
processData(response);
})
}
在上面的代码中,我使用了简单的fetch
方法来发起请求。它基本上是一个fetch
模板。我们processData
稍后再实现函数。
现在将表单数据发送到后端。
else{
// submit form
loader.style.display = 'block';
sendData('/signup', {
name: name.value,
email: email.value,
password: password.value,
number: number.value,
tac: tac.checked,
notification: notification.checked,
seller: false
})
}
这是表单验证之后
在内部制定signup
路线server.js
来处理表单提交。
注册 - POST
在创建路由之前,请在顶部添加此行。这将启用表单共享。否则,您将无法接收表单数据。
app.use(express.json());
app.post('/signup', (req, res) => {
let { name, email, password, number, tac, notification } = req.body;
// form validations
if(name.length < 3){
return res.json({'alert': 'name must be 3 letters long'});
} else if(!email.length){
return res.json({'alert': 'enter your email'});
} else if(password.length < 8){
return res.json({'alert': 'password should be 8 letters long'});
} else if(!number.length){
return res.json({'alert': 'enter your phone number'});
} else if(!Number(number) || number.length < 10){
return res.json({'alert': 'invalid number, please enter valid one'});
} else if(!tac){
return res.json({'alert': 'you must agree to our terms and conditions'});
}
})
这里,我首先从请求中提取数据。这样我们就可以把表单数据从前端发送出去了。你可以看到,我在后端也使用了相同的名称。
let { name, email, password, number, tac, notification } = req.body;
之后,我执行表单验证,当然我们在前端完成,但最好也在后端进行验证,因为前端可以轻松绕过。
if(name.length < 3){
return res.json({'alert': 'name must be 3 letters long'});
} else if .....
注意,我没有使用value
这里,因为name
这里不是输入,而是我们从前端获取的字符串。作为响应,我发送的是 JSON 数据。如下所示。
JSON = {
'key': 'value'
}
它类似于 JS 对象,但用于在网络上传输数据。
太好了。现在JSON
在前端处理数据。
const processData = (data) => {
loader.style.display = null;
if(data.alert){
showAlert(data.alert);
}
}
当然是隐藏第loader
一个。之后检查接收到的数据是否包含alert
密钥。如果包含,就用showAlert
函数提醒用户。是不是很简单?
好的,现在让我们将用户存储在数据库或firestore中。
将用户存储在 Firestore 中
在编写更多代码之前,请确保你已创建 Firebase 项目并从仪表板下载密钥文件。你可以参考此处下载密钥。
获得密钥文件后,将其移动到项目文件public
夹外部。
然后初始化里面的firebase server.js
。
// firebase admin setup
let serviceAccount = require("path of key file");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
let db = admin.firestore();
初始化 Firebase 后。在signup
POST 路由内。验证后将用户存储在数据库中。
// store user in db
db.collection('users').doc(email).get()
.then(user => {
if(user.exists){
return res.json({'alert': 'email already exists'});
} else{
// encrypt the password before storing it.
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, (err, hash) => {
req.body.password = hash;
db.collection('users').doc(email).set(req.body)
.then(data => {
res.json({
name: req.body.name,
email: req.body.email,
seller: req.body.seller,
})
})
})
})
}
})
在 Firebase 中,我们有集合,它们存储同一组数据。在本例中,我们users
在 firstore 中有一个集合。db.collection
用于访问集合。进入集合后,您可以通过调用 获取文档doc(docname)
;找到文档后,您可以通过调用get()
方法 获取文档。获取文档后,您可以使用 访问它then
。这就是整行代码的意思。
db.collection('users').doc(email).get()
.then(...)
上面的代码用于检查邮箱地址是否已存在于数据库中。如果存在,则发送警报。如果不存在,则将用户信息存储到数据库中。
if(user.exists){
return res.json({'alert': 'email already exists'});
} else{
// encrypt the password before storing it.
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, (err, hash) => {
req.body.password = hash;
db.collection('users').doc(email).set(req.body)
.then(data => {
res.json({
name: req.body.name,
email: req.body.email,
seller: req.body.seller,
})
})
})
})
}
bycrypt
是加密包,如果需要,您可以阅读其文档。但是要对密码进行哈希处理,您只需编写代码即可。genSalt
是要对文本执行多少加盐操作。hash
是将文本转换为哈希值。之后,直到一切都相同doc()
,但这次我们不必这样get()
做,这几乎是不言而喻的。最后,作为响应,我将用户和状态set()
发送到前端。name
email
seller
现在让我们将其存储在前端。
const processData = (data) => {
loader.style.display = null;
if(data.alert){
showAlert(data.alert);
} else if(data.name){
// create authToken
data.authToken = generateToken(data.email);
sessionStorage.user = JSON.stringify(data);
location.replace('/');
}
}
使用会话存储来存储用户数据session
。但我们不能简单地使用用户邮箱来验证其真实性,我们至少需要一些可以验证的东西。为此,我们需要为用户生成一个身份验证令牌。这虽然不算先进,但我确实想过把它做成一个。
首先将token.js
文件添加到signup.html
。
<script src="js/token.js"></script>
之后创建generateToken
函数。
Token.js
let char = `123abcde.fmnopqlABCDE@FJKLMNOPQRSTUVWXYZ456789stuvwxyz0!#$%&ijkrgh'*+-/=?^_${'`'}{|}~`;
const generateToken = (key) => {
let token = '';
for(let i = 0; i < key.length; i++){
let index = char.indexOf(key[i]) || char.length / 2;
let randomIndex = Math.floor(Math.random() * index);
token += char[randomIndex] + char[index - randomIndex];
}
return token;
}
```
This above code, it will simply generate a text of whose sets of 2 letters index number add to give the original text index from the char string. It is simple but complex also. It okay, to copy it if you want.
Now we also want a function to validate the token.
```JS
const compareToken = (token, key) => {
let string = '';
for(let i = 0; i < token.length; i=i+2){
let index1 = char.indexOf(token[i]);
let index2 = char.indexOf(token[i+1]);
string += char[index1 + index2];
}
if(string === key){
return true;
}
return false;
}
```
Great! we are almost done with the page. Till now we have successfully stored the used in session, so let's validate it.
######form.js
```Js
// redirect to home page if user logged in
window.onload = () => {
if(sessionStorage.user){
user = JSON.parse(sessionStorage.user);
if(compareToken(user.authToken, user.email)){
location.replace('/');
}
}
}
```
we are adding load event to window, which is checking is user in session or not. If it is in session, we are validating the auth token. And it its legit. I am redirecting user to home page. As he/she really don't need sign up.
Great! Our sign up page is done. Since the blog is being too much lengthy. I think that enough for today. But yes, in the second part, I have made login page and seller's dashboard. Which I made in the tutorial. So if you want to make all the features, of course you want. [Watch the tutorial](https://www.youtube.com/watch?v=yYSfOe0QBOk)
I hope you understood each and everything. If you have doubt or I missed something let me know in the comments.
#Articles you may find Useful
1. [Best CSS Effect](https://dev.to/kunaal438/css-the-best-css-effects-of-all-time-most-underrated-web-ux-2chj)
2. [Infinte CSS loader](https://dev.to/kunaal438/quick-css-make-infinity-loading-animation-for-your-next-website-187k)
3. [Disney+ Clone](https://dev.to/kunaal438/how-to-create-disney-plus-clone-for-beginner-in-2021-html-css-js-m3p)
4. [Youtube API - Youtube Clone](https://dev.to/kunaal438/create-working-youtube-clone-with-search-box-youtube-api-2a6e)
5. [TMDB - Netflix Clone](https://dev.to/kunaal438/how-to-create-netflix-clone-netflix-clone-with-hmtl-css-js-989)
I really appreciate if you can subscribe my youtube channel. I create awesome web contents.
<a href="https://www.youtube.com/c/modern_web?sub_confirmation=1"><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jeitpawvax07cx7r9yoa.png"></a>
[Source Code](https://www.patreon.com/posts/56549627), [Donate me on Paypal](https://paypal.me/modernwebchannel)
Your donation really motivates me to do more amazing tutorials like this. Support me on [patreon](https://www.patreon.com/modernweb), [Buy me a coffee](https://ko-fi.com/modernweb), [Donate me on paypal](https://paypal.me/modernwebchannel)
Thanks For reading.