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

@owocow/saber-ui

v0.1.31

Published

Saber application foundation for Vue 3 projects built on Nuxt UI

Readme

@owocow/saber-ui

Saber application foundation for Vue 3 projects, built on top of @nuxt/ui.

Documentation

The VitePress documentation site lives in docs/ and runs directly against the local src/ code:

pnpm docs:dev
pnpm docs:build
pnpm docs:preview

近期新增能力和升级注意事项见近期更新与升级。

图片相关文档:SaberImageViewer 图片查看器、SaberUploadImage 图片上传。

Install

npm install @owocow/saber-ui @nuxt/ui

Requires: vue ^3, @nuxt/ui ^4

Consumption contract

重要:saber-ui 的组件在宿主编译时需要 nuxt/ui 的 #imports / #build/* 虚拟模块, 任何使用 saber-ui 的宿主都必须运行 @nuxt/ui/vite(Vite 项目下让 nuxt/ui 脱离 Nuxt 运行的标准插件)。 不满足该条件时,组件内的 <UBadge> / <UIcon> 与主题配置将无法解析。

// vite.config.ts
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      theme: {
        colors: ['primary', 'secondary', 'success', 'info', 'warning', 'error'],
      },
      // ... 主题配置会同样作用到 saber-ui 内部的 nuxt/ui 组件
    }),
  ],
})

关于 @owocow/saber-ui/vite:它只是 @nuxt/ui/vite 的转发导出(re-export), saber-ui 不自带 Vite 插件。直接使用 @nuxt/ui/vite 即可,不必引入这个入口。

Usage

npm install @owocow/saber-ui @nuxt/ui
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import SaberUI from '@owocow/saber-ui'

const app = createApp(App)
app.use(SaberUI)
app.mount('#app')
<!-- Anywhere in your app -->
<template>
  <BadgeStatus
    :value="row.status"
    :config="['闲置:0:green', '已预订:1:blue', '使用中:2:orange']"
  />
</template>

On-demand import

组件始终需要宿主提供 @nuxt/ui/vite,此处仅指按需引入组件而不是注册全部:

<script setup>
import { BadgeStatus } from '@owocow/saber-ui'
</script>

<template>
  <BadgeStatus
    :value="row.status"
    :config="['闲置:0:green', '已预订:1:blue', '使用中:2:orange']"
  />
</template>

Image viewer

SaberImageViewer 用于查看已有图片,支持底部切图、25%–300% 缩放和每次 90° 旋转。执行 app.use(SaberUI) 后可全局使用,也可按需导入:

<script setup lang="ts">
import SaberImageViewer from '@owocow/saber-ui/components/SaberImageViewer'

const images = [
  { src: '/license.jpg', label: '营业执照' },
  { src: '/identity.jpg', label: '法人身份证' },
]
</script>

<template>
  <SaberImageViewer v-slot="{ open }" :images="images">
    <button type="button" @click="open(0)">查看证照</button>
  </SaberImageViewer>
</template>

支持 :masked="true" 图片脱敏,默认关闭。开启后,每张图片独立覆盖 12px 背景模糊遮罩,并禁用点击、键盘和程序打开预览;已打开的预览立即关闭,取消脱敏后需重新打开。

Ref 方法、Model 控制、API 和迁移方式见 SaberImageViewer 文档。缩略图铺满容器、圆角和遮罩范围见缩略图布局与遮罩范围。查看已有图片无需上传数据源。

Forms

app.use(SaberUI) 会全局注册 SaberFilterBar、SaberFormModal、SaberModal 和基础 Saber 字段,并为动态字段提供 app 级 registry。

<script setup lang="ts">
import { ref } from 'vue'
import type { FilterField } from '@owocow/saber-ui/forms'

const params = ref({ keyword: '', status: undefined })

const fields: FilterField[] = [
  {
    key: 'keyword',
    which: 'ui-input',
    props: { placeholder: '搜索名称' },
  },
  {
    key: 'status',
    which: 'ui-select',
    props: { items: [{ label: '启用', value: 'enabled' }] },
    searchOn: 'update:model-value',
  },
]
</script>

<template>
  <SaberFilterBar v-model="params" :fields="fields" @search="loadData" @reset="resetParams" />
</template>

基础 registry 包含 saber-input、saber-select、saber-date-picker、saber-month-picker、saber-date-time-picker、saber-time-picker 等 Saber 字段,以及 ui-input、ui-select、ui-textarea 等 Nuxt UI 原生字段映射。日期时间字段族也会被全局注册,统一支持字符串值和边界控制。

文本输入默认在 change 时触发搜索;Select、Radio 等值变更后立即搜索的字段应设置 searchOn: 'update:model-value'。复杂业务字段可使用 searchOn: 'manual'。

项目自定义字段在安装时提供:

import SaberUI from '@owocow/saber-ui'
import SupplierPicker from './SupplierPicker.vue'

app.use(SaberUI, {
  forms: {
    components: {
      'supplier-picker': SupplierPicker,
    },
  },
})

字典字段由宿主提供数据源,saber-ui 不依赖具体 store 或接口:

const dictStore = useDictStore()

app.use(SaberUI, {
  forms: {
    dictDataSource: {
      async loadOptions(dictType) {
        await dictStore.loadDict(dictType)
        return dictStore.getOptions(dictType)
      },
    },
  },
})

配置后可使用 saber-dict-radio、saber-dict-select、saber-dict-checkbox,或直接使用全局组件 SaberDictRadio、SaberDictSelect、SaberDictCheckbox。SaberDictSelect 默认基于 SaberPopSelect;加上 searchable 后使用 USelectMenu,在弹层内按标签本地过滤。

需要直接在外层输入框搜索静态选项或业务接口时,使用 SaberSearchSelectV1:

<script setup lang="ts">
import { ref } from 'vue'
import { SaberSearchSelectV1 } from '@owocow/saber-ui/forms'

const brandId = ref<string | null>(null)
const brands = [{ label: 'Apple', value: 'apple' }, { label: '华为', value: 'huawei' }]
</script>

<template>
  <SaberSearchSelectV1 v-model="brandId" :options="brands" placeholder="搜索品牌" />
</template>

静态数据支持 options / items;远程模式传入 api 与 search-key,兼容 { data: { rows } } 和 { data: [] }。支持多选、initialOptions 回显、params 级联和 change(value, row)。右侧同一位置按 loading → 清除 → 下拉箭头互斥显示。组件默认注册为 saber-search-select-v1,完整 API 与示例见 SaberSearchSelectV1。

树形数据使用 SaberCascader:默认按钮选择,searchable 开启外层输入搜索;节点 disabled: true 禁用整棵子树,optionsDisabled 可接管禁用判定。SaberDistrictSelect 则通过 maxLevel="province" / "city" / "district" 控制选择深度,提交值的 districtCode 始终表示最终选中节点的编码。

行政区划选择和地址解析共用 app 级地区服务。使用内置高德数据源时只需配置 Web 服务 Key:

app.use(SaberUI, {
  forms: {
    district: {
      amapKey: import.meta.env.VITE_APP_AMAP_KEY,
    },
  },
})

配置后,动态表单可直接使用 saber-district-select,无需页面引入组件、加载数据或传递 districts:

const fields = [{
  key: 'districtValue',
  label: '所在地区',
  which: 'saber-district-select',
}]

地区数据仅在 SaberDistrictSelect 首次渲染或地址首次解析时加载;内置高德数据源默认使用 7 天 localStorage 缓存。也可以通过 forms.district.dataSource 替换成其他数据源。

自定义数据源、DistrictService 和区划类型可从 @owocow/saber-ui/district 独立导入;该入口不加载 UI 组件。

图片上传与图片/文档混合上传组件通过独立入口按需使用,上传、删除和响应字段由宿主适配:

import type { UploadImageDataSource } from '@owocow/saber-ui/forms'

const uploadImageDataSource: UploadImageDataSource<{ ossId: string; url: string }> = {
  upload: (file, { onProgress }) => uploadFile(file, onProgress),
  remove: value => deleteFile(value.ossId),
  getKey: value => value.ossId,
  getUrl: value => value.url,
}

app.use(SaberUI, {
  forms: { uploadImageDataSource },
})
<script setup lang="ts">
import SaberUploadImage from '@owocow/saber-ui/forms/upload-image'
</script>

<template>
  <SaberUploadImage v-model="avatar" />
</template>

SaberUploadImage 与 SaberUploadFile 都不进入默认 registry;动态表单需要时显式注册 saber-upload-image / saber-upload-file。混合上传入口为 @owocow/saber-ui/forms/upload-file,两个组件共用 forms.uploadImageDataSource。

remove(value, { origin: 'uploaded' }) 仅为当前实例中新上传的文件调用;初始或父组件写入的文件移除时只更新 Model,clear() 也不调用远程删除。SaberUploadImage 的大图预览复用 SaberImageViewer,支持底部切图、缩放和旋转;混合上传中的文档点击后在新窗口打开原文件。

图片上传框和缩略图默认统一为 96×96px,通过 thumbnail-size="size-32" 可同时调整两者尺寸。表单自动传入 size="lg" 等通用大小不会影响缩略图,旧的 size="size-32" 写法继续兼容。示例与完整 API 见 SaberUploadImage 文档。

Utilities

通用应用能力通过独立子路径提供,不会从 @owocow/saber-ui 根入口自动加载:

import { setupHttp, http } from '@owocow/saber-ui/utils/http'
import { setupStorage, storage } from '@owocow/saber-ui/utils/storage'
import { setupPermission, canAccess } from '@owocow/saber-ui/utils/permission'
import { getToast, setToast } from '@owocow/saber-ui/utils/toast'

初始化 Permission 后,可以使用全局 SaberAuth 组件控制模板片段,并提供无权限回退内容:

<SaberAuth :permissions="['customer:info:add']">
  <UButton>新增客户</UButton>

  <template #fallback>
    <span>无操作权限</span>
  </template>
</SaberAuth>

HTTP 对带数值 code 的业务响应,只有 200 正常 resolve,其他业务码会提示并抛出 HttpBusinessError;仅 401 调用 onAuthError。useTable 会消化请求阶段的业务错误并保留现有列表;useDelete 对取消、重复调用或删除业务错误返回 false,确认弹窗和接口执行期间均有重复调用守卫。升级时应检查依赖“错误响应仍 resolve”的调用代码,详见 HTTP和确认与删除。

Vue 组件内推荐使用 composable;它会自动等待安装时配置的共享地区服务,并复用地址解析索引:

import { useAddressParse } from '@owocow/saber-ui/composables/use-address-parse'

const { parseAddress } = useAddressParse()
const results = await parseAddress('张三 浙江省杭州市西湖区文三路90号 13800138000', true)

底层地址解析器仍是纯函数,适用于 Node 脚本、单元测试或自定义数据:

import { createAddressParser } from '@owocow/saber-ui/utils/addressParse'
const parser = createAddressParser(customDistricts)
const results = parser.parse('张三 浙江省杭州市西湖区文三路90号 13800138000', true)

行政区划节点至少需要包含 name、adcode 和可选的 districts。传入数据应以省级节点作为数组第一层;底层解析器不会请求网络,也不会依赖 Vue。

可用入口包括 http、storage、rsa、crypto、permission、toast、format、filter、tree、klona、nanoid 和 addressParse。

公共后端协议和基础类型通过独立入口导入:

import type { ApiResponse, AuthUserInfo, Id, ListResponse, PageParams } from '@owocow/saber-ui/types'

该入口只包含基础、API 和认证协议类型,不包含具体 System 或业务 DTO。

http、storage、rsa、crypto 与 toast 共享模块级初始化状态,迁移或初始化时应始终从 @owocow/saber-ui/utils/* 引用,不能与旧的 @owocow/saber-utils/* 混用。

版本对齐

saber-ui 通过 @nuxt/ui 的裸导入引用宿主的 nuxt/ui 副本,请保持 @nuxt/ui 主版本(^4)一致;宿主如遇解析到多份副本,可在 Vite 配置中加 resolve: { dedupe: ['@nuxt/ui', '@nuxt/icon'] } 强制解析到宿主副本。

Components

BadgeStatus

Status badge driven by label:value:color config. Renders <UBadge> from @nuxt/ui.

Colors use inline styles (hex → rgba) to avoid Tailwind v4 JIT dynamic-class issues.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | value | string \| number | (required) | Current value, matched against config's value segment | | config | string[] | (required) | 'label:value:color' array, color = Tailwind color name (500/600 shades) | | bgOpacity | number | 20 | Background opacity (0–100) | | textColorWeight | number | 500 | Text color Tailwind shade |

License

MIT