npm package: @linch-tech/print-sdk
Description: Linch Print Studio SDK - 打印解决方案 SDK，支持 Web 和 Node.js
Author: Linch
Latest version: 0.1.1

# @linch-tech/print-sdk

Linch Print Studio 的 JavaScript/TypeScript SDK，支持 Web 应用和 Node.js 服务调用。

## 安装

```bash
npm install @linch-tech/print-sdk
# or
pnpm add @linch-tech/print-sdk
# or
yarn add @linch-tech/print-sdk
```

## 快速开始

### Web 应用

```typescript
import { LinchPrint } from '@linch-tech/print-sdk';

// 单次打印
await LinchPrint.template('order-tpl')
  .data({
    orderNo: 'ORD-2024-001',
    customer: { name: '张三', phone: '13800138000' },
    items: [
      { name: '商品A', qty: 2, price: 99.00 },
      { name: '商品B', qty: 1, price: 199.00 }
    ]
  })
  .print({ printer: 'HP LaserJet', copies: 2 });
```

### Node.js 服务

```typescript
import { LinchPrint } from '@linch-tech/print-sdk/node';

// 服务端打印
await LinchPrint.template('order-tpl')
  .data({ orderNo: 'ORD-001', customer: { name: '张三' } })
  .print({ printer: 'Office Printer' });
```

## API 参考

### 初始化

```typescript
import { createLinchPrint } from '@linch-tech/print-sdk';

// 自定义配置
const printer = createLinchPrint({
  baseUrl: 'http://localhost:4500',  // API 地址
  timeout: 30000,                     // 请求超时（毫秒）
});

// 或使用默认配置
import { LinchPrint } from '@linch-tech/print-sdk';
```

### LinchPrint

主入口对象，提供以下方法：

#### `LinchLinchPrint.template(id)`

选择模板，返回 `TemplateClient` 实例。

```typescript
const tpl = LinchLinchPrint.template('order-tpl');
```

#### `LinchLinchPrint.printers()`

获取可用打印机列表。

```typescript
const printers = await LinchLinchPrint.printers();
// ['HP LaserJet Pro', 'Canon PIXMA', 'Microsoft Print to PDF']
```

#### `LinchPrint.templates()`

获取所有模板列表。

```typescript
const templates = await LinchPrint.templates();
// [{ id: 'order-tpl', name: '订单打印模板', updatedAt: 1703001234 }]
```

#### `LinchPrint.getJob(jobId)`

查询打印任务状态。

```typescript
const job = await LinchPrint.getJob('job_1703001234567');
// { id: '...', status: 'completed', printer: 'HP LaserJet', ... }
```

---

### TemplateClient

通过 `LinchPrint.template(id)` 获取。

#### `template.data(payload)`

绑定单条数据，返回 `PrintTask` 实例。

```typescript
const task = LinchPrint.template('order-tpl').data({
  orderNo: 'ORD-001',
  customer: { name: '张三' }
});
```

#### `template.batch(payloads)`

绑定多条数据（批量打印），返回 `PrintTask` 实例。

```typescript
const task = LinchPrint.template('order-tpl').batch([
  { orderNo: 'ORD-001', customer: { name: '张三' } },
  { orderNo: 'ORD-002', customer: { name: '李四' } },
  { orderNo: 'ORD-003', customer: { name: '王五' } },
]);
```

#### `template.schema()`

获取模板的数据 Schema 定义。

```typescript
const schema = await LinchPrint.template('order-tpl').schema();
// {
//   version: '1.0',
//   fields: [
//     { name: 'orderNo', type: 'string', required: true },
//     { name: 'customer.name', type: 'string' },
//     ...
//   ]
// }
```

#### `template.validate(data)`

验证数据是否符合模板 Schema。

```typescript
const result = LinchPrint.template('order-tpl').validate({
  orderNo: 'ORD-001',
  customer: { name: '张三' }
});

if (!result.valid) {
  console.error('数据验证失败:', result.errors);
}
```

---

### PrintTask

通过 `template.data()` 或 `template.batch()` 获取。

#### `task.preview()`

生成预览 HTML。

```typescript
const html = await LinchPrint.template('order-tpl')
  .data({ orderNo: 'ORD-001' })
  .preview();

// 在页面中显示预览
document.getElementById('preview').innerHTML = html;
```

#### `task.print(options?)`

执行打印。

```typescript
const result = await LinchPrint.template('order-tpl')
  .data({ orderNo: 'ORD-001' })
  .print({
    printer: 'HP LaserJet',  // 打印机名称
    copies: 2,                // 份数
  });

console.log('打印任务ID:', result.jobId);
```

#### `task.printBatch(options?)`

批量打印（需要先调用 `batch()` 绑定多条数据）。

```typescript
const results = await LinchPrint.template('order-tpl')
  .batch(orders)
  .printBatch({
    printer: 'HP LaserJet',
    copies: 1,
    continueOnError: true,  // 遇错继续
    onProgress: (current, total) => {
      console.log(`打印进度: ${current}/${total}`);
    }
  });

// 检查结果
results.forEach((r, i) => {
  if (r.status === 'failed') {
    console.error(`订单 ${i} 打印失败:`, r.error);
  }
});
```

---

## 类型定义

### PrintOptions

```typescript
interface PrintOptions {
  printer?: string;    // 打印机名称，不指定则使用默认打印机
  copies?: number;     // 打印份数，默认 1
}
```

### BatchPrintOptions

```typescript
interface BatchPrintOptions extends PrintOptions {
  continueOnError?: boolean;  // 遇错是否继续，默认 false
  onProgress?: (current: number, total: number) => void;  // 进度回调
}
```

### PrintResult

```typescript
interface PrintResult {
  jobId: string;
  status: 'pending' | 'printing' | 'completed' | 'failed';
  error?: string;
}
```

### TemplateSchema

```typescript
interface TemplateSchema {
  version: '1.0';
  fields: FieldDefinition[];
}

interface FieldDefinition {
  name: string;           // 字段名（支持 customer.name）
  type: FieldType;        // 类型
  label?: string;         // 显示名称
  required?: boolean;     // 是否必填
  default?: any;          // 默认值
  description?: string;   // 描述
  items?: FieldDefinition;       // 数组元素定义
  properties?: FieldDefinition[]; // 对象属性定义
}

type FieldType = 'string' | 'number' | 'boolean' | 'date' | 'image' | 'array' | 'object';
```

### ValidationResult

```typescript
interface ValidationResult {
  valid: boolean;
  errors: ValidationError[];
}

interface ValidationError {
  field: string;
  message: string;
}
```

---

## 使用场景示例

### 1. 电商订单打印

```typescript
import { LinchPrint } from '@linch-tech/print-sdk';

// 订单详情页 - 打印单个订单
async function printOrder(order) {
  try {
    const result = await LinchPrint.template('order-receipt')
      .data({
        orderNo: order.id,
        orderDate: order.createdAt,
        customer: {
          name: order.customerName,
          phone: order.customerPhone,
          address: order.shippingAddress
        },
        items: order.items.map(item => ({
          name: item.productName,
          sku: item.sku,
          qty: item.quantity,
          price: item.unitPrice,
          total: item.quantity * item.unitPrice
        })),
        totalAmount: order.totalAmount,
        paymentMethod: order.paymentMethod
      })
      .print({ copies: 2 });

    console.log('打印成功:', result.jobId);
  } catch (error) {
    console.error('打印失败:', error.message);
  }
}
```

### 2. 批量打印发货单

```typescript
// 批量发货页面
async function batchPrintShippingLabels(orders) {
  const dataList = orders.map(order => ({
    orderNo: order.id,
    recipient: order.customerName,
    phone: order.customerPhone,
    address: order.shippingAddress,
    items: order.items.map(i => i.productName).join(', '),
    weight: order.totalWeight,
    courier: order.courierName,
    trackingNo: order.trackingNumber
  }));

  const results = await LinchPrint.template('shipping-label')
    .batch(dataList)
    .printBatch({
      printer: 'Label Printer',
      continueOnError: true,
      onProgress: (current, total) => {
        updateProgressBar(current / total * 100);
      }
    });

  // 统计结果
  const success = results.filter(r => r.status === 'completed').length;
  const failed = results.filter(r => r.status === 'failed').length;

  alert(`打印完成: 成功 ${success} 个, 失败 ${failed} 个`);
}
```

### 3. React 组件集成

```tsx
import { useState } from 'react';
import { LinchPrint } from '@linch-tech/print-sdk';

function OrderPrintButton({ order }) {
  const [loading, setLoading] = useState(false);
  const [printers, setPrinters] = useState([]);
  const [selectedPrinter, setSelectedPrinter] = useState('');

  // 加载打印机列表
  useEffect(() => {
    LinchPrint.printers().then(setPrinters);
  }, []);

  const handlePrint = async () => {
    setLoading(true);
    try {
      await LinchPrint.template('order-tpl')
        .data(order)
        .print({ printer: selectedPrinter });
      toast.success('打印成功');
    } catch (e) {
      toast.error('打印失败: ' + e.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <select value={selectedPrinter} onChange={e => setSelectedPrinter(e.target.value)}>
        {printers.map(p => <option key={p} value={p}>{p}</option>)}
      </select>
      <button onClick={handlePrint} disabled={loading}>
        {loading ? '打印中...' : '打印订单'}
      </button>
    </div>
  );
}
```

### 4. Vue 组件集成

```vue
<template>
  <div>
    <select v-model="selectedPrinter">
      <option v-for="p in printers" :key="p" :value="p">{{ p }}</option>
    </select>
    <button @click="handlePrint" :disabled="loading">
      {{ loading ? '打印中...' : '打印订单' }}
    </button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { LinchPrint } from '@linch-tech/print-sdk';

const props = defineProps(['order']);
const printers = ref([]);
const selectedPrinter = ref('');
const loading = ref(false);

onMounted(async () => {
  printers.value = await LinchPrint.printers();
  if (printers.value.length) {
    selectedPrinter.value = printers.value[0];
  }
});

const handlePrint = async () => {
  loading.value = true;
  try {
    await LinchPrint.template('order-tpl')
      .data(props.order)
      .print({ printer: selectedPrinter.value });
    alert('打印成功');
  } catch (e) {
    alert('打印失败: ' + e.message);
  } finally {
    loading.value = false;
  }
};
</script>
```

### 5. Node.js Express 服务

```typescript
import express from 'express';
import { LinchPrint } from '@linch-tech/print-sdk/node';

const app = express();
app.use(express.json());

// 打印订单接口
app.post('/api/orders/:id/print', async (req, res) => {
  try {
    const order = await db.orders.findById(req.params.id);
    if (!order) {
      return res.status(404).json({ error: 'Order not found' });
    }

    const result = await LinchPrint.template('order-receipt')
      .data({
        orderNo: order.id,
        customer: order.customer,
        items: order.items,
        totalAmount: order.totalAmount
      })
      .print({
        printer: req.body.printer,
        copies: req.body.copies || 1
      });

    res.json({ success: true, jobId: result.jobId });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 批量打印接口
app.post('/api/orders/batch-print', async (req, res) => {
  try {
    const { orderIds, printer } = req.body;
    const orders = await db.orders.findByIds(orderIds);

    const results = await LinchPrint.template('order-receipt')
      .batch(orders.map(o => ({
        orderNo: o.id,
        customer: o.customer,
        items: o.items,
        totalAmount: o.totalAmount
      })))
      .printBatch({
        printer,
        continueOnError: true
      });

    res.json({
      success: true,
      total: results.length,
      completed: results.filter(r => r.status === 'completed').length,
      failed: results.filter(r => r.status === 'failed').length
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);
```

### 6. 预览打印内容

```typescript
// 生成预览 HTML
const previewHtml = await LinchPrint.template('invoice')
  .data(invoiceData)
  .preview();

// 在新窗口中显示预览
const previewWindow = window.open('', '_blank');
previewWindow.document.write(previewHtml);
previewWindow.document.close();

// 如需导出 PDF，可选择 "Microsoft Print to PDF" 打印机
await LinchPrint.template('invoice')
  .data(invoiceData)
  .print({ printer: 'Microsoft Print to PDF' });
```

---

## 注意事项

1. **Linch Print 必须运行**：SDK 通过 HTTP API 与本地运行的 Linch Print 应用通信，确保应用已启动。

2. **跨域访问**：Linch Print 默认允许跨域请求，Web 应用可以直接调用。

3. **模板 ID**：使用 Linch Print 设计器创建模板后，在模板列表中查看模板 ID。

4. **数据字段**：确保传入的数据字段与模板中定义的变量名一致（支持嵌套路径如 `customer.name`）。

5. **错误处理**：所有异步方法可能抛出异常，请使用 try-catch 处理。

---

## License

MIT