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

总结

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

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端