Compare commits

..

6 Commits

Author SHA1 Message Date
zdl
a446f71c04 feat: 添加 pre-commit hook 检查代码规范
- 新增文件必须使用 TypeScript (.ts/.tsx)
- 禁止使用 fetch,提示使用 axios
- 安装 husky 和 lint-staged 依赖

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 19:19:18 +08:00
zdl
e02cbcd9b7 feat(性能监控): 补全 T0 标记 + PostHog 上报
- index.js: 添加 html-loaded 标记(T0 监控点)
- performanceMonitor.ts: 调用 reportPerformanceMetrics 上报到 PostHog
- 现在完整监控 T0-T5 全部阶段并上报性能指标

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 18:29:35 +08:00
zdl
9bb9eab922 fix(MSW): Bytedesk 添加 mock 数据响应
- 未读消息数量返回 { count: 0 }
- 其他 API 返回通用成功响应
- 解决 mock 模式下 404 错误

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 16:41:45 +08:00
zdl
3d7b0045b7 fix(NotificationContext): Mock 模式下跳过 Socket 连接
- 添加 REACT_APP_ENABLE_MOCK 环境变量检查
- Mock 模式下直接 return,避免连接生产服务器失败的错误
- 消除开发环境控制台的 WebSocket 连接错误

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 16:34:44 +08:00
zdl
ada9f6e778 chore(StockQuoteCard): 删除未使用的 mockData.ts
- mockStockQuoteData 未被任何地方引用
- 数据现在通过 useStockQuoteData hook 从 API 获取

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 16:01:49 +08:00
zdl
07aebbece5 refactor(marketService): 移除 apiRequest 包装函数,统一使用 axios.get
- getMarketSummary, getTradeData, getFundingData, getPledgeData, getRiseAnalysis 改为直接使用 axios.get
- 删除 apiRequest<T> 包装函数
- 代码风格与 getBigDealData, getUnusualData, getMinuteData 保持一致

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 16:00:08 +08:00
8 changed files with 160 additions and 61 deletions

110
.husky/pre-commit Executable file
View File

@@ -0,0 +1,110 @@
#!/bin/sh
# ============================================
# Git Pre-commit Hook
# ============================================
# 规则:
# 1. src 目录下新增的代码文件必须使用 TypeScript (.ts/.tsx)
# 2. 修改的代码不能使用 fetch应使用 axios
# ============================================
# 颜色定义
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
has_error=0
echo ""
echo "🔍 正在检查代码规范..."
echo ""
# ============================================
# 规则 1: 新文件必须使用 TypeScript
# ============================================
# 获取新增的文件(只检查 src 目录下的代码文件)
new_js_files=$(git diff --cached --name-only --diff-filter=A | grep -E '^src/.*\.(js|jsx)$' || true)
if [ -n "$new_js_files" ]; then
echo "${RED}❌ 错误: 发现新增的 JavaScript 文件${NC}"
echo "${YELLOW} 新文件必须使用 TypeScript (.ts/.tsx)${NC}"
echo ""
echo " 以下文件需要改为 TypeScript:"
echo "$new_js_files" | while read file; do
echo " - $file"
done
echo ""
echo " 💡 提示: 请将文件扩展名改为 .ts 或 .tsx"
echo ""
has_error=1
fi
# ============================================
# 规则 2: 禁止使用 fetch应使用 axios
# ============================================
# 获取所有暂存的文件(新增 + 修改)
staged_files=$(git diff --cached --name-only --diff-filter=AM | grep -E '^src/.*\.(js|jsx|ts|tsx)$' || true)
if [ -n "$staged_files" ]; then
# 检查暂存内容中是否包含 fetch 调用
# 使用 git diff --cached 检查实际修改的内容
fetch_found=""
for file in $staged_files; do
# 检查该文件暂存的更改中是否有 fetch 调用
# 排除注释和字符串中的 fetch
# 匹配: fetch(, await fetch, .fetch(
fetch_matches=$(git diff --cached -U0 "$file" 2>/dev/null | grep -E '^\+.*[^a-zA-Z_]fetch\s*\(' | grep -v '^\+\s*//' || true)
if [ -n "$fetch_matches" ]; then
fetch_found="$fetch_found
$file"
fi
done
if [ -n "$fetch_found" ]; then
echo "${RED}❌ 错误: 检测到使用了 fetch API${NC}"
echo "${YELLOW} 请使用 axios 进行 HTTP 请求${NC}"
echo ""
echo " 以下文件包含 fetch 调用:"
echo "$fetch_found" | while read file; do
if [ -n "$file" ]; then
echo " - $file"
fi
done
echo ""
echo " 💡 修改建议:"
echo " ${GREEN}// 替换前${NC}"
echo " fetch('/api/data').then(res => res.json())"
echo ""
echo " ${GREEN}// 替换后${NC}"
echo " import axios from 'axios';"
echo " axios.get('/api/data').then(res => res.data)"
echo ""
has_error=1
fi
fi
# ============================================
# 检查结果
# ============================================
if [ $has_error -eq 1 ]; then
echo "${RED}========================================${NC}"
echo "${RED}提交被阻止,请修复以上问题后重试${NC}"
echo "${RED}========================================${NC}"
echo ""
exit 1
fi
echo "${GREEN}✅ 代码规范检查通过${NC}"
echo ""
# 运行 lint-staged如果配置了
# 可选:在 package.json 中添加 "lint-staged" 配置来启用代码格式化
# if [ -f "package.json" ] && grep -q '"lint-staged"' package.json; then
# npx lint-staged
# fi

View File

@@ -131,12 +131,14 @@
"eslint-plugin-prettier": "3.4.0",
"gulp": "4.0.2",
"gulp-append-prepend": "1.0.9",
"husky": "^9.1.7",
"imagemin": "^9.0.1",
"imagemin-mozjpeg": "^10.0.0",
"imagemin-pngquant": "^10.0.0",
"kill-port": "^2.0.1",
"less": "^4.4.2",
"less-loader": "^12.3.0",
"lint-staged": "^16.2.7",
"msw": "^2.11.5",
"prettier": "2.2.1",
"react-error-overlay": "6.0.9",

View File

@@ -661,6 +661,12 @@ export const NotificationProvider = ({ children }) => {
// ========== 连接到 Socket 服务(⚡ 异步初始化,不阻塞首屏) ==========
useEffect(() => {
// ⚡ Mock 模式下跳过 Socket 连接(避免连接生产服务器失败的错误)
if (process.env.REACT_APP_ENABLE_MOCK === 'true') {
logger.debug('NotificationContext', 'Mock 模式,跳过 Socket 连接');
return;
}
// ⚡ 防止 React Strict Mode 导致的重复初始化
if (socketInitialized) {
logger.debug('NotificationContext', 'Socket 已初始化跳过重复执行Strict Mode 保护)');

View File

@@ -5,6 +5,17 @@ import { BrowserRouter as Router } from 'react-router-dom';
// ⚡ 性能监控:在应用启动时尽早标记
import { performanceMonitor } from './utils/performanceMonitor';
// T0: HTML 加载完成时间点
if (document.readyState === 'complete') {
performanceMonitor.mark('html-loaded');
} else {
window.addEventListener('load', () => {
performanceMonitor.mark('html-loaded');
});
}
// T1: React 开始初始化
performanceMonitor.mark('app-start');
// ⚡ 已删除 brainwave.css项目未安装 Tailwind CSS该文件无效

View File

@@ -1,16 +1,28 @@
// src/mocks/handlers/bytedesk.js
/**
* Bytedesk 客服 Widget MSW Handler
* 使用 passthrough 让请求通过到真实服务器,消除 MSW 警告
* Mock 模式下返回模拟数据
*/
import { http, passthrough } from 'msw';
import { http, HttpResponse, passthrough } from 'msw';
export const bytedeskHandlers = [
// Bytedesk API 请求 - 直接 passthrough
// 匹配 /bytedesk/* 路径(通过代理访问后端)
// 未读消息数量
http.get('/bytedesk/visitor/api/v1/message/unread/count', () => {
return HttpResponse.json({
code: 200,
message: 'success',
data: { count: 0 },
});
}),
// 其他 Bytedesk API - 返回通用成功响应
http.all('/bytedesk/*', () => {
return passthrough();
return HttpResponse.json({
code: 200,
message: 'success',
data: null,
});
}),
// Bytedesk 外部 CDN/服务请求

View File

@@ -2,6 +2,7 @@
// 性能监控工具 - 统计白屏时间和性能指标
import { logger } from './logger';
import { reportPerformanceMetrics } from '../lib/posthog';
/**
* 性能指标接口
@@ -208,6 +209,9 @@ class PerformanceMonitor {
// 性能分析建议
this.analyzePerformance();
// 上报性能指标到 PostHog
reportPerformanceMetrics(this.metrics);
return this.metrics;
}

View File

@@ -23,19 +23,6 @@ interface ApiResponse<T> {
message?: string;
}
/**
* 通用 API 请求函数
*/
const apiRequest = async <T>(url: string): Promise<ApiResponse<T>> => {
try {
const { data } = await axios.get<ApiResponse<T>>(url);
return data;
} catch (error) {
logger.error('marketService', 'apiRequest', error, { url });
throw error;
}
};
/**
* 市场数据服务
*/
@@ -45,7 +32,8 @@ export const marketService = {
* @param stockCode 股票代码
*/
async getMarketSummary(stockCode: string): Promise<ApiResponse<MarketSummary>> {
return apiRequest<MarketSummary>(`/api/market/summary/${stockCode}`);
const { data } = await axios.get<ApiResponse<MarketSummary>>(`/api/market/summary/${stockCode}`);
return data;
},
/**
@@ -54,7 +42,8 @@ export const marketService = {
* @param days 天数,默认 60 天
*/
async getTradeData(stockCode: string, days: number = 60): Promise<ApiResponse<TradeDayData[]>> {
return apiRequest<TradeDayData[]>(`/api/market/trade/${stockCode}?days=${days}`);
const { data } = await axios.get<ApiResponse<TradeDayData[]>>(`/api/market/trade/${stockCode}?days=${days}`);
return data;
},
/**
@@ -63,7 +52,8 @@ export const marketService = {
* @param days 天数,默认 30 天
*/
async getFundingData(stockCode: string, days: number = 30): Promise<ApiResponse<FundingDayData[]>> {
return apiRequest<FundingDayData[]>(`/api/market/funding/${stockCode}?days=${days}`);
const { data } = await axios.get<ApiResponse<FundingDayData[]>>(`/api/market/funding/${stockCode}?days=${days}`);
return data;
},
/**
@@ -91,7 +81,8 @@ export const marketService = {
* @param stockCode 股票代码
*/
async getPledgeData(stockCode: string): Promise<ApiResponse<PledgeData[]>> {
return apiRequest<PledgeData[]>(`/api/market/pledge/${stockCode}`);
const { data } = await axios.get<ApiResponse<PledgeData[]>>(`/api/market/pledge/${stockCode}`);
return data;
},
/**
@@ -109,7 +100,8 @@ export const marketService = {
if (startDate && endDate) {
url += `?start_date=${startDate}&end_date=${endDate}`;
}
return apiRequest<RiseAnalysis[]>(url);
const { data } = await axios.get<ApiResponse<RiseAnalysis[]>>(url);
return data;
},
/**

View File

@@ -1,38 +0,0 @@
import type { StockQuoteCardData } from './types';
/**
* 贵州茅台 Mock 数据
*/
export const mockStockQuoteData: StockQuoteCardData = {
// 基础信息
name: '贵州茅台',
code: '600519.SH',
indexTags: ['沪深300'],
// 价格信息
currentPrice: 2178.5,
changePercent: 3.65,
todayOpen: 2156.0,
yesterdayClose: 2101.0,
todayHigh: 2185.0,
todayLow: 2150.0,
// 关键指标
pe: 38.62,
pb: 14.82,
marketCap: '2.73万亿',
week52Low: 1980,
week52High: 2350,
// 主力动态
mainNetInflow: 1.28,
institutionHolding: 72.35,
buyRatio: 85,
sellRatio: 15,
// 更新时间
updateTime: '2025-12-03 14:30:25',
// 自选状态
isFavorite: false,
};