首页 > 其他分享 >React 《入门案例》

React 《入门案例》

时间:2024-04-26 15:56:45浏览次数:13  
标签:入门 color height React 案例 reply position margin display

一、案例

image

二、创建项目

npm init vite@latest
# 选择react 
# 删除不必要的css,文件等
# 安装依赖classnames、sass、uuid、dayjs、lodash
npm i -S classnames # 处理className属性
npm i -S uuid   #生成uuid
npm i -S dayjs  # 日期处理
npm i -S lodash # 操作数组
npm i -D sass # sass支持

三、目录结构

image

主要文件

App.jsx

import './App.scss'
import avatar from './assets/bozai.png'
import { useRef, useState } from "react";
import _ from "lodash";
import classNames from "classnames";
import { v4 as uuidV4 } from "uuid";
import dayjs from "dayjs";
/**
 * 评论列表的渲染和操作
 *
 * 1. 根据状态渲染评论列表
 * 2. 删除评论
 */
// 评论列表数据
const defaultList = [
  {
    // 评论id
    rpid: 3,
    // 用户信息
    user: {
      uid: '13258165',
      avatar: 'https://paylove.online/images/sky/sky66134a508f8ac637b440b340.png',
      uname: '周杰伦',
    },
    // 评论内容
    content: '哎哟,不错哦',
    // 评论时间
    ctime: '10-18 08:15',
    like: 88,
  },
  {
    rpid: 2,
    user: {
      uid: '36080105',
      avatar: 'https://paylove.online/images/sky/sky66134b908f8ad1ad9020fc6e.png',
      uname: '许嵩',
    },
    content: '我寻你千百度 日出到迟暮',
    ctime: '11-13 11:29',
    like: 88,
  },
  {
    rpid: 1,
    user: {
      uid: '30009257',
      avatar,
      uname: '小三',
    },
    content: '小三报道',
    ctime: '10-19 09:00',
    like: 66,
  },
]
// 当前登录用户信息
const user = {
  // 用户id
  uid: '30009257',
  // 用户头像
  avatar,
  // 用户昵称
  uname: '小三',
}
/**
 * 导航 Tab 的渲染和操作
 *
 * 1. 渲染导航 Tab 和高亮
 * 2. 评论列表排序
 *  最热 => 喜欢数量降序
 *  最新 => 创建时间降序
 */

// 导航 Tab 数组
const tabs = [
  { type: 'hot', text: '最热' },
  { type: 'time', text: '最新' }
]

const App = () => {
  //评论列表
  const [commentList, setCommentList] = useState(_.orderBy(defaultList, 'ctime', 'desc'))
  const handleDel = (rpid) => {
    setCommentList(commentList.filter(item => item.rpid !== rpid))
  }
  //tab切换功能
  // const [tabList,setTabList] = useState(tabs)
  const [type, setType] = useState('hot')



  const changeTab = (type) => {
    setType(type)
    //基于列表的排序
    if (type === 'hot') {
      //lodash

      setCommentList(_.orderBy(commentList, 'like', 'desc'))
    } else {
      setCommentList(_.orderBy(commentList, 'ctime', 'desc'))
    }
  }
  const sort=(newList)=>{
    if (type === 'hot') {
      setCommentList(_.orderBy(newList, 'like', 'desc'))
    } else {
      setCommentList(_.orderBy(newList, 'ctime', 'desc'))
    }
  }

  // 发表评论
  const [comment, setComment] = useState('')
  const inputRef = useRef(null)
  const publishComment = () => {
    if (!comment) {
      return
    }

    const newComment = {
      rpid: uuidV4(),
      user: {
        uid: '30009257',
        avatar,
        uname: '小三',
      },
      content: comment,
      ctime: dayjs(new Date()).format('MM-DD hh:mm'),
      like: 999,
    }
    sort([
      ...commentList,
      newComment
    ])
    //clear input
    setComment('')
    ///
    inputRef.current.focus()



  }

  return (
    <div className="app">
      {/* 导航 Tab */}
      <div className="reply-navigation">
        <ul className="nav-bar">
          <li className="nav-title">
            <span className="nav-title-text">评论</span>
            {/* 评论数量 */}
            <span className="total-reply">{10}</span>
          </li>
          <li className="nav-sort">
            {/* 高亮类名: active */}
            {tabs.map(tab => (
              // <span className={`nav-item ${type === tab.type && 'active'}`} key={tab.type} onClick={()=>changeTab(tab.type)}>{tab.text}</span>
              <span className={classNames('nav-item', { active: type === tab.type })} key={tab.type} onClick={() => changeTab(tab.type)}>{tab.text}</span>
            ))}
            {/* <span className='nav-item'>最热</span> */}
          </li>
        </ul>
      </div>

      <div className="reply-wrap">
        {/* 发表评论 */}
        <div className="box-normal">
          {/* 当前用户头像 */}
          <div className="reply-box-avatar">
            <div className="bili-avatar">
              <img className="bili-avatar-img" src={avatar} alt="用户头像" />
            </div>
          </div>
          <div className="reply-box-wrap">
            {/* 评论框 */}
            <textarea
              className="reply-box-textarea"
              value={comment}
              onChange={(e) => setComment(e.target.value)}
              placeholder="发一条友善的评论"
              ref={inputRef}
            />
            {/* 发布按钮 */}
            <div className="reply-box-send" onClick={publishComment}>
              <div className="send-text">发布</div>
            </div>
          </div>
        </div>
        {/* 评论列表 */}
        <div className="reply-list">
          {/* 评论项 */}
          {commentList.map(item => (
            <div className="reply-item" key={item.rpid}>
              {/* 头像 */}
              <div className="root-reply-avatar">
                <div className="bili-avatar">
                  <img
                    className="bili-avatar-img"
                    alt=""
                    src={item.user.avatar}
                  />
                </div>
              </div>

              <div className="content-wrap">
                {/* 用户名 */}
                <div className="user-info">
                  <div className="user-name">{item.user.uname}</div>
                </div>
                {/* 评论内容 */}
                <div className="root-reply">
                  <span className="reply-content">{item.content}</span>
                  <div className="reply-info">
                    {/* 评论时间 */}
                    <span className="reply-time">{item.ctime}</span>
                    {/* 评论数量 */}
                    <span className="reply-time">点赞数:{item.like}</span>
                    {/* user.id == item.user.id */}
                    {item.user.uid === user.uid &&
                      <span className="delete-btn" onClick={() => handleDel(item.rpid)}>
                        删除
                      </span>}


                  </div>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}

export default App

App.scss

.app {
    width: 80%;
    margin: 50px auto;
  }
  .reply-navigation {
    margin-bottom: 22px;
    .nav-bar {
      display: flex;
      align-items: center;
      margin: 0;
      padding: 0;
      list-style: none;
      .nav-title {
        display: flex;
        align-items: center;
        width: 114px;
        font-size: 20px;
        .nav-title-text {
          color: #18191c;
          font-weight: 500;
        }
        .total-reply {
          margin: 0 36px 0 6px;
          color: #9499a0;
          font-weight: normal;
          font-size: 13px;
        }
      }
      .nav-sort {
        display: flex;
        align-items: center;
        color: #9499a0;
        font-size: 13px;
        .nav-item {
          cursor: pointer;
          &:hover {
            color: #00aeec;
          }
          &:last-child::after {
            display: none;
          }
          &::after {
            content: ' ';
            display: inline-block;
            height: 10px;
            width: 1px;
            margin: -1px 12px;
            background-color: #9499a0;
          }
        }
        .nav-item.active {
          color: #eb1c1c;
        }
      }
    }
  }
  
  .reply-wrap {
    position: relative;
  }
  .box-normal {
    display: flex;
    transition: 0.2s;
  
    .reply-box-avatar {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 80px;
      height: 50px;
    }
  
    .reply-box-wrap {
      display: flex;
      position: relative;
      flex: 1;
  
      .reply-box-textarea {
        width: 100%;
        height: 50px;
        padding: 5px 10px;
        box-sizing: border-box;
        color: #181931;
        font-family: inherit;
        line-height: 38px;
        background-color: #f1f2f3;
        border: 1px solid #f1f2f3;
        border-radius: 6px;
        outline: none;
        resize: none;
        transition: 0.2s;
  
        &::placeholder {
          color: #9499a0;
          font-size: 12px;
        }
        &:focus {
          height: 60px;
          background-color: #fff;
          border-color: #c9ccd0;
        }
      }
    }
  
    .reply-box-send {
      position: relative;
      display: flex;
      flex-basis: 86px;
      align-items: center;
      justify-content: center;
      margin-left: 10px;
      border-radius: 4px;
      cursor: pointer;
      transition: 0.2s;
  
      & .send-text {
        position: absolute;
        z-index: 1;
        color: #fff;
        font-size: 16px;
      }
      &::after {
        position: absolute;
        width: 100%;
        height: 100%;
        background-color: #00aeec;
        border-radius: 4px;
        opacity: 0.5;
        content: '';
      }
      &:hover::after {
        opacity: 1;
      }
    }
  }
  .bili-avatar {
    position: relative;
    display: block;
    width: 48px;
    height: 48px;
    margin: 0;
    padding: 0;
    border-radius: 50%;
  }
  .bili-avatar-img {
    position: absolute;
    top: 50%;
    left: 50%;
    display: block;
    width: 48px;
    height: 48px;
    object-fit: cover;
    border: none;
    border-radius: 50%;
    image-rendering: -webkit-optimize-contrast;
    transform: translate(-50%, -50%);
  }
  
  // 评论列表
  .reply-list {
    margin-top: 14px;
  }
  .reply-item {
    padding: 22px 0 0 80px;
    .root-reply-avatar {
      position: absolute;
      left: 0;
      display: flex;
      justify-content: center;
      width: 80px;
      cursor: pointer;
    }
  
    .content-wrap {
      position: relative;
      flex: 1;
  
      &::after {
        content: ' ';
        display: block;
        height: 1px;
        width: 100%;
        margin-top: 14px;
        background-color: #e3e5e7;
      }
  
      .user-info {
        display: flex;
        align-items: center;
        margin-bottom: 4px;
  
        .user-name {
          height: 30px;
          margin-right: 5px;
          color: #61666d;
          font-size: 13px;
          line-height: 30px;
          cursor: pointer;
        }
      }
  
      .root-reply {
        position: relative;
        padding: 2px 0;
        color: #181931;
        font-size: 15px;
        line-height: 24px;
        .reply-info {
          position: relative;
          display: flex;
          align-items: center;
          margin-top: 2px;
          color: #9499a0;
          font-size: 13px;
  
          .reply-time {
            width: 86px;
            margin-right: 20px;
          }
          .reply-like {
            display: flex;
            align-items: center;
            margin-right: 19px;
  
            .like-icon {
              width: 14px;
              height: 14px;
              margin-right: 5px;
              color: #9499a0;
              background-position: -153px -25px;
              &:hover {
                background-position: -218px -25px;
              }
            }
            .like-icon.liked {
              background-position: -154px -89px;
            }
          }
          .reply-dislike {
            display: flex;
            align-items: center;
            margin-right: 19px;
            .dislike-icon {
              width: 16px;
              height: 16px;
              background-position: -153px -153px;
              &:hover {
                background-position: -217px -153px;
              }
            }
            .dislike-icon.disliked {
              background-position: -154px -217px;
            }
          }
          .delete-btn {
            cursor: pointer;
            &:hover {
              color: #00aeec;
            }
          }
        }
      }
    }
  }
  
  .reply-none {
    height: 64px;
    margin-bottom: 80px;
    color: #99a2aa;
    font-size: 13px;
    line-height: 64px;
    text-align: center;
  }
  

运行

npm run dev

标签:入门,color,height,React,案例,reply,position,margin,display
From: https://www.cnblogs.com/paylove/p/18160235

相关文章

  • 金融案例:统一查询方案助力数据治理与分析应用更高效、更安全
    随着企业数据规模的增长和业务多元化发展,海量数据实时、多维地灵活查询变成业务常见诉求。同时多套数据库系统成为常态,这既带来了数据管理的复杂性,又加大了数据使用的难度,面对日益复杂的数据环境和严格的数据安全要求,需要解决多数据库系统并存、数据孤岛严重、权限管理混乱和数据......
  • Linux 根文件系统的移植(从入门到精通)
    一、简介提到操作系统的安装,还得从大学的时候说起,刚入学的时,朋友的系统本崩了,跑去电脑城换个系统花了40大洋,震惊了贫穷的我。好像发现了商机,果断开始了折腾自己的电脑,然后用朋友的电脑进行测试,由于对启动项不了解,有次蹦了过后,自己花钱去维修电脑哪里安装了一次,偷偷的学习。在这样......
  • 爬虫 1(入门基础)
    爬虫1(入门基础)一、什么是爬虫通过编写代码,模拟正常用户使用浏览器的过程,使其能够在互联网自动进行数据抓取二、HTTP协议三、URL是什么URL:资源定位符,是用于完整地描述Internet上网页和其他资源的地址的一种标识方法四、Header请求头五、请求头参数的含义六、req......
  • E. Chain Reaction
    https://codeforces.com/contest/1954/problem/E题意:n个数,可以对每个数释放闪电,闪电从释放的位置一直传到左右边界或者传到某个小于等于0的数终止,并且每个数都会减去闪电值k。问最少多少次释放闪电后可以让所有的数小于等于0。思路:从左往右考虑,假设第一个数的权值为1,如果当前数>......
  • MinIO 常用 API 快速入门
    快速入门minio中文网minio官网minio有开源版和收费版,使用开源版时,若修改了minio的源代码,需要将修改后的源代码完全公开。启动miniominio文档提供了多个运行环境的安装流程,此处以windows为例,其它运行环境文档上都有介绍。相关文档下载minio.exe:https://dl.minio......
  • 【vue3入门】-【13】class绑定
    class绑定数据绑定的一个常见需求场景是操纵元素的CSSclass列表,因为class是Attribute,我们可以和其他Attribute一样使用v-bind将它们动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,vue专门为class和v-bind用法提供了特殊的功能增强。除......
  • 【vue3入门】-【15】侦听器
    侦听器我们可以使用watch选项在每次响应式属性发生变化时触发一个函数<template><h3>侦听器</h3><!--不可以被监听,是固定的数据--><p>{{message}}</p><!--可以被监听,只能监听响应式数据(变化的数据)--><button@click="updateHandle">修改数据</button>&l......
  • 【vue3入门】-【14】style绑定
    style绑定数据绑定的一个常见需求场景式操作元素的cssstyle列表,因为style是一个Attribute,我们可以和其他Attribute一样使用v-bind,将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且容易出错的。因此,vue专门为style的v-bind用法提供了特殊的功能......
  • 【vue3入门】-【16】表单输入绑定
    表单输入绑定在前端处理表单时,我们常常需要将表单输入框的内容同步给JavaScript中相应的变量。手动连接值绑定和更改事件监听器可能会比较麻烦,v-model指令帮我们简化了这一步骤。<template><h3>表单输入绑定</h3><form><!--v-model:在页面中输入信息的同时,下......
  • 【vue3入门】-【18】组件组成
    组件组成组件最大的优势就是可复用性当使用构建步骤是,我们一般将vue组件定义在一个单独的.vue文件中,这杯叫做单文件组件(简称SFC)组件组成结构<!--承载所有的html标签,组件中必须要有的部分--><template> <div>承载标签</div></template><!--承载所有的业务逻辑,组件中可选......