---
title: 开发文档
order: 3
category: pro
---

﻿# PisellEmail 组件开发文档

## 1 组件信息

### 1.1 基本信息

- **组件名称**：PisellEmail
- **组件版本**：V1.0.0
- **组件分类**：表单输入 / 文本展示
- **开发状态**：开发中
- **最后更新**：2026-01-25
- **设计稿链接
  **：https://www.figma.com/design/pM8Ho6d7kCMv9vIBvFHUlj/Pisell-2.0-Component-Library?node-id=3350-5733&m=dev

### 1.2 组件描述

PisellEmail 是一个专注于邮箱地址处理的输入/展示组件，支持只读、编辑、禁用三种状态模式。组件基于 Ant Design Input 和
AutoComplete 组件开发，提供邮箱格式校验、域名智能补全、邮件发送（mailto）和域名隐藏功能，并复用 PisellSingleLineText 的公共逻辑。

## 2 需求点罗列

### 2.1 功能需求

#### 核心功能

- ✅ 支持三种状态：只读态、编辑态、禁用态
- ✅ 支持受控和非受控两种使用模式
- ✅ 基于 Ant Design Input + AutoComplete 组件封装
- ✅ 完整的校验框架（复用 @pisell/utils）
- ✅ 复用公共逻辑（@pisell/utils）

#### 邮箱特有功能

- ✅ 邮箱格式校验（标准/严格 RFC 5322）
- ✅ 域名智能补全（常用邮箱域名）
- ✅ 白名单/黑名单域名限制
- ✅ 邮件发送功能（mailto 协议）
- ✅ 域名隐藏/脱敏（partial/full 模式）

#### 展示功能（只读态）

- ✅ 显示邮箱地址
- ✅ 点击唤起邮件客户端（mailto）
- ✅ 域名隐藏/脱敏显示
- ✅ 邮件图标（可选）
- ✅ 超链接样式（可选）

#### 编辑功能（编辑态）

- ✅ 输入时域名智能补全
- ✅ 实时格式校验
- ✅ 错误提示展示
- ✅ 支持邮件图标前缀

#### 校验功能

- ✅ 必填校验
- ✅ 格式校验（标准/严格）
- ✅ 白名单域名校验
- ✅ 黑名单域名校验
- ✅ 自定义校验
- ✅ 错误提示展示

### 2.2 非功能需求

- ✅ TypeScript 类型定义完整
- ✅ 代码注释使用 JSDoc 规范
- ✅ 性能优化（React.memo、useCallback、useMemo）
- ✅ 代码复用（hooks 和 utils 来自 @pisell/utils）
- ✅ 支持国际化（预留接口）

## 3 组件 API

### 3.1 Props 定义

#### 核心属性（继承自 BaseTextInputProps）

| 属性名          | 类型               | 默认值    | 必填 | 说明       |
|--------------|------------------|--------|----|----------|
| value        | string           | -      | 否  | 邮箱地址     |
| defaultValue | string           | -      | 否  | 默认值（非受控） |
| mode         | 'read' \| 'edit' | 'edit' | 否  | 状态模式     |
| disabled     | boolean          | false  | 否  | 是否禁用     |

#### 邮箱特有配置

| 属性名                 | 类型                      | 默认值       | 必填 | 说明                 |
|---------------------|-------------------------|-----------|----|--------------------|
| strictValidation    | boolean                 | false     | 否  | 是否使用严格校验（RFC 5322） |
| allowedDomains      | string[]                | -         | 否  | 白名单域名列表            |
| blockedDomains      | string[]                | -         | 否  | 黑名单域名列表            |
| enableAutoComplete  | boolean                 | true      | 否  | 是否启用域名自动补全         |
| autoCompleteDomains | string[]                | 预设常用域名    | 否  | 自定义补全域名列表          |
| showAllSuggestions  | boolean                 | false     | 否  | 是否显示所有建议（不过滤）      |
| enableMailto        | boolean                 | true      | 否  | 是否启用 mailto（只读态）   |
| showEmailIcon       | boolean                 | false     | 否  | 是否显示邮件图标           |
| mailtoOptions       | MailtoOptions           | -         | 否  | mailto 默认参数        |
| onMailto            | (email: string) => void | -         | 否  | 发送邮件回调             |
| hideDomain          | boolean                 | false     | 否  | 是否隐藏域名（只读态）        |
| maskPattern         | 'partial' \| 'full'     | 'partial' | 否  | 脱敏模式               |
| displayAsLink       | boolean                 | true      | 否  | 是否显示为链接（只读态）       |

#### 展示配置（只读态）

| 属性名        | 类型               | 默认值     | 必填 | 说明        |
|------------|------------------|---------|----|-----------|
| fontSize   | string \| number | 16      | 否  | 字号        |
| fontWeight | string \| number | 500     | 否  | 字重        |
| color      | string           | #101828 | 否  | 文本颜色      |
| linkColor  | string           | #7F56DA | 否  | 链接颜色（只读态） |

#### 编辑配置（编辑态）

| 属性名         | 类型      | 默认值     | 必填 | 说明             |
|-------------|---------|---------|----|----------------|
| placeholder | string  | 请输入邮箱地址 | 否  | 占位文本           |
| maxLength   | number  | 254     | 否  | 最大长度（RFC 5321） |
| allowClear  | boolean | false   | 否  | 显示清空按钮         |
| autoFocus   | boolean | false   | 否  | 自动聚焦           |

#### 校验配置

| 属性名             | 类型                                   | 默认值      | 必填 | 说明      |
|-----------------|--------------------------------------|----------|----|---------|
| required        | boolean                              | false    | 否  | 是否必填    |
| customValidator | (email: string) => boolean \| string | -        | 否  | 自定义校验函数 |
| validateTrigger | 'onChange' \| 'onBlur' \| 'both'     | 'onBlur' | 否  | 校验触发时机  |
| errorMessage    | string                               | -        | 否  | 自定义错误提示 |

#### 事件回调

| 属性名        | 类型                                           | 说明       |
|------------|----------------------------------------------|----------|
| onChange   | (value: string) => void                      | 值变化回调    |
| onBlur     | (value: string) => void                      | 失焦回调     |
| onFocus    | () => void                                   | 聚焦回调     |
| onSelect   | (value: string) => void                      | 选择补全建议回调 |
| onMailto   | (email: string) => void                      | 发送邮件回调   |
| onValidate | (isValid: boolean, errors: string[]) => void | 校验回调     |

### 3.2 MailtoOptions 类型定义

```
interface MailtoOptions {
  subject?: string;  // 邮件主题
  body?: string;     // 邮件正文
  cc?: string;       // 抄送
  bcc?: string;      // 密送
}
```

## 4 组件模块拆解

### 4.1 目录结构

```
pisellEmail/
├── index.tsx                          # 导出文件
├── PisellEmail.tsx                   # 组件主文件
├── PisellEmail.less                  # 样式文件
├── PisellEmail.test.tsx              # 单元测试
├── PisellEmail.stories.tsx           # Storybook 故事
├── types.ts                          # 类型定义
├── utils/                            # 工具函数（邮箱特有）
│   ├── index.ts
│   ├── emailValidator.ts            # 邮箱校验工具
│   ├── emailHelper.ts               # 邮箱辅助函数
│   └── domainSuggestions.ts         # 域名补全工具
├── components/                       # 子组件
│   ├── ReadOnlyView.tsx             # 只读态视图（mailto）
│   ├── EditView.tsx                 # 编辑态视图（自动补全）
│   └── DisabledView.tsx             # 禁用态视图
├── constants/                        # 常量
│   ├── emailDomains.ts              # 常用邮箱域名列表
│   └── emailRegex.ts                # 邮箱正则表达式
└── docs/                            # 文档
    ├── pisellEmail.md                        # 使用文档
    ├── pisellEmail.$tab-design.md            # 设计文档
    ├── pisellEmail.$tab-dev.md               # 开发文档（本文档）
    └── pisellEmail.$tab-test.md              # 测试文档
```

### 4.2 模块职责

#### 主组件（PisellEmail.tsx）

- 统筹组件渲染逻辑
- 根据 mode 和 disabled 决定渲染哪个子组件
- 管理组件整体状态

#### 子组件

**EditView.tsx**（核心差异）

- 职责：渲染编辑态输入
- 功能：
  - 使用 Ant Design AutoComplete 实现域名补全
  - 输入时实时校验邮箱格式
  - 输入 @ 后自动显示域名建议
  - 支持键盘上下键选择，回车确认
  - 校验和错误提示
- 依赖：Ant Design AutoComplete、Input、MailOutlined

**ReadOnlyView.tsx**

- 职责：渲染只读态展示和 mailto 发送
- 功能：
  - 显示邮箱地址
  - 可选域名隐藏/脱敏
  - 点击唤起邮件客户端（mailto 协议）
  - 可选邮件图标
  - 超链接样式
- 依赖：Ant Design Typography.Link/Text、Button、MailOutlined

**DisabledView.tsx**

- 职责：渲染禁用态视图
- 功能：基于原始 mode 渲染，透明度 40%
- 依赖：ReadOnlyView 或 EditView

#### 工具函数（utils/）

**emailValidator.ts**

- validateEmail - 标准邮箱格式校验
- validateEmailStrict - 严格邮箱格式校验（RFC 5322）
- validateEmailParts - 分部校验（本地部分、域名部分）
- validateEmailDomain - 域名校验（白名单/黑名单）

**emailHelper.ts**

- extractEmailDomain - 提取邮箱域名
- extractEmailLocal - 提取邮箱本地部分
- normalizeEmail - 规范化邮箱地址（转小写）
- hideEmailDomain - 域名脱敏（partial 模式）
- maskEmailDomain - 域名完全隐藏（full 模式）
- handleEmailClick - 处理 mailto 点击

**domainSuggestions.ts**

- getEmailSuggestions - 根据输入生成域名建议
- filterDomains - 过滤匹配的域名
- sortDomains - 排序域名建议

#### 常量（constants/）

**emailDomains.ts**

- COMMON_EMAIL_DOMAINS - 常用邮箱域名列表（gmail.com、qq.com、163.com 等）

**emailRegex.ts**

- EMAIL_REGEX - 标准邮箱正则
- STRICT_EMAIL_REGEX - 严格邮箱正则（RFC 5322）

## 5 组件实现方案

### 5.1 技术栈

- React 18+
- TypeScript
- Ant Design 5.x（AutoComplete、Input、Typography、Button）
- Less（样式）
- 公共逻辑来自 @pisell/utils

### 5.2 核心实现

#### 5.2.1 邮箱格式校验

```
// utils/emailValidator.ts

/**
 * 标准邮箱格式校验
 * @param email 邮箱地址
 * @returns 是否通过校验
 */
export function validateEmail(email: string): boolean {
  const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return EMAIL_REGEX.test(email);
}

/**
 * 严格邮箱格式校验（RFC 5322）
 * @param email 邮箱地址
 * @returns 是否通过校验
 */
export function validateEmailStrict(email: string): boolean {
  const STRICT_EMAIL_REGEX = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i;
  return STRICT_EMAIL_REGEX.test(email);
}

/**
 * 邮箱分部校验
 * @param email 邮箱地址
 * @returns 校验结果
 */
export function validateEmailParts(email: string): { isValid: boolean; errors: string[] } {
  const errors: string[] = [];
  
  // 1. 基本格式检查
  if (!email.includes('@')) {
    errors.push('邮箱地址必须包含 @ 符号');
    return { isValid: false, errors };
  }
  
  const [localPart, domainPart] = email.split('@');
  
  // 2. 本地部分校验
  if (!localPart || localPart.length === 0) {
    errors.push('@ 符号前不能为空');
  }
  if (localPart.length > 64) {
    errors.push('@ 符号前最多 64 个字符');
  }
  
  // 3. 域名部分校验
  if (!domainPart || domainPart.length === 0) {
    errors.push('@ 符号后不能为空');
  }
  if (!domainPart.includes('.')) {
    errors.push('域名必须包含 . 符号');
  }
  if (domainPart.endsWith('.')) {
    errors.push('域名不能以 . 结尾');
  }
  
  // 4. 顶级域名校验
  const parts = domainPart.split('.');
  const tld = parts[parts.length - 1];
  if (tld.length < 2) {
    errors.push('顶级域名至少 2 个字符');
  }
  
  return {
    isValid: errors.length === 0,
    errors
  };
}

/**
 * 域名白名单/黑名单校验
 * @param email 邮箱地址
 * @param allowedDomains 白名单
 * @param blockedDomains 黑名单
 * @returns 校验结果
 */
export function validateEmailDomain(
  email: string,
  allowedDomains?: string[],
  blockedDomains?: string[]
): { isValid: boolean; errors: string[] } {
  const errors: string[] = [];
  const domain = extractEmailDomain(email);
  
  // 白名单校验
  if (allowedDomains && allowedDomains.length > 0) {
    if (!allowedDomains.includes(domain)) {
      errors.push(`只允许以下域名：${allowedDomains.join(', ')}`);
    }
  }
  
  // 黑名单校验
  if (blockedDomains && blockedDomains.length > 0) {
    if (blockedDomains.includes(domain)) {
      errors.push('该邮箱域名不可用');
    }
  }
  
  return {
    isValid: errors.length === 0,
    errors
  };
}
```

#### 5.2.2 域名智能补全

```
// utils/domainSuggestions.ts

/**
 * 获取邮箱域名补全建议
 * @param input 用户输入
 * @param domains 域名列表
 * @returns 补全建议列表
 * 
 * @example
 * getEmailSuggestions('user@g', COMMON_DOMAINS) 
 * // ['user@gmail.com']
 */
export function getEmailSuggestions(
  input: string,
  domains: string[] = COMMON_EMAIL_DOMAINS
): string[] {
  if (!input.includes('@')) {
    return [];
  }
  
  const [localPart, partialDomain] = input.split('@');
  
  if (!localPart) {
    return [];
  }
  
  // 过滤匹配的域名
  const matchedDomains = domains.filter(domain => 
    domain.toLowerCase().startsWith(partialDomain.toLowerCase())
  );
  
  // 返回完整邮箱建议
  return matchedDomains.map(domain => `${localPart}@${domain}`);
}

// constants/emailDomains.ts

/**
 * 常用邮箱域名列表
 */
export const COMMON_EMAIL_DOMAINS = [
  // 国内常用
  'qq.com',
  '163.com',
  '126.com',
  'sina.com',
  'sina.cn',
  'sohu.com',
  '139.com',
  '189.cn',
  
  // 国际常用
  'gmail.com',
  'outlook.com',
  'hotmail.com',
  'yahoo.com',
  'icloud.com',
  'live.com',
  'msn.com',
  
  // 企业邮箱示例（可自定义）
  'company.com',
];
```

#### 5.2.3 域名隐藏/脱敏

```
// utils/emailHelper.ts

/**
 * 邮箱域名脱敏（partial 模式）
 * @param email 邮箱地址
 * @returns 脱敏后的邮箱 (u***@gmail.com)
 */
export function hideEmailDomain(email: string): string {
  const [localPart, domainPart] = email.split('@');
  
  if (!localPart || !domainPart) {
    return email;
  }
  
  // 本地部分脱敏
  const visibleChars = Math.min(1, localPart.length);
  const maskedLocal = localPart.substring(0, visibleChars) + '***';
  
  return `${maskedLocal}@${domainPart}`;
}

/**
 * 邮箱域名完全隐藏（full 模式）
 * @param email 邮箱地址
 * @returns 脱敏后的邮箱 (user@***)
 */
export function maskEmailDomain(email: string): string {
  const [localPart] = email.split('@');
  return `${localPart}@***`;
}

/**
 * 提取邮箱域名
 */
export function extractEmailDomain(email: string): string {
  const parts = email.split('@');
  return parts.length === 2 ? parts[1] : '';
}

/**
 * 提取邮箱本地部分
 */
export function extractEmailLocal(email: string): string {
  const parts = email.split('@');
  return parts.length >= 1 ? parts[0] : '';
}

/**
 * 规范化邮箱地址（转小写）
 */
export function normalizeEmail(email: string): string {
  return email.trim().toLowerCase();
}
```

#### 5.2.4 mailto 邮件发送

```
// utils/emailHelper.ts

/**
 * 处理 mailto 点击
 * @param email 邮箱地址
 * @param options mailto 参数
 */
export function handleEmailClick(email: string, options?: MailtoOptions) {
  if (!email) return;
  
  // 构建 mailto URL
  let mailtoUrl = `mailto:${email}`;
  
  // 添加可选参数
  const params: string[] = [];
  if (options?.subject) {
    params.push(`subject=${encodeURIComponent(options.subject)}`);
  }
  if (options?.body) {
    params.push(`body=${encodeURIComponent(options.body)}`);
  }
  if (options?.cc) {
    params.push(`cc=${encodeURIComponent(options.cc)}`);
  }
  if (options?.bcc) {
    params.push(`bcc=${encodeURIComponent(options.bcc)}`);
  }
  
  if (params.length > 0) {
    mailtoUrl += '?' + params.join('&');
  }
  
  // 唤起邮件客户端
  window.location.href = mailtoUrl;
}
```

#### 5.2.5 EditView 实现（域名补全）

```
import React, { useState, useMemo } from 'react';
import { AutoComplete, Input } from 'antd';
import { MailOutlined } from '@ant-design/icons';
import { useTextInputState } from '@pisell/utils';
import { getEmailSuggestions, validateEmail } from '../utils';
import { COMMON_EMAIL_DOMAINS } from '../constants';
import type { PisellEmailProps } from '../types';

const EditView: React.FC<PisellEmailProps> = (props) => {
  const [suggestions, setSuggestions] = useState<string[]>([]);
  
  // 状态管理（复用）
  const { value, isValid, errors, handleChange, handleBlur, handleFocus } = 
    useTextInputState({
      ...props,
      validator: (val) => {
        // 自定义邮箱校验逻辑
        if (props.strictValidation) {
          return validateEmailStrict(val) || '邮箱地址格式不正确';
        }
        return validateEmail(val) || '邮箱地址格式不正确';
      }
    });
  
  // 域名列表
  const domains = useMemo(() => {
    return props.autoCompleteDomains || COMMON_EMAIL_DOMAINS;
  }, [props.autoCompleteDomains]);
  
  // 处理输入（生成补全建议）
  const handleSearch = (searchText: string) => {
    if (props.enableAutoComplete !== false) {
      const suggestions = getEmailSuggestions(searchText, domains);
      setSuggestions(suggestions);
    }
  };
  
  // 处理输入变化
  const handleInputChange = (val: string) => {
    handleChange(val);
    handleSearch(val);
  };
  
  // 选择建议
  const handleSelect = (val: string) => {
    handleChange(val);
    props.onSelect?.(val);
  };
  
  return (
    <div className="pisell-email-edit">
      <AutoComplete
        value={value}
        onChange={handleInputChange}
        onSelect={handleSelect}
        onBlur={handleBlur}
        onFocus={handleFocus}
        options={suggestions.map(email => ({ value: email }))}
        placeholder={props.placeholder || '请输入邮箱地址'}
        style={{ width: '100%' }}
        disabled={props.disabled}
        status={isValid ? undefined : 'error'}
      >
        <Input
          prefix={<MailOutlined />}
          maxLength={props.maxLength || 254}
          allowClear={props.allowClear}
          autoFocus={props.autoFocus}
          className={props.className}
          style={props.style}
        />
      </AutoComplete>
      
      {!isValid && errors.length > 0 && (
        <div className="pisell-email-error">
          {errors[0]}
        </div>
      )}
    </div>
  );
};

export default EditView;
```

#### 5.2.6 ReadOnlyView 实现（mailto）

```
import React from 'react';
import { Typography, Button, Space } from 'antd';
import { MailOutlined } from '@ant-design/icons';
import { handleEmailClick, hideEmailDomain, maskEmailDomain } from '../utils';
import type { PisellEmailProps } from '../types';

const ReadOnlyView: React.FC<PisellEmailProps> = (props) => {
  const email = props.value || '';
  
  // 处理域名隐藏
  const displayEmail = props.hideDomain
    ? (props.maskPattern === 'full' 
        ? maskEmailDomain(email)
        : hideEmailDomain(email))
    : email;
  
  // 邮件发送处理
  const handleMailto = () => {
    if (props.enableMailto !== false) {
      props.onMailto?.(email);
      handleEmailClick(email, props.mailtoOptions);
    }
  };
  
  const canMailto = props.enableMailto !== false;
  const showAsLink = props.displayAsLink !== false && canMailto;
  
  return (
    <div className="pisell-email-read">
      <Space size="small">
        {showAsLink ? (
          <Typography.Link
            onClick={handleMailto}
            style={{
              fontSize: props.fontSize || 16,
              fontWeight: props.fontWeight || 500,
              color: props.linkColor || '#7F56DA',
            }}
          >
            {displayEmail}
          </Typography.Link>
        ) : (
          <Typography.Text
            style={{
              fontSize: props.fontSize || 16,
              fontWeight: props.fontWeight || 500,
              color: props.color || '#101828',
            }}
          >
            {displayEmail}
          </Typography.Text>
        )}
        
        {props.showEmailIcon && canMailto && (
          <Button
            type="link"
            size="small"
            icon={<MailOutlined />}
            onClick={handleMailto}
          />
        )}
      </Space>
    </div>
  );
};

export default ReadOnlyView;
```

## 6 组件组成与引用

### 6.1 依赖的外部组件和工具

| 组件/工具             | 来源                | 用途       |
|-------------------|-------------------|----------|
| AutoComplete      | antd              | 域名自动补全   |
| Input             | antd              | 邮箱输入框    |
| Typography.Link   | antd              | 只读态链接展示  |
| Typography.Text   | antd              | 只读态文本展示  |
| Button            | antd              | 邮件图标按钮   |
| MailOutlined      | @ant-design/icons | 邮件图标     |
| useTextInputState | @pisell/utils     | 状态管理（复用） |
| getDisplayState   | @pisell/utils     | 状态计算（复用） |

### 6.2 组件结构图

```
PisellEmail
├── EditView
│   ├── AutoComplete
│   │   └── Input（前缀：邮件图标）
│   └── 错误提示
├── ReadOnlyView
│   ├── Typography.Link 或 Typography.Text（邮箱地址，可点击发邮件）
│   └── Button（邮件图标，可选）
└── DisabledView
    ├── EditView（复用）
    └── ReadOnlyView（复用）
```

### 6.3 复用关系

```
PisellSingleLineText（基础组件）
    ↓ 公共逻辑（@pisell/utils）
PisellEmail（邮箱组件）
    ↓ 复用
├── useTextInputState（状态管理）
├── validateValue（基础校验）
└── getDisplayState（状态计算）
    ↓ 扩展
├── validateEmail（邮箱格式校验）
├── getEmailSuggestions（域名补全）
├── handleEmailClick（mailto 发送）
└── hideEmailDomain（域名隐藏）
```

## 7 扩展性设计

### 7.1 通用性

- 常用邮箱域名开箱即用
- 支持自定义域名补全列表
- 支持白名单/黑名单域名限制
- 不绑定业务逻辑
- 支持自定义校验

### 7.2 可扩展点

1. **自定义域名补全列表**：通过 autoCompleteDomains 配置
2. **域名白名单**：通过 allowedDomains 限制可用域名
3. **域名黑名单**：通过 blockedDomains 禁用某些域名
4. **自定义校验**：通过 customValidator 函数
5. **自定义 mailto 参数**：通过 mailtoOptions 配置

### 7.3 国际化扩展

支持多语言错误提示（预留接口）：

```
const ERROR_MESSAGES = {
  'zh-CN': {
    required: '请输入邮箱地址',
    invalid: '邮箱地址格式不正确',
    domainNotAllowed: '只允许以下域名：{domains}',
    domainBlocked: '该邮箱域名不可用',
  },
  'en-US': {
    required: 'Please enter email address',
    invalid: 'Invalid email format',
    domainNotAllowed: 'Only these domains are allowed: {domains}',
    domainBlocked: 'This email domain is not available',
  }
};
```

## 8 性能优化

### 8.1 优化策略

1. **React.memo**：避免不必要的重渲染
2. **useMemo**：缓存域名列表
3. **useCallback**：缓存事件处理函数
4. **复用公共逻辑**：减少代码量
5. **防抖搜索**：域名补全可选防抖（避免频繁计算）

### 8.2 性能指标

- 首次渲染：< 20ms
- 重渲染：< 10ms
- 域名补全响应：< 5ms

## 9 开发注意事项

### 9.1 代码规范

1. 使用 TypeScript 严格模式
2. 所有函数添加 JSDoc 注释
3. 遵循 ESLint 和 Prettier 规则
4. 复用 @pisell/utils 中的公共逻辑

### 9.2 关键实现点

1. **邮箱规范化**：
  - 统一转小写处理
  - 使用 `normalizeEmail` 函数

2. **域名补全触发时机**：
  - 输入 @ 后开始显示建议
  - 输入域名部分时过滤匹配项

3. **校验优先级**：
  - 必填校验 > 格式校验 > 白名单校验 > 黑名单校验 > 自定义校验

4. **mailto 参数编码**：
  - 使用 encodeURIComponent 编码参数
  - 防止特殊字符导致的问题

5. **域名隐藏场景**：
  - 仅在只读态生效
  - 不影响实际值

### 9.3 常见问题

**Q1：为什么域名补全不显示？**
A：检查是否输入了 @ 符号，且 enableAutoComplete 未设置为 false

**Q2：严格校验和标准校验的区别？**
A：严格校验遵循 RFC 5322 标准，支持更复杂的邮箱格式；标准校验覆盖99%常见场景

**Q3：如何限制只能使用公司邮箱？**
A：使用 allowedDomains 配置白名单：`allowedDomains={['company.com']}`

## 10 总结

PisellEmail 是一个功能丰富的邮箱地址输入组件，通过复用 @pisell/utils 的公共逻辑，大幅减少了开发工作量。组件提供了域名智能补全、格式校验、mailto
发送和域名隐藏等特性。

**核心优势**：

- ✅ 常用邮箱域名开箱即用
- ✅ 智能域名补全（基于 AutoComplete）
- ✅ 严格的邮箱格式校验（标准/RFC 5322）
- ✅ 域名白名单/黑名单限制
- ✅ mailto 邮件发送（一键唤起邮件客户端）
- ✅ 域名隐藏/脱敏（隐私保护）
- ✅ 完整复用公共逻辑（70% 代码复用）
- ✅ 与 SingleLineText 一致的 API 风格
