案例
案例效果
基本结构
先搞 index 里边的东西
index.js
import { createRoot } from "react-dom/client";
// 引入组件
import App from "./App";
const root = createRoot(document.querySelector("#root"));
// 使用
root.render(<App />);
使用 scss
如果你要在 react 中使用 scss,需要下载对应的包:
npm i sass
不要下载错了,虽然我们用的是 .scss ,但是要下载的是 sass
App.scss 样式代码
App.scss
* {
margin: 0;
padding: 0;
list-style: none;
}
.App {
/* width: 1090px; */
width: 80%;
margin: 50px auto;
}
.comment-head {
margin: 0 0 20px;
font-size: 18px;
line-height: 24px;
color: #222;
}
.comment-send {
margin: 10px 0;
}
.user-face {
float: left;
margin: 7px 0 0 5px;
position: relative;
}
.user-head {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
}
.textarea-container {
position: relative;
margin-left: 85px;
margin-right: 80px;
}
.textarea-container:hover .ipt-txt {
background-color: #fff;
border-color: #00a1d6;
}
.ipt-txt {
font-size: 12px;
display: inline-block;
box-sizing: border-box;
background-color: #f4f5f7;
border: 1px solid #e5e9ef;
overflow: auto;
border-radius: 4px;
color: #555;
width: 100% !important;
height: 65px;
transition: 0s;
padding: 5px 10px;
line-height: normal;
resize: none;
outline: none;
}
.comment-submit {
width: 70px;
height: 64px;
position: absolute;
right: -80px;
top: 0;
padding: 4px 15px;
font-size: 14px;
color: #fff;
border-radius: 4px;
text-align: center;
min-width: 60px;
vertical-align: top;
cursor: pointer;
background-color: #00a1d6;
border: 1px solid #00a1d6;
transition: 0.1s;
user-select: none;
outline: none;
}
.comment-submit:hover {
background-color: #00b5e5;
border-color: #00b5e5;
}
.comment-emoji {
padding: 0;
width: 66px;
height: 24px;
color: #99a2aa;
border: 1px solid #e5e9ef;
border-radius: 4px;
position: relative;
font-size: 12px;
text-align: center;
line-height: 23px;
margin-left: 86px;
margin-top: 3px;
cursor: pointer;
display: inline-block;
}
.comment-emoji:hover {
color: #6d757a;
}
.face {
display: inline-block;
vertical-align: middle;
line-height: 1;
width: 16px;
height: 16px;
margin-right: 5px;
background: url("./img/2.jpg") no-repeat -408px -24px;
}
.comment-emoji:hover .face {
background-position: -472px -24px;
}
.comment-emoji .text {
display: inline-block;
vertical-align: middle;
line-height: 1;
font-size: 12px !important;
}
.tabs-order {
margin: 0 0 24px 0;
border-bottom: 1px solid #e5e9ef;
}
.sort-container {
display: flex;
}
.tabs-order li {
background-color: transparent;
border-radius: 0;
border: 0;
padding: 8px 0;
margin-right: 16px;
border-bottom: 1px solid transparent;
position: relative;
float: left;
cursor: pointer;
line-height: 20px;
height: 20px;
font-size: 14px;
font-weight: bold;
color: #222;
}
.tabs-order li:last-child {
margin: 0 16px;
}
.tabs-order li.on {
border-bottom: 1px solid #00a1d6;
color: #00a1d6;
}
.tabs-order li.on::after {
content: "";
width: 6px;
height: 3px;
background: transparent url("./img/2.jpg") -669px -31px no-repeat;
position: absolute;
bottom: 0;
left: 50%;
margin-left: -3px;
visibility: visible;
}
.list-item {
display: flex;
}
.list-item:first-child {
padding-top: 22px;
}
.comment {
flex: 1;
position: relative;
margin-left: 35px;
padding: 22px 0 14px 0;
border-top: 1px solid #e5e9ef;
}
.list-item:last-child .comment {
border-bottom: 1px solid #e5e9ef;
}
.comment .user {
color: #6d757a;
font-size: 12px;
font-weight: bold;
line-height: 18px;
padding-bottom: 4px;
display: block;
word-wrap: break-word;
position: relative;
}
.comment .text {
line-height: 20px;
padding: 2px 0;
font-size: 14px;
text-shadow: none;
overflow: hidden;
word-wrap: break-word;
word-break: break-word;
white-space: pre-wrap;
}
.info {
color: #99a2aa;
line-height: 26px;
font-size: 12px;
}
.icon {
cursor: pointer;
background: url("./img/2.jpg") no-repeat;
}
.time {
margin-right: 20px;
}
.like {
cursor: pointer;
margin-right: 20px;
}
.like > i {
display: inline-block;
width: 14px;
height: 14px;
vertical-align: text-top;
margin-right: 5px;
background-position: -153px -25px;
}
.like:hover > i {
background-position: -218px -25px;
}
.info .liked > i {
background-position: -154px -89px;
border: #00a1d6 2px solid;
}
.hate {
cursor: pointer;
margin-right: 15px;
}
.hate > i {
display: inline-block;
width: 14px;
height: 14px;
vertical-align: text-top;
margin-right: 5px;
background-position: -153px -153px;
}
.hate:hover > i {
background-position: -217px -153px;
}
.info .hated > i {
background-position: -154px -217px;
border: #00a1d6 2px solid;
}
.btn-hover {
padding: 0 5px;
border-radius: 4px;
margin-right: 15px;
cursor: pointer;
display: inline-block;
}
.btn-hover:hover {
color: #00a1d6;
background: #e5e9ef;
}
index.js 用到的数据
现在才开始写代码
index.js
你要用到的数据
// 数据
const [moke, setMoke] = useState({
tabs: [
{
id: 1,
name: "热度",
type: "hot",
},
{
id: 2,
name: "时间",
type: "time",
},
],
active: "hot",
list: [
{
id: 1,
author: "唐朝",
comment: "床前明月光,疑是地上霜",
time: new Date("2021-12-10 09:09:00"),
// 1: 点赞 0:无态度 -1:踩
attitude: 1,
avatar:
"https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp",
},
{
id: 2,
author: "宋词",
comment: "大江东去,浪淘尽,千古风流人物",
time: new Date("2021-12-11 09:09:00"),
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
avatar:
"https://p9-passport.byteacctimg.com/img/user-avatar/35e9831939c32731d1f9a2c2ff23a2ea~200x200.awebp?",
},
{
id: 3,
author: "元曲",
comment: "枯藤老树昏鸦,小桥流水人家",
time: new Date("2021-12-11 10:09:00"),
// 1: 点赞 0:无态度 -1:踩
attitude: -1,
avatar:
"https://p3-passport.byteimg.com/img/user-avatar/949162ec762d3993dfd413eaf124a19a~100x100.awebp",
},
],
});
moment 日期格式化插件
在 moke 数据中有用到日期,这个要单独处理,再此处使用的是 moment 插件
// 日期格式化
function formattingDate(time) {
return moment(time).format("YYYY-MM-DD HH:mm:ss");
}
moment() 创建一个 Moment 对象,表示当前的日期和时间,format('YYYY-MM-DD') 会将日期格式化为 “YYYY-MM-DD” 的形式(例如 “2023-07-25”)。
你还可以使用 Moment.js 的函数来执行各种日期和时间的操作。请参考 Moment.js 文档 获取更多详细信息和示例。
请注意,在你想要使用 Moment.js 的每个组件中都需要引入 Moment.js 库。
需要注意的是,由于 Moment.js 目前被认为是一个传统的项目,你可能考虑使用替代方案,例如 Day.js 或者现代 JavaScript 环境中内置的 Date 对象。
完整的index.js 代码
import { useState } from "react";
import "./App.scss";
// 格式化时间
import moment from "moment/moment";
const App = () => {
// 数据
const [moke, setMoke] = useState({
tabs: [
{
id: 1,
name: "热度",
type: "hot",
},
{
id: 2,
name: "时间",
type: "time",
},
],
active: "hot",
list: [
{
id: 1,
author: "唐朝",
comment: "床前明月光,疑是地上霜",
time: new Date("2021-12-10 09:09:00"),
// 1: 点赞 0:无态度 -1:踩
attitude: 1,
avatar:
"https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp",
},
{
id: 2,
author: "宋词",
comment: "大江东去,浪淘尽,千古风流人物",
time: new Date("2021-12-11 09:09:00"),
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
avatar:
"https://p9-passport.byteacctimg.com/img/user-avatar/35e9831939c32731d1f9a2c2ff23a2ea~200x200.awebp?",
},
{
id: 3,
author: "元曲",
comment: "枯藤老树昏鸦,小桥流水人家",
time: new Date("2021-12-11 10:09:00"),
// 1: 点赞 0:无态度 -1:踩
attitude: -1,
avatar:
"https://p3-passport.byteimg.com/img/user-avatar/949162ec762d3993dfd413eaf124a19a~100x100.awebp",
},
],
});
// 日期格式化
function formattingDate(time) {
return moment(time).format("YYYY-MM-DD HH:mm:ss");
}
return (
<div className="App">
<div className="comment-container">
{/* 评论数 */}
<div className="comment-head">
<span>{moke.list.length} 评论</span>
</div>
{/* 排序 */}
<div className="tabs-order">
<ul className="sort-container">
{moke.tabs.map((item) => (
<li
onClick={() => {
setMoke({ ...moke, active: item.type });
console.log("切换了排序", item.type, item.time);
}}
key={item.id}
className={moke.active === item.type ? "on" : ""}
>
按{item.name}排序
</li>
))}
{/* <li>按时间排序</li> */}
</ul>
</div>
{/* 添加评论 */}
<div className="comment-send">
<div className="user-face">
<img
className="user-head"
src="https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp"
alt=""
/>
</div>
<div className="textarea-container">
<textarea
cols="80"
rows="5"
placeholder="发条友善的评论"
className="ipt-txt"
/>
<button className="comment-submit">发表评论</button>
</div>
<div className="comment-emoji">
<i className="face"></i>
<span className="text">表情</span>
</div>
</div>
{/* 评论列表 */}
{moke.list.map((item, index) => {
return (
<div className="comment-list" key={item.id}>
<div className="list-item">
<div className="user-face">
<img className="user-head" src={item.avatar} alt="" />
</div>
<div className="comment">
<div className="user">{item.author}</div>
<p className="text">{item.comment}</p>
<div className="info">
<span className="time">{formattingDate(item.time)}</span>
<span
className={`like ${item.attitude === 1 ? "liked" : ""}`}
>
<i className="icon" />
</span>
<span
className={`hate ${item.attitude === -1 ? "hated" : ""}`}
>
<i className="icon" />
</span>
<span className="reply btn-hover">删除</span>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
);
};
export default App;
添加优化功能
增加了删除功能,添加评论功能,判断输入框是否为空自动获取焦点功能,拆分了部分组件
代码示例:
import { useRef, useState } from "react";
import "./App.scss";
// 导入 格式化时间
import formattingDate from "./components/module/formattingDate";
// 导入 数据
import mokeData from "./components/module/contentData";
const App = () => {
const [moke, setMoke] = useState(mokeData);
// 点击了发表评论
const textRef = useRef(null);
const [value, setValue] = useState("");
const publish = () => {
console.log("点击了发表评论", value);
// 当判断输入框为空时,获取焦点
// .trim() 用于去除字符串两端的空格
if (value.trim() === "") return textRef.current.focus();
// 组装评论数据
const newData = {
id: 4,
author: "大明",
time: Date.now(),
attitude: 0,
avatar:
"http://qpic.y.qq.com/music_cover/yHVEyKkoR4yXKPS2zkqODa4NPG4PZT8nT29ZUb8WXTThC1xgJmXgVg/300?n=1",
};
// 将数据添加到数组
const newList = [newData, ...moke.list];
// 更新状态
setMoke({ ...moke, list: newList });
};
// 删除
const delData = (id) => {
const delId = moke.list.filter((item) => item.id !== 2);
// console.log(id, moke.list.id, xid);
setMoke({ ...moke, list: delId });
};
return (
<div className="App">
<div className="comment-container">
{/* 评论数 */}
<div className="comment-head">
<span>{moke.list.length} 评论</span>
</div>
{/* 排序 */}
<div className="tabs-order">
<ul className="sort-container">
{moke.tabs.map((item) => (
<li
onClick={() => {
setMoke({ ...moke, active: item.type });
console.log("切换了排序", item.type, item.time);
}}
key={item.id}
className={moke.active === item.type ? "on" : ""}
>
按{item.name}排序
</li>
))}
{/* <li>按时间排序</li> */}
</ul>
</div>
{/* 添加评论 */}
<div className="comment-send">
<div className="user-face">
<img
className="user-head"
src="https://p3-passport.byteimg.com/img/user-avatar/732186508929940c0ea6978021bc9f76~100x100.awebp"
alt=""
/>
</div>
<div className="textarea-container">
<textarea
ref={textRef}
cols="80"
rows="5"
placeholder="发条友善的评论"
className="ipt-txt"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button className="comment-submit" onClick={publish}>
发表评论
</button>
</div>
<div className="comment-emoji">
<i className="face"></i>
<span className="text">表情</span>
</div>
</div>
{/* 评论列表 */}
{moke.list.map((item, index) => {
return (
<div className="comment-list" key={item.id}>
<div className="list-item">
<div className="user-face">
<img className="user-head" src={item.avatar} alt="" />
</div>
<div className="comment">
<div className="user">{item.author}</div>
<p className="text">{item.comment}</p>
<div className="info">
<span className="time">{formattingDate(item.time)}</span>
<span
className={`like ${item.attitude === 1 ? "liked" : ""}`}
>
<i className="icon" />
</span>
<span
className={`hate ${item.attitude === -1 ? "hated" : ""}`}
>
<i className="icon" />
</span>
<span
className={`reply btn-hover ${
item.id === 2 ? "" : "btn-hover-vanish"
}`}
onClick={() => delData(item.id)}
>
删除
</span>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
);
};
export default App;
1