所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

react 仿微信风格通讯录功能实现

项目概述

仿微信风格的通讯录页面,采用现代化设计,界面简洁美观,交互流畅自然。页面左侧为分组联系人列表,右侧为字母索引栏,支持点击和滚动高亮,带有动态圆形头像,整体体验高度还原主流社交 App 通讯录。

功能特点

  • 字母分组与索引:联系人自动按首字母分组,右侧字母索引栏支持点击快速跳转分组,滑动列表时索引栏自动高亮当前分组。
  • 动态高亮:滚动通讯录时,右侧字母索引栏会精准高亮当前分组,体验媲美微信、QQ 等主流 App。
  • 圆形头像:每个联系人自动生成彩色圆形头像,首字母展示,提升辨识度与美观度。
  • 响应式设计:适配桌面与移动端,支持触摸与鼠标操作。
  • 美观 UI:采用 Tailwind CSS,渐变背景、卡片阴影、圆角等细节处理,整体风格现代、清新。
  • 高性能渲染:分组渲染高效,滚动流畅不卡顿,适合大数据量通讯录场景。

技术亮点

  • React Hooks:全程使用函数组件与 Hooks(useRef、useState、useEffect),代码简洁易维护。
  • DOM 精确监听:通过 getBoundingClientRect 精确计算分组位置,实现高精度的索引高亮切换。
  • Tailwind CSS:极简高效的样式方案,快速实现美观 UI,易于自定义扩展。
  • 无第三方依赖:核心功能零依赖,易于集成到任意 React 项目。
  • 可扩展性强:支持自定义头像、分组规则、索引栏样式等,满足多样化业务需求。

使用场景

  • 企业/团队通讯录:适用于企业内部员工通讯录、组织架构展示等场景。
  • 社交/IM 应用:可作为微信、QQ、钉钉等社交 App 的通讯录模块。
  • 教育/校友录:班级、年级、校友录等分组联系人展示。
  • 客户/会员管理:CRM、会员系统等需要分组管理大量联系人的场景。
  • 移动端/小程序:适配移动端,体验友好,适合 H5、React Native、小程序等多端开发。

全部源码

import { useRef, useState, useEffect } from "react";

// 生成随机颜色(根据首字母)
function getColor(letter) {
  const colors = [
    "bg-blue-400",
    "bg-green-400",
    "bg-yellow-400",
    "bg-pink-400",
    "bg-purple-400",
    "bg-red-400",
    "bg-indigo-400",
    "bg-teal-400",
    "bg-orange-400",
    "bg-cyan-400",
  ];
  return colors[letter.charCodeAt(0) % colors.length];
}

// 示例数据,可替换为你的实际数据
const contacts = [
  { name: "Alice", initial: "A" },
  { name: "Aaron", initial: "A" },
  { name: "Bob", initial: "B" },
  { name: "Bella", initial: "B" },
  { name: "Cindy", initial: "C" },
  { name: "David", initial: "D" },
  { name: "Eve", initial: "E" },
  { name: "Frank", initial: "F" },
  { name: "Grace", initial: "G" },
  { name: "Helen", initial: "H" },
  { name: "Ivy", initial: "I" },
  { name: "Jack", initial: "J" },
  { name: "Kathy", initial: "K" },
  { name: "Leo", initial: "L" },
  { name: "Mona", initial: "M" },
  { name: "Nina", initial: "N" },
  { name: "Oscar", initial: "O" },
  { name: "Paul", initial: "P" },
  { name: "Queen", initial: "Q" },
  { name: "Rose", initial: "R" },
  { name: "Sam", initial: "S" },
  { name: "Tom", initial: "T" },
  { name: "Uma", initial: "U" },
  { name: "Vera", initial: "V" },
  { name: "Will", initial: "W" },
  { name: "Xander", initial: "X" },
  { name: "Yuki", initial: "Y" },
  { name: "Zack", initial: "Z" },
];

// 分组
const grouped = contacts.reduce((acc, cur) => {
  acc[cur.initial] = acc[cur.initial] || [];
  acc[cur.initial].push(cur);
  return acc;
}, {});
const letters = Array.from({ length: 26 }, (_, i) =>
  String.fromCharCode(65 + i)
);

export default function Contacts() {
  const refs = useRef({});
  const listRef = useRef();
  const [activeLetter, setActiveLetter] = useState(letters[0]);

  const scrollTo = (letter) => {
    refs.current[letter]?.scrollIntoView({
      behavior: "smooth",
      block: "start",
    });
  };

  useEffect(() => {
    const container = listRef.current;
    if (!container) return;

    const handleScroll = () => {
      const offsets = letters
        .filter((letter) => grouped[letter])
        .map((letter) => {
          const el = refs.current[letter];
          if (!el) return null;
          const rect = el.getBoundingClientRect();
          return { letter, top: rect.top, bottom: rect.bottom };
        })
        .filter(Boolean);

      // 80 是标题高度,可根据实际调整
      const threshold = 80;
      // 找到第一个 bottom >= 80 的分组
      const idx = offsets.findIndex((o) => o.bottom >= threshold);
      if (idx === -1) {
        setActiveLetter(offsets[offsets.length - 1].letter);
      } else {
        setActiveLetter(offsets[idx].letter);
      }
    };

    container.addEventListener("scroll", handleScroll);
    handleScroll();
    return () => container.removeEventListener("scroll", handleScroll);
  }, [grouped, letters]);

  return (
    <div className="flex relative h-screen bg-gradient-to-br from-blue-50 to-white">
      {/* 通讯录列表 */}
      <div ref={listRef} className="flex-1 overflow-y-auto px-4 py-4">
        <h1 className="text-2xl font-bold mb-4 text-blue-700 tracking-wide">
          通讯录
        </h1>
        {letters.map(
          (letter) =>
            grouped[letter] && (
              <div key={letter} ref={(el) => (refs.current[letter] = el)}>
                <div className="sticky top-0 z-10 text-blue-600 font-bold px-2 py-1 rounded mt-2 mb-1 shadow-sm backdrop-blur">
                  {letter}
                </div>
                <div className="space-y-2 mb-4">
                  {grouped[letter].map((c) => (
                    <div
                      key={c.name}
                      className="flex items-center gap-3 px-4 py-2 bg-white rounded-xl shadow hover:bg-blue-50 transition cursor-pointer group"
                    >
                      <div
                        className={`w-10 h-10 flex items-center justify-center rounded-full text-white text-lg font-bold shadow ${getColor(
                          c.initial
                        )}`}
                      >
                        {c.name[0]}
                      </div>
                      <span className="text-gray-800 text-base font-medium group-hover:text-blue-700 transition">
                        {c.name}
                      </span>
                    </div>
                  ))}
                </div>
              </div>
            )
        )}
      </div>
      {/* 右侧字母索引栏 */}
      <div
        className="fixed right-2 top-1/2 -translate-y-1/2 z-20 flex flex-col items-center select-none rounded-xl shadow px-1 py-2 backdrop-blur"
        style={{ userSelect: "none" }}
      >
        {letters.map((letter) => (
          <div
            key={letter}
            className={`text-xs font-medium px-2 py-1 cursor-pointer rounded transition
              ${
                grouped[letter]
                  ? "text-gray-700"
                  : "text-gray-300 cursor-default"
              }
              ${
                activeLetter === letter && grouped[letter]
                  ? "bg-blue-500 text-white font-bold scale-110 shadow"
                  : ""
              }
              hover:bg-blue-200 hover:text-blue-700`}
            onClick={() => grouped[letter] && scrollTo(letter)}
          >
            {letter}
          </div>
        ))}
      </div>
    </div>
  );
}
资源下载
下载价格免费
注意:本网站资源属于虚拟产品,不支持退款。请谨慎购买! 购买后资源无法下载,请联系客服QQ:844475003,微信号:th844475003。
原文链接:https://code.ifrontend.net/archives/661,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?