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

@gindow/vue

v1.0.8

Published

Vue3 通用基础包,包含工具函数、Hooks、类型定义、国际化、基础样式

Readme

@gindow/vue

Vue 3 + TypeScript 通用基础包:工具类、Hooks、类型、i18n 底座、级联层样式。 是 @gindow/element-go(桌面端)与 @gindow/vant-go(移动端)的公共依赖,也可单独使用。

当前版本:1.0.8 · 无 UI 框架依赖(不依赖 element-plus / vant)


1. 速查表

import Vue, {
  // 组件
  VueIcon,
  // Hooks
  useBreak, useCaptcha, useIcon, useNanoid,
  // 工具类(静态类,直接调用)
  Formatter, Validate, DateTime, Platform,
  // i18n
  setLocale, getLocale, useLocale,
} from '@gindow/vue'

import type {
  IModel, IAsset, IField, IFilter, IParams,
  IPagination, IResult, IUploadUserFile, IMenu,
} from '@gindow/vue'

import '@gindow/vue/style.css'                    // 级联层顺序 + Tailwind + 工具类
import { VueResolver } from '@gindow/vue/resolver' // unplugin-vue-components 解析器

⚠️ provider 不从根路径导出(element-go / vant-go 才导出它)。本包只能通过 app.use(Vue, { locale, messages }) 初始化语言。

子路径导出(package.json exports

| 子路径 | 内容 | | --- | --- | | @gindow/vue | 主入口(含 import './style.css' 副作用) | | @gindow/vue/resolver | VueResolver | | @gindow/vue/style.css | 样式(级联层声明 + Tailwind + 工具类) | | @gindow/vue/src/* | 源码直取,用于未在主入口导出的东西(如图表组件) | | @gindow/vue/package.json | — |


2. 安装

pnpm add @gindow/vue vue

vue ^3.5 是唯一必装 peer。其余 peer 全部 optional

| peer 包 | 用途 | npm 包模式是否必装 | | --- | --- | --- | | @iconify/vue ^5 | VueIcon / useIcon | (未打进产物,external) | | dayjs ^1.11 | DateTime | 否(已内联进 dist) | | nanoid ^5 | useNanoid | 否(已内联进 dist) | | @visactor/vchart ^2 | 图表组件(@gindow/vue/src/components/chart) | 用到才装 | | @vueuse/core ^12/13/14 | 图表暗色跟随(useDark) | 用到图表才装 | | lodash ^4.17 | 图表 spec 合并(vLine / vPie) | 用到图表才装 |

为什么 dayjs / nanoid 不用装:本包构建时只把 vue@iconify/vue 列为 external, dayjsnanoid 已被打进 dist/vue.mjs但源码包模式(走 src)例外——那时源码直接 import dayjs from 'dayjs',必须自行安装。


3. 注册

// main.ts
import { createApp } from 'vue'
import Vue from '@gindow/vue'
import '@gindow/vue/style.css'
import App from './App.vue'

createApp(App)
  .use(Vue, { locale: 'zh-CN' })   // options 可省
  .mount('#app')

app.use(Vue, options) 做两件事:初始化 locale(provider(options))、全局注册 VueIcon

options 类型:{ locale?: string; messages?: Record<string, any> }

自动导入(可选)

// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import { VueResolver } from '@gindow/vue/resolver'

export default defineConfig({
  plugins: [Components({ resolvers: [VueResolver()] })],
})

VueResolver(options?)

| 选项 | 类型 | 默认 | 说明 | | --- | --- | --- | --- | | prefix | string | 'Vue' | 只解析以此开头的组件名 | | importStyle | boolean | true | 附带 @gindow/vue/style.css 副作用导入(unplugin 会去重) |

本包只有 VueIcon 一个组件,解析器价值有限;多数项目直接 app.use(Vue) 即可。


4. 组件

VueIcon

基于 Iconify 的图标组件,额外支持按文件类型/MIME 自动选图标

| Prop | 类型 | 默认 | 说明 | | --- | --- | --- | --- | | icon | string | '' | 图标名。含 : 时视为完整名(mdi:home),否则拼 vendor | | type | string | '' | 文件扩展名 / MIME / 业务类型;仅在 icon 为空时生效 | | vendor | string | 'icon-park-outline' | 图标集前缀 | | size | number | 16 | 宽高(px) | | strokeWidth | number | 3 | 线宽(通过 v-bind 注入 g/pathstroke-width) | | color | string | — | 颜色 |

无 emits、无 slots。icontype 都为空时不渲染任何元素

type 解析优先级:icontype === 'folder' → MIME 子类型表 → MIME 主类型表 → 扩展名表 → 兜底 file-text

<VueIcon icon="home" :size="20" color="#093" />
<VueIcon type="application/pdf" />   <!-- → file-pdf -->
<VueIcon type="xlsx" />              <!-- → file-excel -->
<VueIcon type="image/png" />         <!-- → pic-one -->
<VueIcon icon="mdi:rocket" />        <!-- 含 : 时忽略 vendor -->

内置映射覆盖:图片(png/jpg/gif/webp/bmp/svg)、视频(mp4/avi/mov/wmv/flv/mkv/webm)、音频(mp3/wav/wma/aac/ogg/flac)、pdf、Word、Excel、PPT、文本、代码(json/xml/html/css/js/ts/py/php/cpp/java/go/rs…)、压缩包(zip/rar/7z/tar/gz/bz2),以及业务类型 image/video/audio/text/application/font/pdf/doc/sheet/ppt/md/canvas/album/yearbook

图表组件(未在主入口导出)

只能通过源码子路径引入,且需自行安装 @visactor/vchart@vueuse/corelodash

import { vBar, vLine, vPie, vFunnel, vNum, useChart } from '@gindow/vue/src/components/chart'

⚠️ 走 src 需要宿主 Vite 能编译 .vue,且 node_modules 不被 AutoImport 排除。详见 §9。

| 组件 | 说明 | Props | | --- | --- | --- | | vBar | 柱状图 | title(必填)、props(合并进 spec,支持 xField/yField/autoRotate)、values | | vLine | 折线图(默认堆叠) | titlepropsseriesvaluesspec | | vPie | 饼图(默认环形 innerRadius:0.4) | title(必填)、propsvalues | | vFunnel | 漏斗图 | title(必填)、propsvalues | | vNum | 数字指标卡(非图表,纯 DOM) | iconcolor(默认 #093)、title(默认 'Total')、amount(默认 0)、growth |

  • vLine / vPie 触发 @elementClick="(datum) => {}"vBar / vFunnel / vNum 无此事件。
  • 图表根元素为 <div class="h-full min-h-60">高度由父容器决定vNummin-h-40)。
  • vNum.growthnull 时不显示涨跌行;为字符串时取其中第一个数字判断正负(正数绿色带 +,否则红色)。

useChart() 返回:

| 方法 | 签名 | 说明 | | --- | --- | --- | | draw | (spec: ISpec, dom?: HTMLDivElement) => void | 创建并渲染;自动跟随 useDark() 切换 light/dark 主题 | | update | (spec: ISpec) => Promise<void> | 更新 spec 并重渲染 | | onElementClick | (cb: (datum) => void) => void | 监听 mark 级点击 |

组件卸载时自动 chart.release()


5. Hooks

useBreak()

响应式断点,阈值固定 768pxmin-width: 768px 视为桌面)。

const { isMobile, isDesktop } = useBreak()   // 两个 ComputedRef<boolean>

必须在 setup 内调用(内部用了 onMounted / onBeforeUnmount)。SSR 安全(typeof window 守卫,默认 true)。

useCaptcha(fetch)

验证码发送 + 60 秒倒计时。

const { waiting, send } = useCaptcha((data) => api.sendOTP(data))

await send({ phone: '13800138000' })   // 成功后 waiting 从 60 递减到 0

| 返回 | 类型 | 说明 | | --- | --- | --- | | waiting | Ref<number> | 剩余秒数,0 表示可再次发送 | | send | (para?: object) => Promise<unknown> | 调用传入的 fetch(para)成功后才启动倒计时;失败 reject 原始错误 |

send 内部有 loading 互斥:请求进行中重复调用会静默返回一个永不 settle 的 Promise,不要 await 后依赖它继续。

useIcon()

const { i } = useIcon()
i('home', { size: 20, color: '#093' })   // → VNode(VueIcon) | undefined

i(icon, para?)icon 为空时返回 undefined。用于给只接受 VNode 的 API 传图标(如 el-button :icon)。

useNanoid()

const { nanoid, numeric } = useNanoid()
nanoid()        // 'V1StGXR8_Z5jdHi6B-myT'(21 位,安全随机)
numeric(6)      // '834192'(纯数字,非安全随机,默认长度 10)

6. 工具类

四个都是静态类,无需实例化。

Formatter — 格式化 / 脱敏

Formatter.set({ locale: 'zh-CN', currency: 'CNY', decimals: 2, cents: true })

| 配置项 | 类型 | 默认 | 说明 | | --- | --- | --- | --- | | locale | string | 跟随环境 | 传给 Intl.NumberFormat | | currency | string | 'CNY' | 默认币种 | | decimals | number | 2 | 小数位(同时作为最小/最大位数) | | cents | boolean | true | 入参单位是否为「分」。默认 true → price 会除以 100;存「元」的系统必须设 false |

⚠️ Formatter.set()整体替换 config,不是合并。每次调用要传完整配置。

| 方法 | 示例 | 输出 | | --- | --- | --- | | id(str) | Formatter.id('a1b2c3d4e5f6g7h8') | 'C3D4E5F6G7H8'(取末 12 位转大写) | | date(str) | Formatter.date('2026-08-14T10:30:00Z') | '2026-08-14 10:30:00'(纯字符串截取,不解析时区) | | phone(str, naked?) | Formatter.phone('13800138000') | '138****8000'naked=true 时原样返回 | | idcard(str) | Formatter.idcard('110101199001011234') | '110101****1234' | | email(str) | Formatter.email('[email protected]') | 'z****[email protected]'(用户名 ≤2 位时不脱敏) | | bankcard(str) | Formatter.bankcard('6222021234567890') | '6222****7890' | | url(str) | Formatter.url('https://a.com//b//c') | 'https://a.com/b/c'(压缩重复斜杠,保留协议) | | price(v, currency?) | Formatter.price(12345) | '¥123.45'cents:true 时除以 100) | | currency(v, currency?) | — | price 的别名 |

Validate — 校验

Validate.set('CN')            // 影响 phone 规则;默认 '' 走国际规则
Validate.phone('13800138000') // true

| 成员 | 说明 | | --- | --- | | set(country) | 设置国家码(大小写不敏感) | | phone(str) | country === 'CN'1[3-9]\d{9};否则国际号 +?[1-9]\d{7,14}\d{6,11} | | email(str) | 常规邮箱 | | idcard(str) | 15 / 18 位(末位可为 X) | | cname(str) | 中文姓名 2–16 字(含 ·) | | phone_pattern / email_pattern / idcard_pattern / cname_pattern / password_pattern | 直接取正则 |

⚠️ 有 password_pattern(8–20 位,且不能是纯字母/纯数字/纯符号),但没有 Validate.password() 方法,需自行 Validate.password_pattern.test(str)

DateTime — 日期格式化(dayjs 封装)

所有方法签名 (date?: dayjs.ConfigType, symbol = '/')symbol 替换日期分隔符。

| 方法 | 输出格式 | 示例(symbol='-') | | --- | --- | --- | | DateTime.s(d) | YYYY/MM/DD HH:mm:ss | 2026-08-14 10:30:00 | | DateTime.m(d) | YYYY/MM/DD HH:mm | 2026-08-14 10:30 | | DateTime.h(d) | YYYY/MM/DD HH | 2026-08-14 10 | | DateTime.d(d) / DateTime.date(d) | YYYY/MM/DD | 2026-08-14 | | DateTime.M(d) | YYYY/MM | 2026-08 | | DateTime.y(d) | YYYY | 2026(无 symbol 参数) | | DateTime.time(d) | HH:mm | 10:30(无 symbol 参数) | | DateTime.format(d, fmt?) | 自定义,默认 YYYY/MM/DD HH:mm:ss | — | | DateTime.dayjs | 原始 dayjs 实例 | 需要插件/额外能力时用 |

Platform — 环境判断

全部是 getterPlatform.isIOS,不要加括号)。依赖 window不可在 SSR 顶层调用

| Getter | 判断依据 | | --- | --- | | userAgent | 小写 UA | | isWeb | matchMedia('(min-width: 992px)') —— 注意这是宽度判断,与 useBreak 的 768px 阈值不同 | | isMobile | UA 含 phone/iPhone/Android/Mobile/BlackBerry… | | isIOS | UA 含 iphone/ipad/ipod/ios | | isFlutter | UA 含 flutter | | isFlutterIOS | isFlutter && isIOS | | isWechat | UA 含 MicroMessenger | | isWxwork | MicroMessenger + wxwork | | isMiniprogram | MicroMessenger + miniprogram |


7. 类型定义

interface IModel {                    // 所有业务模型的基类,带索引签名
  id?: string | any
  created_at?: string
  updated_at?: string
  deleted_at?: string
  [property: string]: any
}

interface IAsset extends IModel {     // 资源(图片/视频/文件)
  id: string; type: string; title: string
  url: string                         // 原图
  shrink: string                      // 缩略图
}

interface IField extends IModel {     // 表单字段描述
  name: string
  type: string
  readonly?: boolean
  multiple?: boolean
  required?: boolean
  options?: { label: string; value: string }[] | any[]
}

type IFilter = Omit<IField, 'name'> & { label?: string; rules?: any[] }

interface IParams extends IModel {    // 列表查询参数
  filter?: Object; search?: string; include?: string
  page?: number; size?: number
}

interface IPagination {               // 后端分页元信息
  current_page: number; per_page: number
  count: number; total: number; total_pages: number
}

interface IResult {                   // 统一响应体
  code: number
  message: string
  data?: IModel | IModel[] | null
  meta?: { pagination?: IPagination }
}

interface IUploadUserFile {
  id?: string; percentage?: number; asset?: IAsset
  title?: string; width?: number; height?: number
  type?: string; mimeType?: string
}

interface IMenu {
  key?: string                        // i18n key,优先于 title
  title: string
  path: string
  name?: string; icon?: string; depend?: string
  hidden?: boolean; divider?: boolean; disabled?: boolean
  isGroup?: boolean
  children?: IMenu[]
}

element-go 有一份字段更多的同名类型(IField.hidden/advanced/componentIUploadUserFile.uid/name/status/raw 等)。 在 element-go 工程里请从 @gindow/element-go 导入,不要从本包导入。


8. 国际化

Vue3 provide/inject 实现,不依赖 vue-i18n

import { setLocale, getLocale, useLocale } from '@gindow/vue'

setLocale('en')
getLocale()                       // 'en'

const { locale, t } = useLocale()
t('empty.noData')                 // 'No data'
t('some.key', { name: 'Tom' })    // 支持 {name} 占位符插值

初始语言探测顺序:localStorage.getItem('locale')navigator.language 完全匹配 → 语言族匹配(en-USen)→ 兜底 'en'。可用码:['en', 'zh-CN']

内置文案(zh-CN / en 两份):

cancel · confirm · empty.noData · dialog.tip · dialog.confirm

扩展/覆盖语言包:

app.use(Vue, {
  locale: 'zh-CN',
  messages: { 'zh-CN': { ...myMessages } },
})

⚠️ 本包的 t() 没有兜底messages[locale] 被整体替换后,找不到的 key 会原样返回路径字符串。 传 messages 时必须把内置的 5 个 key 一起带上,或改用 element-go / vant-go —— 那两个包有永不被覆盖的 base 底座(见各自 README)。


9. 样式与级联层

@gindow/vue/style.css 做三件事:

  1. 声明级联层规范顺序(整个体系只声明这一次):
    @layer theme, base, vendor, element-plus, components, utilities;
    每层放一条 .gindow-layer-anchor 占位规则,防止构建期压缩把「空层」从顺序声明里剪掉导致层序错乱。
  2. @import "tailwindcss",并 @source 扫描本包 + ../../vant-go/src + ../../element-go/srcmonorepo 相对路径,npm 安装场景下这两条不生效)。
  3. 提供通用工具类。

| 工具类 | 作用 | | --- | --- | | .flex-center | flex + 居中(主轴/交叉轴) | | .flex-center-end | flex + 交叉轴居中 + 主轴靠右 | | .flex-center-between | flex + 交叉轴居中 + space-between | | .flex-center-items | flex + 仅交叉轴居中 | | .bg-light / .text-light | #ccc.dark 下自动变 #333) | | .mob | ≥768px 时隐藏(仅手机端显示) | | .web | ≤768px 时隐藏(仅电脑端显示) |

为什么必须分层:CSS 规范里无层样式永远赢过有层样式,与源码顺序、特异性无关。 Tailwind v4 把工具类放进 @layer utilities,而第三方组件库默认无层——不处理的话 <van-button class="mt-6">mt-6 会被静默吃掉,只能靠满屏 ! 去救。


10. 两种消费模式

exports@source 条件指向 src,默认条件指向 dist

npm 包模式(默认,推荐)

直接安装使用,吃 dist,组件已编译完毕。不要vite.config.ts 里开 resolve.conditions: ['@source']

源码包模式(submodule / monorepo)

// vite.config.ts
export default defineConfig({
  resolve: {
    conditions: ['@source'],
    alias: { '@gindow/vue': resolve(__dirname, '../packages/vue/src') },
  },
})

此时源码由宿主 app 编译,因此宿主必须满足:

  • unplugin-auto-import 覆盖到这些文件(本包源码依赖自动导入注入 computed / ref 等);
  • 自行安装 dayjsnanoid(npm 模式下它们已内联,源码模式下需要真实依赖)。

反过来,npm 包模式下开 @source 会直接报 computed is not defined—— AutoImport 默认不处理 node_modules


11. 开发

pnpm install
pnpm build       # vue-tsc --noEmit 类型检查 + vite 构建(dist/ 双产物 + style.css)

产物:dist/vue.mjsdist/vue.cjsdist/vue.d.tsdist/resolver.*dist/style.css。 external 仅 vue@iconify/vue,其余依赖内联。

License

MIT © joming