电商网站 - 在数据库中添加/删除产品。(三)
视频教程
代码
您可能觉得有用的文章
你好,希望你一切顺利。在上一篇博文中,我们制作了登录/退出功能和卖家控制面板,用户可以申请成为卖家,并可以使用自己的控制面板管理产品。在今天的博文中,我们将制作一个产品添加功能,这是一个非常重要的功能。我们将学习如何在将表单存储到数据库之前进行验证,如何从前端上传图片到AWS,以及添加、删除产品、编辑产品等等。
如果你还没看过之前的片段,现在就观看
如果您想观看演示或完整的编码教程视频以更好地理解,您可以观看下面的教程。
视频教程
如果您能订阅我的 YouTube 频道来支持我,我将不胜感激。
代码
在开始编码之前,您可以先看一下文件夹结构。由于代码文件太多,我甚至无法像在博客中那样设计文件结构。不过您可以看看下面的截图。
那么,开始编码吧。首先,让我们创建/add-product
到服务addProduct.html
页面的路由。
服务器.js
// add product
app.get('/add-product', (req, res) => {
res.sendFile(path.join(staticPath, "addProduct.html"));
})
确保在
404
路线之前添加此路线,正如我之前所说,如果在路线之后添加任何路线404
,您将始终获得404
页面。
之后,让我们制作添加产品页面,在那里我们可以填写产品详细信息。
添加产品页面 - 设计
首先,从 HTML 5 模板开始,制作元素、loader
所有CSS 和 JS 文件。alert
link
<head>
// other head tags
<link rel="stylesheet" href="css/signup.css">
<link rel="stylesheet" href="css/addProduct.css">
</head>
<body>
<img src="img/loader.gif" class="loader" alt="">
<div class="alert-box">
<img src="img/error.png" class="alert-img" alt="">
<p class="alert-msg"></p>
</div>
<script src="js/token.js"></script>
<script src="js/addProduct.js"></script>
</body>
当然,完成基本结构后就可以制作形状了。
<img src="img/dark-logo.png" class="logo" alt="">
<div class="form">
<input type="text" id="product-name" placeholder="product name">
<input type="text" id="short-des" placeholder="short line about the product">
<textarea id="des" placeholder="detail description"></textarea>
<!-- product image -->
<div class="product-info">
<div class="product-image"><p class="text">product image</p></div>
<div class="upload-image-sec">
<!-- upload inputs -->
<p class="text"><img src="img/camera.png" alt="">upload image</p>
<div class="upload-catalouge">
<input type="file" class="fileupload" id="first-file-upload-btn" hidden>
<label for="first-file-upload-btn" class="upload-image"></label>
<input type="file" class="fileupload" id="second-file-upload-btn" hidden>
<label for="second-file-upload-btn" class="upload-image"></label>
<input type="file" class="fileupload" id="third-file-upload-btn" hidden>
<label for="third-file-upload-btn" class="upload-image"></label>
<input type="file" class="fileupload" id="fourth-file-upload-btn" hidden>
<label for="fourth-file-upload-btn" class="upload-image"></label>
</div>
</div>
<div class="select-sizes">
<p class="text">size available</p>
<div class="sizes">
<input type="checkbox" class="size-checkbox" id="xs" value="xs">
<input type="checkbox" class="size-checkbox" id="s" value="s">
<input type="checkbox" class="size-checkbox" id="m" value="m">
<input type="checkbox" class="size-checkbox" id="l" value="l">
<input type="checkbox" class="size-checkbox" id="xl" value="xl">
<input type="checkbox" class="size-checkbox" id="xxl" value="xxl">
<input type="checkbox" class="size-checkbox" id="xxxl" value="xxxl">
</div>
</div>
</div>
<div class="product-price">
<input type="number" id="actual-price" placeholder="actual price">
<input type="number" id="discount" placeholder="discount percentage">
<input type="number" id="sell-price" placeholder="selling price">
</div>
<input type="number" id="stock" min="20" placeholder="item in sstocks (minimum 20)">
<textarea id="tags" placeholder="Enter categories here, for example - Men, Jeans, Blue Jeans, Rough jeans (you sholud add men or women at start)"></textarea>
<input type="checkbox" class="checkbox" id="tac" checked>
<label for="tac">clothing take 30% from your total sell</label>
<div class="buttons">
<button class="btn" id="add-btn">add product</button>
<button class="btn" id="save-btn">save draft</button>
</div>
</div>
这一下子就有很多 HTML,您可以参考视频来获取分步指南,因为我们在博客中主要关注 Javascript,但如果您对任何部分有任何疑问,请随时在讨论中询问我。
添加产品.css
body{
display: block;
padding: 0 10vw;
}
.logo{
margin: 20px auto 50px;
}
input, textarea{
font-weight: 500;
}
input:not(input[type="checkbox"]){
width: 100%;
}
textarea{
width: 100%;
height: 270px;
resize: none;
padding: 10px 20px;
}
.product-info{
width: 100%;
height: 500px;
display: grid;
grid-template-columns: .75fr 1.25fr;
grid-template-rows: repeat(2, 1fr);
grid-gap: 20px;
margin-bottom: 20px;
}
.product-image{
display: flex;
justify-content: center;
align-items: center;
background: #fff;
background-size: cover;
border-radius: 10px;
grid-row: span 2;
text-shadow: 0 0 10px #fff;
}
.text{
text-transform: capitalize;
color: #383838;
font-size: 20px;
font-weight: 500;
}
.upload-image-sec, .select-sizes{
background: #fff;
border-radius: 10px;
padding: 20px;
}
.text img{
height: 20px;
margin-right: 10px;
}
.upload-catalouge{
width: 100%;
margin: 20px 0;
display: grid;
grid-template-columns: repeat(4, 100px);
grid-gap: 10px;
}
.upload-image{
width: 100%;
height: 100px;
background: #f5f5f5;
cursor: pointer;
background-size: cover;
}
.upload-image:hover{
background: rgba(0, 0, 0, 0.2);
background-size: cover;
}
.sizes{
margin-top: 30px;
}
.size-checkbox{
-webkit-appearance: none;
width: 100px;
height: 40px;
border-radius: 5px;
border: 1px solid #383838;
cursor: pointer;
margin-bottom: 10px;
margin-right: 10px;
position: relative;
color: #383838;
}
.size-checkbox::after{
content: attr(value);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 16px;
text-transform: uppercase;
}
.size-checkbox:checked{
background: #383838;
color: #fff;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button{
-webkit-appearance: none;
margin: 0;
}
.product-price{
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 20px;
}
.product-price input{
margin: 0;
}
.buttons{
margin: 20px 0 50px;
}
.btn{
padding: 10px 30px;
text-transform: capitalize;
color: #fff;
background: #383838;
border-radius: 5px;
border: none;
outline: none;
margin-right: 10px;
cursor: pointer;
}
#save-btn{
background: #a9a9a9;
}
你可能会注意到一个新的 CSS 选择器input::-webkit-outer-spin-button
。如果是这样,那么它就是简单的选择输入箭头按钮。在本例中,我们想要隐藏number
输入的箭头。这就是我使用它的原因。
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button{
-webkit-appearance: none;
margin: 0;
}
如果您从一开始就关注该系列,请对signpu.css
文件进行一些小的更改。
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"], // add this new line
textarea{
// properties
}
或者您可以简单地用它替换整个选择器。
input:not(input[type="checkbox"]),
textarea{
// properties
}
输出
太棒了!现在,让表单具备功能性。
表单提交
在将表单提交到后端之前,我们必须使用大量的 JS 来验证表单并向元素添加特定的触发器。
首先,由于这仅适用于卖家,因此首先检查用户访问页面时是否已登录。当然,如果用户未登录,则将用户重定向到登录页面。
let user = JSON.parse(sessionStorage.user || null);
let loader = document.querySelector('.loader');
// checknig user is logged in or not
window.onload = () => {
if(user){
if(!compareToken(user.authToken, user.email)){
location.replace('/login');
}
} else{
location.replace('/login');
}
}
从那里访问用户,
sessionStorage
因为我将用户存储在那里。
完成这些之后,我们开始添加动态定价。什么意思呢?就是说,我们添加一个功能,用户添加actual price
一个discounted price
,系统会自动将selling price
精确的折扣信息填充到“价格”中。而且这个功能是可逆的。
添加产品.js
// price inputs
const actualPrice = document.querySelector('#actual-price');
const discountPercentage = document.querySelector('#discount');
const sellingPrice = document.querySelector('#sell-price');
首先选择所有三个输入,然后添加click
事件discountPercentage
并进行计算。
discountPercentage.addEventListener('input', () => {
if(discountPercentage.value > 100){
discountPercentage.value = 90;
} else{
let discount = actualPrice.value * discountPercentage.value / 100;
sellingPrice.value = actualPrice.value - discount;
}
})
在上面的代码中,我只是检查折扣百分比是否超过 100,然后将其设置为 90,因为没有人真的想卖免费产品,对吧?之后,只需进行简单的百分比与价值的计算并设置sellingPrice
价值即可。
之后添加sellingPrice
反向
sellingPrice.addEventListener('input', () => {
let discount = (sellingPrice.value / actualPrice.value) * 100;
discountPercentage.value = discount;
})
太棒了!我们成功了!完成之后,让我们使用 AWS 在线存储上传的图片。要查看 AWS 设置,请参阅此处的教程。
好了,我们先来看看如何在服务器端进行配置。在此之前,我们先来了解一下具体要做什么。
首先,我们在服务器中配置 AWS,然后向 AWS 发出请求,获取一个安全链接。获取到链接后,我们会将该链接发送到前端。当用户使用file
输入框上传图片时,前端PUT
会向服务器生成的链接发出请求,上传图片。最后,我们将该链接存储在一个数组中,以便跟踪。
因此,首先安装这两个包。
npm i aws-sdk dotenv
aws-sdk
- 当然是针对 aws 的dotenv
- 针对环境变量来保护您的凭证。
服务器.js
AWS Config
// aws config
const aws = require('aws-sdk');
const dotenv = require('dotenv');
dotenv.config();
// aws parameters
const region = "ap-south-1";
const bucketName = "ecom-website-tutorial-2";
const accessKeyId = process.env.AWS_ACCESS_KEY;
const secretAccessKey = process.env.AWS_SECRET_KEY;
aws.config.update({
region,
accessKeyId,
secretAccessKey
})
// init s3
const s3 = new aws.S3();
S3
是我们用来存储文件的 AWS 服务。
之后,创建一个生成链接的函数来生成链接。
// generate image upload link
async function generateUrl(){
let date = new Date();
let id = parseInt(Math.random() * 10000000000);
const imageName = `${id}${date.getTime()}.jpg`;
const params = ({
Bucket: bucketName,
Key: imageName,
Expires: 300, //300 ms
ContentType: 'image/jpeg'
})
const uploadUrl = await s3.getSignedUrlPromise('putObject', params);
return uploadUrl;
}
创建一个async
函数,因为我们不知道获取响应需要多长时间,而我们的其他代码都依赖于此。这getSignedUrlPromise
是一个用于获取put
链接的 AWS 方法。您也可以参考他们的文档。
现在只需创建一条/s3url
路线,将链接传送到前端。
// get the upload link
app.get('/s3url', (req, res) => {
generateUrl().then(url => res.json(url));
})
太棒了!现在我们需要在前端访问它。那就开始吧。
添加产品.js
选择上传输入
// upload image handle
let uploadImages = document.querySelectorAll('.fileupload');
let imagePaths = []; // will store all uploaded images paths;
现在查看每个上传按钮并为其添加更改事件。然后访问上传的文件。
uploadImages.forEach((fileupload, index) => {
fileupload.addEventListener('change', () => {
const file = fileupload.files[0];
let imageUrl;
if(file.type.includes('image')){
// means user uploaded an image
} else{
showAlert('upload image only');
}
})
})
此后只需fetch
从服务器获取 url,然后再次使用fetch
make PUT
request 上传图像。
if(file.type.includes('image')){
// means user uploaded an image
fetch('/s3url').then(res => res.json())
.then(url => {
fetch(url,{
method: 'PUT',
headers: new Headers({'Content-Type': 'multipart/form-data'}),
body: file
}).then(res => {
console.log(url)
})
})
}
大功告成,图片上传成功。现在要让它对用户可见。只需使用style
属性设置元素的background-image
,
if(file.type.includes('image')){
// means user uploaded an image
fetch('/s3url').then(res => res.json())
.then(url => {
fetch(url,{
method: 'PUT',
headers: new Headers({'Content-Type': 'multipart/form-data'}),
body: file
}).then(res => {
imageUrl = url.split("?")[0];
imagePaths[index] = imageUrl;
let label = document.querySelector(`label[for=${fileupload.id}]`);
label.style.backgroundImage = `url(${imageUrl})`;
let productImage = document.querySelector('.product-image');
productImage.style.backgroundImage = `url(${imageUrl})`;
})
})
}
输出
现在,还剩下什么?我知道很多东西了 XD 现在,因为我们有自定义复选框,为了跟踪存储的大小,我们必须创建一个函数来跟踪它。
// store size function
const storeSizes = () => {
sizes = [];
let sizeCheckBox = document.querySelectorAll('.size-checkbox');
sizeCheckBox.forEach(item => {
if(item.checked){
sizes.push(item.value);
}
})
}
上面的代码很简单,我想你已经明白了。现在让我们选择剩下的所有表单元素。
// form submission
const productName = document.querySelector('#product-name');
const shortLine = document.querySelector('#short-des');
const des = document.querySelector('#des');
let sizes = []; // will store all the sizes
const stock = document.querySelector('#stock');
const tags = document.querySelector('#tags');
const tac = document.querySelector('#tac');
// buttons
const addProductBtn = document.querySelector('#add-btn');
const saveDraft = document.querySelector('#save-btn');
现在添加click
事件addProductBtn
和类storeSizes
函数来存储大小。
addProductBtn.addEventListener('click', () => {
storeSizes();
// validate form
})
好的,为了验证表单,我们将使用单独的函数。但该函数会根据验证结果返回 true 或 false。
const validateForm = () => {
if(!productName.value.length){
return showAlert('enter product name');
} else if(shortLine.value.length > 100 || shortLine.value.length < 10){
return showAlert('short description must be between 10 to 100 letters long');
} else if(!des.value.length){
return showAlert('enter detail description about the product');
} else if(!imagePaths.length){ // image link array
return showAlert('upload atleast one product image')
} else if(!sizes.length){ // size array
return showAlert('select at least one size');
} else if(!actualPrice.value.length || !discount.value.length || !sellingPrice.value.length){
return showAlert('you must add pricings');
} else if(stock.value < 20){
return showAlert('you should have at least 20 items in stock');
} else if(!tags.value.length){
return showAlert('enter few tags to help ranking your product in search');
} else if(!tac.checked){
return showAlert('you must agree to our terms and conditions');
}
return true;
}
addProductBtn.addEventListener('click', () => {
storeSizes();
// validate form
if(validateForm()){ // validateForm return true or false while doing validation
}
})
现在如果你注意到,不是返回,validateForm
而是返回,这是为什么呢,因为我不想在每个函数中都写,所以我只是把它写在函数里面。false
showAlert
return false
if else
showAlert
Token.js
// alert function
const showAlert = (msg) => {
// previous code
return false;
}
如果你运行代码,你会收到警报。但是,有一个问题。我们会在页面顶部收到警报。当我从底部提交表单时,因为我没有将其位置设置为fixed
。
注册.css
/* alert */
.alert-box{
// previous code
position: fixed;
z-index: 2;
}
我做了同样的事情loader
。
注册.css
.loader{
position: fixed;
}
到目前为止,验证部分也完成了。现在只需提交数据。但是要提交数据,首先我们需要数据,对吧?为此,我们需要另一个函数productData()
来返回数据。
添加产品.js
const productData = () => {
return data = {
name: productName.value,
shortDes: shortLine.value,
des: des.value,
images: imagePaths,
sizes: sizes,
actualPrice: actualPrice.value,
discount: discountPercentage.value,
sellPrice: sellingPrice.value,
stock: stock.value,
tags: tags.value,
tac: tac.checked,
email: user.email
}
}
现在,一旦我们在前端获得了数据,我们就使用我们的提交它sendData()
。
addProductBtn.addEventListener('click', () => {
storeSizes();
// validate form
if(validateForm()){ // validateForm return true or false while doing validation
loader.style.display = 'block';
let data = productData();
sendData('/add-product', data);
}
})
太棒了。但是我们/add-product
的服务器里有 POST 路由吗?我觉得没有,我们自己做一个吧。
服务器.js
// add product
app.post('/add-product', (req, res) => {
let { name, shortDes, des, images, sizes, actualPrice, discount, sellPrice, stock, tags, tac, email } = req.body;
// validation
if(!draft){
if(!name.length){
return res.json({'alert': 'enter product name'});
} else if(shortDes.length > 100 || shortDes.length < 10){
return res.json({'alert': 'short description must be between 10 to 100 letters long'});
} else if(!des.length){
return res.json({'alert': 'enter detail description about the product'});
} else if(!images.length){ // image link array
return res.json({'alert': 'upload atleast one product image'})
} else if(!sizes.length){ // size array
return res.json({'alert': 'select at least one size'});
} else if(!actualPrice.length || !discount.length || !sellPrice.length){
return res.json({'alert': 'you must add pricings'});
} else if(stock < 20){
return res.json({'alert': 'you should have at least 20 items in stock'});
} else if(!tags.length){
return res.json({'alert': 'enter few tags to help ranking your product in search'});
} else if(!tac){
return res.json({'alert': 'you must agree to our terms and conditions'});
}
}
// add product
let docName = `${name.toLowerCase()}-${Math.floor(Math.random() * 5000)};
db.collection('products').doc(docName).set(req.body)
.then(data => {
res.json({'product': name});
})
.catch(err => {
return res.json({'alert': 'some error occurred. Try again'});
})
})
在上面的路由中,我只是简单地从请求中访问变量,并对数据执行验证。验证与前端相同,区别在于我们showAlert
在那里返回,而在这里我们返回JSON
。最后,我在产品名称后生成一个随机文档名称,并将数据添加到 Firestore。
现在只需收到产品添加的确认,我们就可以编辑文件processData()
中的一点点内容token.js
。
token.js
const processData = (data) => {
// previous conditions
else if(data.product){
location.href = '/seller';
}
}
所以,我们正式完成了验证,将产品添加到第一个库。干得好。博客开始有点长了。我想今天就讲到这里吧。当然,还要制作delete
、edit
和其他功能。请参阅视频教程。
希望你理解了所有内容。如果你有疑问或者我遗漏了什么,请在评论区告诉我。
您可能觉得有用的文章
如果您能订阅我的YouTube频道,我将不胜感激。我创作了非常棒的网络内容。
您的捐赠真的激励我创作更多类似的精彩教程。请在Patreon上支持我,请我喝杯咖啡,或在 PayPal 上捐款。
感谢您的阅读。
鏂囩珷鏉ユ簮锛�https://dev.to/themodernweb/e-commerce-website-adding-deleting-product-from-database-part-3-18np