npm package: @kuangyep/templates-package
Description: 基于 Vue 3 + Vite Library Mode 的证书/打印模板组件库。   当前主要产物是两个 Custom Element：
Latest version: 1.0.3

﻿# Templates Web Component 组件库

基于 Vue 3 + Vite Library Mode 的证书/打印模板组件库。  
当前主要产物是两个 Custom Element：

- `canvas-certificate`（Fabric 渲染证书）
- `hiprint-template`（hiprint 打印模板）

## 安装

```bash
npm i @kuangyep/templates-package fabric
```

说明：

- `fabric` 是 `peerDependencies`，由宿主项目提供。
- `hiprint-template` 依赖全局 `hiprint` / `jQuery`（见下方说明）。

## 导出入口

`package.json` 当前导出：

- `@kuangyep/templates-package` -> `fabricDesign`
- `@kuangyep/templates-package/fabricDesign`
- `@kuangyep/templates-package/hiprintDesign`

## 快速使用

### 1) Fabric 证书组件（推荐）

Vue 3 项目中，在宿主入口注册组件：

```ts
import '@kuangyep/templates-package/fabricDesign'
```

如果宿主是 Vue 3 + Vite，建议在 `vite.config.ts` 中把组件声明为 Custom Element：

```ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) =>
            tag === 'canvas-certificate' || tag === 'hiprint-template'
        }
      }
    })
  ]
})
```

Vue 页面中使用：

```vue
<template>
  <canvas-certificate
    template-id="tpl_001"
    template-api="/api/certificate/template/detail"
    :data.prop="certificateData"
  ></canvas-certificate>
</template>

<script setup lang="ts">
const certificateData = {
  name: '张三',
  cardNo: '510...'
}
</script>
```

说明：

- `template-id`、`template-api` 这类字符串可以直接按 attribute 传递。
- `data` 如果是对象，Vue 3 中推荐使用 `:data.prop="certificateData"`，确保值按 DOM property 传给 Web Component。
- 如果传 JSON 字符串，也可以直接写 `data='{"name":"张三"}'`。

#### Vue 2（Webpack / Vite）自定义元素配置

Vue 2 中不需要 `isCustomElement`，统一在 `main.js` 里配置 `ignoredElements`：

```js
import Vue from 'vue'
import App from './App.vue'

// 根包当前等价于 fabricDesign
import '@kuangyep/templates-package'
// 或者显式使用：import '@kuangyep/templates-package/fabricDesign'

Vue.config.ignoredElements = ['canvas-certificate']
// 若同时使用打印组件可加上：'hiprint-template'
// Vue.config.ignoredElements = ['canvas-certificate', 'hiprint-template']

new Vue({
  render: h => h(App)
}).$mount('#app')
```

说明：

- Vue 2 + Webpack（Vue CLI）和 Vue 2 + Vite 都是这套配置，无需额外改打包器参数。
- `ignoredElements` 的作用是告诉 Vue 2 这些标签是 Custom Element，避免“Unknown custom element”警告。
- 如果宿主是老构建链路（如 Webpack2/Babel6）并报 `Unexpected token ?`，请使用下方 `build:legacy` 产物（降级到 `es2015`）。

### 2) hiprint 组件

在宿主入口注册组件：

```ts
import '@kuangyep/templates-package/hiprintDesign'
```

并确保宿主已提供全局依赖（CDN 顺序）：

```html
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4/dist/socket.io.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-plugin-hiprint@latest/dist/vue-plugin-hiprint.js"></script>
```

`print-lock.css` 已由组件构建时内联注入，宿主无需额外再引入该 CSS。

Vue 页面使用：

```vue
<template>
  <hiprint-template :data.prop="printData"></hiprint-template>
</template>

<script setup lang="ts">
const printData = {
  title: '道路运输服务',
  tablelist: [{ love: '篮球', sex: '男' }]
}
</script>
```

## 组件 API

### `canvas-certificate`

Props：

- `template-id: string | number`（必填）
- `template-api: string`（默认 `/api/certificate/template/detail`）
- `data: object | string`（支持对象或 JSON 字符串）

暴露方法：

- `toDataURL(multiplier?: number)`：导出 PNG dataURL（默认 `2`）
- `reloadTemplate()`：重新拉取模板并渲染

接口返回支持以下结构之一：

- `{ templateJson: {...} }`
- `{ template: {...} }`
- `{ data: { templateJson: {...} } }`
- `{ data: { template: {...} } }`
- `{ data: {...} }`

### Vue2 / Vue3 调用导出方法

`canvas-certificate` 是 Web Component，导出图片时直接拿到元素实例调用：

- `el.toDataURL(multiplier?)`

#### Vue 3（`<script setup>`）

```vue
<template>
  <canvas-certificate
    ref="certRef"
    template-id="tpl_001"
    template-api="/api/certificate/template/detail"
    :data.prop="certificateData"
  />
  <button @click="onExport">导出图片</button>
</template>

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

type CanvasCertificateEl = HTMLElement & {
  toDataURL?: (multiplier?: number) => string
}

const certRef = ref<CanvasCertificateEl | null>(null)
const certificateData = { name: '张三', cardNo: '510...' }

const onExport = async () => {
  await customElements.whenDefined('canvas-certificate')
  const dataUrl = certRef.value?.toDataURL?.(2)
  if (!dataUrl) return

  const link = document.createElement('a')
  link.href = dataUrl
  link.download = `certificate-${Date.now()}.png`
  link.click()
}
</script>
```

#### Vue 2（Options API）

```vue
<template>
  <div>
    <canvas-certificate
      ref="certRef"
      template-id="tpl_001"
      template-api="/api/certificate/template/detail"
      :data="dataJson"
    />
    <button @click="onExport">导出图片</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      certificateData: { name: '张三', cardNo: '510...' }
    }
  },
  computed: {
    dataJson() {
      return JSON.stringify(this.certificateData)
    }
  },
  methods: {
    async onExport() {
      await customElements.whenDefined('canvas-certificate')
      const el = this.$refs.certRef
      const dataUrl = el && el.toDataURL ? el.toDataURL(2) : ''
      if (!dataUrl) return

      const link = document.createElement('a')
      link.href = dataUrl
      link.download = `certificate-${Date.now()}.png`
      link.click()
    }
  }
}
</script>
```

说明：

- `multiplier` 越大，导出越清晰，文件也会更大（常用 `1~2`）。
- Vue 2 场景建议 `data` 传 JSON 字符串，或在 `mounted` 后通过 `this.$refs.certRef.data = {...}` 赋值。

### `hiprint-template`

Props：

- `data: object | string`

暴露方法：

- `print()`：调用 hiprint 打印

## 传参说明（重点）

在 Vue 3 模板中：

- `data` 如果是对象，推荐使用 `.prop` 修饰符按 DOM property 传值。

```vue
<canvas-certificate
  template-id="tpl_001"
  template-api="/api/certificate/template/detail"
  :data.prop="certificateData"
></canvas-certificate>
```

```ts
const certificateData = {
  name: '张三'
}
```

说明：

- `:data="certificateData"` 在 Vue 3 + Web Component 场景下不够稳定，不建议作为 README 推荐写法。
- `:data.prop="certificateData"`、`el.data = {...}`、`data='{"name":"张三"}'` 都是支持的。

在纯 HTML 中：

- attribute 只能是字符串，所以 `data` 通常传 JSON 字符串。

```html
<canvas-certificate data='{"name":"张三"}'></canvas-certificate>
```

如果想传对象，请用 DOM property：

```html
<canvas-certificate id="cert"></canvas-certificate>
<script>
  const el = document.getElementById('cert')
  el.templateId = 'tpl_001'
  el.templateApi = '/api/certificate/template/detail'
  el.data = { name: '张三' }
</script>
```

## 构建命令

```bash
# 启动设计器开发环境
npm run dev

# 构建全部模板入口（扫描 src/templates/*/main-ce.ts）
npm run build

# 打包当前项目（应用构建，入口 src/main.js，输出 dist）
npm run prod

# 构建单个目标（默认会回退到 fabricDesign）
npm run build:single -- fabricDesign
npm run build:single -- hiprintDesign

# 构建 Vue2/vue3 legacy 产物（降级语法到 es2015，避免 ?. / ??）
npm run build:legacy

# 构建单个 legacy 目标
npm run build:single:legacy -- fabricDesign

# 预览
npm run preview
```

## 本地测试（项目服务页面 + mock 接口）

```bash
# 1) 启动 Vite 项目服务（页面）
npm run dev

# 2) 启动 mock 接口服务
npm run test:mock
```

启动后：

- 页面（项目服务）：`http://localhost:5173/webview/certificate.html`
- 健康检查（mock）：`http://localhost:8090/health`
- 模板接口（mock）：`POST/GET http://localhost:8090/api/certificate/template/detail`

说明：

- `public/webview/certificate.html` 由 Vite 直接托管，不再由 node mock 服务提供 HTML。
- 开发模式下，`/api/certificate/*` 会自动代理到 `http://127.0.0.1:8090`（可用 `MOCK_API_TARGET` 覆盖）。
- 可通过 URL 参数覆盖模板接口：`/webview/certificate.html?templateApi=https://你的域名/api/certificate/template/detail`
- 导出页在小程序中会按 `fileIndex.html` 规则自动选择上传接口：
  `env=prod` -> `https://chengkebus.com/safety-certificate/admin/api/v1/region/a/uploadFile`
  其他环境 -> `http://182.140.209.47:8081/safety-certificate/admin/api/v1/region/a/uploadFile`

## WebView 预览与小程序接入

`public/webview/certificate.html` 预览地址：

- 本机：`http://localhost:5173/webview/certificate.html`
- 真机：`http://你的局域网IP:5173/webview/certificate.html`

常用 URL 参数：

- `templateApi`：模板接口地址
- `env`：上传环境（`prod` 或其他）
- `redirectPage`：小程序回跳页面（推荐方式）
- `resultPrefix`：redirect 回传参数前缀（默认 `cert`）
- `uploadFieldName`：上传接口文件字段名（默认 `file`）

### 小程序方式一（推荐）：redirect 回传

小程序 `web-view` 的 `src` 传 `redirectPage`：

```text
http://10.50.40.112:5173/webview/certificate.html?env=test&redirectPage=/pages/webview/index
```

H5 会在导出完成后 `redirectTo(redirectPage)`，并把结果拼到 query。

回传参数（默认前缀 `cert`）：

- `certStatus`
- `certUrl`
- `certError`
- `certFileName`
- `certAction`
- `certTs`

小程序接收（`onLoad`）：

```ts
onLoad((options) => {
  if (options.certStatus === 'success' && options.certUrl) {
    // options.certUrl 就是回传的图片 URL
  } else if (options.certStatus === 'fail') {
    // options.certError
  }
})
```

### 小程序方式二（兼容）：postMessage 回传

如果未传 `redirectPage` 或 redirect 失败，H5 会回退到 `postMessage + navigateBack`。

小程序接收（`@message`）：

```ts
const onMessage = (e: any) => {
  const res = e?.detail?.data?.[0]
  if (!res) return

  if (res.action === 'exportComplete' && res.status === 'success') {
    // res.url 就是回传的图片 URL
  } else {
    // res.error
  }
}
```

`postMessage` 回传结构：

- `action: 'exportComplete'`
- `status: 'success' | 'fail'`
- `url?: string`
- `error?: string`
- `fileName?: string`

真机调试注意：

- `web-view` 在手机端不能使用 `localhost`，请改为电脑局域网 IP，例如 `http://10.50.40.112:5173/webview/certificate.html`

mock 模板目录：

- `test-app/mock-templates/*.json`（例如 `tpl_001.json`）

## 发布

```bash
# 默认发布（legacy 产物，Vue2 友好）
npm run pub

# 显式发布 legacy 产物（与 pub 等价）
npm run pub:legacy
```

当前发布配置：

- registry: `https://registry.npmjs.org`
- 模板构建输出目录：`templatedist`
- 项目默认构建输出目录：`dist`
- 发布文件白名单：`templatedist/*.js`、`templatedist/*.css`（不会上传 `dist/stats.html`）

若账号开启了 npm 2FA，发布时需要 OTP：

```bash
npm publish --registry=https://registry.npmjs.org --access public --otp=123456
```

## 目录结构

```text
shared-widget/
├─ src/
│  ├─ templates/
│  │  ├─ fabricDesign/
│  │  │  ├─ main-ce.ts
│  │  │  └─ canvasCertificate.vue
│  │  └─ hiprintDesign/
│  │     ├─ main-ce.ts
│  │     └─ HiprintTemplate.vue
│  ├─ views/           # 设计器与预览页
│  └─ controls/        # 设计器控件
├─ scripts/
│  ├─ build-templates.js
│  └─ build-single.js
├─ public/
│  └─ webview/
│     ├─ certificate.html
│     └─ uni.webview.1.5.6.js
├─ test-app/
│  ├─ server.js
│  └─ mock-templates/
├─ dist/
├─ templatedist/        # 模板构建产物（js/css）
└─ package.json
```