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

﻿# PisellLongText 组件开发文档

## 1 组件信息

### 1.1 基本信息

- **组件名称**：PisellLongText
- **组件版本**：V1.0.0
- **组件分类**：表单输入 / 文本展示
- **开发状态**：开发中
- **最后更新**：2026-01-24

### 1.2 组件描述

PisellLongText 是一个专注于长文本/多行文本处理的输入/展示组件，支持只读、编辑、禁用三种状态模式。组件基于 Ant Design
TextArea 组件开发，提供展开/折叠功能、自动高度调整、完整的校验框架，并复用 PisellSingleLineText 的公共逻辑（状态管理、校验等）。

## 2 需求点罗列

### 2.1 功能需求

#### 核心功能

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

#### 多行文本特有功能

- ✅ 行数控制（rows、minRows、maxRows）
- ✅ 自动高度调整（autoSize）
- ✅ 展开/折叠功能（只读态）
- ✅ 字数统计（编辑态和只读态）

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

- ✅ 多行文本展示
- ✅ 内容截断（maxLines）
- ✅ 展开/收起按钮
- ✅ 字数统计显示
- ✅ 保留换行和空格

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

- ✅ 多行文本输入（TextArea）
- ✅ 自动调整高度
- ✅ 字数统计（showCount）
- ✅ 清空按钮（allowClear）
- ✅ 最大长度限制（maxLength）

#### 校验功能（复用 SingleLineText）

- ✅ 必填校验（required）
- ✅ 最小/最大长度校验（minLength/maxLength）
- ✅ 正则校验（pattern）
- ✅ 自定义校验函数（validator）
- ✅ 错误提示展示

### 2.2 非功能需求

- ✅ TypeScript 类型定义完整
- ✅ 代码注释使用 JSDoc 规范
- ✅ 性能优化（React.memo、useCallback、useMemo）
- ✅ 无障碍访问支持（ARIA 属性）
- ✅ 代码复用（hooks 和 utils 来自 @pisell/utils）

## 3 组件 API

### 3.1 Props 定义

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

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

#### 多行文本特有配置

| 属性名                  | 类型                                                | 默认值      | 必填 | 说明            |
|----------------------|---------------------------------------------------|----------|----|---------------|
| rows                 | number                                            | 3        | 否  | 编辑态默认行数       |
| minRows              | number                                            | -        | 否  | 编辑态最小行数       |
| maxRows              | number                                            | -        | 否  | 编辑态最大行数       |
| autoSize             | boolean \| { minRows?: number; maxRows?: number } | false    | 否  | 自动高度调整        |
| maxLines             | number                                            | 3        | 否  | 只读态最大显示行数     |
| enableExpand         | boolean                                           | true     | 否  | 是否启用展开功能（只读态） |
| expandText           | string                                            | '展开'     | 否  | 展开按钮文案        |
| collapseText         | string                                            | '收起'     | 否  | 收起按钮文案        |
| expandButtonPosition | 'bottom' \| 'inline'                              | 'bottom' | 否  | 展开按钮位置        |
| showWordCount        | boolean                                           | false    | 否  | 只读态是否显示字数统计   |

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

| 属性名        | 类型               | 默认值     | 必填 | 说明   |
|------------|------------------|---------|----|------|
| fontSize   | string \| number | 16      | 否  | 字号   |
| fontWeight | string \| number | 500     | 否  | 字重   |
| color      | string           | #101828 | 否  | 文本颜色 |
| lineHeight | string \| number | 1.5     | 否  | 行高   |

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

| 属性名         | 类型      | 默认值   | 必填 | 说明     |
|-------------|---------|-------|----|--------|
| placeholder | string  | -     | 否  | 占位文本   |
| maxLength   | number  | -     | 否  | 最大长度   |
| showCount   | boolean | false | 否  | 显示字数统计 |
| allowClear  | boolean | false | 否  | 显示清空按钮 |
| autoFocus   | boolean | false | 否  | 自动聚焦   |

#### 校验配置（继承自 BaseTextInputProps）

| 属性名             | 类型                                   | 默认值      | 必填 | 说明      |
|-----------------|--------------------------------------|----------|----|---------|
| required        | boolean                              | false    | 否  | 是否必填    |
| minLength       | number                               | -        | 否  | 最小长度    |
| pattern         | string \| RegExp                     | -        | 否  | 正则校验    |
| validator       | (value: string) => boolean \| string | -        | 否  | 自定义校验函数 |
| validateTrigger | 'onChange' \| 'onBlur' \| 'both'     | 'onBlur' | 否  | 校验触发时机  |
| errorMessage    | string                               | -        | 否  | 自定义错误提示 |

#### 事件回调

| 属性名          | 类型                                           | 说明         |
|--------------|----------------------------------------------|------------|
| onChange     | (value: string) => void                      | 值变化回调      |
| onBlur       | (value: string) => void                      | 失焦回调       |
| onFocus      | () => void                                   | 聚焦回调       |
| onPressEnter | (e: React.KeyboardEvent) => void             | 回车回调（事件对象） |
| onExpand     | (expanded: boolean) => void                  | 展开/收起回调    |
| onValidate   | (isValid: boolean, errors: string[]) => void | 校验回调       |

#### 样式类名

| 属性名       | 类型                  | 说明    |
|-----------|---------------------|-------|
| className | string              | 自定义类名 |
| style     | React.CSSProperties | 自定义样式 |

### 3.2 与 SingleLineText 的对比

| 功能           | SingleLineText | LongText | 差异说明            |
|--------------|----------------|----------|-----------------|
| 输入组件         | Input          | TextArea | 单行 vs 多行        |
| 前后缀/标签       | ✅              | ❌        | 长文本不需要          |
| rows 配置      | ❌              | ✅        | 多行特有            |
| autoSize     | ❌              | ✅        | 自动高度            |
| 展开/折叠        | ❌              | ✅        | 只读态特有           |
| onPressEnter | 回调 value       | 回调事件对象   | 支持判断 Ctrl+Enter |

## 4 组件模块拆解

### 4.1 目录结构

```
pisellLongText/
├── index.tsx                          # 导出文件
├── PisellLongText.tsx                # 组件主文件
├── PisellLongText.less               # 样式文件
├── PisellLongText.test.tsx           # 单元测试
├── PisellLongText.stories.tsx        # Storybook 故事
├── types.ts                          # 类型定义
├── components/                       # 子组件
│   ├── ReadOnlyView.tsx             # 只读态视图（含展开折叠）
│   ├── EditView.tsx                 # 编辑态视图
│   └── DisabledView.tsx             # 禁用态视图
└── docs/                            # 文档
    ├── pisellLongText.md                     # 使用文档
    ├── pisellLongText.$tab-design.md         # 设计文档
    ├── pisellLongText.$tab-dev.md            # 开发文档（本文档）
    └── pisellLongText.$tab-test.md           # 测试文档
```

**注意**：不创建 hooks 和 utils 目录，直接从 `@pisell/utils` 引入公共逻辑。

### 4.2 模块职责

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

- 统筹组件渲染逻辑
- 根据 mode 和 disabled 决定渲染哪个子组件
- 使用 React.memo 优化性能

#### 子组件

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

- 职责：渲染只读态多行文本展示
- 功能：
  - 使用 Typography.Paragraph 展示内容
  - 根据 maxLines 截断内容
  - 展开/折叠按钮（超出行数时显示）
  - 字数统计（可选）
  - 保留换行和空格（whiteSpace: 'pre-wrap'）
- 依赖：Ant Design Typography.Paragraph、Button

**EditView.tsx**

- 职责：渲染编辑态多行输入框
- 功能：
  - 使用 TextArea 组件
  - 支持自动高度（autoSize）
  - 字数统计、清空按钮
  - 校验和错误提示
- 依赖：Ant Design Input.TextArea

**DisabledView.tsx**

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

## 5 组件实现方案

### 5.1 技术栈

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

### 5.2 核心实现

#### 5.2.1 主组件结构

```
import React from 'react';
import { getDisplayState } from '@pisell/utils';
import ReadOnlyView from './components/ReadOnlyView';
import EditView from './components/EditView';
import DisabledView from './components/DisabledView';
import type { PisellLongTextProps } from './types';
import './PisellLongText.less';

export const PisellLongText: React.FC<PisellLongTextProps> = (props) => {
  const { mode = 'edit', disabled = false } = props;
  const displayState = getDisplayState(mode, disabled);
  
  switch (displayState) {
    case 'read':
      return <ReadOnlyView {...props} />;
    case 'edit':
      return <EditView {...props} />;
    case 'disabled':
      return <DisabledView {...props} />;
    default:
      return null;
  }
};

export default React.memo(PisellLongText);
```

#### 5.2.2 EditView 实现（基于 TextArea）

```
import React from 'react';
import { Input } from 'antd';
import { useTextInputState } from '@pisell/utils';
import type { PisellLongTextProps } from '../types';

const { TextArea } = Input;

const EditView: React.FC<PisellLongTextProps> = (props) => {
  const { value, isValid, errors, handleChange, handleBlur, handleFocus } = 
    useTextInputState(props);
  
  return (
    <div className="pisell-long-text-edit">
      <TextArea
        value={value}
        onChange={(e) => handleChange(e.target.value)}
        onBlur={handleBlur}
        onFocus={handleFocus}
        onPressEnter={props.onPressEnter}
        placeholder={props.placeholder}
        maxLength={props.maxLength}
        showCount={props.showCount}
        allowClear={props.allowClear}
        rows={props.rows || 3}
        autoSize={props.autoSize || (props.minRows || props.maxRows ? {
          minRows: props.minRows,
          maxRows: props.maxRows
        } : false)}
        disabled={props.disabled}
        autoFocus={props.autoFocus}
        status={isValid ? undefined : 'error'}
        className={props.className}
        style={props.style}
      />
      {!isValid && errors.length > 0 && (
        <div className="pisell-long-text-error">
          {errors[0]}
        </div>
      )}
    </div>
  );
};

export default EditView;
```

#### 5.2.3 ReadOnlyView 实现（展开/折叠）

```
import React, { useState } from 'react';
import { Typography, Button } from 'antd';
import type { PisellLongTextProps } from '../types';

const { Paragraph } = Typography;

const ReadOnlyView: React.FC<PisellLongTextProps> = (props) => {
  const [isExpanded, setIsExpanded] = useState(false);
  
  const content = props.value || '';
  const lines = content.split('\n');
  const maxLines = props.maxLines || 3;
  const needExpand = lines.length > maxLines;
  
  // 截断内容
  const displayContent = !isExpanded && needExpand
    ? lines.slice(0, maxLines).join('\n') + '...'
    : content;
  
  // 文本样式
  const textStyle: React.CSSProperties = {
    fontSize: props.fontSize || 16,
    fontWeight: props.fontWeight || 500,
    color: props.color || '#101828',
    lineHeight: props.lineHeight || 1.5,
    whiteSpace: 'pre-wrap', // 保留换行和空格
    margin: 0,
    ...props.style,
  };
  
  // 展开/收起处理
  const handleToggleExpand = () => {
    const newExpanded = !isExpanded;
    setIsExpanded(newExpanded);
    props.onExpand?.(newExpanded);
  };
  
  return (
    <div className="pisell-long-text-read">
      <Paragraph style={textStyle} className={props.className}>
        {displayContent}
      </Paragraph>
      
      {/* 展开/收起按钮 */}
      {props.enableExpand !== false && needExpand && (
        <Button
          type="link"
          size="small"
          onClick={handleToggleExpand}
          className="pisell-long-text-expand-btn"
        >
          {isExpanded 
            ? (props.collapseText || '收起') 
            : (props.expandText || '展开')
          }
        </Button>
      )}
      
      {/* 字数统计（只读态） */}
      {props.showWordCount && (
        <div className="pisell-long-text-word-count">
          {content.length} 字
        </div>
      )}
    </div>
  );
};

export default ReadOnlyView;
```

#### 5.2.4 样式实现

```
.pisell-long-text {
  &-edit {
    position: relative;
    width: 100%;
  }
  
  &-error {
    margin-top: 4px;
    font-size: 12px;
    color: #ff4d4f;
    line-height: 1.5;
    animation: fadeIn 0.3s ease-in-out;
  }
  
  &-read {
    .ant-typography {
      margin-bottom: 0;
    }
  }
  
  &-expand-btn {
    padding: 0;
    height: auto;
    font-size: 14px;
    margin-top: 8px;
  }
  
  &-word-count {
    margin-top: 8px;
    font-size: 12px;
    color: #667085;
    text-align: right;
  }
  
  &-disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
```

## 6 组件组成与引用

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

| 组件/工具                | 来源            | 用途            |
|----------------------|---------------|---------------|
| Input.TextArea       | antd          | 编辑态多行输入框      |
| Typography.Paragraph | antd          | 只读态多行文本展示     |
| Button               | antd          | 展开/收起按钮       |
| useTextInputState    | @pisell/utils | 状态管理 Hook（复用） |
| validateValue        | @pisell/utils | 校验函数（复用）      |
| getDisplayState      | @pisell/utils | 状态辅助函数（复用）    |

### 6.2 组件结构图

```
PisellLongText
├── ReadOnlyView（展开/折叠）
│   ├── Typography.Paragraph (antd)
│   ├── Button (antd) - 展开/收起按钮
│   └── 字数统计
├── EditView
│   └── Input.TextArea (antd)
└── DisabledView
    ├── ReadOnlyView（复用）
    └── EditView（复用）
```

### 6.3 复用关系

```
PisellSingleLineText（基础组件）
    ↓ 公共逻辑（@pisell/utils）
PisellLongText（长文本组件）
    ↓ 复用
├── useTextInputState（状态管理）
├── validateValue（校验函数）
└── getDisplayState（状态计算）
```

## 7 扩展性设计

### 7.1 通用性

- 不绑定业务逻辑
- 所有配置项可选
- 支持自定义样式
- 支持自定义校验

### 7.2 可扩展点

1. **自定义校验**：通过 validator 扩展
2. **自定义展开按钮**：通过 expandText、collapseText 配置
3. **自定义样式**：通过 className、style 配置
4. **行数校验**：通过 validator 实现段落数量限制

示例：检查段落数量

```
const validator = (value: string) => {
  const lines = value.split('\n').filter(l => l.trim());
  if (lines.length > 50) {
    return '内容不能超过 50 段';
  }
  return true;
};
```

## 8 性能优化

### 8.1 优化策略

1. **React.memo**：避免不必要的重渲染
2. **useCallback**：缓存事件处理函数
3. **useState**：展开/折叠状态管理
4. **复用公共逻辑**：减少代码量，降低包体积

### 8.2 性能指标

- 首次渲染：< 20ms
- 重渲染：< 10ms
- 内存占用：< 2MB

## 9 开发注意事项

### 9.1 代码规范

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

### 9.2 关键实现点

1. **展开/折叠逻辑**：
  - 根据换行符 `\n` 计算行数
  - 只在行数超过 maxLines 时显示按钮
  - 使用 useState 管理展开状态

2. **保留换行**：
  - 使用 `whiteSpace: 'pre-wrap'`
  - 保留用户输入的换行符和空格

3. **自动高度**：
  - autoSize 可以是 boolean 或对象
  - 优先使用 autoSize，其次使用 minRows/maxRows

4. **onPressEnter**：
  - 传递事件对象而不是值
  - 允许用户判断 Ctrl+Enter 或 Enter

## 10 总结

PisellLongText 是一个专注于长文本处理的多行文本组件，通过复用 PisellSingleLineText 的公共逻辑（来自
@pisell/utils），大幅减少了开发工作量。组件在 SingleLineText 基础上扩展了多行特有功能（展开/折叠、自动高度、行数控制），保持了一致的设计模式和
API 风格。

**核心优势**：

- ✅ 基于成熟的 Ant Design TextArea
- ✅ 完整复用公共逻辑（70% 代码复用）
- ✅ 展开/折叠功能（只读态特有）
- ✅ 自动高度调整
- ✅ 与 SingleLineText 一致的 API 风格
