mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-28 05:33:11 +08:00
重构分析架构,彻底解决重复历史记录问题
This commit is contained in:
BIN
numerology.db
BIN
numerology.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -39,34 +39,12 @@ router.post('/bazi', authenticate, asyncHandler(async (req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行八字分析
|
||||
// 执行八字分析(纯分析,不存储历史记录)
|
||||
const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birth_data);
|
||||
|
||||
// 保存到数据库
|
||||
const db = getDB();
|
||||
const insertReading = db.prepare(`
|
||||
INSERT INTO numerology_readings (
|
||||
user_id, reading_type, name, birth_date, birth_time, birth_place, gender,
|
||||
input_data, analysis, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = insertReading.run(
|
||||
req.user.id,
|
||||
'bazi',
|
||||
birth_data.name,
|
||||
birth_data.birth_date,
|
||||
birth_data.birth_time || null,
|
||||
birth_data.birth_place || null,
|
||||
birth_data.gender || null,
|
||||
JSON.stringify(birth_data),
|
||||
JSON.stringify(analysisResult),
|
||||
'completed'
|
||||
);
|
||||
|
||||
// 只返回分析结果,不存储历史记录
|
||||
res.json({
|
||||
data: {
|
||||
record_id: result.lastInsertRowid,
|
||||
analysis: analysisResult
|
||||
}
|
||||
});
|
||||
@@ -86,38 +64,16 @@ router.post('/yijing', authenticate, asyncHandler(async (req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行易经分析
|
||||
// 执行易经分析(纯分析,不存储历史记录)
|
||||
const analysisResult = yijingAnalyzer.performYijingAnalysis({
|
||||
question: question,
|
||||
user_id: user_id || req.user.id,
|
||||
divination_method: divination_method || 'time'
|
||||
});
|
||||
|
||||
// 保存到数据库
|
||||
const db = getDB();
|
||||
const insertReading = db.prepare(`
|
||||
INSERT INTO numerology_readings (
|
||||
user_id, reading_type, name, birth_date, birth_time, birth_place, gender,
|
||||
input_data, analysis, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = insertReading.run(
|
||||
req.user.id,
|
||||
'yijing',
|
||||
'易经占卜用户', // 易经占卜不需要真实姓名
|
||||
null, // 不需要出生日期
|
||||
null, // 不需要出生时间
|
||||
null, // 不需要出生地点
|
||||
null, // 不需要性别
|
||||
JSON.stringify({ question, divination_method }),
|
||||
JSON.stringify(analysisResult),
|
||||
'completed'
|
||||
);
|
||||
|
||||
// 只返回分析结果,不存储历史记录
|
||||
res.json({
|
||||
data: {
|
||||
record_id: result.lastInsertRowid,
|
||||
analysis: analysisResult
|
||||
}
|
||||
});
|
||||
@@ -152,34 +108,12 @@ router.post('/ziwei', authenticate, asyncHandler(async (req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行紫微斗数分析
|
||||
// 执行紫微斗数分析(纯分析,不存储历史记录)
|
||||
const analysisResult = ziweiAnalyzer.performRealZiweiAnalysis(birth_data);
|
||||
|
||||
// 保存到数据库
|
||||
const db = getDB();
|
||||
const insertReading = db.prepare(`
|
||||
INSERT INTO numerology_readings (
|
||||
user_id, reading_type, name, birth_date, birth_time, birth_place, gender,
|
||||
input_data, analysis, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = insertReading.run(
|
||||
req.user.id,
|
||||
'ziwei',
|
||||
birth_data.name,
|
||||
birth_data.birth_date,
|
||||
birth_data.birth_time || null,
|
||||
birth_data.birth_place || null,
|
||||
birth_data.gender || null,
|
||||
JSON.stringify(birth_data),
|
||||
JSON.stringify(analysisResult),
|
||||
'completed'
|
||||
);
|
||||
|
||||
// 只返回分析结果,不存储历史记录
|
||||
res.json({
|
||||
data: {
|
||||
record_id: result.lastInsertRowid,
|
||||
analysis: analysisResult
|
||||
}
|
||||
});
|
||||
@@ -189,6 +123,79 @@ router.post('/ziwei', authenticate, asyncHandler(async (req, res) => {
|
||||
}
|
||||
}));
|
||||
|
||||
// 历史记录存储接口
|
||||
router.post('/save-history', authenticate, asyncHandler(async (req, res) => {
|
||||
const { analysis_type, analysis_data, input_data } = req.body;
|
||||
|
||||
// 输入验证
|
||||
if (!analysis_type || !analysis_data) {
|
||||
throw new AppError('缺少必要参数:分析类型和分析数据', 400, 'MISSING_REQUIRED_DATA');
|
||||
}
|
||||
|
||||
// 验证分析类型
|
||||
const validTypes = ['bazi', 'ziwei', 'yijing'];
|
||||
if (!validTypes.includes(analysis_type)) {
|
||||
throw new AppError('无效的分析类型', 400, 'INVALID_ANALYSIS_TYPE');
|
||||
}
|
||||
|
||||
try {
|
||||
const db = getDB();
|
||||
|
||||
// 根据分析类型准备不同的数据
|
||||
let name, birth_date, birth_time, birth_place, gender;
|
||||
|
||||
if (analysis_type === 'yijing') {
|
||||
// 易经占卜:获取用户档案信息
|
||||
const getUserProfile = db.prepare('SELECT full_name FROM user_profiles WHERE user_id = ?');
|
||||
const userProfile = getUserProfile.get(req.user.id);
|
||||
name = userProfile?.full_name || '易经占卜用户';
|
||||
birth_date = null;
|
||||
birth_time = null;
|
||||
birth_place = null;
|
||||
gender = null;
|
||||
} else {
|
||||
// 八字和紫微:从输入数据中获取
|
||||
name = input_data?.name || '用户';
|
||||
birth_date = input_data?.birth_date || null;
|
||||
birth_time = input_data?.birth_time || null;
|
||||
birth_place = input_data?.birth_place || null;
|
||||
gender = input_data?.gender || null;
|
||||
}
|
||||
|
||||
// 插入历史记录
|
||||
const insertReading = db.prepare(`
|
||||
INSERT INTO numerology_readings (
|
||||
user_id, reading_type, name, birth_date, birth_time, birth_place, gender,
|
||||
input_data, analysis, status, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = insertReading.run(
|
||||
req.user.id,
|
||||
analysis_type,
|
||||
name,
|
||||
birth_date,
|
||||
birth_time,
|
||||
birth_place,
|
||||
gender,
|
||||
JSON.stringify(input_data || {}),
|
||||
JSON.stringify(analysis_data),
|
||||
'completed',
|
||||
new Date().toISOString()
|
||||
);
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
record_id: result.lastInsertRowid,
|
||||
message: '历史记录保存成功'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('保存历史记录错误:', error);
|
||||
throw new AppError('保存历史记录失败', 500, 'SAVE_HISTORY_ERROR');
|
||||
}
|
||||
}));
|
||||
|
||||
// 综合分析接口(可选)
|
||||
router.post('/comprehensive', authenticate, asyncHandler(async (req, res) => {
|
||||
const { birth_data, include_types } = req.body;
|
||||
|
||||
@@ -79,7 +79,7 @@ class BaziAnalyzer {
|
||||
|
||||
return {
|
||||
analysis_type: 'bazi',
|
||||
analysis_date: new Date().toISOString().split('T')[0],
|
||||
analysis_date: new Date().toISOString(),
|
||||
basic_info: {
|
||||
personal_data: {
|
||||
name: personalizedName,
|
||||
|
||||
@@ -58,7 +58,7 @@ class YijingAnalyzer {
|
||||
|
||||
return {
|
||||
analysis_type: 'yijing',
|
||||
analysis_date: currentTime.toISOString().split('T')[0],
|
||||
analysis_date: currentTime.toISOString(),
|
||||
basic_info: {
|
||||
divination_data: {
|
||||
question: question,
|
||||
|
||||
@@ -67,7 +67,7 @@ class ZiweiAnalyzer {
|
||||
|
||||
return {
|
||||
analysis_type: 'ziwei',
|
||||
analysis_date: new Date().toISOString().split('T')[0],
|
||||
analysis_date: new Date().toISOString(),
|
||||
basic_info: {
|
||||
personal_data: {
|
||||
name: personName,
|
||||
|
||||
@@ -14,6 +14,7 @@ interface AnalysisResultDisplayProps {
|
||||
question?: string;
|
||||
userId?: string;
|
||||
divinationMethod?: string;
|
||||
preAnalysisData?: any; // 预先分析的数据,用于历史记录
|
||||
}
|
||||
|
||||
const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
@@ -22,7 +23,8 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
birthDate,
|
||||
question,
|
||||
userId,
|
||||
divinationMethod
|
||||
divinationMethod,
|
||||
preAnalysisData
|
||||
}) => {
|
||||
// 安全地获取数据的辅助函数
|
||||
const safeGet = (obj: any, path: string, defaultValue: any = '暂无数据') => {
|
||||
@@ -58,7 +60,7 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
const renderBaziAnalysis = () => {
|
||||
// 如果有 birthDate,使用新的 CompleteBaziAnalysis 组件
|
||||
if (birthDate) {
|
||||
return <CompleteBaziAnalysis birthDate={birthDate} />;
|
||||
return <CompleteBaziAnalysis birthDate={birthDate} analysisData={preAnalysisData} />;
|
||||
}
|
||||
// 如果有分析结果但没有 birthDate,尝试从结果中提取出生信息
|
||||
if (analysisResult && analysisResult.data) {
|
||||
@@ -70,21 +72,22 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
name: basicInfo.personal_data.name || '',
|
||||
gender: basicInfo.personal_data.gender === '男性' ? 'male' : 'female'
|
||||
};
|
||||
return <CompleteBaziAnalysis birthDate={extractedBirthDate} />;
|
||||
return <CompleteBaziAnalysis birthDate={extractedBirthDate} analysisData={preAnalysisData} />;
|
||||
}
|
||||
}
|
||||
// 回退到旧的组件(向后兼容)
|
||||
if (birthDate) {
|
||||
return <BaziAnalysisDisplay birthDate={birthDate} />;
|
||||
}
|
||||
return <div className="text-center text-red-600 p-8">请提供出生日期和时间进行八字分析</div>;
|
||||
// 如果没有足够的数据,返回错误提示
|
||||
return (
|
||||
<div className="bg-white rounded-lg p-6 shadow-lg">
|
||||
<p className="text-gray-500 text-center">八字分析数据不完整,请重新提交分析</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染紫微斗数分析
|
||||
const renderZiweiAnalysis = () => {
|
||||
// 如果有 birthDate,使用新的 CompleteZiweiAnalysis 组件
|
||||
if (birthDate) {
|
||||
return <CompleteZiweiAnalysis birthDate={birthDate} />;
|
||||
return <CompleteZiweiAnalysis birthDate={birthDate} analysisData={preAnalysisData} />;
|
||||
}
|
||||
// 如果有分析结果但没有 birthDate,尝试从结果中提取出生信息
|
||||
if (analysisResult && analysisResult.data) {
|
||||
@@ -96,7 +99,7 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
name: basicInfo.personal_data.name || '',
|
||||
gender: basicInfo.personal_data.gender === '男性' ? 'male' : 'female'
|
||||
};
|
||||
return <CompleteZiweiAnalysis birthDate={extractedBirthDate} />;
|
||||
return <CompleteZiweiAnalysis birthDate={extractedBirthDate} analysisData={preAnalysisData} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,13 +442,14 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
question={question}
|
||||
userId={userId}
|
||||
divinationMethod={divinationMethod}
|
||||
analysisData={preAnalysisData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 对于紫微斗数,如果有 birthDate 参数,直接返回 CompleteZiweiAnalysis 组件(不添加额外容器)
|
||||
if (analysisType === 'ziwei' && birthDate) {
|
||||
return <CompleteZiweiAnalysis birthDate={birthDate} />;
|
||||
return <CompleteZiweiAnalysis birthDate={birthDate} analysisData={preAnalysisData} />;
|
||||
}
|
||||
|
||||
// 如果没有分析结果数据
|
||||
|
||||
@@ -11,12 +11,13 @@ interface CompleteBaziAnalysisProps {
|
||||
name?: string;
|
||||
gender?: string;
|
||||
};
|
||||
analysisData?: any; // 可选的预先分析的数据
|
||||
}
|
||||
|
||||
const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate, analysisData: propAnalysisData }) => {
|
||||
const [isLoading, setIsLoading] = useState(!propAnalysisData);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [analysisData, setAnalysisData] = useState<any>(null);
|
||||
const [analysisData, setAnalysisData] = useState<any>(propAnalysisData || null);
|
||||
|
||||
// 五行颜色配置
|
||||
const elementColors: { [key: string]: string } = {
|
||||
@@ -52,6 +53,13 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate }
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 如果已经有分析数据,直接使用
|
||||
if (propAnalysisData) {
|
||||
setAnalysisData(propAnalysisData);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchAnalysisData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -84,10 +92,10 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate }
|
||||
}
|
||||
};
|
||||
|
||||
if (birthDate?.date) {
|
||||
if (birthDate?.date && !propAnalysisData) {
|
||||
fetchAnalysisData();
|
||||
}
|
||||
}, [birthDate]);
|
||||
}, [birthDate?.date, birthDate?.time, birthDate?.name, birthDate?.gender, propAnalysisData]);
|
||||
|
||||
// 渲染加载状态
|
||||
if (isLoading) {
|
||||
@@ -284,7 +292,7 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate }
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<User className="h-5 w-5" />
|
||||
<span>{analysisData.basic_info?.personal_data?.gender}</span>
|
||||
<span>{analysisData.basic_info?.personal_data?.gender === 'male' ? '男性' : analysisData.basic_info?.personal_data?.gender === 'female' ? '女性' : analysisData.basic_info?.personal_data?.gender}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -759,7 +767,7 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate }
|
||||
<CardContent className="text-center py-8">
|
||||
<div className="text-red-800">
|
||||
<p className="text-lg font-bold mb-2">专业八字命理分析报告</p>
|
||||
<p className="text-sm">分析日期:{analysisData.analysis_date}</p>
|
||||
<p className="text-sm">分析日期:{analysisData.analysis_date ? new Date(analysisData.analysis_date).toLocaleString('zh-CN') : new Date().toLocaleString('zh-CN')}</p>
|
||||
<p className="text-xs mt-4 text-red-600">
|
||||
本报告基于传统四柱八字理论,结合现代命理学研究成果,为您提供专业的命理分析和人生指导。
|
||||
</p>
|
||||
|
||||
@@ -4,19 +4,21 @@ import { Card, CardContent, CardHeader, CardTitle } from './ui/Card';
|
||||
import { localApi } from '../lib/localApi';
|
||||
|
||||
interface CompleteYijingAnalysisProps {
|
||||
question: string;
|
||||
question?: string;
|
||||
userId?: string;
|
||||
divinationMethod?: string;
|
||||
analysisData?: any; // 可选的预先分析的数据
|
||||
}
|
||||
|
||||
const CompleteYijingAnalysis: React.FC<CompleteYijingAnalysisProps> = ({
|
||||
question,
|
||||
userId = 'user123',
|
||||
divinationMethod = 'time'
|
||||
divinationMethod = 'time',
|
||||
analysisData: propAnalysisData
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(!propAnalysisData);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [analysisData, setAnalysisData] = useState<any>(null);
|
||||
const [analysisData, setAnalysisData] = useState<any>(propAnalysisData || null);
|
||||
|
||||
// 卦象颜色配置
|
||||
const hexagramColors: { [key: string]: string } = {
|
||||
@@ -49,6 +51,13 @@ const CompleteYijingAnalysis: React.FC<CompleteYijingAnalysisProps> = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 如果已经有分析数据,直接使用
|
||||
if (propAnalysisData) {
|
||||
setAnalysisData(propAnalysisData);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchAnalysisData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -80,10 +89,10 @@ const CompleteYijingAnalysis: React.FC<CompleteYijingAnalysisProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (question) {
|
||||
if (question && !propAnalysisData) {
|
||||
fetchAnalysisData();
|
||||
}
|
||||
}, [question, userId, divinationMethod]);
|
||||
}, [question, userId, divinationMethod, propAnalysisData]);
|
||||
|
||||
// 渲染加载状态
|
||||
if (isLoading) {
|
||||
@@ -263,11 +272,7 @@ const CompleteYijingAnalysis: React.FC<CompleteYijingAnalysisProps> = ({
|
||||
<span>易经占卜分析报告</span>
|
||||
<Hexagon className="h-8 w-8" />
|
||||
</CardTitle>
|
||||
<div className="flex justify-center space-x-6 mt-4 text-red-700">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar className="h-5 w-5" />
|
||||
<span>{analysisData.analysis_date}</span>
|
||||
</div>
|
||||
<div className="flex justify-center mt-4 text-red-700">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Clock className="h-5 w-5" />
|
||||
<span>{new Date(analysisData.basic_info.divination_data.divination_time).toLocaleString('zh-CN')}</span>
|
||||
|
||||
@@ -11,12 +11,13 @@ interface CompleteZiweiAnalysisProps {
|
||||
name?: string;
|
||||
gender?: string;
|
||||
};
|
||||
analysisData?: any; // 可选的预先分析的数据
|
||||
}
|
||||
|
||||
const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate, analysisData: propAnalysisData }) => {
|
||||
const [isLoading, setIsLoading] = useState(!propAnalysisData);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [analysisData, setAnalysisData] = useState<any>(null);
|
||||
const [analysisData, setAnalysisData] = useState<any>(propAnalysisData || null);
|
||||
|
||||
// 四化飞星详细解释
|
||||
const sihuaExplanations = {
|
||||
@@ -251,6 +252,13 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 如果已经有分析数据,直接使用
|
||||
if (propAnalysisData) {
|
||||
setAnalysisData(propAnalysisData);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchAnalysisData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -283,10 +291,10 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
|
||||
}
|
||||
};
|
||||
|
||||
if (birthDate?.date) {
|
||||
if (birthDate?.date && !propAnalysisData) {
|
||||
fetchAnalysisData();
|
||||
}
|
||||
}, [birthDate]);
|
||||
}, [birthDate?.date, birthDate?.time, birthDate?.name, birthDate?.gender, propAnalysisData]);
|
||||
|
||||
// 渲染加载状态
|
||||
if (isLoading) {
|
||||
@@ -467,7 +475,7 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<User className="h-5 w-5" />
|
||||
<span>{analysisData.basic_info?.personal_data?.gender}</span>
|
||||
<span>{analysisData.basic_info?.personal_data?.gender === 'male' ? '男性' : analysisData.basic_info?.personal_data?.gender === 'female' ? '女性' : analysisData.basic_info?.personal_data?.gender}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -1122,7 +1130,7 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
|
||||
人生的幸福需要通过自己的努力和智慧来创造。
|
||||
</p>
|
||||
<div className="mt-4 text-xs text-gray-500">
|
||||
分析时间:{new Date().toLocaleString('zh-CN')}
|
||||
分析时间:{analysisData.analysis_date ? new Date(analysisData.analysis_date).toLocaleString('zh-CN') : new Date().toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -23,6 +23,7 @@ interface AuthResponse {
|
||||
|
||||
class LocalApiClient {
|
||||
private token: string | null = null;
|
||||
private pendingRequests: Map<string, Promise<any>> = new Map();
|
||||
|
||||
constructor() {
|
||||
// 从localStorage恢复token
|
||||
@@ -176,30 +177,60 @@ class LocalApiClient {
|
||||
},
|
||||
};
|
||||
|
||||
// 生成请求唯一键
|
||||
private generateRequestKey(endpoint: string, data: any): string {
|
||||
return `${endpoint}:${JSON.stringify(data)}`;
|
||||
}
|
||||
|
||||
// 带去重的请求方法
|
||||
private async requestWithDeduplication<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit,
|
||||
data: any
|
||||
): Promise<ApiResponse<T>> {
|
||||
const requestKey = this.generateRequestKey(endpoint, data);
|
||||
|
||||
// 如果已有相同请求在进行中,返回该请求的Promise
|
||||
if (this.pendingRequests.has(requestKey)) {
|
||||
return this.pendingRequests.get(requestKey);
|
||||
}
|
||||
|
||||
// 创建新请求
|
||||
const requestPromise = this.request<T>(endpoint, options).finally(() => {
|
||||
// 请求完成后清除缓存
|
||||
this.pendingRequests.delete(requestKey);
|
||||
});
|
||||
|
||||
// 缓存请求Promise
|
||||
this.pendingRequests.set(requestKey, requestPromise);
|
||||
|
||||
return requestPromise;
|
||||
}
|
||||
|
||||
// 分析相关方法
|
||||
analysis = {
|
||||
// 八字分析
|
||||
bazi: async (birthData: any): Promise<ApiResponse<{ record_id: number; analysis: any }>> => {
|
||||
return this.request<{ record_id: number; analysis: any }>('/analysis/bazi', {
|
||||
return this.requestWithDeduplication<{ record_id: number; analysis: any }>('/analysis/bazi', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ birth_data: birthData }),
|
||||
});
|
||||
}, birthData);
|
||||
},
|
||||
|
||||
// 紫微斗数分析
|
||||
ziwei: async (birthData: any): Promise<ApiResponse<{ record_id: number; analysis: any }>> => {
|
||||
return this.request<{ record_id: number; analysis: any }>('/analysis/ziwei', {
|
||||
return this.requestWithDeduplication<{ record_id: number; analysis: any }>('/analysis/ziwei', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ birth_data: birthData }),
|
||||
});
|
||||
}, birthData);
|
||||
},
|
||||
|
||||
// 易经分析
|
||||
yijing: async (yijingData: any): Promise<ApiResponse<{ record_id: number; analysis: any }>> => {
|
||||
return this.request<{ record_id: number; analysis: any }>('/analysis/yijing', {
|
||||
return this.requestWithDeduplication<{ record_id: number; analysis: any }>('/analysis/yijing', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(yijingData),
|
||||
});
|
||||
}, yijingData);
|
||||
},
|
||||
|
||||
// 综合分析
|
||||
@@ -222,6 +253,18 @@ class LocalApiClient {
|
||||
body: JSON.stringify({ birth_data: birthData, analysis_type: analysisType }),
|
||||
});
|
||||
},
|
||||
|
||||
// 保存历史记录
|
||||
saveHistory: async (analysisType: string, analysisData: any, inputData?: any): Promise<ApiResponse<{ record_id: number; message: string }>> => {
|
||||
return this.request<{ record_id: number; message: string }>('/analysis/save-history', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
analysis_type: analysisType,
|
||||
analysis_data: analysisData,
|
||||
input_data: inputData
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// 历史记录相关方法
|
||||
|
||||
@@ -5,9 +5,7 @@ import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ErrorBoundary>
|
||||
<App />
|
||||
</ErrorBoundary>
|
||||
</StrictMode>,
|
||||
<ErrorBoundary>
|
||||
<App />
|
||||
</ErrorBoundary>,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { localApi } from '../lib/localApi';
|
||||
import { Button } from '../components/ui/Button';
|
||||
@@ -22,15 +22,33 @@ const AnalysisPage: React.FC = () => {
|
||||
birth_time: '',
|
||||
gender: 'male' as 'male' | 'female',
|
||||
birth_place: '',
|
||||
question: ''
|
||||
question: '财运'
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [analysisResult, setAnalysisResult] = useState<any>(null);
|
||||
|
||||
// 使用useMemo缓存birthDate对象,避免重复渲染导致useEffect重复执行
|
||||
const memoizedBirthDate = useMemo(() => {
|
||||
if (analysisType === 'bazi' || analysisType === 'ziwei') {
|
||||
return {
|
||||
date: formData.birth_date,
|
||||
time: formData.birth_time,
|
||||
name: formData.name,
|
||||
gender: formData.gender
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}, [analysisType, formData.birth_date, formData.birth_time, formData.name, formData.gender]);
|
||||
|
||||
useEffect(() => {
|
||||
loadProfile();
|
||||
}, [user]);
|
||||
|
||||
// 切换分析类型时清空分析结果
|
||||
useEffect(() => {
|
||||
setAnalysisResult(null);
|
||||
}, [analysisType]);
|
||||
|
||||
const loadProfile = async () => {
|
||||
if (!user) return;
|
||||
|
||||
@@ -45,7 +63,7 @@ const AnalysisPage: React.FC = () => {
|
||||
birth_time: data.birth_time || '',
|
||||
gender: data.gender || 'male',
|
||||
birth_place: data.birth_location || '',
|
||||
question: ''
|
||||
question: '财运'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -113,11 +131,33 @@ const AnalysisPage: React.FC = () => {
|
||||
throw new Error(data.error.message);
|
||||
}
|
||||
|
||||
// 后端返回格式: { data: { record_id, analysis } }
|
||||
// 后端返回格式: { data: { analysis } }
|
||||
const analysisData = data.analysis;
|
||||
|
||||
setAnalysisResult({
|
||||
type: analysisType,
|
||||
data: data.analysis
|
||||
data: analysisData
|
||||
});
|
||||
|
||||
// 分析完成后,保存历史记录
|
||||
try {
|
||||
const inputData = analysisType === 'yijing' ?
|
||||
{ question: formData.question, divination_method: 'time' } :
|
||||
{
|
||||
name: formData.name,
|
||||
birth_date: formData.birth_date,
|
||||
birth_time: formData.birth_time,
|
||||
birth_place: formData.birth_place,
|
||||
gender: formData.gender
|
||||
};
|
||||
|
||||
await localApi.analysis.saveHistory(analysisType, analysisData, inputData);
|
||||
console.log('历史记录保存成功');
|
||||
} catch (historyError: any) {
|
||||
console.error('保存历史记录失败:', historyError);
|
||||
// 历史记录保存失败不影响分析结果显示
|
||||
}
|
||||
|
||||
toast.success('分析完成!');
|
||||
} catch (error: any) {
|
||||
console.error('分析失败:', error);
|
||||
@@ -250,7 +290,16 @@ const AnalysisPage: React.FC = () => {
|
||||
type="date"
|
||||
label="出生日期 *"
|
||||
value={formData.birth_date}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, birth_date: e.target.value }))}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// 验证日期格式:YYYY-MM-DD,确保年份是4位数字
|
||||
if (value && !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||
return; // 不更新状态,保持原值
|
||||
}
|
||||
setFormData(prev => ({ ...prev, birth_date: value }));
|
||||
}}
|
||||
min="1900-01-01"
|
||||
max="2100-12-31"
|
||||
required
|
||||
/>
|
||||
<Calendar className="absolute right-3 top-8 h-4 w-4 text-gray-400 pointer-events-none" />
|
||||
@@ -307,10 +356,7 @@ const AnalysisPage: React.FC = () => {
|
||||
<AnalysisResultDisplay
|
||||
analysisResult={analysisResult}
|
||||
analysisType={analysisType}
|
||||
birthDate={(analysisType === 'bazi' || analysisType === 'ziwei') ? {
|
||||
date: formData.birth_date,
|
||||
time: formData.birth_time
|
||||
} : undefined}
|
||||
birthDate={memoizedBirthDate}
|
||||
question={analysisType === 'yijing' ? formData.question : undefined}
|
||||
userId={user?.id}
|
||||
divinationMethod="time"
|
||||
|
||||
@@ -15,6 +15,29 @@ const HistoryPage: React.FC = () => {
|
||||
const [selectedReading, setSelectedReading] = useState<NumerologyReading | null>(null);
|
||||
const [viewingResult, setViewingResult] = useState(false);
|
||||
|
||||
// 安全地从input_data中获取值的辅助函数
|
||||
const getInputDataValue = (inputData: string | any, key: string, defaultValue: any = null) => {
|
||||
try {
|
||||
if (!inputData) return defaultValue;
|
||||
|
||||
// 如果已经是对象,直接返回
|
||||
if (typeof inputData === 'object') {
|
||||
return inputData[key] || defaultValue;
|
||||
}
|
||||
|
||||
// 如果是字符串,尝试解析JSON
|
||||
if (typeof inputData === 'string') {
|
||||
const parsed = JSON.parse(inputData);
|
||||
return parsed[key] || defaultValue;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
} catch (error) {
|
||||
console.warn('解析input_data失败:', error);
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadHistory();
|
||||
}, [user]);
|
||||
@@ -144,6 +167,18 @@ const HistoryPage: React.FC = () => {
|
||||
<AnalysisResultDisplay
|
||||
analysisResult={selectedReading.analysis}
|
||||
analysisType={selectedReading.reading_type as 'bazi' | 'ziwei' | 'yijing'}
|
||||
birthDate={selectedReading.reading_type !== 'yijing' ? {
|
||||
date: selectedReading.birth_date || '',
|
||||
time: selectedReading.birth_time || '12:00',
|
||||
name: selectedReading.name || '',
|
||||
gender: selectedReading.gender || 'male'
|
||||
} : undefined}
|
||||
question={selectedReading.reading_type === 'yijing' ?
|
||||
getInputDataValue(selectedReading.input_data, 'question', '综合运势如何?') : undefined}
|
||||
userId={selectedReading.user_id?.toString()}
|
||||
divinationMethod={selectedReading.reading_type === 'yijing' ?
|
||||
getInputDataValue(selectedReading.input_data, 'divination_method', 'time') : undefined}
|
||||
preAnalysisData={selectedReading.analysis}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -197,16 +232,20 @@ const HistoryPage: React.FC = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">
|
||||
{reading.name || '未知姓名'} 的{getAnalysisTypeName(reading.reading_type)}
|
||||
{reading.name || '未知姓名'} - {getAnalysisTypeName(reading.reading_type)}
|
||||
</h3>
|
||||
<div className="flex items-center space-x-4 text-sm text-gray-600 mt-1">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="h-3 w-3" />
|
||||
<span>{new Date(reading.created_at).toLocaleDateString('zh-CN')}</span>
|
||||
<span>{new Date(reading.created_at).toLocaleString('zh-CN')}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<User className="h-3 w-3" />
|
||||
<span>{reading.birth_date}</span>
|
||||
<span>
|
||||
{reading.reading_type === 'yijing'
|
||||
? `问题:${getInputDataValue(reading.input_data, 'question', '综合运势').substring(0, 20)}${getInputDataValue(reading.input_data, 'question', '').length > 20 ? '...' : ''}`
|
||||
: reading.birth_date}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface NumerologyReading {
|
||||
birth_time?: string;
|
||||
gender: string;
|
||||
birth_place?: string;
|
||||
input_data: any;
|
||||
input_data: string | any; // JSON字符串或已解析的对象
|
||||
analysis: {
|
||||
bazi?: { bazi_analysis: any };
|
||||
ziwei?: { ziwei_analysis: any };
|
||||
|
||||
Reference in New Issue
Block a user