@gindow/vue

Vue3 通用基础包,包含工具函数、Hooks、类型定义、国际化、基础样式
2,267
Install
npm install @gindow/vue
Latest Version:1.0.8
Vue:3
License:MIT
Last Updated:Aug 14, 2026
Links: npm
Maintainer: gindow

@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, dayjs 与 nanoid 已被打进 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/path 的 stroke-width)
color string — 颜色

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

type 解析优先级:icon → type === '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/core、lodash:

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 折线图(默认堆叠) title、props、series、values、spec
vPie 饼图(默认环形 innerRadius:0.4) title(必填)、props、values
vFunnel 漏斗图 title(必填)、props、values
vNum 数字指标卡(非图表,纯 DOM) icon、color(默认 #093)、title(默认 'Total')、amount(默认 0)、growth
  • vLine / vPie 触发 @elementClick="(datum) => {}";vBar / vFunnel / vNum 无此事件。
  • 图表根元素为 <div class="h-full min-h-60">,高度由父容器决定(vNum 是 min-h-40)。
  • vNum.growth 为 null 时不显示涨跌行;为字符串时取其中第一个数字判断正负(正数绿色带 +,否则红色)。

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()

响应式断点,阈值固定 768px(min-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('zhangsan@qq.com') 'z****n@qq.com'(用户名 ≤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 — 环境判断

全部是 getter(Platform.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/component、IUploadUserFile.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-US → en)→ 兜底 '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/src(monorepo 相对路径,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 等);
  • 自行安装 dayjs、nanoid(npm 模式下它们已内联,源码模式下需要真实依赖)。

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


11. 开发

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

产物:dist/vue.mjs、dist/vue.cjs、dist/vue.d.ts、dist/resolver.*、dist/style.css。 external 仅 vue 与 @iconify/vue,其余依赖内联。

License

MIT © joming

Version History

Version Released License
1.0.8 MIT
1.0.7 MIT
1.0.6 MIT
1.0.5 MIT
1.0.4 MIT