npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

label-template-designer

v0.1.8

Published

Vue 3 visual label template designer with template library

Downloads

909

Readme

label-template-designer

可直接安装到 Vue 3 项目的标签模板设计器组件。组件内部使用 Shadow DOM 隔离样式,设计器常驻,模板库以 Dialog 风格弹窗显示,不监听 location.hash,也不要求宿主项目使用路由。

安装

npm install label-template-designer vue

包内置 TypeScript 声明文件,包含 props、事件和模板 ref 方法的类型,TypeScript 项目不需要另行安装 @types/label-template-designer:

import LabelTemplateDesigner, {
  type DataSource,
  type LabelTemplateDesignerInstance,
  type TemplateList,
} from 'label-template-designer'

默认导出是 Vue 组件,工作区会在内部的 Shadow DOM 中运行,不需要额外引入全局 CSS。组件根节点使用 width: 100%; height: 100%,宿主项目需要给组件本身或其父级提供明确高度(例如父容器 height: 600px,或父级一路继承 height: 100%)。设计器内部样式不会修改宿主页面;宿主页面的普通 CSS 也不会穿透到设计器内部。

默认行为:

  • show-tmpl 控制模板库弹窗是否显示;默认不显示,宿主可以使用 v-model:show-tmpl 双向接管;
  • 模板列表和模板内容由宿主传入,组件不在浏览器本地缓存模板内容;
  • 点击设计器中的“模板库”打开弹窗;如果已有当前模板,弹窗顶部显示模板名称和纸张大小;
  • 弹窗没有已选模板时,点击遮罩、关闭按钮或按 Esc 都不能关闭,并会触发 template-library-close-blocked;
  • 点击模板库中的列表项会选择模板、关闭弹窗并通过 template-request 请求对应模板详情;
  • 点击模板库中的“预览”会以同样的模板选择流程加载详情,但进入只读预览模式;此模式仅允许修改右侧示例值、导出 HTML/JSON、打开模板库、使用该模板以及实时预览全屏/退出全屏;
  • 点击模板库列表项会触发 open-template,参数为模板 ID,可用于埋点或宿主的附加处理;
  • 模板库支持按模板名称搜索和重置;按钮会向宿主抛出查询事件,列表区域占用弹窗剩余空间,超出后独立滚动;
  • 模板加载完成后实时预览保持干净;模板再次编辑且未获宿主确认保存时显示“未保存”水印;
  • 预览模式点击“使用该模板”会触发 use-template(templateData),参数结构与 save 的第一个参数一致;组件保持预览状态,不会自动发起保存请求。

独立库接入

label-template-designer 是一个只负责界面和编辑状态的 Vue 3 组件包。模板列表、模板详情、数据源以及所有增删改查接口都由宿主项目管理,组件通过 props 接收数据,通过事件把用户操作抛给宿主。组件不依赖宿主路由,也不会修改宿主的 URL 或 location.hash。

组件内部通过 Vue Custom Element 使用 Shadow DOM 隔离样式,但这是实现细节,Vue 项目不需要手动注册 Custom Element,也不需要额外引入 CSS。需要传数组或对象时,必须使用 Vue 的动态绑定语法,例如 :template-list、:template-detail 和 :data-source,不要写成普通字符串属性。

Props

所有 props 都是可选的。Vue 模板中推荐使用下表的短横线写法,JavaScript 渲染函数或 JSX 中使用驼峰写法。

| Vue 属性 | 模板写法 | 类型 | 默认值 | 详细说明 | | --- | --- | --- | --- | --- | | templateList | template-list | Array \| Object | null | 模板库数据。可以传模板数组,也可以传服务端分页对象。优先级高于 list 和 templates。 | | list | list | Array \| Object | null | templateList 的别名,适合宿主接口直接使用 list 作为属性名;当 template-list 已传入时忽略。 | | templates | templates | Array \| Object | null | templateList 的语义别名;当 template-list、list 已传入时忽略。 | | showTmpl | show-tmpl | Boolean | false | 是否显示模板库弹窗。推荐使用 v-model:show-tmpl;弹窗内部交互会通过 update:showTmpl 通知宿主,同时保留 update:show-tmpl 兼容监听。 | | pageSize | page-size | Number \| String | null | 每页数量。未传时读取分页对象中的 pageSize、limit 或 perPage,都没有时默认 10。 | | dataSource | data-source | Array | [] | 设计器左侧数据源面板及实时预览的模拟数据。组件不会请求或修改真实业务数据。 | | templateDetail | template-detail | Object | null | 当前模板的完整详情,至少应包含与当前选中 ID 相同的 id。列表项只用于展示和选中,设计器实际使用此对象。 |

template-list 的数据格式

直接传数组时,组件在模板库内做前端分页:

const templateList = [
  {
    id: 1025,
    name: '商品标签',
    type: 0,
    paper: { width: 100, height: 150 },
    elements: [],
  },
]

传入数组时不需要宿主处理 page-change 才能翻页,但如果需要从接口查询、搜索或删除,仍然应该监听对应事件并更新数组。

服务端分页时传入对象,并且至少包含 list 和 total;page、limit 用于让组件同步当前页和每页数量:

const templateList = {
  list: [
    { id: 1025, name: '商品标签', paper: { width: 100, height: 150 }, elements: [] },
  ],
  total: 35,
  page: 1,
  limit: 10,
}

列表项至少需要 id。常用字段包括 name、type、paper、elements、html、is_del 以及后端时间字段。列表项只承担两项职责:在模板库中显示摘要,以及确定需要请求详情的模板 ID;不要把列表摘要当作设计器完整模板使用。

template-detail 的数据格式

宿主收到 template-request 后请求详情,再把结果传回 template-detail:

const templateDetail = {
  id: 1025,
  name: '商品标签',
  type: 0,
  paper: { width: 100, height: 150 },
  elements: [
    {
      id: 'text-1',
      type: 'text',
      x: 10,
      y: 10,
      w: 40,
      h: 8,
      content: '{{name}}',
      fontFamily: 'Arial',
      fontSize: 10,
    },
  ],
  sampleValues: { name: '示例商品' },
}

template-detail.id 必须和当前模板 ID 一致,否则工作区不会把这份详情应用到当前设计器。模板中的几何值使用毫米;sampleValues 只用于设计器实时预览,不会写入最终导出的 HTML。宿主可以额外保留后端的 html、时间字段等字段,保存事件会把模板数据快照交给宿主。

data-source 的数据格式

data-source 是分组数组,外层 title 是分组标题,内层 value 是字段数组:

const dataSource = [
  {
    title: '基础信息',
    value: [
      { field: 'name', title: '商品名称', value: '标签示例' },
      { field: 'price', title: '价格', value: 12.5 },
    ],
  },
  {
    title: '动态列表',
    value: [
      { field: 'serialNumbers', title: '序列号', value: ['SN001', 'SN002'] },
    ],
  },
]

field 用于模板绑定,title 用于面板显示和搜索,value 用作示例值。条码/二维码也可在属性面板直接填写静态 content,不产生字段依赖。数组值用于动态列表;组件不会从接口自动加载数据源,也不会把数据源自动保存到模板接口。

完整接入示例

下面示例演示服务端分页、接口搜索、详情加载、保存确认以及模板库增删改查。接口地址和响应结构需要替换为宿主项目自己的实现;示例假定接口返回 { data: ... },其中列表数据为 { list, total },详情数据为模板对象。

<script setup>
import { ref } from 'vue'
import LabelTemplateDesigner from 'label-template-designer'

const showTmpl = ref(true)
const templateList = ref({ list: [], total: 0, page: 1, limit: 10 })
const templateDetail = ref(null)
const dataSource = ref([
  {
    title: '基础信息',
    value: [{ field: 'name', title: '名称', value: '示例商品' }],
  },
])

async function loadTemplateDetail(id) {

}

function onTemplateListRequest({ page, pageSize, keyword }) {
  // 弹窗打开时由宿主请求列表并更新 templateList
}

function onTemplateOpen(id) {
  // 模板库点击列表项时收到模板 ID,可用于埋点或附加处理。
}

function onUseTemplate(templateData) {
  // 预览模式点击“使用该模板”时收到与 save 第一个参数相同的完整模板数据。
}

function onPageChange({ page, pageSize }) {
  // 按 page/pageSize 重新请求,并更新 templateList
}

function onSearch({ keyword, page, pageSize }) {
  // 按 keyword 从第 1 页重新请求,并更新 templateList
}

function onReset({ page, pageSize }) {
  // 清空查询条件,从第 1 页重新请求,并更新 templateList
}

function onShowTmplChange(visible) {
  showTmpl.value = visible
}

async function onSave(templateData, confirmSave) {
  // 将 templateData(包含 templateData.html)提交给宿主接口
  // 接口确认成功后必须调用 confirmSave()
}

function onJsonImportSuccess(message) {
  // 导入成功;组件不会在工具栏内显示提示
  console.log(message)
}

function onJsonImportError(message) {
  // 导入失败;组件不会在工具栏内显示提示
  console.error(message)
}
</script>

<template>
  <LabelTemplateDesigner
    v-model:show-tmpl="showTmpl"
    :template-list="templateList"
    :template-detail="templateDetail"
    :data-source="dataSource"
    @template-list-request="onTemplateListRequest"
    @show-tmpl-change="onShowTmplChange"
    @open-template="onTemplateOpen"
    @template-request="loadTemplateDetail"
    @page-change="onPageChange"
    @search="onSearch"
    @reset="onReset"
    @save="onSave"
    @use-template="onUseTemplate"
    @json-import-success="onJsonImportSuccess"
    @json-import-error="onJsonImportError"
  />
</template>

组件不渲染详情加载状态、错误提示或重试弹窗;宿主可以在自己的页面中按业务处理请求状态、错误反馈、请求取消和过期响应。

Props 与事件的配合顺序

  1. 宿主把 show-tmpl 设为 true,组件触发 template-list-request,宿主请求列表并把结果传给 template-list;服务端分页时应包含 total、当前 page 和 limit。
  2. 用户在模板库点击列表项,组件更新当前模板 ID,触发 open-template、template-change、update:showTmpl(false)(并同步发出 kebab-case 兼容事件),随后触发 template-request;点击“预览”时还会触发 preview-template 并进入只读预览模式。
  3. 预览模式点击“使用该模板”会触发 use-template(templateData),不会退出只读预览或触发 save;普通编辑模式点击“保存”才会触发 save。其中 templateData 与 save 的第一个参数结构一致。
  4. 宿主收到 template-request 后请求完整模板详情,成功后传入匹配的 template-detail;组件不会替宿主显示加载或错误 UI。
  5. 弹窗没有当前模板时,关闭按钮、遮罩和 Esc 会触发 template-library-close-blocked,不会关闭弹窗;已有当前模板时可正常关闭。删除当前模板后同样回到未选择状态,必须重新选择模板才能关闭。
  6. 用户修改模板时,设计器内部管理编辑状态;宿主不需要为了每次拖动重新请求详情。
  7. 用户点击保存时,宿主接收 save(templateData, confirmSave, isTemporaryTemplate);第三个参数为 true 时表示当前是没有真实详情的临时模板,接口成功后调用 confirmSave();如果接口失败,不要调用该回调,设计器会继续保持未保存状态。
  8. 模板库的查询、重置、翻页、新建、重命名和删除都只抛事件,宿主完成接口调用后重新更新对应 props。

事件

| 事件 | 参数 | 触发时机和宿主处理 | | --- | --- | --- | | update:showTmpl | visible | 用户打开或关闭弹窗时触发,供 v-model:show-tmpl 自动同步。 | | update:show-tmpl | visible | 与 update:showTmpl 同步发出的 DOM 事件兼容别名。 | | show-tmpl-change | visible, reason | 弹窗由组件内部交互改变时触发,reason 说明是设计器按钮、选择模板或关闭按钮。 | | template-library-open | { page, pageSize, keyword } | 弹窗打开时触发,宿主可据此请求初始列表。 | | template-library-close | reason | 弹窗成功关闭时触发;没有当前模板时不会触发。 | | template-library-close-blocked | { reason, action } | 没有选择模板却尝试关闭时触发;组件会保持弹窗打开。 | | template-list-request | { page, pageSize, keyword } | 弹窗打开后请求初始列表;组件不发 HTTP,宿主返回数据后更新 template-list。 | | template-change | id | 用户在模板库打开一个模板时触发,表示当前模板 ID 已切换。 | | open-template | id | 用户点击模板库列表项时触发,只表示选择动作;详情请求仍通过 template-request 处理。 | | preview-template | id | 用户点击模板库“预览”按钮时触发;模板选择、详情请求和弹窗关闭流程相同,但设计器进入只读预览模式。 | | preview-mode-change | previewOnly, id | 预览模式切换时触发;true 表示只读预览,false 表示普通编辑。 | | use-template | templateData | 预览模式点击“使用该模板”时触发;参数结构与 save 的第一个参数一致,包含模板数据和 html。组件保持只读预览,不会触发 save。 | | template-request | id | 组件需要完整详情时触发。宿主应按 ID 请求接口并回传 template-detail;组件不会发 HTTP 或显示请求错误 UI。 | | save | templateData, confirmSave, isTemporaryTemplate | 普通编辑模式点击“保存”时触发。第三个参数为 true 时表示当前是临时模板;templateData.html 是当前模板生成的完整 HTML;接口成功后调用 confirmSave()。预览模式的“使用该模板”不会触发此事件。 | | create-template | { name, type, paper, elements } | 点击“新建模板”时触发,默认纸型为 100×150mm。宿主创建成功后刷新列表。 | | rename-template | 模板参数对象 | 确认重命名时触发,原对象字段会保留,只覆盖新的 name。 | | delete-template | id | 确认删除时触发,宿主调用删除接口后刷新列表。 | | page-change | { page, pageSize } | 翻页时触发。服务端分页时按这两个参数重新请求列表。 | | search | { keyword, page: 1, pageSize } | 点击“查询”或按 Enter 时触发。宿主把 keyword 作为接口查询条件,并从第 1 页开始请求。 | | reset | { page: 1, pageSize } | 点击“重置”或按 Esc 时触发。宿主清空接口查询条件并重新请求第 1 页。 | | json-import-success | message | JSON 导入成功时触发;消息由组件传出,组件不显示内置提示。 | | json-import-error | message | JSON 导入失败时触发;消息由组件传出,组件不显示内置提示。 |

模板库不直接写入后端;模板 JSON 的导入/导出由设计器负责,模板持久化仍由宿主完成。导入成功或失败只触发上述事件,不会在组件内显示提示。

Ref 方法

<script setup>
import { ref } from 'vue'
import LabelTemplateDesigner from 'label-template-designer'

const designer = ref(null)

function showLibrary() {
  designer.value?.openLibrary()
}

function closeLibrary() {
  designer.value?.closeLibrary()
}

function openTemplate(id) {
  designer.value?.openTemplate(id)
}

function previewTemplate(id) {
  designer.value?.previewTemplate(id)
}

function reloadDetail() {
  designer.value?.requestTemplateDetail(true)
}

function save() {
  // 会触发 save 事件,并返回本次保存的 templateData 快照
  const templateData = designer.value?.saveTemplate()
  return templateData
}

function markSaved() {
  // 仅在宿主保存接口确认成功后调用
  return designer.value?.confirmSave()
}

function print() {
  // 打印当前实时预览(示例值);返回 false 表示当前环境不支持打印
  return designer.value?.printPreview()
}

function notifyJsonImportSuccess(message) {
  return designer.value?.jsonImportSuccess(message)
}

function notifyJsonImportError(message) {
  return designer.value?.jsonImportError(message)
}
</script>

<template>
  <LabelTemplateDesigner ref="designer" />
  <button type="button" @click="showLibrary">打开模板库</button>
  <button type="button" @click="closeLibrary">关闭模板库</button>
  <button type="button" @click="save">保存模板</button>
  <button type="button" @click="print">打印预览</button>
</template>

暴露的方法和属性如下:

| 名称 | 类型/参数 | 说明 | | --- | --- | --- | | showTmpl | ComputedRef<Boolean> | 模板库弹窗是否显示,只读读取状态。 | | activeTemplateId | ComputedRef<String \| null> | 当前模板 ID,只读读取状态。 | | previewOnly | ComputedRef<Boolean> | 是否处于模板库“预览”进入的只读模式,只读读取状态。 | | openLibrary() | 无 | 打开模板库弹窗。 | | closeLibrary() | reason | 关闭模板库弹窗;没有当前模板时返回 false 并触发 template-library-close-blocked。 | | openTemplate(id) | id | 打开指定模板;模板摘要列表为空或不包含该 ID 时也可以直接打开。随后会触发详情请求事件。 | | previewTemplate(id) | id | 以只读预览模式打开指定模板;流程与 openTemplate(id) 相同。 | | exitPreviewMode() | 无 | 退出只读预览模式并恢复普通编辑能力;模板库中的列表项选择也会自动退出该模式。 | | requestTemplateDetail(force = false) | force | 重新请求当前模板详情;传 true 可忽略同一 ID 的去重限制。 | | saveTemplate() | 无 | 生成当前模板快照,触发 save 事件并返回 templateData。 | | confirmSave() | 无 | 宿主接口成功后确认保存,清除未保存状态并显示已保存。 | | printPreview() | 无 | 使用隐藏 iframe 调用浏览器打印,只打印当前实时预览的示例值内容,不打开新标签页;返回 false 表示当前环境不支持打印。 | | jsonImportSuccess(message) | message | 转发一次 JSON 导入成功事件;组件不显示内置提示。设计器内部导入成功时会自动调用。 | | jsonImportError(message) | message | 转发一次 JSON 导入失败事件;组件不显示内置提示。设计器内部导入失败时会自动调用。 |