程序员鸡皮

文章 分类 评论
61 3 9

站点介绍

一名PHP全栈程序员的日常......

React实战爱彼迎项目(二)

abzzp 2024-08-31 356 0条评论 前端 React

首页 / 正文
本站是作为记录一名北漂程序员编程学习以及日常的博客,欢迎添加微信BmzhbjzhB咨询交流......

发布于2024-07-04

头部布局

1.先将我们的header和footer从App.jsx中抽离到组件中去。

创建components/app-header文件夹和app-footer文件夹,内容如下

// components/app-header/index.jsx
import React,{memo} from "react";

const AppHeader = memo(()=>{
    return (
        <div>AppHeader</div>
    )
})

export default AppHeader
// components/app-footer/index.jsx
import React,{memo} from "react";

const AppFooter = memo(()=>{
    return (
        <div>AppFooter</div>
    )
})

export default AppFooter

然后我们修改App.jsx文件

import React,{memo} from "react";
import { useRoutes } from "react-router-dom";
import routes from "./router";
import AppHeader from "./components/app-header";
import AppFooter from "./components/app-footer";


const App = memo(()=>{
    return (
        <div>
            <AppHeader></AppHeader>
            <div>
                {useRoutes(routes)}
            </div>
            <AppFooter></AppFooter>
        </div>
    )
})

export default App

为了我们的样式,需要安装style-components组件。

npm install styled-components

这样我们就可以创建style.js来编写我们的样式了。

// components/app-header/style.js

import styled from "styled-components"

export const HeaderWraper = styled.div`
    display:flex;
    align-items:center;
    height:80px;
    border-bottom:1px solid #eee;
`

然后我们在app-header/index.jsx中使用

import React,{memo} from "react";
import { HeaderWraper } from "./style";

const AppHeader = memo(()=>{
    return (
        <HeaderWraper>AppHeader</HeaderWraper>
    )
})

export default AppHeader

接着,我们将app-header分成三块分别拆分到组件中,header-center,header-left,header-right。具体代码如下

// components/app-header/c-cpns/header-center/index.jsx
import React,{memo} from "react";
import { CenterWraper } from "./style";

const HeaderCenter = memo(()=>{
    return <CenterWraper>
        header-center
    </CenterWraper>
})

export default HeaderCenter
    
// components/app-header/c-cpns/header-center/style.js
import styled from "styled-components";

export const CenterWraper = styled.div`

` 

// components/app-header/c-cpns/header-left/index.jsx
import React,{memo} from "react";
import { LeftWraper } from "./style";

const HeaderLeft = memo(()=>{
    return <LeftWraper> header-left </LeftWraper>
})

export default HeaderLeft
    
// components/app-header/c-cpns/header-left/style.js
import styled from "styled-components";

export const LeftWraper = styled.div`
    flex:1;
`

// components/app-header/c-cpns/header-right/index.jsx
import React,{memo} from "react";
import { RightWraper } from "./style";

const HeaderRight = memo(()=>{
    return <RightWraper>header-right</RightWraper>
})

export default HeaderRight
    
// components/app-header/c-cpns/header-right/style.js
import styled from "styled-components";

export const RightWraper = styled.div`
    flex:1;
    display:flex;
    justify-content:flex-end;
`

然后我们再修改app-header/index.jsx文件,如下

// components/app-header/index.jsx
import React,{memo} from "react";
import { HeaderWraper } from "./style";
import HeaderLeft from "./c-cpns/header-left";
import HeaderCenter from "./c-cpns/header-center";
import HeaderRight from "./c-cpns/header-right";

const AppHeader = memo(()=>{
    return (
        <HeaderWraper>
            <HeaderLeft></HeaderLeft>
            <HeaderCenter></HeaderCenter>
            <HeaderRight></HeaderRight>
        </HeaderWraper>
    )
})

export default AppHeader

这样我们的布局就搭建好了,接下来就是我们的样式以及特效了。

使用svg格式Logo

首先我们在assets/svg目录创建icon_logo.jsx文件,目的是为了将我们的svg像组件一样导出导入,代码如下

import React, { memo } from 'react'
import styleStrToObject from './utils'

const IconLogo = memo(() => {
  return (
    <svg width="102" height="32" style={styleStrToObject("display:block")}><path d="M29.3864 22.7101C29.2429 22.3073 29.0752 21.9176 28.9157 21.5565C28.6701 21.0011 28.4129 20.4446 28.1641 19.9067L28.1444 19.864C25.9255 15.0589 23.5439 10.1881 21.0659 5.38701L20.9607 5.18316C20.7079 4.69289 20.4466 4.18596 20.1784 3.68786C19.8604 3.0575 19.4745 2.4636 19.0276 1.91668C18.5245 1.31651 17.8956 0.833822 17.1853 0.502654C16.475 0.171486 15.7005 -9.83959e-05 14.9165 4.23317e-08C14.1325 9.84805e-05 13.3581 0.171877 12.6478 0.503224C11.9376 0.834571 11.3088 1.31742 10.8059 1.91771C10.3595 2.46476 9.97383 3.05853 9.65572 3.68858C9.38521 4.19115 9.12145 4.70278 8.8664 5.19757L8.76872 5.38696C6.29061 10.1884 3.90903 15.0592 1.69015 19.8639L1.65782 19.9338C1.41334 20.463 1.16057 21.0102 0.919073 21.5563C0.75949 21.9171 0.592009 22.3065 0.448355 22.7103C0.0369063 23.8104 -0.094204 24.9953 0.0668098 26.1585C0.237562 27.334 0.713008 28.4447 1.44606 29.3804C2.17911 30.3161 3.14434 31.0444 4.24614 31.4932C5.07835 31.8299 5.96818 32.002 6.86616 32C7.14824 31.9999 7.43008 31.9835 7.71027 31.9509C8.846 31.8062 9.94136 31.4366 10.9321 30.8639C12.2317 30.1338 13.5152 29.0638 14.9173 27.5348C16.3194 29.0638 17.6029 30.1338 18.9025 30.8639C19.8932 31.4367 20.9886 31.8062 22.1243 31.9509C22.4045 31.9835 22.6864 31.9999 22.9685 32C23.8664 32.002 24.7561 31.8299 25.5883 31.4932C26.6901 31.0444 27.6554 30.3161 28.3885 29.3804C29.1216 28.4447 29.5971 27.3341 29.7679 26.1585C29.9287 24.9952 29.7976 23.8103 29.3864 22.7101ZM14.9173 24.377C13.1816 22.1769 12.0678 20.1338 11.677 18.421C11.5169 17.7792 11.4791 17.1131 11.5656 16.4573C11.6339 15.9766 11.8105 15.5176 12.0821 15.1148C12.4163 14.6814 12.8458 14.3304 13.3374 14.0889C13.829 13.8475 14.3696 13.7219 14.9175 13.7219C15.4655 13.722 16.006 13.8476 16.4976 14.0892C16.9892 14.3307 17.4186 14.6817 17.7528 15.1151C18.0244 15.5181 18.201 15.9771 18.2693 16.4579C18.3556 17.114 18.3177 17.7803 18.1573 18.4223C17.7661 20.1349 16.6526 22.1774 14.9173 24.377ZM27.7406 25.8689C27.6212 26.6908 27.2887 27.4674 26.7762 28.1216C26.2636 28.7759 25.5887 29.2852 24.8183 29.599C24.0393 29.9111 23.1939 30.0217 22.3607 29.9205C21.4946 29.8089 20.6599 29.5239 19.9069 29.0824C18.7501 28.4325 17.5791 27.4348 16.2614 25.9712C18.3591 23.3846 19.669 21.0005 20.154 18.877C20.3723 17.984 20.4196 17.0579 20.2935 16.1475C20.1791 15.3632 19.8879 14.615 19.4419 13.9593C18.9194 13.2519 18.2378 12.6768 17.452 12.2805C16.6661 11.8842 15.798 11.6777 14.9175 11.6777C14.0371 11.6777 13.1689 11.8841 12.383 12.2803C11.5971 12.6765 10.9155 13.2515 10.393 13.9589C9.94707 14.6144 9.65591 15.3624 9.5414 16.1465C9.41524 17.0566 9.4623 17.9822 9.68011 18.8749C10.1648 20.9993 11.4748 23.384 13.5732 25.9714C12.2555 27.4348 11.0845 28.4325 9.92769 29.0825C9.17468 29.5239 8.34007 29.809 7.47395 29.9205C6.64065 30.0217 5.79525 29.9111 5.0162 29.599C4.24581 29.2852 3.57092 28.7759 3.05838 28.1217C2.54585 27.4674 2.21345 26.6908 2.09411 25.8689C1.97932 25.0334 2.07701 24.1825 2.37818 23.3946C2.49266 23.0728 2.62663 22.757 2.7926 22.3818C3.0274 21.851 3.27657 21.3115 3.51759 20.7898L3.54996 20.7197C5.75643 15.9419 8.12481 11.0982 10.5894 6.32294L10.6875 6.13283C10.9384 5.64601 11.1979 5.14267 11.4597 4.6563C11.7101 4.15501 12.0132 3.68171 12.3639 3.2444C12.6746 2.86903 13.0646 2.56681 13.5059 2.35934C13.9473 2.15186 14.4291 2.04426 14.9169 2.04422C15.4047 2.04418 15.8866 2.15171 16.3279 2.35911C16.7693 2.56651 17.1593 2.86867 17.4701 3.24399C17.821 3.68097 18.1242 4.15411 18.3744 4.65538C18.6338 5.13742 18.891 5.63623 19.1398 6.11858L19.2452 6.32315C21.7097 11.0979 24.078 15.9415 26.2847 20.7201L26.3046 20.7631C26.5498 21.2936 26.8033 21.8419 27.042 22.382C27.2082 22.7577 27.3424 23.0738 27.4566 23.3944C27.7576 24.1824 27.8553 25.0333 27.7406 25.8689Z" fill="currentcolor"></path><path d="M41.6847 24.1196C40.8856 24.1196 40.1505 23.9594 39.4792 23.6391C38.808 23.3188 38.2327 22.8703 37.7212 22.2937C37.2098 21.7172 36.8263 21.0445 36.5386 20.3078C36.2509 19.539 36.123 18.7062 36.123 17.8093C36.123 16.9124 36.2829 16.0475 36.5705 15.2787C36.8582 14.51 37.2737 13.8373 37.7852 13.2287C38.2966 12.6521 38.9039 12.1716 39.6071 11.8513C40.3103 11.531 41.0455 11.3708 41.8765 11.3708C42.6756 11.3708 43.3788 11.531 44.0181 11.8833C44.6574 12.2037 45.1688 12.6841 45.5843 13.2927L45.6802 11.7232H48.6209V23.7992H45.6802L45.5843 22.0375C45.1688 22.6781 44.6254 23.1906 43.9222 23.575C43.2829 23.9274 42.5158 24.1196 41.6847 24.1196ZM42.4519 21.2367C43.0272 21.2367 43.5386 21.0765 44.0181 20.7882C44.4656 20.4679 44.8172 20.0515 45.1049 19.539C45.3606 19.0265 45.4884 18.4179 45.4884 17.7452C45.4884 17.0725 45.3606 16.4639 45.1049 15.9514C44.8492 15.4389 44.4656 15.0225 44.0181 14.7022C43.5706 14.3818 43.0272 14.2537 42.4519 14.2537C41.8765 14.2537 41.3651 14.4139 40.8856 14.7022C40.4382 15.0225 40.0866 15.4389 39.7989 15.9514C39.5432 16.4639 39.4153 17.0725 39.4153 17.7452C39.4153 18.4179 39.5432 19.0265 39.7989 19.539C40.0546 20.0515 40.4382 20.4679 40.8856 20.7882C41.3651 21.0765 41.8765 21.2367 42.4519 21.2367ZM53.6392 8.4559C53.6392 8.80825 53.5753 9.12858 53.4154 9.38483C53.2556 9.64109 53.0319 9.86531 52.7442 10.0255C52.4565 10.1856 52.1369 10.2497 51.8173 10.2497C51.4976 10.2497 51.178 10.1856 50.8903 10.0255C50.6026 9.86531 50.3789 9.64109 50.2191 9.38483C50.0592 9.09654 49.9953 8.80825 49.9953 8.4559C49.9953 8.10355 50.0592 7.78323 50.2191 7.52697C50.3789 7.23868 50.6026 7.04649 50.8903 6.88633C51.178 6.72617 51.4976 6.66211 51.8173 6.66211C52.1369 6.66211 52.4565 6.72617 52.7442 6.88633C53.0319 7.04649 53.2556 7.27072 53.4154 7.52697C53.5433 7.78323 53.6392 8.07152 53.6392 8.4559ZM50.2191 23.7672V11.6911H53.4154V23.7672H50.2191V23.7672ZM61.9498 14.8623V14.8943C61.79 14.8303 61.5982 14.7982 61.4383 14.7662C61.2466 14.7342 61.0867 14.7342 60.895 14.7342C60 14.7342 59.3287 14.9904 58.8812 15.535C58.4018 16.0795 58.178 16.8483 58.178 17.8413V23.7672H54.9817V11.6911H57.9223L58.0182 13.517C58.3379 12.8763 58.7214 12.3958 59.2648 12.0435C59.7762 11.6911 60.3835 11.531 61.0867 11.531C61.3105 11.531 61.5342 11.563 61.726 11.595C61.8219 11.6271 61.8858 11.6271 61.9498 11.6591V14.8623ZM63.2283 23.7672V6.72617H66.4247V13.2287C66.8722 12.6521 67.3836 12.2036 68.0229 11.8513C68.6622 11.531 69.3654 11.3388 70.1645 11.3388C70.9635 11.3388 71.6987 11.4989 72.3699 11.8193C73.0412 12.1396 73.6165 12.588 74.128 13.1646C74.6394 13.7412 75.0229 14.4139 75.3106 15.1506C75.5983 15.9194 75.7261 16.7522 75.7261 17.6491C75.7261 18.546 75.5663 19.4109 75.2787 20.1796C74.991 20.9484 74.5755 21.6211 74.064 22.2297C73.5526 22.8063 72.9453 23.2867 72.2421 23.6071C71.5389 23.9274 70.8037 24.0875 69.9727 24.0875C69.1736 24.0875 68.4704 23.9274 67.8311 23.575C67.1918 23.2547 66.6804 22.7742 66.2649 22.1656L66.169 23.7352L63.2283 23.7672ZM69.3973 21.2367C69.9727 21.2367 70.4841 21.0765 70.9635 20.7882C71.411 20.4679 71.7626 20.0515 72.0503 19.539C72.306 19.0265 72.4339 18.4179 72.4339 17.7452C72.4339 17.0725 72.306 16.4639 72.0503 15.9514C71.7626 15.4389 71.411 15.0225 70.9635 14.7022C70.5161 14.3818 69.9727 14.2537 69.3973 14.2537C68.822 14.2537 68.3106 14.4139 67.8311 14.7022C67.3836 15.0225 67.032 15.4389 66.7443 15.9514C66.4886 16.4639 66.3608 17.0725 66.3608 17.7452C66.3608 18.4179 66.4886 19.0265 66.7443 19.539C67 20.0515 67.3836 20.4679 67.8311 20.7882C68.3106 21.0765 68.822 21.2367 69.3973 21.2367ZM76.9408 23.7672V11.6911H79.8814L79.9773 13.2607C80.3289 12.6841 80.8084 12.2357 81.4157 11.8833C82.023 11.531 82.7262 11.3708 83.5253 11.3708C84.4203 11.3708 85.1874 11.595 85.8267 12.0115C86.4979 12.4279 87.0094 13.0365 87.361 13.8053C87.7126 14.574 87.9043 15.5029 87.9043 16.56V23.7992H84.708V16.9764C84.708 16.1436 84.5162 15.4709 84.1326 14.9904C83.7491 14.51 83.2376 14.2537 82.5664 14.2537C82.0869 14.2537 81.6714 14.3498 81.2878 14.574C80.9362 14.7982 80.6486 15.0865 80.4248 15.503C80.2011 15.8873 80.1052 16.3678 80.1052 16.8483V23.7672H76.9408V23.7672ZM89.5025 23.7672V6.72617H92.6989V13.2287C93.1464 12.6521 93.6578 12.2036 94.2971 11.8513C94.9364 11.531 95.6396 11.3388 96.4387 11.3388C97.2378 11.3388 97.9729 11.4989 98.6442 11.8193C99.3154 12.1396 99.8907 12.588 100.402 13.1646C100.914 13.7412 101.297 14.4139 101.585 15.1506C101.873 15.9194 102 16.7522 102 17.6491C102 18.546 101.841 19.4109 101.553 20.1796C101.265 20.9484 100.85 21.6211 100.338 22.2297C99.8268 22.8063 99.2195 23.2867 98.5163 23.6071C97.8131 23.9274 97.0779 24.0875 96.2469 24.0875C95.4478 24.0875 94.7446 23.9274 94.1053 23.575C93.466 23.2547 92.9546 22.7742 92.5391 22.1656L92.4432 23.7352L89.5025 23.7672ZM95.7035 21.2367C96.2788 21.2367 96.7903 21.0765 97.2697 20.7882C97.7172 20.4679 98.0688 20.0515 98.3565 19.539C98.6122 19.0265 98.7401 18.4179 98.7401 17.7452C98.7401 17.0725 98.6122 16.4639 98.3565 15.9514C98.1008 15.4389 97.7172 15.0225 97.2697 14.7022C96.8222 14.3818 96.2788 14.2537 95.7035 14.2537C95.1281 14.2537 94.6167 14.4139 94.1373 14.7022C93.6898 15.0225 93.3382 15.4389 93.0505 15.9514C92.7628 16.4639 92.6669 17.0725 92.6669 17.7452C92.6669 18.4179 92.7948 19.0265 93.0505 19.539C93.3062 20.0515 93.6898 20.4679 94.1373 20.7882C94.6167 21.0765 95.0962 21.2367 95.7035 21.2367Z" fill="currentcolor"></path></svg>
  )
})

export default IconLogo

其中引入了util中一个类,获取相关代码请查看,
JavaScript中将style的String类型转换成Object类型

然后我们修改components中的app-header文件夹下的c-cpns/header-left/index.jsx

// components/app-header/c-cpns/header-left/index.jsx
import React,{memo} from "react";
import { LeftWraper } from "./style";
import IconLogo from "@/assets/svg/icon_logo";

const HeaderLeft = memo(()=>{
    return <LeftWraper><IconLogo></IconLogo> </LeftWraper>
})

export default HeaderLeft

顺便修改该文件夹下的style.js

// components/app-header/c-cpns/header-left/style.js
import styled from "styled-components";

export const LeftWraper = styled.div`
    flex:1;
    display:flex;
    align-items:center;
    color:red;
    .logo{
        margin-left:24px;
        cursor:pointer;
    }
`

这样我们的svg图片就调用好了。

配置主题色

方式一:css变量

我们可以用css变量的形式配置主题色

// 定义
:root{
    --primary-color:#ff3854c;
}

// 使用

color:var(--primary-color);

方式二:css in js

我们需要在assets中创建theme文件夹,其中的index.js如下所示。

const theme = {
    color:{
        primaryColor:"#ff385c",
        secondaryColor:"#00848A" 
    }
}

export default theme

然后我们修改index.js

import React,{Suspense} from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter } from "react-router-dom";
import { Provider } from 'react-redux';
import { ThemeProvider } from "styled-components"; // 这里使用ThemeProvider注入我们的theme


import "normalize.css";
import "./assets/css/index.less";
import App from '@/App';
import store from './store';
import theme from './assets/theme';  // 刚刚创建的theme/index.js

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // <React.StrictMode>
    <Suspense fallback="loading">
      <Provider store={store}>
       <ThemeProvider theme={theme}>  // 注入代码
          <HashRouter>
              <App />
          </HashRouter>
       </ThemeProvider>
      </Provider>  
    </Suspense>
  // </React.StrictMode>
);

接着我们就可以在components/app-header/header-left/style.js中使用了

// components/app-header/c-cpns/header-left/style.js
import styled from "styled-components";

export const LeftWraper = styled.div`
    flex:1;
    display:flex;
    align-items:center;
    color:${props => props.theme.color.primaryColor};  // 这里就是使用的注入的theme
    .logo{
        margin-left:24px;
        cursor:pointer;
    }
`

注册登录样式

修改我们components/app-header/c-cpns/header-right文件下的index.js

// components/app-header/c-cpns/header-right/index.jsx
import React,{memo} from "react";
import IconAvatar from "@/assets/svg/icon-avatar";
import IconGlobal from "@/assets/svg/icon_global";
import IconMenu from "@/assets/svg/icon_menu";

import { RightWrapper } from "./style";

const HeaderRight = memo(()=>{
    // 事件处理函数
    function profileClickHandle(){

    } 
    return (
        <RightWrapper>
        <div className="btns">
            <span className="btn">登录</span>
            <span className="btn">注册</span>
            <span className="btn">
                <IconGlobal></IconGlobal>
            </span>
        </div>

        <div className="profile" >
            <IconMenu></IconMenu>
            <IconAvatar></IconAvatar>
        </div>
    </RightWrapper>
    )
})

export default HeaderRight

然后修改我们当前文件夹下的style.js文件

import styled from "styled-components";


export const RightWrapper = styled.div`
  flex: 1;
  display: flex;
  justify-content: flex-end;
  align-items: center;

  color: ${props => props.theme.text.primaryColor};
  font-weight: 600;

  .btns {
    display: flex;

    .btn {
      height: 18px;
      line-height: 18px;
      padding: 12px 15px;
      border-radius: 22px;
      cursor: pointer;
      box-sizing: content-box;

      &:hover {
        background-color: #f5f5f5;
      }
    }
  }

  .profile {
    position: relative;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    width: 77px;
    height: 42px;
    margin-right: 24px;
    box-sizing: border-box;
    border: 1px solid #ccc;
    border-radius: 25px;
    background-color: #fff;
    color: ${props => props.theme.text.primaryColor};
    cursor: pointer;

    ${props => props.theme.mixin.boxShadow};

    .panel {
      position: absolute;
      top: 54px;
      right: 0;
      width: 240px;
      background-color: #fff;
      border-radius: 10px;
      box-shadow: 0 0 6px rgba(0,0,0,.2);
      color: #666;

      .top, .bottom {
        padding: 10px 0;

        .item {
          height: 40px;
          line-height: 40px;
          padding: 0 16px;

          &:hover {
            background-color: #f5f5f5;
          }
        }
      }

      .top {
        border-bottom: 1px solid #ddd;
      }
    }
  }
`

发现其中有几个全局主题变量我们没有定义,然后我们去添加我们的theme主题变量

// assets/theme/index.js
const theme = {
    color:{
        primaryColor:"#ff385c",
        secondaryColor:"#00848A" 
    },
    text:{
        primaryColor:"#484848",
        secondaryColor:"#222"
    },
    mixin:{
        boxShadow:`
             transition: box-shadow 200ms ease;
      &:hover {
        box-shadow: 0 2px 4px rgba(0,0,0,.18);
      }
        `
    }
}
 
export default theme

布局头部搜索栏

首先,我们需要先修改components/app-header下的内容,代码如下。

// components/app-header/c-cpns/header-center/index.jsx
import IconSearchBar from '@/assets/svg/icon-search-bar';
import React,{memo} from "react";
import { CenterWraper } from "./style";

const HeaderCenter = memo(()=>{
    return (
        <CenterWraper>
        <div className='search-bar'>
            <div className='text'>
                搜索房源和体验
            </div>
            <div className='icon'>
                <IconSearchBar></IconSearchBar>
            </div>
        </div>
    </CenterWraper>
    )
})

export default HeaderCenter
    

// components/app-header/c-cpns/header-center/style.js
import styled from "styled-components";

export const CenterWraper = styled.div`
    .search-bar{
        display:flex;
        justify-content:space-between;
        align-items:center;
        width:300px;
        height:48px;
        box-sizing:border-box;
        padding:0 8px;
        border:1px solid #ddd;
        border-radius:24px;
        cursor:pointer;
        ${props => props.theme.mixin.boxShadow}

        .text{
            padding:0 16px;
            color:#222;
            font-weight:600;
        }

        .icon{
            display:flex;
            align-items:center;
            justify-content:center;
            width:32px;
            height:32px;
            border-radius:50%;
            color:#fff;
            background-color:${props => props.theme.color.primaryColor};
        }
    }
` 

然后,我们添加全局默认字体大小,assets/css中创建common.less,内容如下:

body {
    font-size: 14px;
    font-family: "Circular", "PingFang-SC", "Hiragino Sans GB", "微软雅黑", "Microsoft YaHei", "Heiti SC";
    color: #484848;
 }

最后别忘了在assets/index.less中引入文件奥。这里不做过多赘述。

做登录注册个人中心下拉框

首先,修改我们的header-right组件内容,代码如下所示

// components/app-header/c-cpns/header-right/index.jsx
import React,{memo,useEffect,useState } from "react";
import IconAvatar from "@/assets/svg/icon-avatar";
import IconGlobal from "@/assets/svg/icon_global";
import IconMenu from "@/assets/svg/icon_menu";

import { RightWrapper } from "./style";

const HeaderRight = memo(()=>{
    // 定义组件内部状态
    const [ showPanel,setShowPanel ] = useState(false)
    // 副作用代码
    useEffect(()=>{
        function windowHandleClick(){
            setShowPanel(false)
        }
        window.addEventListener("click",windowHandleClick,true)
        return () => {
            window.removeEventListener("click",windowHandleClick,true)
        }
    },[]);

    // 事件处理函数
    function profileClickHandle(){
        setShowPanel(true)
    } 
    return (
        <RightWrapper>
        <div className="btns">
            <span className="btn">登录</span>
            <span className="btn">注册</span>
            <span className="btn">
                <IconGlobal></IconGlobal>
            </span>
        </div>

        <div className="profile" onClick={profileClickHandle}>
            <IconMenu></IconMenu>
            <IconAvatar></IconAvatar>

            { showPanel && (
                <div className="panel">
                    <div className="top">
                        <div className="item register">注册</div>
                        <div className="item login">登录</div>
                    </div>
                    <div className="bottom">
                        <div className="item">出租房源</div>
                        <div className="item">开展体验</div>
                        <div className="item">帮助</div>
                    </div>
                </div>
            )}
        </div>
    </RightWrapper>
    )
})

export default HeaderRight

接着修改样式文件style.js.

import styled from "styled-components";


export const RightWrapper = styled.div`
  flex: 1;
  display: flex;
  justify-content: flex-end;
  align-items: center;

  color: ${props => props.theme.text.primaryColor};
  font-weight: 600;

  .btns {
    display: flex;

    .btn {
      height: 18px;
      line-height: 18px;
      padding: 12px 15px;
      border-radius: 22px;
      cursor: pointer;
      box-sizing: content-box;

      &:hover {
        background-color: #f5f5f5;
      }
    }
  }

  .profile {
    position: relative;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    width: 77px;
    height: 42px;
    margin-right: 24px;
    box-sizing: border-box;
    border: 1px solid #ccc;
    border-radius: 25px;
    background-color: #fff;
    color: ${props => props.theme.text.primaryColor};
    cursor: pointer;

    ${props => props.theme.mixin.boxShadow};

    .panel {
      position: absolute;
      top: 54px;
      right: 0;
      width: 240px;
      background-color: #fff;
      border-radius: 10px;
      box-shadow: 0 0 6px rgba(0,0,0,.2);
      color: #666;

      .top, .bottom {
        padding: 10px 0;

        .item {
          height: 40px;
          line-height: 40px;
          padding: 0 16px;

          &:hover {
            background-color: #f5f5f5;
          }
        }
      }

      .top {
        border-bottom: 1px solid #ddd;
      }
    }
  }
`

首页-内容部分

我们以及完场了首页头部公共页面组件的搭建,接下来我们就开始写我们的首页文件了。

我们创建banner组件,代码如下

//  views/home/c-cpns/home-banner/index.jsx
import React,{memo} from "react";
import { BannerWrapper } from  './style';

const HomeBanner = memo(()=>{
    return (
        <BannerWrapper>

        </BannerWrapper>  
    )
})

export default HomeBanner

    
//  views/home/c-cpns/home-banner/style.js
import styled from 'styled-components'

export const BannerWrapper = styled.div`
    height:529px;
    background:url(${require("@/assets/img/cover_01.jpeg")}) center/cover;
`

然后,我们在home/index.jsx中引入他就好了。

高性价比房源

首先需要创建我们的sotre模块下的home.js代码如下。

//  store/modules/home.js
import { createSlice,createAsyncThunk } from "@reduxjs/toolkit"
import { getHomeGoodPriceData } from "@/services"

export const fetchHomeDataAction = createAsyncThunk("fetchdata",async ()=>{
    const res = await getHomeGoodPriceData();
    return res;
})

const homeSlice = createSlice({
    name:"home",
    initialState:{
        goodPriceInfo:{}
    },
    reducers:{
        changeGoodPriceInfoAction(state,{payload}){
            state.goodPriceInfo = payload
        }
    },
    // extraReducers:{
    //     [fetchHomeDataAction.fulfilled](state,{payload}){
    //         state.goodPriceInfo = payload
    //     }
    // }
    extraReducers: (builder) => { 
        builder
          .addCase(fetchHomeDataAction.pending, (state, action) => {
            console.log("fetchHomeDataAction pending");
          })
          .addCase(fetchHomeDataAction.fulfilled, (state, { payload }) => {
            console.log(payload);
            state.goodPriceInfo = payload
          })
          .addCase(fetchHomeDataAction.rejected, (state, action) => {
            console.log("fetchHomeDataAction rejected");
          });
      }
})

export const { changeGoodPriceInfoAction } = homeSlice.actions

export default homeSlice.reducer

由于学习的时候,extraReducers已经废弃使用,所以用了其他方法extraReducers:(builder){}

然后我们修改网络请求模块。

// services/modules/home.js
import hyRequest from "../request";

export function getHomeGoodPriceData(){
    return hyRequest.get({
        url:"/home/goodprice"
    })
}

接着我们需要在导出文件中调用,如下

// services/index.js
import hyRequest from "./request"

export default hyRequest
export * from "./modules/home"

在第一个store文件夹里面就有引入网络请求文件了,这里我就不赘述了,请看上面代码
然后我们如何使用redux中的数据呢,我们修改views中home模块的代码。如下

// views/home/index.jsx
import React,{memo, useEffect} from "react";
import { shallowEqual,useDispatch,useSelector } from "react-redux";

import HomeBanner from "./c-cpns/home-banner";
import { fetchHomeDataAction } from "@/store/modules/home";
import { HomeWrapper } from './style'
const Home = memo(()=>{
    // 从redux中取数据
    const { goodPriceInfo } = useSelector((state)=>({
        goodPriceInfo:state.home.goodPriceInfo
    }),shallowEqual)
    // 派发异步的事件:发送网络请求
    const dispatch = useDispatch(); 
    useEffect(()=>{
        dispatch(fetchHomeDataAction())
    },[dispatch]);
     
    return (
        <HomeWrapper>
            <HomeBanner></HomeBanner>  
            <div className="content">
                <h2>{ goodPriceInfo.title }</h2>
                <ul>
                    {
                        goodPriceInfo.list?.map(item=>{
                            return <li key={item.id}>{item.name}</li>
                        })
                    }
                </ul>
            </div>
        </HomeWrapper>
    ) 
})

export default Home 

接着我们来写样式

公共的头部文件

在我们的components文件夹中创建section-header模块,代码如下

// components/section-header/index.jsx
import PropTypes from 'prop-types'
import React,{memo} from 'react'
import { HeaderWrapper } from './style'

const SectionHeader = memo((props)=>{
    const { title,subtitle } = props

    return (
        <HeaderWrapper>
            <h2 className='title'>{title}</h2>
            {subtitle && <div className='subtitle'>{subtitle}</div>}
        </HeaderWrapper>
    )
})


SectionHeader.PropTypes = {
    title:PropTypes.string,
    subtitle:PropTypes.string 
}

export default SectionHeader
    
    
// components/section-header/style.js
import styled from "styled-components";

export const HeaderWrapper = styled.div`
    color:#222;

    .title{
        font-size:22px;
        font-weight:700;
        margin-bottom:16px;
    }

    .subtitle{
        font-size:16px;
        margin-bottom:20px;
    }
`

接着,我们在首页使用他。

// views/home/index.jsx
import React,{memo, useEffect} from "react";
import { shallowEqual,useDispatch,useSelector } from "react-redux";

import HomeBanner from "./c-cpns/home-banner";
import { fetchHomeDataAction } from "@/store/modules/home";
import { HomeWrapper } from './style'
import SectionHeader from "@/components/section-header";

const Home = memo(()=>{
    // 从redux中取数据
    const { goodPriceInfo } = useSelector((state)=>({
        goodPriceInfo:state.home.goodPriceInfo
    }),shallowEqual)
    // 派发异步的事件:发送网络请求
    const dispatch = useDispatch(); 
    useEffect(()=>{
        dispatch(fetchHomeDataAction())
    },[dispatch]);
     
    return (
        <HomeWrapper>
            <HomeBanner></HomeBanner>  
            <div className="content">
               <div className="good-price">
                    <SectionHeader title={goodPriceInfo.title}></SectionHeader>
               </div>
                {/* <ul>
                    {
                        goodPriceInfo.list?.map(item=>{
                            return <li key={item.id}>{item.name}</li>
                        })
                    }
                </ul> */}
            </div>
        </HomeWrapper>
    ) 
})

export default Home 
    
    
// views/home/style.js
import styled from "styled-components";

export const HomeWrapper = styled.div`
    > .content{
        width:1032px;
        margin:0 auto;
    }

    .good-price {
        margin-top:30px;
    }
`

搭建公共的底部

首先我来修改components/app-footer文件夹里的,index.jsx,内容如下:

import React,{memo} from "react";
import { FooterWrapper } from './style';
import footerData from "@/assets/data/footer.json"

const AppFooter = memo(()=>{
    return (
        <FooterWrapper>
            <div className="wrapper">
                <div className="service">
                    {
                        footerData.map(item=>{
                            return (

                                <div className="item" key={item.name}>
                                    <div className="name">{item.name}</div>
                                    <div className="list">
                                        {
                                            item.list.map(item=>{
                                                return <div className="iten" key={item}>{item}</div>
                                            })
                                        }
                                    </div>
                                </div>
                            )
                        })
                    }
                </div>
                <div className="statement">© 2022 Airbnb, Inc. All rights reserved.条款 · 隐私政策 · 网站地图 · 全国旅游投诉渠道 12301</div>
            </div>
        </FooterWrapper>
    )
})
 
export default AppFooter

这里我们发现需要引入数据文件footer.json,然后我们来创建数据文件,内容如下:

// assets/data/footer.json
[
  {
    "name": "爱彼迎",
    "list": ["工作机会", "爱彼迎新闻", "政策", "无障碍设施"]
  },
  {
    "name": "发现",
    "list": ["信任与安全", "旅行基金", "商务差旅", "爱彼迎杂志", "Airbnb.org"]
  },
  {
    "name": "出租",
    "list": ["为什么要出租", "待客之道", "房东义务", "开展体验", "资源中心"]
  },
  {
    "name": "客服支持",
    "list": ["帮助", "邻里支持"]
  }
]

还有别忘了我们的style.js文件也需要修改,修改过的内容如下:

// components/app-footer/style.js
import styled from "styled-components";

export const FooterWrapper = styled.div`
    margin-top:100px;
    border-top:1px solid #EBEBEB;

    .wrapper{
        width:1080px;
        margin:0 auto;
        box-sizing:border-box;
        padding:48px 24px;
    }

    .service{
        display:flex;
        .item{
            flex:1;
            .name{
                margin-bottom:16px;
                font-weight:700;
            }

            .list{
                .iten{
                    margin-top:6px;
                    color:#767676;
                    cursor:pointer;
                    &:hover{
                        text-decoration:underline;
                    }
                }
            }
        }
    }

    .statement{
        margin-top:30px;
        border-top:1px solid #EBEBEB;
        padding:20px;
        color:#767676;
        text-align:center;
    }
`

这样我们的底部就改好了,然后我们继续完善我们的房屋列表插件样式。

高性价比房源列表组件

首先我们创建组件,components/section-rooms组件,代码如下:

// components/section-rooms/index.jsx
import PropTypes from 'prop-types'
import React, { memo } from 'react'

import RoomItem from '../room-item'
import { RoomsWrapper } from './style'

const SectionRooms = memo((props) => {
  const { roomList = [] } = props

  return (
    <RoomsWrapper>
      {
        roomList.slice(0, 8)?.map(item => {
          return <RoomItem itemData={item} key={item.id}/>
        })
      }
    </RoomsWrapper>
  )
}) 

SectionRooms.propTypes = {
  roomList: PropTypes.array
}

export default SectionRooms

完善我们的样式,内容如下:

// components/section-rooms/style.js

import styled from "styled-components";

export const RoomsWrapper = styled.div`
    display:flex;
    flex-wrap:wrap;
    margin:0 -8px;
`

在这个过程中我们需要使用类型验证库,prop-types所以我们安装:

npm install prop-types

然后我们完善我们单个item的组件,内容如下:

// components/room-item/index.jsx
import PropTypes from "prop-types"
import React,{ memo } from "react"
// import { Rating } from "@mui/material"
import { ItemWrapper } from "./style"

const RoomItem = memo((props)=>{
    const { itemData } = props

    return (
        <ItemWrapper verifycolor={itemData?.verify_info?.text_color || "#39576a"}>
            <div className="inner">
                <div className="cover">
                    <img src={itemData.picture_url} alt="" />
                </div>
                <div className="desc">
                    {itemData.verify_info.messages.join(" · ")}
                </div>
                <div className="name">{itemData.name}</div>
                <div className="price">¥{itemData.price}/晚</div>
            </div>
        </ItemWrapper> 
    )
})
 
RoomItem.propTypes = {
    itemData: PropTypes.object
}
  
  export default RoomItem

还要完善我们的style.js

// components/room-item/style.js

import styled from "styled-components";

export const ItemWrapper = styled.div`
    box-sizing:border-box;
    width:25%;
    padding:8px;

    .inner{
        width:100%;
    }

    .cover{
        position:relative;
        box-sizing:border-box;
        padding:66.66% 8px 0;
        border-radius:3px;
        overflow:hidden;

        img{
            position:absolute;
            left:0;
            top:0;
            width:100%;
            height:100%;
        }
    }

    .desc{
        margin:10px 0 5px;
        font-size:12px;
        font-weight:700;
        color:${props => props.verifycolor};
    }

    .name{
        font-size:16px;
        font-weight:700;

        overflow:hidden;
        text-overflow:ellipsis;
        display:-webkit-box;
        -webkit-line-clamp:2;
        -webkit-box-orient:vertical;
    }

    .price{
        margin:8px 0;
    }

    .bottom{
        display:flex;
        align-items:center;
        font-size:12px;
        color:${props => props.theme.text.primaryColor};
    }

    .count{
        margin:0 2px 0 4px;
    }

    .MuiRating-decimal{
        margin-right:-2px;
    }
`

最后我们在views/home中调用组件。

// views/home/index.jsx
import React,{memo, useEffect} from "react";
import { shallowEqual,useDispatch,useSelector } from "react-redux";

import HomeBanner from "./c-cpns/home-banner";
import { fetchHomeDataAction } from "@/store/modules/home";
import { HomeWrapper } from './style'
import SectionHeader from "@/components/section-header";
import SectionRooms from '@/components/section-rooms' // 这里我们调用总的组件就行

const Home = memo(()=>{
    // 从redux中取数据
    const { goodPriceInfo } = useSelector((state)=>({
        goodPriceInfo:state.home.goodPriceInfo
    }),shallowEqual)
    // 派发异步的事件:发送网络请求
    const dispatch = useDispatch(); 
    useEffect(()=>{
        dispatch(fetchHomeDataAction())
    },[dispatch]);
     
    return (
        <HomeWrapper>
            <HomeBanner></HomeBanner>  
            <div className="content">
               <div className="good-price">
                    <SectionHeader title={goodPriceInfo.title}></SectionHeader>
                    <SectionRooms roomList={goodPriceInfo.list}></SectionRooms>
               </div>
                {/* <ul>
                    {
                        goodPriceInfo.list?.map(item=>{
                            return <li key={item.id}>{item.name}</li>
                        })
                    }
                </ul> */}
            </div>
        </HomeWrapper>
    ) 
})

export default Home 

这样我们就完成了高性价比房源的初步,后期我们还要使用第三方库优化...

优化我们的评分

修改我们components/room-item下的文件,内容如下:

// components/room-item/index.jsx
import PropTypes from "prop-types"
import React,{ memo } from "react"
import { Rating } from "@mui/material"
import { ItemWrapper } from "./style"

const RoomItem = memo((props)=>{
    const { itemData } = props

    return (
        <ItemWrapper verifycolor={itemData?.verify_info?.text_color || "#39576a"}>
            <div className="inner">
                <div className="cover">
                    <img src={itemData.picture_url} alt="" />
                </div>
                <div className="desc">
                    {itemData.verify_info.messages.join(" · ")}
                </div>
                <div className="name">{itemData.name}</div>
                <div className="price">¥{itemData.price}/晚</div>
            </div>

            {/* 评分 */}
            <div className="bottom">
                <Rating value={itemData.star_rating ?? 5}
                    precision={0.1}
                    readOnly
                    sx={{fontSize:"12px",color:"#00848A",marginRight:"-1px"}}
                ></Rating>
                <span className="count">{itemData.reviews_count}</span>
                {
                    itemData.bottom_info && <span className="extra">
                        ·{itemData.bottom_info?.content}
                    </span>
                }
            </div>
        </ItemWrapper> 
    )
})
 
RoomItem.propTypes = {
    itemData: PropTypes.object
}
  
  export default RoomItem

别忘了更新style.js样式文件。

// components/room-item/style.js

import styled from "styled-components";

export const ItemWrapper = styled.div`
    box-sizing:border-box;
    width:25%;
    padding:8px;

    .inner{
        width:100%;
    }

    .cover{
        position:relative;
        box-sizing:border-box;
        padding:66.66% 8px 0;
        border-radius:3px;
        overflow:hidden;

        img{
            position:absolute;
            left:0;
            top:0;
            width:100%;
            height:100%;
        }
    }

    .desc{
        margin:10px 0 5px;
        font-size:12px;
        font-weight:700;
        color:${props => props.verifycolor};
    }

    .name{
        font-size:16px;
        font-weight:700;

        overflow:hidden;
        text-overflow:ellipsis;
        display:-webkit-box;
        -webkit-line-clamp:2;
        -webkit-box-orient:vertical;
    }

    .price{
        margin:8px 0;
    }

    .bottom{
        display:flex;
        align-items:center;
        font-size:12px;
        color:${props => props.theme.text.primaryColor};
    }

    .count{
        margin:0 2px 0 4px;
    }

    .MuiRating-decimal{
        margin-right:-2px;
    }
`

感谢大家观看,我们下次见

评论(0)

最新评论

  • abzzp

    十天看一部剧,还可以吧[[呲牙]]

  • ab

    @梦不见的梦 行,谢谢提醒,我优化一下

  • 梦不见的梦

    网站的速度有待提升,每次打开都要转半天还进不来呢

  • abzzp

    @React实战爱彼迎项目(二) - 程序员鸡皮 哪里有问题了,报错了吗?[[微笑]]

  • abzzp

    @Teacher Du 那是怕你们毕不了业,我大学那会儿给小礼品[[发呆]]

  • Teacher Du

    我们大学那会,献血还给学分~

  • @ab 我想去学网安,比如网警,但分也贼高😕

  • ab

    @夜 加油,你一样也可以成为程序员的,需要学习资料可以V我

  • 佬发布的好多技术文章似乎我只能评论这篇,真正的程序员!!哇塞 我也想去献血,过两年就成年了可以去献血了

日历

2025年01月

   1234
567891011
12131415161718
19202122232425
262728293031 

文章目录

推荐关键字: vue JavaScript React Golang 观后感 ES6 SEO 读后感

站点公告
本站是作为记录一名北漂程序员编程学习以及日常的博客,欢迎添加微信BmzhbjzhB咨询交流......
点击小铃铛关闭
配色方案