@owocow/saber-ui

Saber application foundation for Vue 3 projects built on Nuxt UI
3,897
Install
npm install @owocow/saber-ui
Latest Version:0.1.31
Vue:3
License:MIT
Last Updated:Oct 16, 2026
Links: npm
Maintainer: owocow

@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

Related Packages

nuxt-ui-colors-no-inline

Nuxt module that ensures nuxt-ui-colors CSS generated by Nuxt UI respects the Nu...

1,609 2
@gits-id/badge

GITS Badge Component

3,584
@lionad/cx-comps-nuxt-ui-v2

cx material components based on Nuxt UI

1,606 0
@lionad/cx-comps-nuxt-ui-v4

cx material components based on Nuxt UI v4

1,602 0
@norbiros/nuxt-auto-form

Auto-generate Nuxt UI forms from Zod 4 schemas

9,137 58

Version History

Version Released License
0.1.31 MIT
0.1.30 MIT
0.1.29 MIT
0.1.28 MIT
0.1.27 MIT