【HarmonyOS】RN of HarmonyOS实战项目:邮箱地址输入与验证

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
项目概述:本文详细介绍在HarmonyOS平台上使用React Native实现智能邮箱地址输入组件,涵盖格式验证、域名检测、实时提示、多邮箱管理等完整功能,提供从基础实现到生产级应用的完整解决方案。
一、项目背景
邮箱地址输入是用户注册、登录、订阅等场景中的核心功能。在HarmonyOS平台上实现完善的邮箱输入功能需要考虑:
- 格式验证:符合RFC 5322标准的邮箱格式验证
- 域名检测:验证邮箱域名的MX记录
- 智能提示:根据输入提供常用邮箱后缀建议
- 多邮箱管理:支持添加、删除、切换多个邮箱
- 用户体验:实时验证反馈、友好的错误提示
二、技术架构
2.1 邮箱验证流程
无效
有效
无效域名
有效域名
无MX记录
有MX记录
用户输入邮箱
格式检查
显示格式错误
域名检查
显示域名警告
MX记录查询
显示无法接收邮件
邮箱可用
显示成功提示
2.2 数据结构定义
typescript
/**
* 邮箱验证结果
*/
interface EmailValidationResult {
email: string;
isValid: boolean;
isDeliverable: boolean; // 是否可接收邮件
hasMXRecord: boolean; // 是否有MX记录
domain: string;
localPart: string;
error?: EmailValidationError;
}
/**
* 邮箱验证错误类型
*/
type EmailValidationError =
| 'EMPTY'
| 'INVALID_FORMAT'
| 'INVALID_DOMAIN'
| 'NO_MX_RECORD'
| 'TEMPORARY_ERROR';
/**
* 常用邮箱域名
*/
const COMMON_EMAIL_DOMAINS = [
'qq.com',
'163.com',
'126.com',
'gmail.com',
'outlook.com',
'hotmail.com',
'yahoo.com',
'sina.com',
'sohu.com',
'aliyun.com',
'foxmail.com',
'icloud.com',
];
三、核心实现代码
3.1 邮箱验证工具类
typescript
/**
* 邮箱验证工具类
* 提供完整的邮箱格式验证和域名检测功能
*
* @platform HarmonyOS 2.0+
* @react-native 0.72+
*/
export class EmailValidator {
/**
* 基础邮箱格式正则
* 符合RFC 5322标准的简化版本
*/
private static readonly EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
/**
* 更严格的邮箱验证正则
*/
private static readonly STRICT_EMAIL_REGEX = /^[a-zA-Z0-9]+([._+-][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-][a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
/**
* 验证邮箱格式
*/
static validateFormat(email: string): { isValid: boolean; error?: EmailValidationError } {
// 空值检查
if (!email || !email.trim()) {
return { isValid: false, error: 'EMPTY' };
}
const trimmed = email.trim().toLowerCase();
// 长度检查
if (trimmed.length > 254) {
return { isValid: false, error: 'INVALID_FORMAT' };
}
// 格式检查
if (!this.STRICT_EMAIL_REGEX.test(trimmed)) {
return { isValid: false, error: 'INVALID_FORMAT' };
}
// 检查是否有多个@
if ((trimmed.match(/@/g) || []).length !== 1) {
return { isValid: false, error: 'INVALID_FORMAT' };
}
// 分割本地部分和域名
const [localPart, domain] = trimmed.split('@');
// 本地部分检查
if (localPart.length > 64) {
return { isValid: false, error: 'INVALID_FORMAT' };
}
// 域名检查
if (!this.isValidDomain(domain)) {
return { isValid: false, error: 'INVALID_DOMAIN' };
}
return { isValid: true };
}
/**
* 验证域名格式
*/
private static isValidDomain(domain: string): boolean {
// 域名长度检查
if (domain.length > 253 || domain.length < 3) {
return false;
}
// 域名格式检查
const domainRegex = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
if (!domainRegex.test(domain)) {
return false;
}
// TLD检查
const tld = domain.split('.').pop();
if (!tld || tld.length < 2) {
return false;
}
return true;
}
/**
* 解析邮箱地址
*/
static parse(email: string): { localPart: string; domain: string; full: string } | null {
const validation = this.validateFormat(email);
if (!validation.isValid) {
return null;
}
const trimmed = email.trim().toLowerCase();
const [localPart, domain] = trimmed.split('@');
return {
localPart,
domain,
full: trimmed,
};
}
/**
* 生成邮箱建议
*/
static suggestDomains(localPart: string): string[] {
return COMMON_EMAIL_DOMAINS.map(domain => `${localPart}@${domain}`);
}
/**
* 自动补全邮箱
*/
static autoComplete(partial: string): string | null {
const trimmed = partial.trim().toLowerCase();
// 如果已经包含@,尝试补全域名
if (trimmed.includes('@')) {
const [localPart, domainPart] = trimmed.split('@');
if (!domainPart) {
// 只有@符号,返回第一个常用域名
return `${localPart}@${COMMON_EMAIL_DOMAINS[0]}`;
}
// 查找匹配的域名
const matched = COMMON_EMAIL_DOMAINS.find(domain =>
domain.startsWith(domainPart)
);
if (matched) {
return `${localPart}@${matched}`;
}
} else {
// 没有@符号,返回第一个建议
return `${trimmed}@${COMMON_EMAIL_DOMAINS[0]}`;
}
return null;
}
/**
* 检查是否为一次性邮箱
*/
static isDisposableEmail(domain: string): boolean {
const disposableDomains = [
'tempmail.com',
'guerrillamail.com',
'mailinator.com',
'10minutemail.com',
];
return disposableDomains.some(d => domain.endsWith(d));
}
/**
* 隐藏邮箱地址(用于显示)
*/
static obscure(email: string): string {
const parsed = this.parse(email);
if (!parsed) return email;
const { localPart, domain } = parsed;
if (localPart.length <= 3) {
return `${localPart[0]}***@${domain}`;
}
const visibleChars = Math.ceil(localPart.length / 3);
const obscuredPart =
localPart.substring(0, visibleChars) +
'*'.repeat(localPart.length - visibleChars * 2) +
localPart.substring(localPart.length - visibleChars);
return `${obscuredPart}@${domain}`;
}
/**
* 提取邮箱头像URL
*/
static getAvatarURL(email: string, size: number = 200): string {
const trimmed = email.trim().toLowerCase();
const hash = this.md5(trimmed);
return `https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon`;
}
/**
* 简单的MD5实现(用于Gravatar)
*/
private static md5(string: string): string {
// 实际项目中应使用crypto-js等库
// 这里返回一个模拟值
return Array.from(string)
.reduce((hash, char) => ((hash << 5) - hash + char.charCodeAt(0)) | 0, 0)
.toString(16)
.padStart(32, '0');
}
}
3.2 邮箱输入组件
typescript
/**
* 智能邮箱输入组件
* 支持实时验证、自动补全、域名建议等功能
*/
import React, { useState, useCallback, useRef, useEffect } from 'react';
import {
View,
TextInput,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
ActivityIndicator,
Image,
} from 'react-native';
import { EmailValidator, EmailValidationError } from './EmailValidator';
interface EmailInputProps {
placeholder?: string;
onEmailChange?: (email: string, isValid: boolean) => void;
onEmailSubmit?: (email: string) => void;
showSuggestions?: boolean;
allowDisposableEmail?: boolean;
}
export const EmailInput: React.FC<EmailInputProps> = ({
placeholder = '请输入邮箱地址',
onEmailChange,
onEmailSubmit,
showSuggestions = true,
allowDisposableEmail = false,
}) => {
const [email, setEmail] = useState('');
const [validation, setValidation] = useState<{
isValid: boolean;
error?: EmailValidationError;
}>({ isValid: false });
const [isValidating, setIsValidating] = useState(false);
const [showDomainSuggestions, setShowDomainSuggestions] = useState(false);
const [suggestions, setSuggestions] = useState<string[]>([]);
const inputRef = useRef<TextInput>(null);
const debounceTimer = useRef<NodeJS.Timeout>();
/**
* 获取错误提示信息
*/
const getErrorMessage = (error?: EmailValidationError): string => {
switch (error) {
case 'EMPTY':
return '邮箱地址不能为空';
case 'INVALID_FORMAT':
return '邮箱格式不正确';
case 'INVALID_DOMAIN':
return '域名格式不正确';
case 'NO_MX_RECORD':
return '该邮箱无法接收邮件';
case 'TEMPORARY_ERROR':
return '网络错误,请稍后重试';
default:
return '邮箱地址无效';
}
};
/**
* 验证邮箱
*/
const validateEmail = useCallback((emailText: string) => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
if (!emailText) {
setValidation({ isValid: false });
setShowDomainSuggestions(false);
onEmailChange?.('', false);
return;
}
setIsValidating(true);
debounceTimer.current = setTimeout(() => {
const result = EmailValidator.validateFormat(emailText);
setValidation(result);
setIsValidating(false);
onEmailChange?.(emailText, result.isValid);
// 显示域名建议
if (showSuggestions && emailText.includes('@')) {
const [localPart, domainPart] = emailText.split('@');
if (domainPart && domainPart.length > 0) {
const matches = EmailValidator.suggestDomains(localPart).filter(email =>
email.includes(domainPart)
);
setSuggestions(matches.slice(0, 3));
setShowDomainSuggestions(matches.length > 1);
} else {
setSuggestions(EmailValidator.suggestDomains(localPart).slice(0, 3));
setShowDomainSuggestions(true);
}
} else {
setShowDomainSuggestions(false);
}
}, 300);
}, [showSuggestions, onEmailChange]);
/**
* 处理输入变化
*/
const handleChange = useCallback((text: string) => {
setEmail(text);
validateEmail(text);
}, [validateEmail]);
/**
* 应用建议
*/
const applySuggestion = useCallback((suggestedEmail: string) => {
setEmail(suggestedEmail);
setValidation({ isValid: true });
setShowDomainSuggestions(false);
onEmailChange?.(suggestedEmail, true);
inputRef.current?.blur();
}, [onEmailChange]);
/**
* 清空输入
*/
const handleClear = useCallback(() => {
setEmail('');
setValidation({ isValid: false });
setShowDomainSuggestions(false);
onEmailChange?.('', false);
inputRef.current?.focus();
}, [onEmailChange]);
/**
* 提交邮箱
*/
const handleSubmit = useCallback(() => {
if (validation.isValid) {
onEmailSubmit?.(email);
}
}, [validation.isValid, email, onEmailSubmit]);
const parsed = EmailValidator.parse(email);
return (
<View style={styles.container}>
{/* 输入框 */}
<View style={styles.inputContainer}>
<TextInput
ref={inputRef}
style={styles.input}
value={email}
onChangeText={handleChange}
onSubmitEditing={handleSubmit}
placeholder={placeholder}
placeholderTextColor="#999"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
textContentType="emailAddress"
returnKeyType="done"
/>
{email.length > 0 && (
<TouchableOpacity onPress={handleClear} style={styles.clearButton}>
<Text style={styles.clearIcon}>×</Text>
</TouchableOpacity>
)}
{isValidating && (
<ActivityIndicator size="small" color="#007AFF" style={styles.spinner} />
)}
</View>
{/* 验证状态指示器 */}
{email.length > 0 && !isValidating && (
<View style={styles.statusContainer}>
{validation.isValid ? (
<View style={styles.statusSuccess}>
<Text style={styles.statusIcon}>✓</Text>
<Text style={styles.statusText}>邮箱格式正确</Text>
</View>
) : (
<View style={styles.statusError}>
<Text style={styles.statusIcon}>!</Text>
<Text style={styles.statusText}>{getErrorMessage(validation.error)}</Text>
</View>
)}
</View>
)}
{/* 域名建议 */}
{showDomainSuggestions && suggestions.length > 0 && (
<View style={styles.suggestionsContainer}>
<Text style={styles.suggestionsTitle}>选择邮箱域名</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.suggestionsList}
>
{suggestions.map((suggestedEmail, index) => (
<TouchableOpacity
key={index}
style={styles.suggestionItem}
onPress={() => applySuggestion(suggestedEmail)}
>
<Text style={styles.suggestionText}>{suggestedEmail}</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
)}
{/* 邮箱头像预览 */}
{validation.isValid && parsed && (
<View style={styles.previewContainer}>
<Image
source={{ uri: EmailValidator.getAvatarURL(email, 80) }}
style={styles.avatar}
/>
<View style={styles.previewInfo}>
<Text style={styles.previewEmail}>{email}</Text>
<Text style={styles.previewDomain}>@{parsed.domain}</Text>
</View>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
width: '100%',
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#E5E5E5',
borderRadius: 12,
paddingHorizontal: 16,
},
input: {
flex: 1,
height: 50,
fontSize: 16,
color: '#333',
},
clearButton: {
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: '#E0E0E0',
justifyContent: 'center',
alignItems: 'center',
marginRight: 8,
},
clearIcon: {
fontSize: 18,
color: '#666',
fontWeight: 'bold',
},
spinner: {
marginRight: 8,
},
statusContainer: {
marginTop: 10,
},
statusSuccess: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#E8F5E9',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 8,
},
statusError: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFEBEE',
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 8,
},
statusIcon: {
fontSize: 14,
marginRight: 6,
},
statusText: {
fontSize: 13,
},
suggestionsContainer: {
marginTop: 12,
},
suggestionsTitle: {
fontSize: 13,
color: '#999',
marginBottom: 8,
},
suggestionsList: {
gap: 8,
},
suggestionItem: {
backgroundColor: '#F5F5F5',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 8,
},
suggestionText: {
fontSize: 14,
color: '#333',
},
previewContainer: {
marginTop: 16,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#F5F5F5',
padding: 12,
borderRadius: 12,
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
marginRight: 12,
},
previewInfo: {
flex: 1,
},
previewEmail: {
fontSize: 15,
fontWeight: '600',
color: '#333',
marginBottom: 2,
},
previewDomain: {
fontSize: 13,
color: '#999',
},
});
export default EmailInput;
3.3 多邮箱管理组件
typescript
/**
* 多邮箱管理组件
* 支持添加、删除、切换多个邮箱地址
*/
import React, { useState, useCallback } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
Alert,
} from 'react-native';
import { EmailValidator } from './EmailValidator';
interface StoredEmail {
id: string;
email: string;
isPrimary: boolean;
isVerified: boolean;
addedAt: Date;
}
interface EmailManagerProps {
emails: StoredEmail[];
onAddEmail: (email: string) => void;
onRemoveEmail: (id: string) => void;
onSetPrimary: (id: string) => void;
onResendVerification: (id: string) => void;
}
export const EmailManager: React.FC<EmailManagerProps> = ({
emails,
onAddEmail,
onRemoveEmail,
onSetPrimary,
onResendVerification,
}) => {
const [showAddForm, setShowAddForm] = useState(false);
/**
* 渲染邮箱项
*/
const renderEmailItem = useCallback(({ item }: { item: StoredEmail }) => {
const parsed = EmailValidator.parse(item.email);
const obscured = EmailValidator.obscure(item.email);
return (
<View style={managerStyles.emailItem}>
<View style={managerStyles.emailInfo}>
<View style={managerStyles.emailHeader}>
<Text style={managerStyles.emailText}>{obscured}</Text>
{item.isPrimary && (
<View style={managerStyles.primaryBadge}>
<Text style={managerStyles.primaryBadgeText}>主要</Text>
</View>
)}
{!item.isVerified && (
<View style={managerStyles.unverifiedBadge}>
<Text style={managerStyles.unverifiedBadgeText}>未验证</Text>
</View>
)}
</View>
{parsed && (
<Text style={managerStyles.domainText}>@{parsed.domain}</Text>
)}
</View>
<View style={managerStyles.actions}>
{!item.isPrimary && (
<TouchableOpacity
style={managerStyles.actionButton}
onPress={() => onSetPrimary(item.id)}
>
<Text style={managerStyles.actionButtonText}>设为主要</Text>
</TouchableOpacity>
)}
{!item.isVerified && (
<TouchableOpacity
style={[managerStyles.actionButton, managerStyles.resendButton]}
onPress={() => onResendVerification(item.id)}
>
<Text style={managerStyles.actionButtonText}>重发验证</Text>
</TouchableOpacity>
)}
{emails.length > 1 && (
<TouchableOpacity
style={[managerStyles.actionButton, managerStyles.removeButton]}
onPress={() => {
Alert.alert(
'删除邮箱',
'确定要删除此邮箱地址吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '删除',
style: 'destructive',
onPress: () => onRemoveEmail(item.id),
},
]
);
}}
>
<Text style={[managerStyles.actionButtonText, managerStyles.removeButtonText]}>
删除
</Text>
</TouchableOpacity>
)}
</View>
</View>
);
}, [emails, onSetPrimary, onResendVerification, onRemoveEmail]);
return (
<View style={managerStyles.container}>
<View style={managerStyles.header}>
<Text style={managerStyles.title}>邮箱地址管理</Text>
<Text style={managerStyles.subtitle}>已添加 {emails.length} 个邮箱</Text>
</View>
<FlatList
data={emails}
renderItem={renderEmailItem}
keyExtractor={(item) => item.id}
contentContainerStyle={managerStyles.list}
ItemSeparatorComponent={() => <View style={managerStyles.separator} />}
/>
<TouchableOpacity
style={managerStyles.addButton}
onPress={() => setShowAddForm(true)}
>
<Text style={managerStyles.addButtonText}>+ 添加新邮箱</Text>
</TouchableOpacity>
</View>
);
};
const managerStyles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
header: {
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#E5E5E5',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: '#999',
},
list: {
paddingVertical: 8,
},
emailItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 12,
},
emailInfo: {
flex: 1,
},
emailHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
marginBottom: 4,
},
emailText: {
fontSize: 15,
color: '#333',
},
primaryBadge: {
backgroundColor: '#E3F2FD',
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 4,
},
primaryBadgeText: {
fontSize: 11,
color: '#1976D2',
fontWeight: '600',
},
unverifiedBadge: {
backgroundColor: '#FFF3E0',
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 4,
},
unverifiedBadgeText: {
fontSize: 11,
color: '#F57C00',
fontWeight: '600',
},
domainText: {
fontSize: 13,
color: '#999',
},
actions: {
flexDirection: 'row',
gap: 8,
},
actionButton: {
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
backgroundColor: '#F5F5F5',
},
actionButtonText: {
fontSize: 13,
color: '#666',
fontWeight: '500',
},
resendButton: {
backgroundColor: '#FFF3E0',
},
removeButton: {
backgroundColor: '#FFEBEE',
},
removeButtonText: {
color: '#C62828',
},
separator: {
height: 1,
backgroundColor: '#F5F5F5',
marginLeft: 20,
},
addButton: {
margin: 20,
backgroundColor: '#007AFF',
paddingVertical: 14,
borderRadius: 10,
alignItems: 'center',
},
addButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});
3.4 使用示例
typescript
/**
* 邮箱输入组件使用示例
*/
import React, { useState } from 'react';
import {
View,
StyleSheet,
ScrollView,
Alert,
SafeAreaView,
} from 'react-native';
import { EmailInput } from './EmailInput';
import { EmailManager, StoredEmail } from './EmailManager';
const EmailInputDemo: React.FC = () => {
const [emails, setEmails] = useState<StoredEmail[]>([
{
id: '1',
email: 'user@example.com',
isPrimary: true,
isVerified: true,
addedAt: new Date(),
},
]);
const handleEmailChange = (email: string, isValid: boolean) => {
console.log('Email changed:', email, 'Valid:', isValid);
};
const handleEmailSubmit = (email: string) => {
Alert.alert('提交成功', `邮箱地址: ${email}`);
};
const handleAddEmail = (email: string) => {
const newEmail: StoredEmail = {
id: Date.now().toString(),
email,
isPrimary: false,
isVerified: false,
addedAt: new Date(),
};
setEmails([...emails, newEmail]);
};
const handleRemoveEmail = (id: string) => {
setEmails(emails.filter(e => e.id !== id));
};
const handleSetPrimary = (id: string) => {
setEmails(
emails.map(e => ({
...e,
isPrimary: e.id === id,
}))
);
};
const handleResendVerification = (id: string) => {
Alert.alert('验证邮件已发送', '请检查您的邮箱');
};
return (
<SafeAreaView style={demoStyles.container}>
<ScrollView contentContainerStyle={demoStyles.content}>
<Text style={demoStyles.title}>邮箱地址输入</Text>
<EmailInput
placeholder="请输入邮箱地址"
onEmailChange={handleEmailChange}
onEmailSubmit={handleEmailSubmit}
showSuggestions
/>
<View style={demoStyles.section}>
<EmailManager
emails={emails}
onAddEmail={handleAddEmail}
onRemoveEmail={handleRemoveEmail}
onSetPrimary={handleSetPrimary}
onResendVerification={handleResendVerification}
/>
</View>
</ScrollView>
</SafeAreaView>
);
};
const demoStyles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 20,
textAlign: 'center',
},
section: {
marginTop: 30,
borderRadius: 12,
overflow: 'hidden',
height: 400,
},
});
export default EmailInputDemo;
四、最佳实践
| 功能 | 实现方式 | 效果 |
|---|---|---|
| 实时验证 | 防抖300ms | 减少不必要的验证 |
| 域名建议 | 显示常用邮箱域名 | 提升输入效率 |
| 邮箱隐藏 | 本地部分部分隐藏 | 保护用户隐私 |
| 头像集成 | Gravatar API | 显示邮箱头像 |
| 多邮箱管理 | 添加、删除、切换 | 灵活的邮箱管理 |
五、总结
本文详细介绍了在HarmonyOS平台上实现邮箱地址输入的完整方案,涵盖:
- 格式验证:RFC标准的邮箱格式验证
- 智能功能:域名建议、自动补全
- 用户体验:实时验证、错误提示
- 高级功能:多邮箱管理、头像显示
完整项目代码 :AtomGitDemos