Save Profile and Download Image Implementation Guide
Overview
This implementation adds two new features to the results page:
- Save Profile Button - Allows users to save their Bazi profile information for quick access later
- Download Image Button - Enables users to download the analysis results as a PNG image using html2canvas
Components Created
1. SaveProfileDialog.tsx (/home/lifekline/components/SaveProfileDialog.tsx)
A modal dialog component that prompts users to enter a profile name and displays the Bazi information before saving.
Features:
- Input field for profile name validation
- Display of four pillars (年柱, 月柱, 日柱, 时柱)
- Shows birth year
- Save/Cancel actions with loading states
- Error handling
Props:
interface SaveProfileDialogProps {
onClose: () => void;
onSave: (profileName: string) => Promise<void>;
baziInfo: {
yearPillar: string;
monthPillar: string;
dayPillar: string;
hourPillar: string;
birthYear: string;
};
isOpen: boolean;
}
2. ResultActions.tsx (/home/lifekline/components/ResultActions.tsx)
A component that provides action buttons for the results page.
Features:
- 保存档案 (Save Profile) - Opens SaveProfileDialog
- 下载图片 (Download Image) - Uses html2canvas to capture and download results
- 分享 (Share) - Triggers the share functionality
- Error handling for download failures
- Loading states during image generation
Props:
interface ResultActionsProps {
baziInfo: {
yearPillar: string;
monthPillar: string;
dayPillar: string;
hourPillar: string;
birthYear: string;
};
userName?: string;
onSaveProfile?: (profileName: string) => Promise<void>;
onShare?: () => void;
resultElementId?: string;
}
Integration with ProgressiveAnalysisResult.tsx
The ResultActions component has been integrated into ProgressiveAnalysisResult.tsx:
Changes Made:
- Import Added:
import ResultActions from './ResultActions';
- Props Extended:
interface ProgressiveAnalysisResultProps {
// ... existing props
userName?: string;
onSaveProfile?: (profileName: string) => Promise<void>;
onShare?: () => void;
}
Component Placement: The ResultActions component is placed after the Agent Status section and before the Bazi Pillars display. It only appears when the analysis is complete and Bazi data is available.
Result Section Wrapper: Added a wrapper div with
id="result-chart-section"around the entire results area to enable html2canvas to capture the content.
Usage Example
Here's how to use the updated ProgressiveAnalysisResult component:
import ProgressiveAnalysisResult from './components/ProgressiveAnalysisResult';
function MyAnalysisPage() {
// Save profile handler - integrates with existing profile management
const handleSaveProfile = async (profileName: string) => {
try {
// Get current user input from state
const profileData = {
name: profileName,
birthYear: currentInput.birthYear,
yearPillar: currentInput.yearPillar,
monthPillar: currentInput.monthPillar,
dayPillar: currentInput.dayPillar,
hourPillar: currentInput.hourPillar,
startAge: currentInput.startAge,
firstDaYun: currentInput.firstDaYun,
birthPlace: currentInput.birthPlace,
gender: currentInput.gender,
// ... other required fields
};
// Save to localStorage or API
const profiles = JSON.parse(localStorage.getItem('lifekline_profiles') || '[]');
const newProfile = {
...profileData,
id: Date.now().toString(),
isDefault: profiles.length === 0,
createdAt: new Date().toISOString(),
};
profiles.push(newProfile);
localStorage.setItem('lifekline_profiles', JSON.stringify(profiles));
// Show success message
alert('Profile saved successfully!');
} catch (error) {
console.error('Failed to save profile:', error);
throw new Error('Failed to save profile. Please try again.');
}
};
// Share handler
const handleShare = () => {
// Open share panel or trigger share functionality
setShowSharePanel(true);
};
return (
<ProgressiveAnalysisResult
requestData={requestData}
userName={userName}
onSaveProfile={handleSaveProfile}
onShare={handleShare}
onComplete={(result) => console.log('Analysis complete:', result)}
onError={(error) => console.error('Analysis error:', error)}
/>
);
}
Download Image Functionality
The download image feature uses html2canvas with the following configuration:
const canvas = await html2canvas(element, {
scale: 2, // High resolution (2x)
useCORS: true, // Handle cross-origin images
backgroundColor: '#ffffff',
logging: false,
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight,
});
The generated image is named: 人生K线_[UserName]_[Date].png
Save Profile Integration with Existing System
The save profile functionality integrates seamlessly with the existing profile management system:
- Uses the same data structure as
CreateProfileModal - Stores in localStorage under
lifekline_profileskey - Compatible with
ProfileManagercomponent - Can be loaded back using the profile selector
Styling and UX
- All buttons use consistent styling with the existing design system
- Loading states with spinners during async operations
- Error messages displayed inline with appropriate styling
- Modal dialogs use backdrop blur for better focus
- Responsive design works on mobile and desktop
Testing Checklist
✓ Build successful with no TypeScript errors
- Save Profile Dialog opens and closes correctly
- Profile name validation works
- Bazi information displays correctly in dialog
- Profile saves to localStorage
- Saved profile can be loaded from ProfileManager
- Download Image generates PNG correctly
- Downloaded image includes all result sections
- Image filename includes username and date
- Error handling works for both features
- Responsive design on mobile devices
- Integration with share functionality
Browser Compatibility
- html2canvas requires modern browsers
- Works with Chrome, Firefox, Safari, Edge
- May have limitations with very old browsers (IE11 not supported)
Future Enhancements
Potential improvements for future versions:
- Add image format options (PNG, JPEG)
- Add image quality/resolution selector
- Save to cloud storage instead of just localStorage
- Batch profile management
- Profile import/export functionality
- Share profile as QR code
- Print-optimized layout for PDF export