使用 MERN 堆栈的绝对力量构建项目案例研究应用程序🔥
这篇博文重点介绍最重要的活动和理念,帮助您更好地理解和自下而上地构建 MERN 堆栈应用程序。它适合真正有兴趣了解 MERN 堆栈并希望专注于实际需要了解内容的人。
另有一篇单独的文章,您可以详细了解 MERN 堆栈。
在本篇博文中,我们将构建一个全栈项目案例研究应用程序,用户可以使用它来跟踪和记录 GitHub 项目,以及使用 GitHub API 和 MERN 堆栈搜索整个 GitHub 项目。本篇博文课程将帮助您学习 MERN 堆栈技术的基础知识以及高级概念和操作。
以下是我们应用程序的最终版本。
设置文件夹结构
在项目目录中创建两个文件夹,分别为客户端和服务器,然后在 Visual Studio Code 或您选择的任何代码编辑器中打开它。
现在,我们将构建一个 MongoDB 数据库,使用 Node 和 Express 设置服务器,创建一个数据库模式来代表我们的项目案例研究应用程序,并设置 API 路由,以便使用 npm 和相应的包从数据库中创建、读取、更新和删除数据和信息。因此,打开命令提示符并导航到服务器上的目录,然后运行下面的代码。
npm init -y
设置我们的 package.json 文件
在终端中执行以下命令来安装依赖项。
npm install cors dotenv express mongoose nodemon body-parser
安装依赖项后,“package.json”文件应如下所示。
另外,请记住更新脚本。
现在转到您的服务器目录并在那里创建一个 index.js 文件。
设置index.js
-
导入 express 模块。
-
导入并配置dotenv模块
-
导入 CORS 模块
-
使用 express() 启动我们的应用程序。
//index.js
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
// dotenv config
require('dotenv').config();
// app config
const app = express();
现在,我们可以在该应用实例上使用所有其他方法了。让我们从基础知识和最基本的设置开始。别忘了也设置端口。
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
// dotenv config
require('dotenv').config();
// app and port config
const app = express();
const port = process.env.PORT || 4000;
// middlewares
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(bodyParser.json());
设置MongoDB云集群
MongoDB 是一个开源、跨平台、面向文档的数据库。MongoDB 是一个 NoSQL 数据库,具有可选的模式,可将数据存储为类似 JSON 的文档。2018 年 10 月 16 日之前的所有版本均遵循 AGPL 许可证分发。SSPL 许可证 v1 适用于 2018 年 10 月 16 日之后发布的所有版本,包括针对旧版本的错误修复。
要设置和启动您的 MongoDB 集群,请按照下面提到的文章中概述的完全相同的步骤进行操作。
现在创建一个单独的数据库文件夹,并在其中创建另一个 index.js 文件。在其中导入 mongoose 库并创建一个字符串,只需粘贴复制的 mongo DB 连接 URL 或直接粘贴环境变量的链接即可。现在,在 Mongo DB 云图集 URL 链接中,输入您的用户名和密码,确保删除所有括号并输入您自己的凭据。最后,我们将使用 mongoose 连接到我们的数据库,因此输入 mongoose.connect(),它是一个具有两个不同参数的函数。第一个是 MONGO_DB_URL,第二个是具有两个不同选项的对象。第一个是 useNewUrlParser,我们将其设置为 true,第二个是 useUnifiedTopology,我们也将其设置为 true。这些对象不是必需的,但我们会在控制台上看到一些错误或警告。接下来,让我们链接 a.then() 和 .catch(),因为这将返回一个承诺,所以在 .then() 内部将调用应用程序并调用 listen,它有两个参数,第一个是 PORT,第二个是回调函数,如果我们的应用程序成功连接,最后,如果与数据库的连接不成功,我们将简单地在控制台记录我们的错误消息并最终导出该数据库。
const mongoose = require('mongoose');
require('dotenv').config();
mongoose.connect(process.env.MONGO_DB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).catch(e => {
console.error('Error while connecting to the database', e.message)
})
const Database = mongoose.connection
module.exports = Database;
将 mongodb+srv 插入到 .env 文件中。
PORT=4000
MONGO_DB_URL=mongodb+srv://pramit:<password>@cluster0.yxjll.mongodb.net/TakeNote?retryWrites=true&w=majority
这就是全部了;我们已经成功创建了数据库。因此,让我们将其导入到我们的主根 index.js 文件中,并将我们的数据库与服务器实际连接起来。
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
// importing database
const database = require('./database')
// dotenv config
require('dotenv').config();
// app and port config
const app = express();
const port = process.env.PORT || 4000;
// middlewares
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(bodyParser.json());
// DB connection
database.on('error', console.error.bind(console, 'MongoDB failed to connect'))
// listening to port
app.listen(port, () => console.log(`Currently server is running at http://localhost:${port}`))
现在我们已经成功将服务器连接到数据库。
现在我们已经成功连接到数据库,让我们开始构建后端应用程序的路由。为此,我们需要在服务器目录上创建一个名为 routes 的新文件夹。我们将在 routes 文件夹中创建一个名为 notes-routes.js 的文件。
首先,将笔记路由导入到 index.js 文件中。现在,我们可以使用 Express 中间件将笔记连接到我们的应用程序。最终,你的根 index.js 文件应该如下所示。
// index.js
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
// importing database
const database = require('./database')
// importing routes
const noteRouter = require('./routes/note-routes')
// dotenv config
require('dotenv').config();
// app and port config
const app = express();
const port = process.env.PORT || 4000;
// middlewares
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cors());
app.use(bodyParser.json());
// DB connection
database.on('error', console.error.bind(console, 'MongoDB failed to connect'))
app.use('/', noteRouter)
// listening to port
app.listen(port, () => console.log(`Currently server is running at http://localhost:${port}`))
我们将路由和控制器文件夹分开。但首先,让我们创建一个笔记模型。创建一个名为 models 的文件夹,并在其中创建一个名为 noteSchema.js 的文件,并将以下代码粘贴到每个文件中。
文件夹结构现在应如下所示。
// models/noteSchema.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const Note = new Schema({
note: {
type: String,
required: true
},
}, {
timestamps: true
}, )
module.exports = mongoose.model('notes', Note)
现在我们可以开始添加我们的路线和控制器。
// routes/note-rotes.js
const express = require('express')
const noteController = require('../controllers/noteControllers')
const router = express.Router()
router.post('/', noteController.createItem)
router.get('/', noteController.getNotes)
module.exports = router;
现在创建一个控制器文件夹,并在其中创建一个名为 noteControllers 的文件,并在其中创建两个名为 createItem 和 getNotes 的控制器
导入注释模式
const Note = require('../models/noteSchema')
创建笔记
createItem = (req, res) => {
const body = req.body
if (!body) {
return res.status(400).json({
success: false,
error: 'Please!! enter a item',
})
}
const note = new Note(body)
if (!note) {
return res.status(400).json({
success: false,
error: err
})
}
note.save().then(() => {
return res.status(200).json({
success: true,
id: note._id,
message: 'Cheers!! Note is Created',
})
})
.catch(error => {
return res.status(400).json({
error,
message: 'Error!! while creating note',
});
});
};
获取所有笔记
getNotes = async(req, res) => {
await Note.find({}, (err, notes) => {
if (err) {
return res.status(400).json({
success: false,
error: err
})
}
if (!notes.length) {
return res
.status(404)
.json({
success: false,
error: `Sorry, Item not found`
})
}
return res.status(200).json({
success: true,
data: notes
})
}).catch(err => console.log(err))
}
最后,你的控制器应该类似于以下内容
//controllers/noteControllers.js
const Note = require('../models/noteSchema')
createItem = (req, res) => {
const body = req.body
if (!body) {
return res.status(400).json({
success: false,
error: 'Please!! enter a item',
})
}
const note = new Note(body)
if (!note) {
return res.status(400).json({
success: false,
error: err
})
}
note.save().then(() => {
return res.status(200).json({
success: true,
id: note._id,
message: 'Cheers!! Note is Created',
})
})
.catch(error => {
return res.status(400).json({
error,
message: 'Error!! while creating note',
});
});
};
getNotes = async (req, res) => {
await Note.find({}, (err, notes) => {
if (err) {
return res.status(400).json({
success: false,
error: err
})
}
if (!notes.length) {
return res
.status(404)
.json({
success: false,
error: `Sorry, Item not found`
})
}
return res.status(200).json({
success: true,
data: notes
})
}).catch(err => console.log(err))
}
module.exports = {
createItem,
getNotes
}
重新启动服务器后,您应该会看到类似这样的内容:
使用 React 设置前端
让我们从前端开始,使用 React 来构建它。如果您的计算机上当前尚未安装 Node.js,则首先需要安装它。因此,请访问 Node.js 官方网站并获取最新版本。要使用 Node 包管理器(也称为 NPM),您需要 Node.js。现在打开您最喜欢的代码编辑器并导航到客户端文件夹。我将使用 Visual Studio Code。接下来,在集成终端中输入 npx create-react-app。此命令将在当前目录中构建一个名为 client 的客户端应用程序。
有一篇单独的文章,你可以从中了解有关 react.js 的所有知识
现在您已经安装并清理了 react-boilerplate,是时候在其中安装一些软件包了。因此,请将以下命令复制并粘贴到您的终端中。
npm i axios moment react-router-dom prop-types
安装所有这些软件包后,客户端的 packge.json 文件应如下所示:
在安装了项目的所有依赖项之后,让我们在 components 文件夹内构建四个单独的文件夹/组件,并将其命名为 Card , CardItemList , Navbar 和 SearchForm ,同时让我们创建一个名为 pages 的文件夹,并在其中创建两个旧文件夹,分别将其命名为 RepoSearch 和 TakeNote。
添加所有组件和页面后,您的文件和文件夹结构应该看起来像这样。
现在转到您的 app.js 文件并从 react-router-dom 和样式导入路由器,以及所有组件,并对代码进行必要的更改,如下所示。
// app.js
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import Navigation from "./components/Navbar";
import RepoSearch from "./pages/RepoSearch/RepoSearch";
import TakeNote from "./pages/TakeNote/TakeNote";
const App = () => {
return (
<BrowserRouter>
<Navigation />
<Route path="/" exact component={RepoSearch} />
<Route path="/note" exact component={TakeNote} />
</BrowserRouter>
);
};
export default App;
然后转到卡片组件并为每个获取的 GitHub 项目创建一个类似卡片的结构。
// components/card
import React from "react";
import "./Card.css";
import moment from "moment";
const Card = (props) => {
return (
<div className="container__cardbox">
<div className="card-body">
<div className="card-title">
<a href={props.link} target="_blank" rel="noreferrer">
{props.title}
</a>
</div>
<div className="card-description">{props.description} 📖</div>
<div className="card-description">Total Forks: {props.forks} 🍴</div>
<div className="card-description">Total Stars: {props.stars} ⭐</div>
<div className="card-description">
Last Updated: {moment(`${props.updatedAt}`).fromNow()} ⌛
</div>
{/* <div className="card-description">License Name: {(props.licenseName === "Other" & null ) ? "Other License" : props.licenseName} 📜</div> */}
<div className="card-description">
Total Watchers: {props.watchCount} 👀
</div>
<div className="card-description">
Open Issues: {props.openIssuesCount} 🏷️
</div>
<div className="card-description">
Repo Size: {props.repoSize} KB ⚖️
</div>
<img className="card-image" src={props.image} alt={props.title} />
</div>
</div>
);
};
export default Card;
并且不要忘记在其中创建一个 card.css 文件并向其中添加以下样式
.container__cardbox {
flex: 1;
/* flex-grow: 4; */
flex-basis: 15%;
margin: 15px;
/* border: solid 2px #383636; */
border-radius: 25px;
/* display: flex; */
flex-flow: row wrap;
}
.card-body {
padding: 10px;
border-radius: 20px;
background: white;
}
.card-title {
font-size: 25px;
text-align: left;
}
.card-description {
font-size: 12px;
margin: 4px;
text-align: center;
}
.card-image {
width: 20%;
margin-top: -130px;
}
然后,在 CardItemList 下,导入 Card 组件并为其提供所有适当的道具,以便它可以显示卡片组件本身内的所有元素。
//components/CardItemList
import React from "react";
import Card from "../Card";
const CardItemList = (props) => {
return (
<div className="container__carditemlist">
{props.items.map((item) => (
<Card
key={item.id}
link={item.html_url}
title={item.full_name}
description={item.description}
image={item.owner.avatar_url}
forks={item.forks_count}
stars={item.stargazers_count}
updatedAt={item.updated_at}
watchCount={item.watchers_count}
openIssuesCount={item.open_issues_count}
repoSize={item.size}
/>
))}
</div>
);
};
export default CardItemList;
再次不要忘记在其中创建一个 CardItemList.css 文件并向其中添加以下样式
.container__carditemlist {
display: flex;
flex-wrap: wrap;
}
因此,在开发搜索表单之前,我们先来处理导航栏部分。转到导航栏组件并将以下代码粘贴到其中。
// components/Navbar
import React from "react";
import { Link } from "react-router-dom";
import "./Navbar.css";
const Navbar = () => {
return (
<div className="container__navbar">
<div className="navbar-title">Github Search</div>
<ul className="navbar-menu">
<li>
<Link to="/">Search-Projects</Link>
</li>
<li>
<Link to="/note">Take-Note</Link>
</li>
</ul>
<div className="navbar-menu"></div>
</div>
);
};
export default Navbar;
记得在其中创建一个 Navbar.css 文件并对其应用以下样式。
@import url("https://fonts.googleapis.com/css?family=Raleway:400,400i,800");
.container__navbar {
display: flexbox;
align-items: center;
background: url("../../assets/gifs/navBack.gif") no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
/* background-color: transparent;
*/
padding: 25px;
width: 100%;
margin-bottom: 20px;
}
@media only screen and (max-width: 900px) {
.container__navbar {
display: flexbox;
align-items: center;
background: url("../../assets/gifs/navBack.gif") no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
/* background-color: transparent;
*/
padding: 25px;
width: 100rem;
margin-bottom: 20px;
}
}
.navbar-title {
color: transparent;
font-size: 28px;
margin-bottom: -50px;
text-align: right;
}
.navbar-menu {
border-radius: 25px;
height: -webkit-fit-content;
height: -moz-fit-content;
height: fit-content;
display: inline-flex;
background-color: rgba(0, 0, 0, 0.4);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
align-items: center;
padding: 0 20px;
margin: 50px 0 0 0;
}
.navbar-menu li {
list-style: none;
color: white;
font-family: sans-serif;
font-weight: bold;
padding: 12px 60px;
margin: 0 8px;
position: relative;
cursor: pointer;
white-space: nowrap;
}
.navbar-menu li::before {
content: " ";
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: -1;
transition: 0.6s;
border-radius: 25px;
}
.navbar-menu li:hover {
color: black;
}
.navbar-menu li:hover::before {
background: linear-gradient(to bottom, #e8edec, #d2d1d3);
box-shadow: 0px 3px 20px 0px black;
transform: scale(1.2);
}
@media only screen and (max-width: 1000px) {
.navbar-menu {
border-radius: 25px;
height: -webkit-fit-content;
height: -moz-fit-content;
height: fit-content;
display: inline-flex;
background-color: rgba(0, 0, 0, 0.4);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
align-items: center;
padding: 0 0px;
margin: 50px 0 0 0;
}
.navbar-menu li {
list-style: none;
color: white;
font-family: sans-serif;
font-weight: bold;
padding: 12px 10px;
margin: 0 1px;
position: relative;
cursor: pointer;
white-space: nowrap;
}
.navbar-menu li::before {
content: " ";
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: -1;
transition: 0.6s;
border-radius: 25px;
}
.navbar-menu li:hover {
color: black;
}
.navbar-menu li:hover::before {
background: linear-gradient(to bottom, #e8edec, #d2d1d3);
box-shadow: 0px 3px 20px 0px black;
transform: scale(1.2);
}
}
最后,让我们开始研究SearchForm
// components/SearchForm
import React, { useState } from "react";
import "./SearchForm.css";
const SearchForm = (props) => {
const [value, setValue] = useState("");
const submitSearchValue = () => {
setValue("");
props.onSubmit(value);
};
return (
<div>
<input
className="search-input"
type="text"
placeholder={props.placeholder}
value={value}
onChange={(event) => setValue(event.target.value)}
/>
<label htmlFor="name" className="search-label">
Search Project
</label>
<button
className="search-button"
type="submit"
onClick={() => submitSearchValue()}
>
{props.buttonText}
</button>
</div>
);
};
export default SearchForm;
记得在 SearchForm.css 文件中包含以下样式
@import url("https://fonts.googleapis.com/css2?family=Finger+Paint&display=swap");
.search-button {
background-image: linear-gradient(
to right,
#02aab0 0%,
#00cdac 51%,
#02aab0 100%
);
}
.search-button {
margin: 0 auto;
padding: 10px 100px;
margin-top: 0px;
text-align: center;
text-transform: uppercase;
transition: 0.5s;
background-size: 200% auto;
color: white;
box-shadow: 0 0 20px #eee;
border-radius: 10px;
display: block;
outline: none;
}
.search-button:hover {
background-position: right center; /* change the direction of the change here */
color: #fff;
text-decoration: none;
}
.search-label {
color: white;
font-family: "Finger Paint", cursive;
font-size: 1.2rem;
margin-left: 2rem;
margin-top: 0.2rem;
display: block;
transition: all 0.3s;
transform: translateY(0rem);
}
.search-input {
color: #333;
font-size: 1.2rem;
margin: 0 auto;
padding: 1rem 0.5rem;
border-radius: 0.6rem;
background-color: rgb(255, 255, 255);
border: none;
width: 50rem;
display: block;
border-bottom: 1rem solid transparent;
transition: all 0.3s;
outline:none;
}
@media only screen and (max-width: 900px) {
.search-input {
color: #333;
font-size: 1.2rem;
margin: 0 auto;
padding: 1rem 0.5rem;
border-radius: 0.6rem;
background-color: rgb(255, 255, 255);
border: none;
width: 100%;
display: block;
border-bottom: 1rem solid transparent;
transition: all 0.3s;
}
}
.search-input:placeholder-shown + .search-label {
opacity: 0;
color: white;
visibility: hidden;
-webkit-transform: translateY(-4rem);
transform: translateY(-4rem);
}
现在我们已经成功将组件集成到应用程序中,接下来该指定页面了。在 pages 目录中,创建一个 RepoSearch 文件夹,并创建两个文件:RepoSearch.js 和 RepoSearch.css。只需将 SearchForm 和 CardItemList 组件导入 RepoSearch 页面,然后在 RepoSearch 中构建一个名为 repos 的 useState 钩子,并以空数组作为初始值。
这个钩子使我们能够将状态集成到我们的函数式组件中。与类组件中的状态不同,useState() 不适用于对象值。如有必要,我们可以直接使用原语,并为多个变量创建多个 React 钩子。const [state, setState] = useState(initialState);
还要记住,React 中的 hooks 必须始终在函数顶部声明。这也有助于在组件的所有渲染之间保存状态。最后,让我们开发 searchRepository 函数,它使用免费的 GitHub API 获取所有项目信息,并直接返回 SearchForm 和 CardItemList 组件,并将 searchRepository 函数作为 prop 传递给 SearchForm 组件,并将 repos 传递给 CardItemLists 组件。
//pages/RepoSearch
import React, { useState } from "react";
import axios from "axios";
import SearchForm from "../../components/SearchForm";
import CardItemList from "../../components/CardItemList";
import "./RepoSearch.css";
const RepoSearch = () => {
const [repos, setRepos] = useState([]);
const searchRepository = (searchQuery) => {
setRepos([]);
axios
.get(
`https://api.github.com/search/repositories?q=${searchQuery}{&page,per_page,sort,order}`
)
.then((result) => setRepos(result.data.items));
};
return (
<div className="container__RepoSearch">
<SearchForm
placeholder="Search Projects."
buttonText="Search"
onSubmit={(value) => searchRepository(value)}
/>
<CardItemList items={repos} />
</div>
);
};
export default RepoSearch;
不要忘记也应用以下样式。
.container__RepoSearch {
display: flex;
flex-direction: column;
align-items: center;
}
最后,让我们在应用程序中构建一个生成笔记的功能。为此,需要构建两个状态:笔记和项目。
const [notes,setNotes] = useState([]);
const [items,setItems] = useState("");
然后返回代码并实现 useEffect 功能。通过使用此 Hook,你告诉 React 你的组件需要在渲染后执行某些操作。React 会记住你传递的函数(我们将其称为“效果”),并在执行 DOM 更新后调用它。为此,我们设置了文档标题,但我们也可以执行数据获取或调用其他一些命令式 API。将 useEffect() 放置在组件内部,我们可以直接从效果中访问 count 状态变量(或任何 props)。我们不需要特殊的 API 来读取它——它已经在函数作用域中了。Hook 拥抱 JavaScript 闭包,并避免在 JavaScript 已经提供解决方案的情况下引入特定于 React 的 API。useEffect() Hook 有点类似于我们所知道的类组件的生命周期方法。它在组件的每次渲染(包括初始渲染)后运行。因此,它可以被认为是 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。如果我们想要控制 effect 的运行时间(仅在初始渲染时,或仅在特定状态变量发生变化时),我们可以将依赖项传递给 effect 来实现。此钩子还提供了一个清理选项,允许在组件销毁前清理资源。effect 的基本语法:useEffect(didUpdate);
这里,didUpdate 是一个执行突变、订阅、计时器、日志记录等操作的函数。它将在组件渲染到屏幕后以及随后每次完成渲染时触发。
useEffect(() => {
axios.get("http://localhost:4000").then((response) => {
let data = [];
for (var i = 0; i < response.data.data.length; i++) {
data.push(response.data.data[i].note);
}
setNotes(data);
});
}, []);
并在实现 useEffect 功能后创建两个名为 clickHandler 和 changeHandler 的函数。
const changeHandler = (e) => {
setItems(e.target.value);
};
和
const clickHandler = async (e) => {
axios({
method: "post",
url: "http://localhost:4000",
data: {
note: items,
},
})
.then(() => {
setItems("");
})
.then(() => {
window.location.reload(false);
});
};
构建完所需的一切之后,只需返回以下语句。
return (
<div className="conatiner__back">
<input
className="todo-input"
placeholder="Organize and keep track of newly explored awesome projects."
type="text"
onChange={changeHandler}
/>
<button className="todo-button" type="submit" onClick={clickHandler}>
➕ Add Notes
</button>
<small style={{ color: "white", fontSize: "10px" }}>
* all notes will get stored inside MongoDB
</small>
<div className="notes__layout">
<ol className="gradient-list">
{notes.map((note) => (
<li key={note._id}>{note}</li>
))}
</ol>
</div>
</div>
);
您的最终代码应该是这样的。
// pages/TakeNote.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./TakeNote.css";
const TakeNote = () => {
const [notes, setNotes] = useState([]);
const [items, setItems] = useState("");
const changeHandler = (e) => {
setItems(e.target.value);
};
const clickHandler = async (e) => {
axios({
method: "post",
url: "http://localhost:4000",
data: {
note: items,
},
})
.then(() => {
setItems("");
})
.then(() => {
window.location.reload(false);
});
};
useEffect(() => {
axios.get("http://localhost:4000").then((response) => {
let data = [];
for (var i = 0; i < response.data.data.length; i++) {
data.push(response.data.data[i].note);
}
setNotes(data);
});
}, []);
return (
<div className="conatiner__back">
<input
className="todo-input"
placeholder="Organize and keep track of newly explored awesome projects."
type="text"
onChange={changeHandler}
/>
<button className="todo-button" type="submit" onClick={clickHandler}>
➕ Add Notes
</button>
<small style={{ color: "white", fontSize: "10px" }}>
* all notes will get stored inside MongoDB
</small>
<div className="notes__layout">
<ol className="gradient-list">
{notes.map((note) => (
<li key={note._id}>{note}</li>
))}
</ol>
</div>
</div>
);
};
export default TakeNote;
最后,在 TakeNote 内部创建一个 TakeNote.css 文件,并向其中添加下面列出的样式。
// TakeNote.css
@import url("https://fonts.googleapis.com/css2?family=Finger+Paint&display=swap");
.conatiner__back {
text-align: center;
background-color: transparent;
}
.todo-button {
background-image: linear-gradient(
to right,
#02aab0 0%,
#00cdac 51%,
#02aab0 100%
);
}
.todo-button {
margin: 0 auto;
padding: 10px 100px;
margin-top: 10px;
text-align: center;
text-transform: uppercase;
transition: 0.5s;
background-size: 200% auto;
color: white;
box-shadow: 0 0 20px #eee;
border-radius: 10px;
display: block;
outline: none;
}
.todo-button:hover {
background-position: right center;
color: #fff;
text-decoration: none;
}
.todo-input {
color: white;
font-size: 1.2rem;
font-family: "Finger Paint", cursive;
margin: 0 auto;
padding: 1rem 0.5rem;
border-radius: 0.6rem;
/* background-color: rgb(255, 255, 255); */
background: url("../../assets/gifs/inputBack.gif");
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
border: none;
width: 50rem;
display: block;
border-bottom: 1rem solid transparent;
transition: all 0.3s;
outline: none;
}
@media only screen and (max-width: 900px) {
.todo-input {
color: #333;
font-size: 1.2rem;
margin: 0 auto;
padding: 1rem 0.5rem;
border-radius: 0.6rem;
background-color: rgb(255, 255, 255);
border: none;
width: 100%;
display: block;
border-bottom: 1rem solid transparent;
transition: all 0.3s;
}
}
/* ------------------------------------------------- */
ol.gradient-list > li::before,
ol.gradient-list > li {
box-shadow: 0.25rem 0.25rem 0.6rem rgba(0, 0, 0, 0.05),
0 0.5rem 1.125rem rgba(75, 0, 0, 0.05);
}
/*** STYLE ***/
*,
*:before,
*:after {
box-sizing: border-box;
}
.notes__layout {
display: block;
margin: 0 auto;
max-width: 40rem;
padding: 1rem;
}
ol.gradient-list {
list-style: none;
margin: 1.75rem 0;
padding-left: 1rem;
}
ol.gradient-list > li {
background: white;
text-align: left;
font-family: "Finger Paint", cursive;
border-radius: 0 0.5rem 0.5rem 0.5rem;
counter-increment: gradient-counter;
margin-top: 2rem;
min-height: 3rem;
border-radius: 20px;
padding: 1rem 1rem 1rem 3rem;
position: relative;
}
ol.gradient-list > li::before,
ol.gradient-list > li::after {
background: linear-gradient(90deg, #83e4e2 0%, #a2ed56 100%);
border-radius: 5rem 5rem 0 5rem;
content: "🔖";
height: 2.5rem;
left: -1rem;
overflow: hidden;
position: absolute;
top: -2rem;
width: 3rem;
}
ol.gradient-list > li::before {
align-items: flex-end;
content: counter(gradient-counter);
color: #1d1f20;
display: flex;
font: 1000 1.5em/1 "Montserrat";
justify-content: center;
justify-content: flex-end;
padding: 0.125em 0.25em;
z-index: 1;
}
ol.gradient-list > li:nth-child(10n + 1):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.2) 0%,
rgba(253, 220, 50, 0.2) 100%
);
}
ol.gradient-list > li:nth-child(10n + 2):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.4) 0%,
rgba(253, 220, 50, 0.4) 100%
);
}
ol.gradient-list > li:nth-child(10n + 3):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.6) 0%,
rgba(253, 220, 50, 0.6) 100%
);
}
ol.gradient-list > li:nth-child(10n + 4):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.8) 0%,
rgba(253, 220, 50, 0.8) 100%
);
}
ol.gradient-list > li:nth-child(10n + 5):before {
background: linear-gradient(135deg, #a2ed56 0%, #fddc32 100%);
}
ol.gradient-list > li:nth-child(10n + 6):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.8) 0%,
rgba(253, 220, 50, 0.8) 100%
);
}
ol.gradient-list > li:nth-child(10n + 7):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.6) 0%,
rgba(253, 220, 50, 0.6) 100%
);
}
ol.gradient-list > li:nth-child(10n + 8):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.4) 0%,
rgba(253, 220, 50, 0.4) 100%
);
}
ol.gradient-list > li:nth-child(10n + 9):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0.2) 0%,
rgba(253, 220, 50, 0.2) 100%
);
}
ol.gradient-list > li:nth-child(10n + 10):before {
background: linear-gradient(
135deg,
rgba(162, 237, 86, 0) 0%,
rgba(253, 220, 50, 0) 100%
);
}
ol.gradient-list > li + li {
margin-top: 2rem;
}
该应用程序的完整源代码可在此处获取。
https://github.com/pramit-marattha/project-case-study-mern-app
主要文章可在此处查看 => https://aviyel.com/post/1419
编码愉快!!
如果您是项目维护者、贡献者或只是开源爱好者,请关注@aviyelHQ或在 Aviyel 上注册以获得早期访问权限。
加入 Aviyel 的 Discord => Aviyel 的世界
推特 =>[ https://twitter.com/AviyelHq ]
文章来源:https://dev.to/aviyel/building-a-project-case-study-app-using-the-absolute-power-of-mern-stack-15ak