商机智能助手项目总结 - 面试准备
📋 项目概述
项目名称:商机智能助手(Agent Chat)
项目定位:面向房产经纪人的智能商机管理和分析平台
项目类型:移动端H5应用(主要运行在贝壳APP内)
开发模式:前后端分离,SPA单页应用
🛠️ 技术栈
核心技术
- 框架:React 18.2.0(函数式组件 + Hooks)
- 语言:TypeScript 5.6.3 + JavaScript (JSX)
- 构建工具:Webpack 5.91.0
- 编译工具:SWC(替代Babel,编译速度更快)
- 路由:React Router DOM 6.26.2
UI框架
- PC端组件库:Ant Design 5.18.1
- 移动端组件库:Ant Design Mobile 5.39.0
- 样式方案:
- Less(主要)
- Sass/SCSS
- Stylus
- PostCSS + postcss-pxtorem(px转rem)
开发工具链
- 代码规范:
- ESLint(Airbnb配置 + TypeScript支持)
- Prettier(代码格式化)
- Husky(Git Hooks)
- lint-staged(提交前检查)
- 类型检查:TypeScript(严格模式)
- CSS处理:CSS-in-JS(@ant-design/cssinjs)
其他技术
- 流式数据处理:SSE (Server-Sent Events) + @ant-design/x
- 图表/可视化:html2canvas(截图功能)
- 工具库:Lodash
- 轮播组件:Swiper 11.2.10
- Markdown渲染:markdown-it
✨ 项目亮点
1. 流式聊天实现(核心亮点)
- 技术方案:使用SSE(Server-Sent Events)实现AI流式对话
- 实现细节:
- 基于
@ant-design/x的useXAgent和useXChatHooks - 实时接收服务器推送的消息片段,逐字显示
- 支持消息中断、重试机制
- 区分prompt和普通消息类型,分别处理
- 基于
- 业务价值:提供类似ChatGPT的实时对话体验,提升用户体验
// 流式消息处理核心逻辑
onUpdate: (msg) => {
if (msg.data && JSON.parse(msg.data).event === 'message') {
const parseData = JSON.parse(msg.data);
let { content, msgId, msgType } = parseData;
// 区分prompt和普通消息,分别处理
if (msgType === 'prompt') {
currentContentObj.promptList.push(content);
} else {
contentText += content; // 逐字累加
}
onUpdate(currentContent);
}
}
2. 完善的响应式布局方案
- 自定义响应式工具类:
ResponsiveUtil - 实现原理:
- 基于设计稿宽度(375px)动态计算根字体大小
- 监听窗口resize和orientationchange事件
- 支持最小宽度(320px)和最大宽度(750px)限制
- 提供px2rem、px2px等工具方法
- 优势:一套代码适配多种移动设备,无需媒体查询
3. 多环境配置管理
- 环境区分:development、test、preview、production
- CDN配置:不同环境使用不同的CDN地址
- 构建脚本:
build:production、build:test、build:preview - 配置文件:独立的conf文件管理各环境配置
4. 完善的错误处理机制
- 错误边界(Error Boundary):每个路由都配置了ErrorBoundary组件
- 系统级加载器:
SystemLoader统一处理加载和错误状态 - 用户上下文错误处理:UserContext中完善的错误捕获和重试机制
- API错误统一处理:
responseHandler.js统一处理登录态、错误码等
5. TypeScript渐进式迁移
- 混合开发:同时支持
.tsx和.jsx文件 - 类型安全:
- API接口定义完整的TypeScript类型
- 组件Props类型定义
- 工具函数类型声明
- 优势:在保持现有代码可用的同时,逐步提升代码质量
6. 性能优化
- Webpack优化:
- 使用SWC替代Babel(编译速度提升)
- 生产环境代码压缩(TerserPlugin)
- CSS提取(MiniCssExtractPlugin)
- 文件系统缓存(filesystem cache)
- 代码分割:按路由进行代码分割
- 资源优化:图片、字体等静态资源hash命名,支持CDN缓存
7. 与原生APP深度集成
- JsBridge集成:通过
JsBridgeV3与贝壳APP通信 - 功能:
- 获取用户信息、token管理
- 设置页面标题
- Cookie域名管理
- 埋点数据上报
- 埋点系统:集成
$ULOG埋点系统,支持页面和事件埋点
8. 组件化设计
- 业务组件:高度封装的业务组件(如
HomeInfo、TaskEditModal等) - 通用组件:可复用的UI组件(如
AppNavBar、CommonFilter等) - 组件文档:部分组件包含README和demo文件
🎯 项目难点与解决方案
1. 流式消息的实时渲染和状态管理
难点:
- SSE连接的生命周期管理
- 消息片段的累积和渲染
- 用户中断请求的处理
- 消息历史记录的保存和恢复
解决方案:
- 使用
useRef保存当前消息内容,避免闭包问题 - 通过
abortRef管理请求中断 - 区分消息类型(prompt/普通消息),分别处理
- 使用
useXChatHook管理消息列表状态
// 中断处理
abortRef.current = async () => {
setIsStopSse(true);
onSuccess(currentContent); // 清空长链接队列
};
2. 移动端适配的复杂性
难点:
- 不同设备尺寸适配
- 横竖屏切换
- 安全区域适配(刘海屏、底部安全区)
- 字体大小和间距的精确控制
解决方案:
- 自定义响应式工具类,统一管理根字体大小
- 使用rem单位,配合postcss-pxtorem自动转换
- 监听orientationchange事件,及时更新布局
- 设置合理的minWidth和maxWidth限制
3. 多环境配置和构建优化
难点:
- 不同环境需要不同的API地址、CDN地址
- 构建产物需要区分环境
- 开发、测试、预发、生产环境的配置管理
解决方案:
- 使用
cross-env设置环境变量 - Webpack的
DefinePlugin注入环境变量 - 独立的配置文件(conf目录)
- 不同环境的构建脚本
4. 用户权限和角色管理
难点:
- 多角色切换(门店管理者、普通经纪人等)
- 权限校验和页面访问控制
- 用户信息获取失败的处理
解决方案:
- 使用Context API(UserContext)全局管理用户状态
- 系统级加载器(SystemLoader)统一处理加载和错误
- 完善的错误提示和重试机制
- 角色选择弹窗,支持动态切换
5. 复杂表单的状态管理
难点:
- 任务编辑弹窗包含多个联动选择器
- 表单验证和预览功能
- 编辑和新增模式的区分
解决方案:
- 使用
useState和useMemo管理复杂表单状态 - 组件化设计,将选择器封装为独立组件(
InfoSelector) - 使用
useCallback优化回调函数性能 - 清晰的类型定义,保证数据一致性
6. 页面状态的多条件判断
难点:
- 首页根据API返回的
statusType展示不同组件 - 加载、错误、空数据等多种状态的统一管理
- 状态切换时的埋点上报
解决方案:
- 使用状态机模式,统一管理页面状态
- 条件渲染,根据状态展示对应组件
useCallback封装埋点函数,避免重复创建- 清晰的类型定义(
PageStatus、HomeState)
实现sse hook封装
import { useCallback, useRef, useState, useEffect } from 'react';
interface SSEChatOptions {
/** 基础地址,如:/sseApi/api/chat/stream */
baseURL: string;
}
interface SSEMessage {
id?: string;
content: string;
raw?: any;
}
export function useSSEChat(options: SSEChatOptions) {
const { baseURL } = options;
const [messages, setMessages] = useState<SSEMessage[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
const [error, setError] = useState<Error | null>(null);
// 当前 EventSource 实例
const eventSourceRef = useRef<EventSource | null>(null);
// 当前累积的回复内容(类似你项目里的 currentContent)
const currentContentRef = useRef<string>('');
const closeEventSource = useCallback(() => {
if (eventSourceRef.current) {
eventSourceRef.current.close();
eventSourceRef.current = null;
}
setIsStreaming(false);
}, []);
// 发送问题并建立 SSE 连接
const sendMessage = useCallback(
(query: string) => {
// 每次新问题先关掉旧连接
closeEventSource();
setError(null);
currentContentRef.current = '';
// 一般 SSE 用 GET + query 参数,这里简单拼一下
const url = `${baseURL}?query=${encodeURIComponent(query)}&stream=true`;
const es = new EventSource(url);
eventSourceRef.current = es;
setIsStreaming(true);
es.onopen = () => {
// console.log('SSE 连接已建立');
};
es.onerror = (e: any) => {
setError(new Error('SSE 连接出错'));
closeEventSource();
};
es.onmessage = (event: MessageEvent) => {
try {
// 和你项目类似,后端通常发 JSON 字符串
const data = JSON.parse(event.data);
// 示例:后端有 event 字段 / msgType / content 等
if (data.event && data.event !== 'message') return;
const { content, msgId, msgType } = data;
// 结束信号
if (content === 'connected' || content === 'completed') {
closeEventSource();
return;
}
// 累加内容(你也可以按 msgType 拆分 prompt / content)
currentContentRef.current += content ?? '';
// 更新 React 状态(这里简单以“最后一条回复”为例)
setMessages((prev) => {
const last = prev[prev.length - 1];
if (last && last.id === msgId) {
// 更新最后一条
const newLast: SSEMessage = {
...last,
content: currentContentRef.current,
raw: data,
};
return [...prev.slice(0, -1), newLast];
}
// 新增一条消息
return [
...prev,
{
id: msgId,
content: currentContentRef.current,
raw: data,
},
];
});
} catch (err) {
console.error('解析 SSE 消息失败', err);
}
};
},
[baseURL, closeEventSource],
);
// 手动停止流
const stop = useCallback(() => {
closeEventSource();
}, [closeEventSource]);
// 组件卸载时清理
useEffect(() => {
return () => {
closeEventSource();
};
}, [closeEventSource]);
return {
messages,
isStreaming,
error,
sendMessage,
stop,
};
}
📊 项目架构
目录结构
src/
├── apis/ # API接口层
│ ├── interface/ # TypeScript接口定义
│ └── services/ # API服务实现
├── components/ # 组件库
│ ├── HomeInfo/ # 业务组件
│ ├── TaskEditModal/# 通用组件
│ └── ...
├── pages/ # 页面组件
│ ├── Home/ # 首页
│ ├── agentChatH5/ # AI聊天页
│ └── ...
├── contexts/ # Context API
│ └── UserContext.jsx
├── hooks/ # 自定义Hooks
├── utils/ # 工具函数
│ ├── request.ts # HTTP请求封装
│ ├── responsive.js # 响应式工具
│ └── ...
└── config/ # 配置文件
└── routes.jsx # 路由配置
数据流
- 用户信息流:UserContext → SystemLoader → 路由组件
- API数据流:services → request.ts → responseHandler → 组件
- 状态管理:组件内部useState + Context API全局状态
🚀 性能优化实践
- 编译优化:SWC替代Babel,编译速度提升3-5倍
- 代码分割:按路由懒加载,减少首屏加载时间
- 资源优化:图片、字体等静态资源CDN加速
- 缓存策略:Webpack filesystem cache,二次构建速度提升
- React优化:
- 使用
useMemo和useCallback避免不必要的重渲染 - 组件拆分,减少单组件复杂度
- 使用
🔒 代码质量保障
- 类型安全:TypeScript类型检查
- 代码规范:ESLint + Prettier
- Git Hooks:Husky + lint-staged,提交前自动检查
- 错误处理:完善的错误边界和异常捕获
- 代码审查:组件文档和注释规范
📱 业务功能模块
- 智能聊天:AI助手对话,流式消息展示
- 首页诊断:门店数据分析和诊断报告
- 任务管理:任务下发、执行、编辑
- 商机跟进:商机列表、跟进记录
- 数据分析:门店转化、用户行为分析
- 经纪人管理:经纪人信息管理
- IM质量报告:IM沟通质量分析
💡 面试重点问题准备
Q1: 为什么选择SWC而不是Babel?
答:SWC是用Rust编写的,编译速度比Babel快10-100倍,同时支持TypeScript和JSX,能够显著提升开发体验和构建速度。
Q2: 流式聊天是如何实现的?
答:使用SSE(Server-Sent Events)技术,通过@ant-design/x的Hooks封装,实时接收服务器推送的消息片段,使用useRef保存当前消息内容,通过onUpdate回调逐字更新UI,支持消息中断和重试机制。
Q3: 移动端适配方案?
答:自定义响应式工具类,基于设计稿宽度(375px)动态计算根字体大小,使用rem单位配合postcss-pxtorem自动转换,监听窗口变化事件,设置合理的宽度限制。
Q4: 如何处理多环境配置?
答:使用cross-env设置环境变量,Webpack的DefinePlugin注入到代码中,不同环境使用不同的CDN地址和API地址,独立的配置文件管理。
Q5: 错误处理机制?
答:路由级错误边界、系统级加载器、API统一错误处理、用户上下文错误捕获,完善的错误提示和重试机制。
Q6: 性能优化做了哪些工作?
答:SWC编译优化、代码分割、资源CDN、Webpack缓存、React性能优化(useMemo、useCallback)、组件拆分。
📝 项目总结
这是一个技术栈现代化、架构清晰、业务复杂的移动端H5项目。项目在以下方面表现突出:
- 技术选型合理:React 18 + TypeScript + Webpack 5,使用SWC提升构建速度
- 用户体验优秀:流式聊天、响应式布局、完善的错误处理
- 代码质量高:TypeScript类型安全、ESLint规范、完善的错误处理
- 工程化完善:多环境配置、Git Hooks、性能优化
项目规模:10+页面,20+组件,完善的API层和工具函数库
技术难点:流式数据处理、移动端适配、复杂状态管理、多环境配置
🎓 学习收获
- 深入理解React Hooks:useState、useEffect、useCallback、useMemo等
- SSE流式数据处理:实时通信的实现和优化
- 移动端适配方案:rem布局、响应式设计
- 工程化实践:Webpack配置、多环境管理、代码规范
- TypeScript实践:类型定义、接口设计、渐进式迁移
最后更新:2024年