参考文章:https://nextjs.org/docs/app/getting-started/css#tailwind-css

参考文章:How to install Tailwind CSS v3 in your Next.js application

注意:

  • globals.css只能放在app/globals.css 或 src/styles/globals.css 这类约定俗成的位置,保持路径清晰;移动后别忘了更新 layout.tsx 里的 import。
  • layout.tsx也只能放在以下位置之一(取决于项目配置):app/layout.tsx、src/app/layout.tsx


在现代前端开发中,Tailwind CSS凭借其实用类(utility classes)和高度可定制性,成为了构建响应式UI的热门选择。今天,我将带你一步步使用Tailwind CSS、React、TypeScript、Next.js和Zustand,实现一个仿ChatGPT的聊天界面。我们将使用假接口模拟后端交互,专注于前端实现。

1. 环境准备

首先,创建一个新的Next.js项目:

npx create-next-app@latest chatgpt-clone --typescript --tailwind --eslint --src-dir --app --import-alias "@/*"

命令解释npx create-next-app@latest 是Next.js官方提供的脚手架命令,用于快速创建Next.js项目。--typescript 表示使用TypeScript,--tailwind 表示集成Tailwind CSS,--eslint 表示集成ESLint,--src-dir 表示将源代码放在src目录下,--app 表示使用App Router,--import-alias "@/*" 表示设置路径别名。

在这里插入图片描述

cd chatgpt-clone

命令解释cd 是切换当前工作目录的命令,进入刚刚创建的项目目录。

npm install zustand

命令解释npm install 是安装Node.js包的命令。这里安装了Zustand状态管理库。

注意:
@types/zustand 已被弃用:
zustand 库从 v4.0.0 开始(2022 年)将类型定义直接打包到主包中(zustand 包内包含 index.d.ts)。 因此,无需单独安装 @types/zustand,npm 官方仓库已移除该包。

2. 项目结构

我们的项目结构将如下:

src/
├── app/
│   ├── layout.tsx
│   ├── page.tsx
├── components/
│   ├── Chat/
│   │   ├── Message.tsx
│   │   └── MessageList.tsx
│   └── InputBox.tsx
├── store/
│   └── chatStore.ts
└── styles/
    └── globals.css

3. 设置Tailwind CSS

Add the PostCSS plugin to your postcss.config.mjs file:

export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

参考文章:PostCSS介绍(一个用JavaScript工具和插件生态系统来转换CSS代码的工具)CSS后处理器,用于支持Tailwind CSS

在这里插入图片描述
我打开文件看,似乎已经配置好了?(虽然语法不太一样)

Import Tailwind in your global CSS file:

src/app/globals.css中增加:

@import 'tailwindcss';

在这里插入图片描述
好像已经加了,懵,跟官方给的有点不一样,我是通过create-next-app工具装的,可能有所不同。

Import the CSS file in your root layout:

将以下内容添加到app/layout.tsx

import './globals.css'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

似乎也已经添加了,可能版本不一样,代码不同:

在这里插入图片描述

打开tailwind.config.js并配置content字段,告诉Tailwind需要扫描哪些文件来生成CSS类:

// tailwind.config.js
module.exports = {
  // 指定Tailwind需要扫描的文件路径,以生成对应的CSS类
  // 这里包括了app目录下的所有JS/TS/JSX/TSX文件
  content: [
    "./src/app/**/*.{js,ts,jsx,tsx}",
    // 也包括components目录下的所有JS/TS/JSX/TSX文件
    "./src/components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    // 扩展Tailwind的默认主题,添加自定义颜色
    extend: {
      colors: {
        // 定义一个名为primary的颜色,用于主要按钮和强调元素
        primary: '#4F46E5',
        // 定义一个名为secondary的颜色,用于次要按钮和强调元素
        secondary: '#10B981',
      }
    },
  },
  // 插件扩展,目前没有使用任何插件
  plugins: [],
}

我没看到有tailwind.config.js文件,只能创建一个了。

在这里插入图片描述

src/app/globals.css中添加Tailwind指令:

/* 从Tailwind的base样式开始,这是Tailwind的基础重置 */
@tailwind base;

/* 从Tailwind的组件样式开始,这是Tailwind的组件样式 */
@tailwind components;

/* 从Tailwind的实用类样式开始,这是Tailwind的核心实用类 */
@tailwind utilities;

在这里插入图片描述

4. 创建状态管理(Zustand)(尴尬,语法看不太懂,回头再研究,先把代码跑通😳)(src/store/chatStore.ts)

我们使用Zustand管理聊天状态:

// src/store/chatStore.ts
// 从zustand库导入create函数,用于创建状态管理
import { create } from 'zustand';

// 定义聊天消息的类型,包含id、文本、是否是用户发送、时间戳
type ChatMessage = {
  // 消息的唯一标识符,使用时间戳生成
  id: string;
  // 消息的文本内容
  text: string;
  // 标记消息是否是用户发送的(true表示用户,false表示AI)
  isUser: boolean;
  // 消息发送的时间戳
  timestamp: number;
};

// 定义ChatState的状态类型,包含消息数组、添加消息函数、清除消息函数
type ChatState = {
  // 存储所有聊天消息的数组
  messages: ChatMessage[];
  // 添加新消息到状态的函数
  addMessage: (message: ChatMessage) => void;
  // 清空所有聊天消息的函数
  clearMessages: () => void;
};

// 使用zustand的create函数创建状态管理
// 传入一个函数,该函数接收set函数,用于更新状态
export const useChatStore = create<ChatState>((set) => ({
  // 初始状态:消息数组为空
  messages: [],
  // 添加消息函数:将新消息添加到消息数组的末尾
  addMessage: (message) => set((state) => ({
    // 通过set函数更新状态,将新消息添加到现有消息数组的末尾
    messages: [...state.messages, message]
  })),
  // 清除消息函数:将消息数组重置为空数组
  clearMessages: () => set({ messages: [] }),
}));

在这里插入图片描述

5. 实现聊天消息组件(src/components/Chat/Message.tsx)

首先,创建消息组件:

// src/components/Chat/Message.tsx
// 从React导入React组件
import React from 'react';

// 定义Message组件的Props类型,包含text和isUser
interface MessageProps {
  // 消息的文本内容
  text: string;
  // 标记消息是否是用户发送的
  isUser: boolean;
}

// 创建Message组件,接收text和isUser作为props
const Message: React.FC<MessageProps> = ({ text, isUser }) => {
  // 返回一个div,根据isUser的值决定消息的对齐方式
  // 如果是用户发送的消息,右对齐;如果是AI发送的消息,左对齐
  return (
    <div className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-4`}>
      {/* 消息容器,根据isUser的值设置背景色和文字颜色 */}
      <div className={`max-w-[70%] rounded-lg p-3 ${isUser ? 'bg-primary text-white' : 'bg-gray-200'}`}>
        {/* 消息文本,设置字体大小 */}
        <p className="text-sm">{text}</p>
      </div>
    </div>
  );
};

// 导出Message组件,使其可以在其他地方使用
export default Message;

在这里插入图片描述

6. 实现消息列表组件(src/components/Chat/MessageList.tsx)

// src/components/Chat/MessageList.tsx
// 从React导入React组件
import React from 'react';

// 从store/chatStore导入useChatStore,用于访问聊天状态
import Message from './Message';
import { useChatStore } from '@/store/chatStore';

// 定义MessageList组件,没有接收任何props
const MessageList: React.FC = () => {
  // 使用useChatStore获取消息状态
  const { messages } = useChatStore();
  
  // 返回消息列表,使用div包裹,设置高度、溢出滚动和间距
  return (
    <div className="h-[calc(100vh-120px)] overflow-y-auto p-4 space-y-4">
      {/* 使用map遍历messages数组,为每条消息渲染Message组件 */}
      {messages.map((message) => (
        // 为每条消息提供唯一的key,确保React可以高效更新
        <Message
          key={message.id}
          text={message.text}
          isUser={message.isUser}
        />
      ))}
    </div>
  );
};

// 导出MessageList组件
export default MessageList;

在这里插入图片描述

7. 实现输入框组件(看不太懂代码,先跑通)(src/components/InputBox.tsx)

// src/components/InputBox.tsx
// 从React导入useState
import React, { useState } from 'react';

// 从store/chatStore导入useChatStore,用于访问聊天状态
import { useChatStore } from '@/store/chatStore';

// 定义InputBox组件,没有接收任何props
const InputBox: React.FC = () => {
  // 使用useState管理输入框的值
  const [input, setInput] = useState('');
  
  // 从useChatStore获取addMessage函数,用于添加新消息
  const { addMessage } = useChatStore();

  // 处理表单提交事件
  const handleSubmit = (e: React.FormEvent) => {
    // 阻止表单默认提交行为
    e.preventDefault();
    
    // 如果输入为空,直接返回,不处理
    if (!input.trim()) return;
    
    // 添加用户消息到状态
    addMessage({
      // 使用当前时间戳作为消息ID
      id: Date.now().toString(),
      // 用户输入的消息文本
      text: input,
      // 标记为用户发送
      isUser: true,
      // 记录消息发送时间
      timestamp: Date.now(),
    });
    
    // 清空输入框
    setInput('');
    
    // 模拟AI响应,1秒后添加AI消息
    setTimeout(() => {
      addMessage({
        // 使用当前时间戳+1作为AI消息ID
        id: (Date.now() + 1).toString(),
        // AI的模拟响应文本
        text: "这是一个模拟的AI响应。在实际应用中,这里会调用API获取真实响应。",
        // 标记为AI发送
        isUser: false,
        // 记录AI消息发送时间
        timestamp: Date.now() + 1000,
      });
    }, 1000);
  };

  // 返回表单,包含输入框和发送按钮
  return (
    <form onSubmit={handleSubmit} className="p-4 border-t border-gray-200">
      <div className="flex">
        {/* 输入框,绑定输入值和输入事件 */}
        <input
          type="text"
          value={input}
          // 当输入框内容变化时,更新input状态
          onChange={(e) => setInput(e.target.value)}
          // 输入框的占位符文本
          placeholder="请输入消息..."
          // 设置输入框的样式,包括边框、内边距、圆角和焦点样式
          className="flex-1 p-3 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-primary"
        />
        {/* 发送按钮,绑定提交事件,禁用状态根据输入是否为空决定 */}
        <button
          type="submit"
          // 设置按钮的背景色、文字颜色、内边距、圆角和悬停效果
          className="bg-primary text-white px-6 rounded-r-lg hover:bg-indigo-600 transition-colors"
          // 如果输入为空,禁用按钮
          disabled={!input.trim()}
        >
          发送
        </button>
      </div>
    </form>
  );
};

// 导出InputBox组件
export default InputBox;

在这里插入图片描述

8. 创建主页面(src/app/page.tsx)

// src/app/page.tsx
// 从React导入React组件
import React from 'react';

// 从components导入MessageList和InputBox组件
import MessageList from '@/components/Chat/MessageList';
import InputBox from '@/components/InputBox';

// 定义Home组件,作为应用程序的主页面
export default function Home() {
  // 返回页面结构,包含头部、主内容区域
  return (
    <div className="min-h-screen bg-gray-50 flex flex-col">
      {/* 头部,包含标题 */}
      <header className="bg-white shadow-md p-4 text-center">
        {/* 标题,设置字体大小、粗细和颜色 */}
        <h1 className="text-xl font-bold text-primary">AI Chat</h1>
      </header>
      
      {/* 主内容区域,包含消息列表和输入框 */}
      <main className="flex-1 flex flex-col">
        {/* 消息列表组件 */}
        <MessageList />
        {/* 输入框组件 */}
        <InputBox />
      </main>
    </div>
  );
}

将以上内容替换page.tsx中的初始内容:

在这里插入图片描述

9. 添加全局样式(src/app/globals.css)

/* src/app/globals.css */
/* 从Google Fonts导入Inter字体,指定字体重量 */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');

/* 设置页面的字体为Inter,如果没有则使用sans-serif */
body {
  font-family: 'Inter', sans-serif;
}

在这里插入图片描述

10. 运行项目

npm run dev

命令解释npm run dev 是Next.js项目默认的开发服务器命令,它会启动一个开发服务器,自动检测代码变化并热重载。

11. 代码解释

Tailwind CSS的实用类设计

  • 布局flex, justify-end, justify-start, flex-1 用于实现响应式布局
  • 间距p-4, mb-4, space-y-4 用于控制元素间距
  • 颜色bg-primary, text-white, bg-gray-200, border-gray-300 用于设置颜色
  • 圆角rounded-lg 用于设置圆角
  • 响应式max-w-[70%] 用于限制消息宽度,确保在不同屏幕下显示良好

Zustand状态管理

  • create:Zustand的创建函数
  • addMessage:添加新消息到状态
  • clearMessages:清空聊天记录
  • useChatStore:在组件中使用状态

交互逻辑

  1. 用户输入消息并提交
  2. 将用户消息添加到状态
  3. 模拟1秒后添加AI响应
  4. 消息列表自动滚动到最新消息

12. 扩展建议

  1. 添加加载状态:在AI响应时显示加载指示器
  2. 实现真正的API调用:替换假接口为实际的API
  3. 添加消息删除功能:允许用户删除消息
  4. 实现消息编辑:允许用户编辑已发送的消息
  5. 添加主题切换:实现深色/浅色模式

13. 总结

通过这个简单的示例,我们使用Tailwind CSS实现了仿ChatGPT的聊天界面。Tailwind CSS的实用类使我们能够快速构建响应式UI,而Zustand则提供了简洁的状态管理。

关键点:

  • 使用Tailwind的实用类快速构建UI
  • 通过Zustand管理应用状态
  • 使用假接口模拟后端交互
  • 确保界面在不同设备上的响应式体验

这个项目是学习Tailwind CSS和现代前端开发模式的绝佳起点。随着你对Tailwind的熟悉,可以尝试添加更多功能,如消息表情、图片上传等。

14. 最终效果

在这里插入图片描述

也太丑了吧。。。

(注:实际效果请运行代码查看)

15. 下一步学习

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐