npm package: @szjy/pdf-coms
Description: vue3 pdf component
Latest version: 0.1.3

# @szjy/pdf-coms

一个功能强大、易于使用的 Vue 3 PDF 编辑器组件库，支持在线编辑 PDF 文档，包括添加图片、文本、绘图等多种对象，并提供完整的交互式编辑体验。

## 项目简介

`@szjy/pdf-coms` 是一个基于 Vue 3 和 TypeScript 开发的 PDF 编辑器组件库，旨在为 Web 应用提供强大的 PDF 在线编辑能力。该组件库解决了以下核心问题：

- **在线 PDF 编辑**：无需下载 PDF 文件即可在浏览器中直接编辑
- **多类型对象支持**：支持添加图片、文本、手绘图形等多种对象类型
- **交互式操作**：提供拖拽、缩放、旋转等丰富的交互操作
- **中文字体支持**：内置微软雅黑等中文字体，完美支持中文内容编辑
- **响应式设计**：适配桌面端和移动端，提供一致的用户体验

### 适用场景

- 合同文档在线签署与标注
- PDF 表单填写与编辑
- 文档审批与批注
- 图纸标注与说明
- 教育培训材料编辑

## 目录结构

```
pdf-coms/
├── packages/                    # 核心源代码目录
│   ├── api/                    # API 接口层
│   │   └── project.ts          # 项目相关 API
│   ├── common/                 # 通用组件
│   │   ├── PDFObject/          # PDF 对象组件
│   │   │   ├── ImageObject.vue # 图片对象组件
│   │   │   ├── TextObject.vue  # 文本对象组件
│   │   │   └── DrawingObject.vue # 绘图对象组件
│   │   ├── PDFPage.vue         # PDF 页面渲染组件
│   │   ├── DrawingCanvas.vue   # 绘图画布组件
│   │   ├── ThumbnailPanel.vue  # 缩略图面板组件
│   │   ├── ObjectToolbar.vue   # 对象工具栏组件
│   │   └── styles/             # 样式文件
│   ├── components/             # 主要组件
│   │   ├── PdfEditor/          # PDF 编辑器主组件
│   │   │   └── src/
│   │   │       └── index.vue   # 编辑器核心实现
│   │   ├── components.ts       # 组件导出
│   │   ├── installer.ts        # 组件安装器
│   │   ├── create.ts           # 组件创建工具
│   │   └── contants.ts         # 常量定义
│   ├── composables/            # 组合式函数
│   │   ├── useObjects.ts       # 对象管理逻辑
│   │   ├── usePDF.ts           # PDF 处理逻辑
│   │   ├── usePannable.ts      # 拖拽功能逻辑
│   │   └── useClickOutside.ts  # 点击外部检测逻辑
│   ├── types/                  # TypeScript 类型定义
│   │   ├── index.ts            # 类型导出
│   │   ├── objects.ts          # 对象类型定义
│   │   ├── pdf.ts              # PDF 类型定义
│   │   └── errors.ts           # 错误类型定义
│   └── utils/                  # 工具函数
│       ├── pdfOperations.ts    # PDF 操作工具
│       ├── asyncReader.ts      # 异步文件读取
│       ├── assetLoader.ts      # 资源加载器
│       ├── helpers.ts          # 辅助函数
│       ├── request.ts          # HTTP 请求工具
│       └── ...                 # 其他工具函数
├── examples/                   # 示例代码
│   ├── App.vue                 # 示例应用入口
│   ├── components/             # 示例组件
│   │   └── PdfEditorTab.vue    # PDF 编辑器示例
│   └── main.ts                 # 示例应用启动文件
├── dist/                       # 构建输出目录
├── public/                     # 静态资源
│   ├── fonts/                  # 字体文件
│   └── scripts/                # 脚本文件
├── index.ts                    # 库入口文件
├── package.json                # 项目配置
├── vite.config.mts             # Vite 构建配置
├── tsconfig.json               # TypeScript 配置
└── README.md                   # 项目文档
```

### 核心模块说明

| 模块 | 职责 | 关键文件 |
|------|------|----------|
| **components** | 提供可复用的 UI 组件 | PdfEditor/index.vue |
| **composables** | 封装业务逻辑的组合式函数 | usePDF.ts, useObjects.ts |
| **types** | TypeScript 类型定义，确保类型安全 | objects.ts, pdf.ts |
| **utils** | 通用工具函数和辅助方法 | pdfOperations.ts, asyncReader.ts |
| **common** | 通用 UI 组件和样式 | PDFPage.vue, PDFObject/* |

### 模块依赖关系

```mermaid
graph TD
    A[PdfEditor 主组件] --> B[composables 组合式函数]
    A --> C[common 通用组件]
    B --> D[utils 工具函数]
    B --> E[types 类型定义]
    C --> E
    D --> E
    C --> B
```

## 环境与依赖配置

### 运行环境要求

- **Node.js**: >= 16.0.0
- **包管理器**: pnpm (推荐) / npm / yarn
- **浏览器兼容性**: 
  - Chrome >= 90
  - Firefox >= 88
  - Safari >= 14
  - Edge >= 90

### 核心依赖


| 依赖 | 版本 | 用途 |
|------|------|------|
| vue | ^3.4.21 | Vue 3 框架 |
| element-plus | 2.7.1 | UI 组件库 |
| pdfjs-dist | ^2.3.200 | PDF 渲染引擎 |
| pdf-lib | ^1.17.1 | PDF 文档操作库 |
| axios | ^1.4.0 | HTTP 请求库 |
| echarts | ^5.5.0 | 图表库（可选） |
| moment | ^2.29.4 | 日期处理库 |
| downloadjs | ^1.4.7 | 文件下载工具 |

### 开发依赖

| 依赖 | 版本 | 用途 |
|------|------|------|
| typescript | ^5.2.2 | TypeScript 支持 |
| vite | ^5.1.6 | 构建工具 |
| vite-plugin-dts | ^3.7.3 | TypeScript 声明文件生成 |
| unplugin-icons | ^22.1.0 | 图标自动导入 |
| unplugin-vue-components | ^0.26.0 | 组件自动导入 |
| less | ^4.2.0 | Less 预处理器 |
| sass | ^1.79.3 | Sass 预处理器 |

### 安装步骤

#### 1. 克隆项目（开发者）

```bash
git clone <repository-url>
cd pdf-coms
```

#### 2. 安装依赖

```bash
# 使用 pnpm（推荐）
pnpm install

# 或使用 npm
npm install

# 或使用 yarn
yarn install
```

#### 3. 作为 npm 包使用

```bash
npm install @szjy/pdf-coms
# 或
pnpm add @szjy/pdf-coms
```

### 推荐开发工具

- **IDE**: Visual Studio Code
- **VSCode 插件**:
  - Vue Language Features (Volar)
  - TypeScript Vue Plugin (Volar)
  - ESLint
  - Prettier

## 项目运行指南

### 开发模式

启动开发服务器，支持热更新：

```bash
pnpm dev
```

服务器将在 `http://localhost:8822` 启动，并自动打开浏览器。

### 生产构建

#### 标准构建（生产环境）

```bash
pnpm build
```

构建产物将输出到 `dist/` 目录，包括：
- `dist/index.mjs` - ES Module 格式
- `dist/index.umd.js` - UMD 格式
- `dist/index.d.ts` - TypeScript 类型声明文件

#### 开发构建（保留调试信息）

```bash
pnpm build:dev
```

此模式会保留变量名和标识符，便于调试。

### 预览构建结果

```bash
pnpm preview
```

### 发布版本

```bash
# 自动更新版本号并生成 CHANGELOG
pnpm release

# 同步到 cnpm
pnpm sync
```

### 运行模式说明

| 命令 | 模式 | 用途 | 端口 |
|------|------|------|------|
| `pnpm dev` | 开发模式 | 本地开发调试 | 8822 |
| `pnpm build` | 生产构建 | 打包发布 | - |
| `pnpm build:dev` | 开发构建 | 调试构建产物 | - |
| `pnpm preview` | 预览模式 | 预览构建结果 | 默认 |

## 功能模块与使用示例

### 核心功能

1. **PDF 文档加载与渲染**
   - 支持本地文件上传
   - 支持拖拽上传
   - 支持通过 API 加载远程 PDF

2. **对象编辑功能**
   - 图片对象：添加、移动、缩放、删除
   - 文本对象：添加、编辑、格式化、删除
   - 绘图对象：手绘、移动、缩放、删除

3. **页面管理**
   - 缩略图预览
   - 页面添加与删除
   - 页面跳转与选择

4. **视图控制**
   - 缩放控制（30% - 300%）
   - 滚轮缩放（Shift + 滚轮）
   - 自适应缩放

5. **导出功能**
   - 下载编辑后的 PDF
   - 打印预览
   - 生成 PDF Blob 对象

### 基础使用示例

#### 1. 全局注册组件

```typescript
// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import PdfComs from '@szjy/pdf-coms'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)
app.use(PdfComs)
app.mount('#app')
```

#### 2. 在组件中使用

```vue
<template>
  <sz-pdf-editor 
    ref="editorRef"
    :show-upload="true"
  >
    <!-- 可选：自定义头部右侧内容 -->
    <template #header-right>
      <el-button @click="handleCustomAction">自定义操作</el-button>
    </template>
  </sz-pdf-editor>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const editorRef = ref(null)

function handleCustomAction() {
  console.log('自定义操作')
}
</script>
```

#### 3. 通过 API 初始化 PDF

```vue
<template>
  <sz-pdf-editor ref="editorRef" :show-upload="false" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'

const editorRef = ref(null)

onMounted(async () => {
  try {
    // 从服务器获取 PDF 数据
    const response = await axios.get('/api/pdf/document.pdf', {
      responseType: 'arraybuffer'
    })
    
    // 初始化 PDF 数据
    await editorRef.value.initPdfData(response.data, 'document.pdf')
  } catch (error) {
    console.error('PDF 加载失败:', error)
  }
})
</script>
```

#### 4. 获取编辑后的 PDF

```vue
<script setup lang="ts">
import { ref } from 'vue'

const editorRef = ref(null)

async function savePdfToServer() {
  try {
    // 获取编辑后的 PDF Blob
    const pdfBlob = await editorRef.value.getPdfBlob()
    
    // 上传到服务器
    const formData = new FormData()
    formData.append('file', pdfBlob, 'edited-document.pdf')
    
    await axios.post('/api/pdf/upload', formData)
    console.log('PDF 保存成功')
  } catch (error) {
    console.error('PDF 保存失败:', error)
  }
}
</script>
```

### 组件 Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| showUpload | boolean | true | 是否显示"选择 PDF"按钮 |

### 组件方法（通过 ref 调用）

| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| initPdfData | (data: File \| ArrayBuffer \| Uint8Array, filename?: string) | Promise\<boolean\> | 初始化 PDF 数据 |
| getPdfBlob | - | Promise\<Blob\> | 获取编辑后的 PDF Blob 对象 |

### 组件插槽

| 插槽名 | 说明 |
|--------|------|
| header-right | 头部工具栏右侧自定义内容 |

### 核心流程图

```mermaid
flowchart TD
    A[用户上传 PDF] --> B[PDF 解析与渲染]
    B --> C[显示 PDF 页面]
    C --> D{用户操作}
    D -->|添加图片| E[图片对象管理]
    D -->|添加文本| F[文本对象管理]
    D -->|添加绘图| G[绘图对象管理]
    D -->|页面管理| H[添加/删除页面]
    E --> I[对象交互编辑]
    F --> I
    G --> I
    I --> J[实时预览]
    J --> K{保存操作}
    K -->|下载| L[生成 PDF 文件]
    K -->|预览| M[打开新窗口预览]
    K -->|获取 Blob| N[返回 PDF Blob]
```

### 数据流转图

```mermaid
sequenceDiagram
    participant User as 用户
    participant Editor as PdfEditor 组件
    participant Composable as usePDF/useObjects
    participant Utils as pdfOperations
    participant PDFLib as pdf-lib

    User->>Editor: 上传 PDF 文件
    Editor->>Composable: loadPDF(file)
    Composable->>Utils: readAsPDF(file)
    Utils-->>Composable: PDF 页面数组
    Composable-->>Editor: 渲染页面
    
    User->>Editor: 添加对象（图片/文本/绘图）
    Editor->>Composable: addObject(pageIndex, object)
    Composable-->>Editor: 更新对象列表
    
    User->>Editor: 保存 PDF
    Editor->>Composable: savePDF(file, objects)
    Composable->>Utils: generatePDFBytes(file, objects)
    Utils->>PDFLib: 嵌入对象到 PDF
    PDFLib-->>Utils: PDF 字节数据
    Utils-->>Composable: 下载文件
```

## 项目维护规范

### 代码风格

项目使用 TypeScript 和 Vue 3 Composition API，遵循以下规范：

#### 命名规范

- **组件名称**: 使用 PascalCase（如 `PdfEditor.vue`）
- **文件名**: 组件使用 PascalCase，工具函数使用 camelCase
- **变量和函数**: 使用 camelCase（如 `loadPDF`, `pdfFile`）
- **常量**: 使用 UPPER_SNAKE_CASE（如 `COMP_PREFIX`）
- **类型定义**: 使用 PascalCase（如 `PDFObjectType`）

#### TypeScript 规范

```typescript
// ✅ 推荐：明确的类型定义
interface Props {
  id: string
  x: number
  y: number
}

// ✅ 推荐：使用类型推断
const count = ref(0) // 自动推断为 Ref<number>

// ❌ 避免：使用 any 类型
const data: any = {} // 应该定义具体类型
```

#### Vue 组件规范

```vue
<script setup lang="ts">
// ✅ 推荐：使用 Composition API
import { ref, computed } from 'vue'

interface Props {
  title: string
}

const props = defineProps<Props>()
const emit = defineEmits<{
  (e: 'update', value: string): void
}>()
</script>
```

### Git 工作流

#### 分支命名规范

- `main` - 主分支，保持稳定
- `develop` - 开发分支
- `feature/xxx` - 功能分支
- `bugfix/xxx` - 修复分支
- `hotfix/xxx` - 紧急修复分支

#### 提交信息格式（Conventional Commits）

```bash
<type>(<scope>): <subject>

<body>

<footer>
```

**Type 类型：**

- `feat`: 新功能
- `fix`: 修复 bug
- `docs`: 文档更新
- `style`: 代码格式调整（不影响功能）
- `refactor`: 代码重构
- `perf`: 性能优化
- `test`: 测试相关
- `chore`: 构建工具或辅助工具的变动

**示例：**

```bash
feat(editor): 添加页面删除功能

- 实现页面删除逻辑
- 更新缩略图面板
- 添加删除确认提示

Closes #123
```

### 代码审查流程

1. **创建 Pull Request**
   - 确保代码通过 TypeScript 类型检查
   - 填写 PR 描述，说明改动内容
   - 关联相关 Issue

2. **代码审查要点**
   - 代码逻辑正确性
   - 类型定义完整性
   - 性能影响评估
   - 代码可读性和可维护性

3. **合并要求**
   - 至少一位审查者批准
   - 通过所有自动化检查
   - 解决所有审查意见

### CI/CD 流程

```mermaid
graph LR
    A[代码提交] --> B[类型检查]
    B --> C[构建测试]
    C --> D{检查通过?}
    D -->|是| E[合并到主分支]
    D -->|否| F[修复问题]
    F --> A
    E --> G[自动发布]
```

### 版本发布流程

1. 更新版本号和 CHANGELOG
   ```bash
   pnpm release
   ```

2. 推送标签到远程仓库
   ```bash
   git push --follow-tags origin main
   ```

3. 发布到 npm
   ```bash
   npm publish
   ```

4. 同步到 cnpm
   ```bash
   pnpm sync
   ```

## 常见问题与故障排查（FAQ）

### 1. PDF 加载失败

**问题描述**: 上传 PDF 文件后无法正常显示

**可能原因**:
- PDF 文件损坏或格式不正确
- PDF 文件过大导致内存溢出
- pdfjs-dist 版本不兼容

**解决方案**:
```typescript
// 检查文件类型
if (file.type !== 'application/pdf') {
  console.error('不是有效的 PDF 文件')
}

// 检查文件大小（建议限制在 50MB 以内）
if (file.size > 50 * 1024 * 1024) {
  console.error('PDF 文件过大')
}
```

### 2. 中文字体显示异常

**问题描述**: 添加中文文本后显示为方块或乱码

**可能原因**:
- 字体文件未正确加载
- 字体路径配置错误
- 浏览器不支持该字体

**解决方案**:
```typescript
// 确保使用支持中文的字体
const fontFamily = '微软雅黑' // 或 'SimSun', 'SimHei'

// 预加载字体
import { loadFont } from '@/utils/assetLoader'
await loadFont(fontFamily)
```

**最佳实践**:
- 优先使用微软雅黑字体
- 在 `public/fonts/` 目录下放置字体文件
- 确保字体文件路径正确配置

### 3. 对象拖拽不流畅

**问题描述**: 拖拽图片或文本对象时出现卡顿

**可能原因**:
- PDF 页面缩放比例过大
- 对象数量过多
- 浏览器性能限制

**解决方案**:
```typescript
// 优化渲染性能
const renderScale = computed(() => {
  // 根据视图缩放调整渲染质量
  return Math.max(1, Math.min(viewScale.value, 2))
})

// 限制对象数量
const MAX_OBJECTS_PER_PAGE = 50
```

### 4. 保存 PDF 时报错

**问题描述**: 点击下载按钮后提示"Failed to save PDF"

**可能原因**:
- 字体嵌入失败
- 图片格式不支持
- 内存不足

**解决方案**:
```typescript
// 捕获详细错误信息
try {
  await savePDF(pdfFile, objects, fileName, pagesScale)
} catch (error) {
  console.error('保存失败详情:', error)
  
  // 检查是否是字体问题
  if (error.message.includes('font')) {
    console.error('字体嵌入失败，请检查字体配置')
  }
}
```

### 5. 移动端触摸事件不响应

**问题描述**: 在移动设备上无法拖拽或编辑对象

**可能原因**:
- 触摸事件未正确绑定
- CSS touch-action 属性设置不当

**解决方案**:
```vue
<style scoped>
.objects-layer {
  touch-action: none; /* 禁用浏览器默认触摸行为 */
}
</style>
```

### 6. 打印预览窗口被拦截

**问题描述**: 点击打印预览按钮后没有反应

**可能原因**:
- 浏览器拦截了弹窗
- 用户未授权弹窗权限

**解决方案**:
```typescript
const previewWindow = window.open(url, '_blank')

if (!previewWindow) {
  ElMessage.warning('请允许浏览器弹窗，以便预览 PDF')
  // 提供备用方案：直接下载
  download(pdfBytes, fileName, 'application/pdf')
}
```

### 7. TypeScript 类型错误

**问题描述**: 编译时出现类型错误

**解决方案**:
```bash
# 清理缓存并重新安装依赖
rm -rf node_modules pnpm-lock.yaml
pnpm install

# 重新生成类型声明文件
pnpm build
```

### 性能优化建议

1. **PDF 文件优化**
   - 压缩 PDF 文件大小
   - 限制单个文件不超过 50MB
   - 避免加载过多页面的 PDF

2. **渲染优化**
   - 使用虚拟滚动加载页面
   - 按需渲染可见区域
   - 降低非活动页面的渲染质量

3. **内存管理**
   - 及时释放不再使用的对象
   - 避免创建过多的临时变量
   - 使用 WeakMap 管理大对象引用

## 参考资料与延伸阅读

1. **Vue 3 官方文档**  
   https://cn.vuejs.org/  
   Vue 3 核心概念、Composition API 和最佳实践

2. **PDF.js 文档**  
   https://mozilla.github.io/pdf.js/  
   Mozilla 开源的 PDF 渲染引擎，本项目用于 PDF 页面渲染

3. **pdf-lib 文档**  
   https://pdf-lib.js.org/  
   JavaScript PDF 操作库，用于创建和修改 PDF 文档

4. **Element Plus 组件库**  
   https://element-plus.org/zh-CN/  
   基于 Vue 3 的 UI 组件库，本项目使用的 UI 框架

5. **TypeScript 官方文档**  
   https://www.typescriptlang.org/docs/  
   TypeScript 类型系统和高级特性

6. **Vite 构建工具文档**  
   https://cn.vitejs.dev/  
   下一代前端构建工具，本项目使用的构建工具

7. **Canvas API 教程**  
   https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API  
   用于图片渲染和绘图功能的 Canvas API 详解

8. **Web 字体加载最佳实践**  
   https://web.dev/font-best-practices/  
   字体加载优化和中文字体处理方案

---

## 许可证

本项目采用 MIT 许可证。详见 LICENSE 文件。

## 贡献指南

欢迎提交 Issue 和 Pull Request！在提交 PR 之前，请确保：

1. 代码通过 TypeScript 类型检查
2. 遵循项目代码规范
3. 提交信息符合 Conventional Commits 规范
4. 更新相关文档

## 联系方式

如有问题或建议，请通过以下方式联系：

- 提交 Issue: [GitHub Issues](https://github.com/szjy-work/pdf-coms/issues)
- 邮箱: [联系邮箱]

---

**版本**: 0.1.1  
**最后更新**: 2025-10-27