# shenlan-designer

> Framework-agnostic POD product designer based on Konva.js. Canvas editing, print areas, mockup preview, export and print validation.

Latest version **0.1.0** (published 2026-09-27) · MIT license · 0 weekly downloads

## Install

```sh
npm install shenlan-designer
pnpm add shenlan-designer
yarn add shenlan-designer
bun add shenlan-designer
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.1.0 |
| Published | 2026-09-27 |
| First published | 2026-09-27 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=18 |
| Dependencies | 3 |
| Unpacked size | 2.9 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | 泉州深蓝网络 |
| Maintainers | lishengjun1 |
| Keywords | pod, designer, konva, print-on-demand, tshirt, canvas, mockup |

## Links

- npm: https://www.npmjs.com/package/shenlan-designer
- Repository: https://gitee.com/quanzhou-deep-blue-network/shenlan-designer
- Issues: https://gitee.com/quanzhou-deep-blue-network/shenlan-designer/issues
- npm.io page: https://npm.io/package/shenlan-designer

## Dependencies (3)

- [konva](https://npm.io/package/konva.md) ^10.3.0
- [three](https://npm.io/package/three.md) ^0.186.0
- [pixi.js](https://npm.io/package/pixi.js.md) ^8.20.1

## Alternatives

- [pagerjs](https://npm.io/package/pagerjs.md) — 60 weekly downloads
- [whistle.savefor-mock](https://npm.io/package/whistle.savefor-mock.md) — 4 weekly downloads
- [@absmartly/api-mocks](https://npm.io/package/@absmartly/api-mocks.md) — 0 weekly downloads
- [foss-design](https://npm.io/package/foss-design.md) — 0 weekly downloads
- [@chrismessina/raycast-faker](https://npm.io/package/@chrismessina/raycast-faker.md) — 0 weekly downloads

## Recent versions

- 0.1.0 (latest) — 2026-09-27

## README

# shenlan-designer 组件包使用文档

`shenlan-designer` 是一个基于 Konva.js 的 POD 商品设计器组件包。组件包只负责设计器核心能力：画布、产品模板、设计元素、选择变换、图层、撤销重做、导出、生产校验和事件通知。商品价格、购物车、订单、素材库、页面布局等业务能力应放在业务项目中实现。

## 安装

```bash
pnpm add shenlan-designer
```

也支持 npm / yarn：

```bash
npm install shenlan-designer
yarn add shenlan-designer
```

本地联调未发布版本时，可以使用 `file:` 或打包文件：

```bash
pnpm add "file:C:/Work/Front/shenlan-designer/packages/designer"
```

## 快速开始

```ts
import { createDesigner, createTshirtTemplate, type Designer } from "shenlan-designer";

const container = document.querySelector<HTMLDivElement>("#designer");

if (!container) {
  throw new Error("缺少设计器容器");
}

const designer: Designer = createDesigner(container, {
  product: createTshirtTemplate(),
  ui: {
    title: "基础按需印制定制短袖",
    assets: [{ id: "badge", name: "徽章", src: "/badge.svg" }]
  }
});
```

Vue 3 中常见写法：

```vue
<template>
  <div ref="designerRef" class="h-full min-h-[600px]"></div>
</template>

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import { createDesigner, createTshirtTemplate, type Designer } from "shenlan-designer";

const designerRef = ref<HTMLDivElement>();
const designer = shallowRef<Designer>();
const unsubscribers: Array<() => void> = [];

onMounted(() => {
  if (!designerRef.value) return;

  designer.value = createDesigner(designerRef.value, {
    product: createTshirtTemplate(),
    ui: {
      title: "基础按需印制定制短袖"
    }
  });

  unsubscribers.push(
    designer.value.on("selection:change", ({ ids }) => {
      console.log("当前选中元素", ids);
    })
  );
});

onBeforeUnmount(() => {
  unsubscribers.forEach((unsubscribe) => unsubscribe());
  designer.value?.destroy();
});
</script>
```

## 产品模板说明与常见品类示例

`createTshirtTemplate()` 是组件包内置的 T 恤默认模板。它的作用是让你快速启动设计器，不用一开始就手写完整的 `ProductTemplate`。

它不是必须使用的。正式业务中，如果商品是杯子、帆布袋、手机壳或其他 POD 商品，可以自己从后端商品数据组装 `ProductTemplate`，再传给 `createDesigner`。

设计器真正依赖的是产品模板里的这些信息：

```ts
interface ProductTemplate {
  id: string;
  name: string;
  category: string;
  previewMode?: "2d" | "3d";
  colors: ProductColor[];
  sizes: ProductSize[];
  views: ProductViewTemplate[];
}
```

其中最重要的是 `views`。每个 view 表示一个可设计面，例如正面、背面、杯身展开面、手机壳背面等。

`previewMode` 告诉预览组件当前商品用 2D 实拍贴图还是 3D 模型预览。省略时默认为 `"2d"`。某一面也可以单独写 `views[].previewMode` 覆盖产品默认值。设计稿始终是 2D 展开面，这个字段只影响效果图，不影响印刷文件。

### 杯子模板示例

```ts
import { createDesigner, type ProductTemplate } from "shenlan-designer";

const mugProduct: ProductTemplate = {
  id: "mug-basic",
  name: "马克杯",
  category: "mug",
  previewMode: "3d",
  colors: [
    { id: "white", name: "白色", value: "#ffffff" }
  ],
  sizes: [
    { id: "default", name: "默认" }
  ],
  views: [
    {
      id: "wrap",
      name: "杯身展开面",
      canvas: { width: 1200, height: 500 },
      printArea: {
        x: 80,
        y: 60,
        width: 1040,
        height: 380,
        safeInset: 24,
        bleed: 16
      },
      model3d: {
        src: "https://cdn.example.com/models/mug-11oz.glb",
        wrapTarget: "PrintArea",
        colorTarget: "Body",
        primitive: "cylinder"
      }
    }
  ]
};

const designer = createDesigner(container, {
  product: mugProduct,
  mockup: previewEl,
  toolbar: false
});

designer.getPreviewMode(); // "3d"
```

### 帆布袋模板示例

```ts
import type { ProductTemplate } from "shenlan-designer";

const bagProduct: ProductTemplate = {
  id: "canvas-bag",
  name: "帆布袋",
  category: "bag",
  colors: [
    { id: "natural", name: "本白", value: "#f5f0e6" },
    { id: "black", name: "黑色", value: "#111111" }
  ],
  sizes: [
    { id: "default", name: "默认" }
  ],
  views: [
    {
      id: "front",
      name: "正面",
      canvas: { width: 900, height: 1100 },
      printArea: {
        x: 180,
        y: 260,
        width: 540,
        height: 620,
        safeInset: 24,
        bleed: 16
      }
    },
    {
      id: "back",
      name: "背面",
      canvas: { width: 900, height: 1100 },
      printArea: {
        x: 180,
        y: 260,
        width: 540,
        height: 620,
        safeInset: 24,
        bleed: 16
      }
    }
  ]
};
```

### 手机壳模板示例

```ts
import type { ProductTemplate } from "shenlan-designer";

const phoneCaseProduct: ProductTemplate = {
  id: "phone-case-15",
  name: "手机壳",
  category: "phone-case",
  colors: [
    { id: "clear", name: "透明", value: "#ffffff" }
  ],
  sizes: [
    { id: "iphone-15", name: "iPhone 15" }
  ],
  views: [
    {
      id: "back",
      name: "背面",
      canvas: { width: 600, height: 1200 },
      printArea: {
        x: 60,
        y: 80,
        width: 480,
        height: 1040,
        safeInset: 30,
        bleed: 20
      }
    }
  ]
};
```

后续如果某些品类会频繁使用，也可以在组件包中继续增加快捷方法，例如 `createMugTemplate()`、`createCanvasBagTemplate()`、`createPhoneCaseTemplate()`。但业务项目始终可以直接传自己的 `ProductTemplate`，不需要被内置模板限制。

## 初始化选项

```ts
createDesigner(container, options);
```

| 选项 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `product` | `ProductTemplate` | `createTshirtTemplate()` | 产品模板。 |
| `document` | `DesignerDocument` | 自动创建 | 已保存的设计文档。传入后优先使用文档。 |
| `branding` | `string \| DesignerBranding` | 无 | SaaS 租户水印文本。可直接传字符串，斜向铺在红线框外的灰色工作区，不进入设计稿和印刷图。 |
| `brandingVisible` | `boolean` | `true` | 是否显示企业水印。可在运行时用 `setBrandingVisible` 开关。 |
| `width` | `number` | 容器宽度 | 画布显示宽度。 |
| `height` | `number` | 容器高度 | 画布显示高度。 |
| `keyboard` | `boolean` | 整页编辑器下默认 `true`，仅画布模式下默认 `false` | 是否启用撤销/复制/删除等快捷键。按住空格拖动画布平移不依赖此选项。 |
| `keyboardTarget` | `EventTarget` | `window` | 快捷键监听目标。 |
| `toolbar` | `boolean` | 仅画布模式下默认 `true` | 旧版画布小工具条。整页编辑器开启时不会出现。 |
| `ui` | `boolean \| DesignerUiOptions` | `true` | 整页编辑器壳。默认开启，安装后即可得到和示例相同的工作台。设为 `false` 则只挂画布。 |
| `guides` | `boolean \| CanvasGuideOptions` | 整页编辑器下为示例辅助线，仅画布模式下为 `true` | 是否显示辅助线，支持细分控制。 |

默认会挂载整页编辑器。容器需要有明确高度，例如 `height: 100vh`。

```ts
createDesigner(container, {
  product: createTshirtTemplate(),
  branding: "创客工作室",
  ui: {
    topbar: true,
    sidebar: true,
    inspector: true,
    canvasToolbar: true,
    floatingToolbar: true,
    floatingPreview: true,
    branding: true,
    zoomControl: true,
    viewSwitcher: true,
    commerce: true,
    productTab: true,
    assetsTab: true,
    uploadTab: true,
    backgroundTab: true,
    layersTab: true,
    title: "基础按需印制定制短袖",
    assets: [{ id: "badge", name: "徽章", src: "/badge.svg" }]
  }
});
```

某个功能设为 `false` 即隐藏，省略则显示。运行时也可以：

```ts
designer.setUiFeature("sidebar", false);
designer.getUiFeature("inspector"); // true
```

只想要中间画布时：

```ts
const designer = createDesigner(container, {
  product: createTshirtTemplate(),
  ui: false,
  keyboard: true
});
```

## Designer 实例 API

### 文档与视图

#### `getDocument()`

获取当前设计文档的 JSON 克隆。

```ts
const document = designer.getDocument();
```

#### `loadDocument(document)`

加载设计文档。加载后会清空撤销重做历史，并清空选区。

```ts
designer.loadDocument(savedDocument);
```

#### `setProduct(product)`

切换产品模板，并创建新的空设计文档。

```ts
designer.setProduct(createTshirtTemplate());
```

#### `getViews()`

获取当前产品的所有设计视图，例如正面、背面、袖子等。

```ts
const views = designer.getViews();
```

#### `getActiveView()`

获取当前正在编辑的视图。

```ts
const activeView = designer.getActiveView();
```

#### `setActiveView(viewId)`

切换当前编辑视图。

```ts
designer.setActiveView("front");
```

### 添加元素

#### `addText(options)`

添加文字元素，返回元素 id。

```ts
const id = designer.addText({
  text: "我的定制文字",
  x: 120,
  y: 160,
  fontSize: 48,
  fill: "#111827"
});
```

支持指定视图：

```ts
designer.addText({
  viewId: "back",
  text: "背面文字"
});
```

#### `addImage(options)`

添加图片元素，返回元素 id。图片会异步加载自然尺寸。

```ts
const id = await designer.addImage({
  src: "https://example.com/image.png",
  x: 100,
  y: 120,
  width: 300,
  height: 300
});
```

支持指定视图：

```ts
await designer.addImage({
  viewId: "front",
  src: imageUrl
});
```

### 更新与删除元素

#### `updateElement(id, patch)`

更新元素属性。

```ts
designer.updateElement(id, {
  x: 180,
  y: 220,
  fill: "#2563eb",
  opacity: 0.8
});
```

#### `removeElements(ids?)`

删除指定元素。不传 `ids` 时删除当前选中的元素。

```ts
designer.removeElements(["text-1"]);
designer.removeElements();
```

### 选择

```ts
designer.select(["text-1"]);
designer.selectAll();
designer.clearSelection();
const ids = designer.getSelection();
```

### 微移与图层

```ts
designer.nudgeSelection({ x: 1, y: 0 });

designer.bringForward();
designer.sendBackward();
designer.bringToFront();
designer.sendToBack();
```

这些方法默认作用于当前选区，也可以传入元素 id：

```ts
designer.bringToFront(["image-1"]);
```

### 复制、剪切、粘贴、复制一份

```ts
const copiedIds = designer.copy();
const cutIds = designer.cut();
const pastedIds = designer.paste({ offset: { x: 20, y: 20 } });
const duplicatedIds = designer.duplicate();
```

### 撤销重做

```ts
designer.undo();
designer.redo();

const canUndo = designer.canUndo();
const canRedo = designer.canRedo();
```

### 辅助线与网格线

辅助线用于 POD 生产提示和位置对齐：

- 网格线：画布中的细小位置辅助线，用于对齐、估算间距和观察素材位置。
- 灰色边框：画布边界。
- 蓝色实线：打印区域。
- 绿色虚线：安全区域，重要内容建议放在内部。
- 橙色虚线：出血/风险区域，靠近或超出可能被裁切或印偏。
- 出血区域外阴影：提示该区域不建议操作，橙色出血范围内才是设计风险可控区域。

关闭全部辅助线：

```ts
designer.setGuides(false);
```

只控制网格线：

```ts
designer.setGridVisible(false);
designer.setGridVisible(true);

const visible = designer.getGridVisible();
```

细分控制显示项和网格样式：

```ts
designer.setGuides({
  canvasBorder: true,
  grid: {
    visible: true,
    size: 100,
    color: "#94a3b8",
    opacity: 0.32
  },
  printArea: true,
  safeArea: false,
  bleedArea: false,
  outsideBleedMask: true
});
```

业务项目也可以通过 API 传入对应矩形范围，覆盖产品模板里的默认打印范围：

```ts
designer.setGuides({
  printArea: {
    visible: true,
    area: { x: 120, y: 0, width: 583, height: 1200 }
  },
  safeArea: {
    visible: true,
    area: { x: 150, y: 30, width: 523, height: 1140 }
  },
  bleedArea: {
    visible: true,
    area: { x: 108, y: 0, width: 607, height: 1200 }
  },
  outsideBleedMask: {
    visible: true,
    fill: "#111827",
    opacity: 0.34
  }
});
```

读取当前配置：

```ts
const guides = designer.getGuides();
```

初始化时也可以配置：

```ts
createDesigner(container, {
  product: createTshirtTemplate(),
  guides: {
    grid: true,
    printArea: true,
    safeArea: true,
    bleedArea: false,
    outsideBleedMask: true
  }
});
```

`CanvasGuideOptions` 常用结构：

```ts
interface CanvasGuideOptions {
  canvasBorder?: boolean;
  grid?: boolean | {
    visible?: boolean;
    size?: number;
    color?: string;
    opacity?: number;
  };
  printArea?: boolean | { visible?: boolean; area?: Rect };
  safeArea?: boolean | { visible?: boolean; area?: Rect };
  bleedArea?: boolean | { visible?: boolean; area?: Rect };
  outsideBleedMask?: boolean | {
    visible?: boolean;
    fill?: string;
    opacity?: number;
  };
}
```

### 导出

#### `exportJSON()`

导出设计 JSON。

```ts
const json = designer.exportJSON();
```

#### `exportImage(options?)`

导出当前视图或指定视图图片。

```ts
const result = await designer.exportImage({
  viewId: "front",
  area: "print",
  pixelRatio: 2,
  transparent: true
});

console.log(result.dataUrl);
```

常用选项：

| 选项 | 类型 | 说明 |
| --- | --- | --- |
| `viewId` | `string` | 指定导出视图，不传则导出当前视图。 |
| `area` | `"canvas" \| "print"` | 导出整个画布或打印区域。 |
| `pixelRatio` | `number` | 导出倍率。 |
| `transparent` | `boolean` | 是否透明背景。 |
| `backgroundColor` | `string` | 非透明背景时的背景色。 |
| `mimeType` | `"image/png" \| "image/jpeg" \| "image/webp"` | 图片格式。 |
| `quality` | `number` | JPEG/WebP 质量。 |

返回值：

```ts
interface ExportResult {
  dataUrl: string;
  width: number;
  height: number;
  mimeType: string;
}
```

#### `exportAllViews(options?)`

导出所有视图，适合做组合预览或提交整套设计图。

```ts
const results = await designer.exportAllViews({
  area: "print",
  pixelRatio: 2,
  transparent: true
});

for (const item of results) {
  console.log(item.viewId, item.viewName, item.result.dataUrl);
}
```

返回值：

```ts
interface ExportAllViewsResult {
  viewId: string;
  viewName: string;
  result: ExportResult;
}
```

### 生产校验

```ts
const warnings = designer.validateForPrint();
```

可能返回：

- 空设计。
- 元素超出打印区域。
- 元素超出安全区域。
- 图片自然尺寸不足。

### 事件

#### `on(type, listener)`

订阅事件，返回取消订阅函数。

```ts
const unsubscribe = designer.on("selection:change", ({ ids }) => {
  console.log(ids);
});

unsubscribe();
```

事件列表：

| 事件 | 数据 | 说明 |
| --- | --- | --- |
| `document:change` | `{ document }` | 文档变化。 |
| `selection:change` | `{ ids }` | 选区变化。 |
| `element:change` | `{ ids }` | 元素变化。 |
| `viewport:change` | `{ zoom, pan }` | 画布缩放或平移。 |
| `warning` | `{ code, message, details }` | 警告。 |
| `export:done` | `{ type, result }` | 导出完成。 |

### 销毁

页面卸载时必须销毁实例。

```ts
designer.destroy();
```

## 多视图、设计稿与实拍贴合

设计稿和效果图是两个空间：

- 设计稿：Konva 编辑器，使用印刷坐标系，是生产数据。
- 效果图：Pixi 把当前打印区贴到带透视/皱褶的商品实拍上，只用于预览。

```ts
const designer = createDesigner(editorEl, {
  product: createTshirtTemplate(),
  mockup: mockupEl,
  mockupQuality: "live",
  toolbar: false,
  keyboard: true
});

designer.setProductColor("navy");
designer.setActiveView("front");
await designer.addImage({ src: uploadedUrl });

const printFile = await designer.exportImage({
  area: "print",
  transparent: true,
  pixelRatio: 2
});

const mockupPreview = await designer.exportMockupImage({
  mimeType: "image/jpeg"
});

const mockupSheet = await designer.exportMockupImage({
  layout: "sheet",
  mimeType: "image/jpeg"
});
```

产品模板可以提供 Smart Mockup 资源：`preview.base`、`mask`、`displacement`、`lighting`，以及 `mapping.dst` 四点透视。没有位移图时会降级为四点贴图。印刷文件仍然只使用 `printArea` 导出，不要把效果图交给工厂。

预览后端由产品上的 `previewMode` 决定，不要用 `category` 猜测：

- `"2d"`（默认）：Pixi Smart Mockup，适合 T 恤、帆布袋等实拍贴图。
- `"3d"`：Three.js 加载 `model3d.src` 的 glTF / glb，把当前印刷图贴到 `wrapTarget` 网格上，可拖转。没有 `src` 或加载失败时，降级为圆柱 / 盒子 / 平面。
- 某一面可写 `views[].previewMode` 覆盖产品默认值。
- `designer.getPreviewMode()` 读取当前生效模式；`setProduct()` 换商品时预览组件会跟着切换。
- 事件 `preview:modechange` 会在 2D / 3D 后端切换时通知宿主。

## 后端 3D 模型数据契约

运营在后台上传的是 **glTF 场景文件**（推荐单个 `.glb`），接口把它的地址和贴图目标发给设计器。设计器 **不会** 再向你们的上传接口发请求，只消费下面这份 JSON。

商品初始化（可直接作为 `DesignerBootstrap`，或组装成 `ProductTemplate`）：

```json
{
  "product": {
    "id": "mug-11oz",
    "name": "11oz 马克杯",
    "category": "mug",
    "previewMode": "3d",
    "colors": [
      { "id": "white", "name": "白色", "value": "#ffffff" },
      { "id": "black", "name": "黑色", "value": "#111111" }
    ],
    "sizes": [{ "id": "default", "name": "默认" }]
  },
  "views": [
    {
      "id": "wrap",
      "name": "杯身展开面",
      "dpi": 300,
      "print": { "widthCm": 21.0, "heightCm": 9.5 },
      "previewMode": "3d",
      "model3d": {
        "src": "https://cdn.example.com/models/mug-11oz.glb",
        "wrapTarget": "PrintArea",
        "colorTarget": "Body",
        "wrapFlipY": true,
        "primitive": "cylinder",
        "camera": {
          "fov": 35,
          "position": [0.18, 0.12, 0.32],
          "target": [0, 0.05, 0]
        }
      }
    }
  ]
}
```

`views[].model3d` 字段说明：

| 字段 | 必填 | 说明 |
|---|---|---|
| `src` | 3D 商品必填 | 模型文件的 **HTTPS 公网 URL**。推荐 `.glb`（单文件）。CDN 必须允许浏览器跨域读取（`Access-Control-Allow-Origin`，且能 GET 二进制）。签名 URL 也可以，只要设计器打开时仍有效。 |
| `wrapTarget` | 强烈建议 | 承接印刷图的 **网格名或材质名**，需与 glb 里的名字一致，例如 `PrintArea`。大小写不敏感。找不到时会警告 `PREVIEW_3D_WRAP_TARGET_NOT_FOUND`，并尝试贴到第一块带 UV 的网格。 |
| `colorTarget` | 建议 | 换商品颜色时要染色的网格/材质名，例如 `Body`。应和印刷面分开。找不到时警告 `PREVIEW_3D_COLOR_TARGET_NOT_FOUND`。 |
| `wrapFlipY` | 否 | 印刷图 UV 是否翻转 Y，默认 `true`。贴反了就改成 `false`。 |
| `primitive` | 否 | `src` 缺失或加载失败时的降级几何：`cylinder` / `box` / `plane`。杯子建议 `cylinder`。 |
| `color` | 否 | 没有选中商品色时的默认杯体颜色。 |
| `camera.fov` | 否 | 透视相机视角，默认 35。 |
| `camera.position` | 否 | `[x, y, z]`。不传则按模型包围盒自动框住。 |
| `camera.target` | 否 | 相机看向的点，默认 `[0, 0, 0]`。 |
| `exposure` | 否 | 曝光，默认 1。 |

模型文件规范（给运营 / 3D 制作）：

1. 每个 SKU 一份 `.glb`，不要多商品共用对不上 UV 的模型。
2. 印刷面做成独立网格或独立材质，名字固定，例如 `PrintArea`。杯体另叫 `Body`。
3. `PrintArea` 的 UV 必须是杯身展开：U 沿圆周，V 沿高度，且和接口里的 `print.widthCm / heightCm` 是同一张展开图。
4. 杯体颜色不要烤进贴图，用可染色材质，方便 `colors[].value` 换色。
5. Y 轴向上，模型尽量放在原点附近。
6. 不要用 `.gltf + .bin + 多张贴图` 的拆分目录，除非这些附属文件也能用 HTTPS 且跨域可读。

宿主接入：

```ts
const template = productTemplateFromBootstrap(apiPayload);
const designer = createDesigner(editorEl, {
  product: template,
  mockup: previewEl,
  toolbar: false
});
```

加载失败会发出 `PREVIEW_3D_MODEL_LOAD_FAILED` 并暂时显示圆柱占位。印刷导出仍然只使用 2D `printArea`，不要把 3D 截图交给工厂。

## 企业水印与默认图案

两类资源不要混用：

- `mockup`：商品实拍底图，只用于效果图预览，不进入印刷文件。
- `branding.text`：SaaS 租户水印文本，斜向铺在红线框以外的灰色工作区，不写入印刷元素，也不会出现在印刷图 / 效果图导出里。
- `views[].defaultArtwork`（bootstrap 里是 `artwork`）：真正要印到商品上的默认图案，会进入设计稿和印刷文件。

SaaS 租户在运行时传入水印文本：

```ts
const designer = createDesigner(container, {
  product: productTemplateFromBootstrap(apiPayload),
  branding: `${tenant.name} - ${user.name}`,
  toolbar: false
});
```

也可以写进后端配置：

```ts
const template = productTemplateFromBootstrap({
  product: {
    id: api.product.id,
    name: api.product.name,
    branding: {
      text: `${tenant.name} - ${user.name}`
    }
  },
  views: api.views.map((view) => ({
    id: view.id,
    name: view.name,
    dpi: view.dpi,
    print: { widthCm: view.widthCm, heightCm: view.heightCm },
    mockup: view.mockup,
    artwork: view.patternUrl
      ? [{ src: view.patternUrl, coverPrintArea: true, imageMode: "fill" }]
      : undefined
  }))
});
```

规则：

- 水印文本斜向铺在画布红线框外的灰色 pasteboard 上，红线以内保持干净；使用设计坐标尺寸，随画布缩放/平移一起变化，并按当前视口重新铺满。
- 默认约 `-30°`、浅灰色。需要更淡、更实或换颜色可传 `branding.opacity` / `branding.color` / `branding.rotation`。
- 页面可用 `designer.setBrandingVisible(false)` / `designer.getBrandingVisible()` 开关显示，不影响设计稿和印刷导出。
- `createDesigner({ branding })` 会覆盖产品模板里的 `product.branding.text`，方便同一个商品模板服务多个租户。可直接传字符串，例如 `branding: "创客工作室"`。
- `views[].artwork` 才是可印刷默认图案；不要把企业水印写进 `artwork`。

后端返回厘米尺寸和 DPI 时，在业务项目里转换，不要让设计器去调接口：

```ts
import { productTemplateFromBootstrap, cmToPx } from "shenlan-designer";

const template = productTemplateFromBootstrap({
  product: api.product,
  views: api.views.map((view) => ({
    id: view.id,
    name: view.name,
    designAreaId: view.designAreaId,
    dpi: view.dpi,
    print: { widthCm: view.widthCm, heightCm: view.heightCm },
    mockup: {
      src: view.mockup.src,
      imageSize: view.mockup.imageSize,
      quad: view.mockup.quad
    }
  }))
});

const printFiles = await designer.exportPrintFiles();
```

`15cm @ 300DPI` 会生成约 `1772px` 的印刷画布。四个点只用于效果图贴合。

## VbenAdmin / Element Plus 接入建议

默认 `createDesigner(container)` 已经带整页编辑器，业务项目可以直接嵌入。如果要继续用 Element Plus 自己做壳，关闭整页 UI，只保留画布：

```ts
const designer = createDesigner(container, {
  product: createTshirtTemplate(),
  ui: false,
  keyboard: true
});
```

然后用 Element Plus 和 TailwindCSS 实现业务 UI：

- 顶部按钮：`el-button`
- 填色：`el-popover` + `el-color-picker`
- 属性面板：`el-form` + `el-input-number` + `el-slider`
- 历史记录：`el-drawer` 或 `el-popover`
- 预览区：业务项目自定义 mockup / 轮播 / 缩略图

示例：

```ts
function addText() {
  designer.addText({
    text: "我的定制文字",
    fontSize: 48,
    fill: "#111827"
  });
}

async function exportPreview() {
  const result = await designer.exportImage({
    area: "print",
    transparent: true
  });
  previewUrl.value = result.dataUrl;
}
```

## 数据边界

设计文档是纯 JSON 数据：

- 不保存 DOM 节点。
- 不保存 Konva 节点。
- 不保存函数和 Promise。
- 图片元素保存 `src`，不默认把二进制写入 JSON。
- 坐标使用设计坐标系，不使用 DOM 像素。
- 旋转使用角度。
- 图层顺序通过 `zIndex` 表达。

---
_Source: https://npm.io/package/shenlan-designer · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
