React+TS前台项目实战(二十一)-- Search业务组件封装实现全局搜索

news2024/11/26 2:21:20

文章目录

  • 前言
  • 一、Search组件封装
    • 1. 效果展示
    • 2. 功能分析
    • 3. 代码+详细注释
    • 4. 使用方式
  • 二、搜索结果展示组件封装
    • 1. 功能分析
    • 2. 代码+详细注释
  • 三、引用到文件,自行取用
  • 总结


前言

今天,我们来封装一个业务灵巧的组件,它集成了全局搜索和展示搜索结果的功能。通过配置文件,我们可以为不同的模块定制展示和跳转逻辑,集中管理不同模块,当要加一个模块时,只需要通过配置即可,从而减少重复的代码,并方便地进行维护和扩展。同时,我们将使用React Query来实现搜索功能,并模拟请求成功、请求失败和中断请求的处理方式。


一、Search组件封装

1. 效果展示

(1)输入内容,当停止输入后,请求接口数据
注:如请求数据时添加加载状态,请求结束后取消加载状态

在这里插入图片描述

(2)点击清除按钮,清除输入框数据,并中止当前请求,重置react-query请求参数

在这里插入图片描述

(3)请求失败,展示失败界面

在这里插入图片描述

(4)是否显示搜索按钮

在这里插入图片描述
(5)移动端效果

在这里插入图片描述

2. 功能分析

(1)搜索功能灵活性: 使用防抖搜索,useMemo,以及react-query自带监听输入状态,只在输入框停止输入后,才会触发接口请求,避免在用户仍在输入时进行不必要的API调用
(2)请求库选择: 使用Tanstack React Query中的useQuery钩子来管理加载状态并获取搜索结果
(3)导航到搜索结果: 点击搜索结果项或在搜索结果显示后按下回车键时,会跳转到对应的页面
(4)清除搜索: 点击清空按钮,会清空输入框的内容,并取消接口请求重置请求参数隐藏搜索结果列表
(5)搜索结果展示: 一旦获取到搜索结果,该组件使用SearchResults组件渲染搜索结果。它还显示搜索结果的加载状态
(6)搜索按钮: 如果hasButton属性为true,还将渲染一个搜索按钮,当点击时触发搜索
(7)使用国际化语言,可全局切换使用;使用联合类型声明使用,不同模块,添加配置即可
(8)使用useCallback,useMemo,useEffect, memo,lodash.debounce等对组件进行性能优化
(9)提供一些回调事件,供外部调用

3. 代码+详细注释

引入之前文章封装的 输入框组件,可自行查看,以及下面封装的结果展示组件

// @/components/Search/index.tsx
import { FC, useCallback, useMemo, memo, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import debounce from "lodash.debounce";
import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { SearchContainer, SearchButton } from "./styled";
import Input from "@/components/Input";
import { querySearchInfo } from "@/api/search";
import { useIsMobile } from "@/hooks";
import { SearchResults } from "./searchResults";
import { getURLBySearchResult } from "./utils";

// 组件的属性类型
type Props = {
  defaultValue?: string;
  hasButton?: boolean;
  onClear?: () => void;
};
// 搜索框组件
const Search: FC<Props> = memo(({ defaultValue, hasButton, onClear: handleClear }) => {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { t } = useTranslation();
  const isMobile = useIsMobile();
  const [keyword, _setKeyword] = useState(defaultValue || "");
  const searchValue = keyword.trim();
  // 获取搜索结果数据
  const fetchData = async (searchValue: string) => {
    const { data } = await querySearchInfo({
      p: searchValue,
    });
    return {
      data,
      total: data.length,
    };
  };
  // 使用useQuery实现搜索
  const {
    refetch: refetchSearch,
    data: _searchResults,
    isFetching,
  } = useQuery(["moduleSearch", searchValue], () => fetchData(searchValue), {
    enabled: false,
  });
  // 从查询结果中获取搜索结果数据
  const searchResultData = _searchResults?.data;
  // 使用useMemo函数创建一个防抖函数debouncedSearch,用于实现搜索请求功能
  const debouncedSearch = useMemo(() => {
    return debounce(refetchSearch, 1500, { trailing: true }); // 在搜索值变化后1.5秒后触发refetchSearch函数
  }, [refetchSearch]); // 当refetchSearch函数发生变化时,重新创建防抖函数debouncedSearch

  // 监听搜索值变化,当有搜索值时,调用debouncedSearch函数进行搜索
  useEffect(() => {
    if (!searchValue) return;
    debouncedSearch();
  }, [searchValue]);

  // 重置搜索
  const resetSearch = useCallback(() => {
    debouncedSearch.cancel(); // 取消搜索轮询
    queryClient.resetQueries(["moduleSearch", searchValue]); // 重置查询缓存
  }, [debouncedSearch, queryClient, searchValue]);

  // 清空搜索
  const onClear = useCallback(() => {
    resetSearch(); // 调用重置方法
    handleClear?.(); // 调用清空回调方法
  }, [resetSearch, handleClear]);

  // 设置搜索内容,如果值为空,则调用清空方法
  const setKeyword = (value: string) => {
    if (value === "") onClear();
    _setKeyword(value);
  };
  // 搜索按钮点击事件
  const handleSearch = () => {
    // 如果没有搜索内容,或者搜索无数据则直接返回
    if (!searchValue || !searchResultData) return;
    // 根据搜索结果数据的第一个元素获取搜索结果对应的URL
    const url = getURLBySearchResult(searchResultData[0]);
    // 跳转到对应的URL,如果获取不到URL,则跳转到失败的搜索页面
    navigate(url ?? `/search/fail?q=${searchValue}`);
  };
  return (
    <SearchContainer>
      {/* 搜索框 */}
      <Input loading={isFetching} value={keyword} hasPrefix placeholder={t("navbar.search_placeholder")} autoFocus={!isMobile} onChange={(event) => setKeyword(event.target.value)} onEnter={handleSearch} onClear={onClear} />
      {/* 搜索按钮,hasButton为true时显示 */}
      {hasButton && <SearchButton onClick={handleSearch}>{t("search.search")}</SearchButton>}
      {/* 搜索结果列表组件展示 */}
      {(isFetching || searchResultData && <SearchResults keyword={keyword} results={searchResultData ?? []} loading={isFetching} />}
    </SearchContainer>
  );
});

export default Search;
------------------------------------------------------------------------------
// @/components/Search/styled.tsx
import styled from "styled-components";
import variables from "@/styles/variables.module.scss";
export const SearchContainer = styled.div`
  position: relative;
  margin: 0 auto;
  width: 100%;
  padding-right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: white;
  border: 0 solid white;
  border-radius: 4px;
`;
export const SearchButton = styled.div`
  flex-shrink: 0;
  width: 72px;
  height: calc(100% - 4px);
  margin: 2px 2px 2px 8px;
  border-radius: 0 4px 4px 0;
  background-color: #121212;
  text-align: center;
  line-height: 34px;
  color: #fff;
  letter-spacing: 0.2px;
  font-size: 14px;
  cursor: pointer;

  @media (max-width: ${variables.mobileBreakPoint}) {
    display: none;
  }
`;

4. 使用方式

// 引入组件
import Search from '@/components/Search'
// 使用
{/* 带搜索按钮 */}
<Search hasButton />
{/* 不带搜索按钮 */}
<Search />

二、搜索结果展示组件封装

注:这个组件在上面Search组件中引用,单独列出来讲讲。运用关注点分离的策略,将页面分割成多个片段,易维护,容易定位代码位置。

1. 功能分析

(1)组件接受搜索内容,是否显示loading加载,以及搜索列表这三个参数
(2)根据搜索结果列表,按模块类型分类数据,这里举例2种类型(如Transaction 和 Block)
(3)对搜索的模块类型列表,添加点击事件,当点击某个模块时,展示该模块的数据
(4)不同模块类型的列表,展示不同效果(例如类型是 Transaction,显示交易信息,包括交易名称和所在区块的编号;类型是 Block,则显示区块信息,包括区块编号)
(5)通过useEffect监听数据变化,发生变化时,重置激活的模块类型分类,默认不选中任何模块类型
(6)封装不同模块匹配对应的地址,名字的方法,统一管理
(7)采用联合等进行类型声明的定义

2. 代码+详细注释

// @/components/Search/SearchResults/index.tsx
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { FC, useEffect, useState } from "react";
import { SearchResultsContainer, CategoryFilterList, SearchResultList, SearchResultListItem } from "./styled";
import { useIsMobile } from "@/hooks";
import Loading from "@/components/Loading";
import { SearchResultType, SearchResult } from "@/models/Search";
// 引入不同模块匹配对应的地址,名字方法
import { getURLBySearchResult, getNameBySearchResult } from "../utils";

// 组件的类型定义
type Props = {
  keyword?: string; // 搜索内容
  loading?: boolean; // 是否显示 loading 状态
  results: SearchResult[]; // 搜索结果列表
};

// 列表数据每一项Item的渲染
const SearchResultItem: FC<{ keyword?: string; item: SearchResult }> = ({ item, keyword = "" }) => {
  const { t } = useTranslation(); // 使用国际化
  const to = getURLBySearchResult(item); // 根据搜索结果项获取对应的 URL
  const displayName = getNameBySearchResult(item); // 根据搜索结果项获取显示名称
  // 如果搜索结果项类型是 Transaction,则显示交易信息
  if (item.type === SearchResultType.Transaction) {
    return (
      <SearchResultListItem to={to}>
        <div className={classNames("content")}>
          {/* 显示交易名称 */}
          <div className={classNames("secondary-text")} title={displayName}>
            {displayName}
          </div>
          {/* 显示交易所在区块的编号 */}
          <div className={classNames("sub-title", "monospace")}>
            {t("search.block")} # {item.attributes.blockNumber}
          </div>
        </div>
      </SearchResultListItem>
    );
  }
  // 否则,类型是Block, 显示区块信息
  return (
    <SearchResultListItem to={to}>
      <div className={classNames("content")} title={displayName}>
        {displayName}
      </div>
    </SearchResultListItem>
  );
};

// 搜索结果列表
export const SearchResults: FC<Props> = ({ keyword = "", results, loading }) => {
  const isMobile = useIsMobile(); // 判断是否是移动端
  const { t } = useTranslation(); // 使用国际化
  // 设置激活的模块类型分类
  const [activatedCategory, setActivatedCategory] = useState<SearchResultType | undefined>(undefined);
  // 当搜索结果列表发生变化时,重置激活的分类
  useEffect(() => {
    setActivatedCategory(undefined);
  }, [results]);

  // 根据搜索结果列表,按模块类型分类数据
  const categories = results.reduce((acc, result) => {
    if (!acc[result.type]) {
      acc[result.type] = [];
    }
    acc[result.type].push(result);
    return acc;
  }, {} as Record<SearchResultType, SearchResult[]>);

  // 按模块类型分类的列表
  const SearchResultBlock = (() => {
    return (
      <SearchResultList>
        {Object.entries(categories)
          .filter(([type]) => (activatedCategory === undefined ? true : activatedCategory === type))
          .map(([type, items]) => (
            <div key={type} className={classNames("search-result-item")}>
              <div className={classNames("title")}>{t(`search.${type}`)}</div>
              <div className={classNames("list")}>
                {items.map((item) => (
                  <SearchResultItem keyword={keyword} key={item.id} item={item} />
                ))}
              </div>
            </div>
          ))}
      </SearchResultList>
    );
  })();

  // 如果搜索结果列表为空,则显示空数据提示;否则显示搜索结果列表
  return (
    <SearchResultsContainer>
      {!loading && Object.keys(categories).length > 0 && (
        <CategoryFilterList>
          {(Object.keys(categories) as SearchResultType[]).map((category) => (
            <div key={category} className={classNames("categoryTagItem", { active: activatedCategory === category })} onClick={() => setActivatedCategory((pre) => (pre === category ? undefined : category))}>
              {t(`search.${category}`)} {`(${categories[category].length})`}
            </div>
          ))}
        </CategoryFilterList>
      )}
      {loading ? <Loading size={isMobile ? "small" : undefined} /> : results.length === 0 ? <div className={classNames("empty")}>{t("search.no_search_result")}</div> : SearchResultBlock}
    </SearchResultsContainer>
  );
};

------------------------------------------------------------------------------
// @/components/Search/SearchResults/styled.tsx
import styled from "styled-components";
import Link from "@/components/Link";
export const SearchResultsContainer = styled.div`
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-height: 292px;
  overflow-y: auto;
  background: #fff;
  color: #000;
  border-radius: 4px;
  box-shadow: 0 4px 4px 0 #1010100d;
  position: absolute;
  z-index: 2;
  top: calc(100% + 8px);
  left: 0;
  .empty {
    padding: 28px 0;
    text-align: center;
    font-size: 16px;
    color: #333;
  }
`;
export const CategoryFilterList = styled.div`
  display: flex;
  flex-wrap: wrap;
  padding: 12px 12px 0;
  gap: 4px;
  .categoryTagItem {
    border: 1px solid #e5e5e5;
    border-radius: 24px;
    padding: 4px 12px;
    cursor: pointer;
    transition: all 0.3s;

    &.active {
      border-color: var(--cd-primary-color);
      color: var(--cd-primary-color);
    }
  }
`;
export const SearchResultList = styled.div`
  .search-result-item {
    .title {
      color: #666;
      font-size: 0.65rem;
      letter-spacing: 0.5px;
      font-weight: 700;
      padding: 12px 12px 6px;
      background-color: #f5f5f5;
      text-align: left;
    }
    .list {
      padding: 6px 8px;
    }
  }
`;
export const SearchResultListItem = styled(Link)`
  display: block;
  width: 100%;
  padding: 4px 0;
  cursor: pointer;
  border-bottom: solid 1px #e5e5e5;
  .content {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 4px;
    border-radius: 4px;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--cd-primary-color);
  }
  .secondary-text {
    flex: 1;
    width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .sub-title {
    font-size: 14px;
    color: #666;
    overflow: hidden;
    margin: 0 4px;
  }
  &:last-child {
    border-bottom: none;
  }
  &:hover,
  &:focus-within {
    .content {
      background: #f5f5f5;
    }
  }
`;

三、引用到文件,自行取用

(1)获取不同模块地址,展示名称的方法

// @/components/Search/utils
import { SearchResultType, SearchResult } from "@/models/Search";
// 根据搜索结果项类型,返回对应的 URL 链接
export const getURLBySearchResult = (item: SearchResult) => {
  const { type, attributes } = item;
  switch (type) {
    case SearchResultType.Block:
      // 如果搜索结果项类型是 Block,则返回对应的区块详情页面链接
      return `/block/${attributes.blockHash}`;
    case SearchResultType.Transaction:
      // 如果搜索结果项类型是 Transaction,则返回对应的交易详情页面链接
      return `/transaction/${attributes.transactionHash}`;
    default:
      // 如果搜索结果项类型不是 Block 或者 Transaction,则返回空字符串
      return "";
  }
};
// 根据搜索结果项类型,返回不同显示名称
export const getNameBySearchResult = (item: SearchResult) => {
  const { type, attributes } = item;
  switch (type) {
    case SearchResultType.Block:
      return attributes?.number?.toString(); // 返回高度
    case SearchResultType.Transaction:
      return attributes?.transactionHash?.toString(); // 返回交易哈希
    default:
      return ""; // 返回空字符串
  }
};

(2)用到的类型声明

// @/models/Search/index.ts
import { Response } from '@/request/types'
import { Block } from '@/models/Block'
import { Transaction } from '@/models/Transaction'
export enum SearchResultType {
  Block = 'block',
  Transaction = 'ckb_transaction',
}
export type SearchResult =
  | Response.Wrapper<Block, SearchResultType.Block>
  | Response.Wrapper<Transaction, SearchResultType.Transaction>
-------------------------------------------------------------------------------------------------------
// @/models/Block/index.ts
export interface Block {
  blockHash: string
  number: number
  transactionsCount: number
  proposalsCount: number
  unclesCount: number
  uncleBlockHashes: string[]
  reward: string
  rewardStatus: 'pending' | 'issued'
  totalTransactionFee: string
  receivedTxFee: string
  receivedTxFeeStatus: 'pending' | 'calculated'
  totalCellCapacity: string
  minerHash: string
  minerMessage: string
  timestamp: number
  difficulty: string
  epoch: number
  length: string
  startNumber: number
  version: number
  nonce: string
  transactionsRoot: string
  blockIndexInEpoch: string
  minerReward: string
  liveCellChanges: string
  size: number
  largestBlockInEpoch: number
  largestBlock: number
  cycles: number | null
  maxCyclesInEpoch: number | null
  maxCycles: number | null
}
-------------------------------------------------------------------------------------------------------
// @/models/Transaction/index.ts
export interface Transaction {
  isBtcTimeLock: boolean
  isRgbTransaction: boolean
  rgbTxid: string | null
  transactionHash: string
  // FIXME: this type declaration should be fixed by adding a transformation between internal state and response of API
  blockNumber: number | string
  blockTimestamp: number | string
  transactionFee: string
  income: string
  isCellbase: boolean
  targetBlockNumber: number
  version: number
  displayInputs: any
  displayOutputs: any
  liveCellChanges: string
  capacityInvolved: string
  rgbTransferStep: string | null
  txStatus: string
  detailedMessage: string
  bytes: number
  largestTxInEpoch: number
  largestTx: number
  cycles: number | null
  maxCyclesInEpoch: number | null
  maxCycles: number | null
  createTimestamp?: number
}

总结

下一篇讲【全局常用Echarts组件封装】。关注本栏目,将实时更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1875914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于vs code中Live Server插件安装后无法打开的问题

一、问题情况 安装好Live Server插件之后&#xff0c;点击open with live server只会出现界面右下角落的提示&#xff0c;但是不会跳转到浏览器的页面&#xff1a;如下所示&#xff1a; 二&#xff1a;解决步骤 1、首先进行扩展设置&#xff0c;默认将浏览器的设置为chrome浏览…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于改进目标级联分析法的交直流混联系统发电-备用分布式协同调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

MFC扩展库BCGControlBar Pro v35.0新版亮点 - 工具栏、菜单全新升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v35.0已全新发布了&#xff0c;这个版本改进类Visual Studio 2022的视觉主题、增强对多个…

昇思25天学习打卡营第11天 | ResNet50迁移学习

内容介绍&#xff1a; 在实际应用场景中&#xff0c;由于训练数据集不足&#xff0c;所以很少有人会从头开始训练整个网络。普遍的做法是&#xff0c;在一个非常大的基础数据集上训练得到一个预训练模型&#xff0c;然后使用该模型来初始化网络的权重参数或作为固定特征提取器…

算法力扣刷题记录 二十三【151.翻转字符串里的单词】

前言 字符串篇&#xff0c;继续。 记录 二十三【151.翻转字符串里的单词】 – 一、题目阅读 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词…

【Python报错】已解决 ModuleNotFoundError: No module named ‘transformers‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 ModuleNotFoundError: No module named ‘transformers’ 是一个常见的错误&#xff0c;它表明你的Python环境中没有安装t…

2023阿里巴巴全球数学竞赛决赛中的LLM背景题解析(应用与计算数学部分第2题)...

早点关注我&#xff0c;精彩不错过&#xff01; 最近闹得沸沸扬扬的姜萍事件果真又成了世界就是个草台班子的有力论据。无论真相如何&#xff0c;各自心怀鬼胎&#xff0c;自有策略的合作看起来就一定是一场场的闹剧。 无意作过多评论&#xff0c;也绝不妄下言论&#xff0c;就…

Unity之自定义Text组件默认属性值

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之自定义Text组件默认属性值 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01;…

硬件实用技巧:刚挠板pcb是什么

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140060334 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

SQLServer 表值构造函数 (Transact-SQL)

在 SQL Server 中&#xff0c;表值构造函数&#xff08;Table Value Constructor, TVC&#xff09;是一种用于在单个语句中插入多行数据到表中的语法。它允许你以行内表值表达式&#xff08;row-valued expression&#xff09;的形式指定多行数据&#xff0c;并将这些数据作为一…

基于weixin小程序周边美食系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;美食店铺管理&#xff0c;菜品分类管理&#xff0c;标签管理&#xff0c;菜品信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;美食店铺&#x…

ROS2使用Python创建服务提供者、消费者

1.创建服务提供者 ros2 pkg create example_service_rclpy --build-type ament_python --dependencies rclpy example_interfaces --node-name service_server_02 service_server_02.py 代码 #!/usr/bin/env python3 import rclpy from rclpy.node import Node # 导入接口 …

办公软件WPS与Office的区别

临近计算机考试很多同学在纠结我是报wps好&#xff1f;还是ms office好&#xff1f;下面就来详细说说。 1、wps属于国内金山公司的办公软件&#xff0c;里面包含word、Excel和PPT。考试是2021年开始的&#xff01; 2、MS&#xff08;Microsoft 微软&#xff09; office属于美…

web安全渗透测试十大常规项(一):web渗透测试之深入JAVA反序列化

渗透测试之PHP反序列化 1. Java反序列化1.1 FastJson反序列化链知识点1.2 FastJson反序列化链分析1.3.1 FastJson 1.2.24 利用链分析1.3.2 FastJson 1.2.25-1.2.47 CC链分析1.3.2.1、开启autoTypeSupport:1.2.25-1.2.411. Java反序列化 1.1 FastJson反序列化链知识点 1、为什…

线程池技术实现及参数工作流程原理

一.什么是线程池 线程池其实就是一种多线程处理形式&#xff0c;处理过程中可以将任务添加到队列中&#xff0c;然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象。 为什么使用多线程…

【RT摩拳擦掌】RT云端测试之百度天工物接入构建(设备型)

【RT摩拳擦掌】RT云端测试之百度天工物接入构建&#xff08;设备型&#xff09; 一&#xff0c; 文档介绍二&#xff0c; 物接入IOT Hub物影子构建2.1 创建设备型项目2.2 创建物模型2.3 创建物影子 三&#xff0c; MQTT fx客户端连接云端3.1 MQTT fx配置3.2 MQTT fx订阅3.3 MQT…

修改docker中mongodb容器的时区

假设容器名称为mongodb&#xff0c;设置时区为上海时区的命令为&#xff1a; docker exec -it mongodb bash -c "ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone"验证时区更改&#xff1a; docker e…

Eigen中关于四元数的常用操作

四元数&#xff08;Quaternion&#xff09;是一种数学工具&#xff0c;广泛用于计算机图形学、机器人学和物理模拟中&#xff0c;特别适合处理三维旋转。Eigen库是一个高性能的C数学库&#xff0c;提供了丰富的线性代数功能&#xff0c;其中就包括对四元数的支持。 1. 为什么选…

element ui form 表单验证

表单验证方法 在el-form元素上总体设置校验规则rules&#xff0c;下面是官方案例 <el-form :model"ruleForm" :rules"rules" ref"ruleForm" label-width"100px" class"demo-ruleForm"><el-form-item label"…

基于Java游戏售卖网站详细设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…