React+TS前台项目实战(二十七)-- 首页响应式构建之banner、搜索、统计模块布局

文章目录


前言

前面我们已经封装了这个项目基本要用到的全局组件了,现在就开始进入页面构建以及接口对接阶段了。首先,我们先来构建首页响应式布局,接下来会讲到真实接口对接,搭配react-query实现轮询,并使用memo,useMemo,usePrevious等钩子来优化页面,避免不必要的重新渲染。

一、 效果展示

二、相关模块

1. Statistic统计模块

功能分析

该模块主要为了展示基本数据统计,以及Echart图表统计

代码+详细注释

(1) 图表统计模块抽离

c 复制代码
// @/pages/Home/StaticBlock/HashRateEchart/index.tsx
import { memo } from "react";
import BigNumber from "bignumber.js";
import "echarts/lib/chart/line";
import "echarts/lib/component/title";
import echarts from "echarts/lib/echarts";
import { useTranslation } from "react-i18next";
import { HomeChartBlock } from "./styled";
import { ReactChartBlock } from "@/components/Echarts/common";
// 使用useOption函数生成Echarts配置对象
const useOption = () => {
  const { t } = useTranslation();
  return (data: any, useMiniStyle: boolean): echarts.EChartOption => {
    return {
      color: ["#ffffff"], // 颜色设置
      title: {
        text: "图表y轴时间", // 标题
        textAlign: "left", // 标题对齐方式
        textStyle: {
          color: "#ffffff", // 字体颜色
          fontSize: 14, // 字体大小
          fontWeight: "lighter", // 字体粗细
          fontFamily: "Lato", // 字体类型
        },
      },
      grid: {
        left: useMiniStyle ? "1%" : "2%", // 图表距离容器左边的距离
        right: "3%", // 图表距离容器右边的距离
        top: useMiniStyle ? "20%" : "15%", // 图表距离容器顶部的距离
        bottom: "2%", // 图表距离容器底部的距离
        containLabel: true, // 是否包含坐标轴的刻度标签
      },
      xAxis: [
        {
          axisLine: {
            lineStyle: {
              color: "#ffffff", // x轴颜色
              width: 1, // x轴宽度
            },
          },
          data: data.map((item: any) => item.xTime), // x轴数据
          axisLabel: {
            formatter: (value: string) => value, // x轴坐标标签格式化
          },
          boundaryGap: false, // 是否留空
        },
      ],
      yAxis: [
        {
          position: "left",
          type: "value",
          scale: true,
          axisLine: {
            lineStyle: {
              color: "#ffffff", // y轴颜色
              width: 1, // y轴宽度
            },
          },
          splitLine: {
            lineStyle: {
              color: "#ffffff", // y轴分割线颜色
              width: 0.5, // y轴分割线宽度
              opacity: 0.2, // y轴分割线透明度
            },
          },
          axisLabel: {
            formatter: (value: string) => new BigNumber(value), // y轴坐标标签格式化
          },
          boundaryGap: ["5%", "2%"], // y轴两侧留空
        },
        {
          position: "right",
          type: "value",
          axisLine: {
            lineStyle: {
              color: "#ffffff", // y轴颜色
              width: 1, // y轴宽度
            },
          },
        },
      ],
      series: [
        {
          name: t("block.hash_rate"), // 系列名称
          type: "line", // 系列类型
          yAxisIndex: 0, // y轴索引
          lineStyle: {
            color: "#ffffff", // 系列颜色
            width: 1, // 系列线条宽度
          },
          symbol: "none", // 系列标记的图形类型
          data: data.map((item: any) =>
            new BigNumber(item.yValue).toNumber()
          ), // 系列数据
        },
      ],
    };
  };
};
// HomeChartBlock组件
export default memo(() => {
  // 后期改为真实接口请求
  const echartData = [
    { xTime: "2020-01-01", yValue: "1500" },
    { xTime: "2020-01-02", yValue: "5220" },
    { xTime: "2020-01-03", yValue: "4000" },
    { xTime: "2020-01-04", yValue: "3500" },
    { xTime: "2020-01-05", yValue: "7800" },
  ];
  // 解析配置对象
  const parseOption = useOption();
  return (
    <HomeChartBlock to="/block-list">
      {/* 使用ReactChartBlock组件展示Echarts图表 */}
      <ReactChartBlock
        option={parseOption(echartData, true)}
        notMerge
        lazyUpdate
        style={{
          height: "180px",
        }}
      ></ReactChartBlock>
    </HomeChartBlock>
  );
});
--------------------------------------------------------------------------------------------------------------
// @/pages/Home/StaticBlock/HashRateEchart/styled.tsx
import styled from "styled-components";
import Link from "@/components/Link";
export const HomeChartBlock = styled(Link)`
  canvas {
    cursor: pointer;
  }
`;
export const ChartLoadingBlock = styled.div`
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  .no-data {
    font-size: 18px;
  }
`;

(2)引入统计图表

c 复制代码
// @/pages/Home/StatisticBlock/index.tsx
import { FC } from "react";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
import HashRateEchart from "./HashRateEchart/index";
import { HomeStatisticBlock, HomeStatisticItem } from "./styled";
import { useStatistics } from './hook'
const StatisticBlock: FC = () => {
  // 区块链统计数据类型声明
  interface EchartsAndData {
    name: string;
    value: string;
  }
  // 使用区块链统计数据
  const useEchartsAndDataList = (): EchartsAndData[] => {
    const { t } = useTranslation();
    const statistics = useStatistics()
    return [
      {
        name: t("home.echartsAndData.name1"),
        value: t(statistics.value1),
      },
      {
        name: t("home.echartsAndData.name2"),
        value: t(statistics.value2),
      },
      {
        name: t("home.echartsAndData.name3"),
        value: t(statistics.value3),
      },
      {
        name: t("home.echartsAndData.name4"),
        value: t(statistics.value4),
      },
    ];
  };
  // 使用区块链统计数据
  const echartsAndDataList = useEchartsAndDataList();
  // 单个统计渲染组件
  const StatisticItem = ({ data }: { data: EchartsAndData }) => (
    <HomeStatisticItem>
      <div className={classNames("statistic-item-left-title")}>{data.name}</div>
      <div className={classNames("statistic-item-left-value")}>{data.value}</div>
    </HomeStatisticItem>
  );
  return (
    <>
      <HomeStatisticBlock>
        <div className={classNames("statistic-item")}>
          <div className={classNames("statistic-item-left")}>
            <StatisticItem data={echartsAndDataList[0]}></StatisticItem>
            <StatisticItem data={echartsAndDataList[1]}></StatisticItem>
          </div>
          <div className={classNames("statistic-item-right")}>
            {/* hash图表模拟 */}
            <HashRateEchart />
          </div>
        </div>
        <div className={classNames("statistic-item")}>
          <div className={classNames("statistic-item-left")}>
            <StatisticItem data={echartsAndDataList[2]}></StatisticItem>
            <StatisticItem data={echartsAndDataList[3]}></StatisticItem>
          </div>
          <div className={classNames("statistic-item-right")}>
            {/* hash图表模拟 */}
            <HashRateEchart />
          </div>
        </div>
      </HomeStatisticBlock>
    </>
  );
};
export default StatisticBlock;
--------------------------------------------------------------------------------------------------------------
// @/pages/Home/StatisticBlock/styled.tsx
import styled from "styled-components";
import variables from "@/styles/variables.module.scss";
export const HomeStatisticBlock = styled.div`
  width: 100%;
  height: 207px;
  display: flex;
  margin-bottom: 20px;
  .statistic-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex: 1;
    background: #232323;
    @media (max-width: ${variables.extraLargeBreakPoint}) {
      flex-direction: column;
    }
    @media (max-width: ${variables.mobileBreakPoint}) {
    }
    .statistic-item-left {
      flex: 1;
      width: 100%;
      height: 100%;
    }
    .statistic-item-right {
      flex: 2;
      width: 100%;
      height: 100%;
      padding: 10px;
      // background: linear-gradient(304deg, #6e85e0 2%, #577cdb 48%, #486ecc 99%);
      @media (max-width: ${variables.extraLargeBreakPoint}) {
      }
      @media (max-width: ${variables.mobileBreakPoint}) {
      }
    }
    &:last-child {
      background: #484e4e;
    }
  }
  @media (max-width: ${variables.extraLargeBreakPoint}) {
    height: 310px;
  }
  @media (max-width: ${variables.mobileBreakPoint}) {
    flex-direction: column;
    height: auto;
  }
`;
export const HomeStatisticItem = styled.div`
  padding: 30px;
  display: flex;
  justify-content: space-between;
  flex-direction: column;
  color: #fff;
  .statistic-item-left-title {
    font-size: 14px;
    margin-bottom: 5px;
  }
  .statistic-item-left-value {
    font-size: 18px;
    font-weight: bold;
  }
  @media (max-width: ${variables.extraLargeBreakPoint}) {
    padding: 15px 30px;
    flex-direction: row;
    margin-bottom: 0;
    .statistic-item-left-value {
      font-size: 16px;
    }
  }
  @media (max-width: ${variables.mobileBreakPoint}) {
    padding: 10px 20px;
  }
`;

使用方式

c 复制代码
// 引入
import StatisticBlock from "./SearchBlock";
// 使用
<StatisticBlock />

2. Search搜索模块

功能分析

(1)引入全局封装的搜索组件,抽离成一个灵巧组件

(2)使用国际化语言

代码+详细注释

c 复制代码
// @/pages/Home/SearchBlock/index.tsx
import { FC, memo } from "react";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import styles from "./index.module.scss";
import Search from "@/components/Search";
// SearchBlock 组件
const SearchBlock: FC = memo(() => {
  // 获取 i18n 的翻译函数
  const [t] = useTranslation();
  return (
    <div className={classNames(styles.searchBlock)}>
      {/* 标题 */}
      <span className={classNames(styles.title)}>{t("common.Explorer")}</span>
      {/* 内容 */}
      <div className={classNames(styles.content)}>
        {/* 搜索组件 */}
        <Search hasButton />
      </div>
    </div>
  );
});
// 导出 SearchBlock 组件
export default SearchBlock;
--------------------------------------------------------------------------------------------------------------
// @/pages/Home/SearchBlock/index.module.scss
@import "@/styles/variables.module";
.searchBlock {
  display: flex;
  align-items: center;
  margin: 20px 0;
  .title {
    display: flex;
    align-items: center;
    font-weight: 800;
    font-size: 20px;
    @media (max-width: $extraLargeBreakPoint) {
      margin-bottom: 20px;
    }
    @media (max-width: $mobileBreakPoint) {
      font-size: 16px;
      margin-bottom: 14px;
    }
  }
  .content {
    flex: 1;
    margin-left: 16px;
    @media (max-width: $extraLargeBreakPoint) {
      width: 100%;
      margin-left: 0;
    }
  }
  @media (max-width: $extraLargeBreakPoint) {
    display: block;
  }
}

使用方式

c 复制代码
// 引入
import SearchBlock from "./SearchBlock";
// 使用
<SearchBlock />

3. banner模块

功能分析

banner展示图,此处PC端和移动端采用不同的图片

代码+详细注释

c 复制代码
// @/pages/Home/Banner/index.tsx
import classNames from "classnames";
import styles from "./index.module.scss";
export default () => <div className={classNames(styles.banner)} />;
--------------------------------------------------------------------------------------------------------------
// @/pages/Home/Banner/index.module.scss
@import "@/styles/variables.module";
$backgroundColor: #232323;
.banner {
  width: 100%;
  height: 200px;
  background: url("./assets/banner.svg") no-repeat center center / auto 100%;
  background-color: $backgroundColor;
  position: relative;
  @media (max-width: $mobileBreakPoint) {
    background-image: url("./assets/banner_phone.svg");
  }
}

使用方式

c 复制代码
// 引入
import Banner from "./Banner";
// 使用
<Banner />

总结

下一篇讲【首页响应式构建之区块、交易列表布局】。关注本栏目,将实时更新。

相关推荐
MarcoPage几秒前
第十九课 Vue组件中的方法
前端·javascript·vue.js
.net开发2 分钟前
WPF怎么通过RestSharp向后端发请求
前端·c#·.net·wpf
**之火24 分钟前
Web Components 是什么
前端·web components
顾菁寒25 分钟前
WEB第二次作业
前端·css·html
前端宝哥25 分钟前
10 个超赞的开发者工具,助你轻松提升效率
前端·程序员
你好龙卷风!!!27 分钟前
vue3 怎么判断数据列是否包某一列名
前端·javascript·vue.js
兔老大的胡萝卜1 小时前
threejs 数字孪生,制作3d炫酷网页
前端·3d
齐 飞2 小时前
MongoDB笔记02-MongoDB基本常用命令
前端·数据库·笔记·后端·mongodb
巧克力小猫猿2 小时前
基于ant组件库挑选框组件-封装滚动刷新的分页挑选框
前端·javascript·vue.js
FinGet3 小时前
那总结下来,react就是落后了
前端·react.js