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 />

总结

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

相关推荐
热爱编程的小曾26 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin38 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox