@cghbcf/xp-ui
@cghbcf/xp-ui
Vue3 组件库 - 基于 Vue3 + Vite 构建的现代化 UI 组件库
特性
- ⚡ 基于 Vue3 和 Vite,性能卓越
- 📦 开箱即用,无需繁琐配置
- 🎨 现代化设计风格,支持主题定制(浅色 / 暗黑双主题)
- 🎛️ 可视化主题定制面板 —— 实时修改颜色、一键导出 JSON/CSS
- 🌈 主色自动派生(-light / -dark / -bg / -border),无需手动配置
- 🔧 丰富的组件库,覆盖常用场景(Button / Input / Select / Dialog / Card / Icon / Tabs / Table / Pagination / Empty / Toast / ContextMenu / Menu 等 27+ 组件)
- 💎 完整的 CSS 变量(Design Tokens)系统,便于深度定制
- 💾 主题配置自动持久化到 localStorage,刷新页面不丢失
快速开始
安装
# npm
npm install @cghbcf/xp-ui --save
# yarn
yarn add @cghbcf/xp-ui
# pnpm
pnpm install @cghbcf/xp-ui
完整引入
import { createApp } from 'vue'
import App from './App.vue'
import XpUi from '@cghbcf/xp-ui'
import '@cghbcf/xp-ui/dist/style.css'
const app = createApp(App)
app.use(XpUi)
app.mount('#app')
按需引入
import { createApp } from 'vue'
import App from './App.vue'
import { XpButton, XpInput, XpDialog, XpCard, XpTabs } from '@cghbcf/xp-ui'
import '@cghbcf/xp-ui/dist/style.css'
const app = createApp(App)
app.component('XpButton', XpButton)
app.component('XpInput', XpInput)
app.mount('#app')
也可以直接使用主题 composable:
import { useTheme, applyThemeConfig, setTheme, toggleTheme } from '@cghbcf/xp-ui'
const { theme, themeConfig, initTheme, applyThemeConfig, buildCssString } = useTheme()
initTheme()
组件
Icon 图标
基于内联 SVG 的图标组件,支持预设/自定义尺寸、颜色、旋转与加载动画。
图标文件统一存放在 src/components/icon/icons/ 目录下,组件在构建时通过 import.meta.glob 自动扫描该目录,新增图标只需放入 .svg 文件,即可用同名 name 直接引用(例如 jiantou.svg 对应 name="jiantou")。
图标内部硬编码的 fill / stroke 颜色(除 none 外)会被自动替换为 currentColor,因此 color 属性始终生效。未指定 color 时图标默认继承当前文字颜色,便于通过父元素着色(如按钮 hover 高亮)。 当 name 不存在时,会展示占位符(?)便于排查。
内置图标
下表为当前内置的 18 个图标(不含占位符):
| name | 视觉 | name | 视觉 |
|---|---|---|---|
back 返回 |
← | forward 前进 |
→ |
up 上一级 |
↑ | arrow-right 右箭头 |
→▸ |
refresh 刷新 |
⟳ | chevron-up 上箭头 |
∧ |
chevron-down 下箭头 |
∨ | close 关闭 |
✕ |
folder 文件夹 |
📁 | folder-add 新建文件夹 |
📁+ |
list 列表视图 |
☰ | grid 网格视图 |
▦ |
settings 设置 |
⚙ | star 星标 |
☆ |
star-fill 星标实心 |
★ | upload 上传 |
⇧ |
home 首页 |
⌂ | jiantou 箭头 |
◀ |
图标总览见演示站
Icon 图标页,点击图标可复制XpIcon代码。新增图标只需放入icons/目录即可自动出现。
基础用法
<template>
<!-- 内置图标 -->
<XpIcon name="jiantou" />
<!-- 方向:通过 rotate 或内联样式旋转 -->
<XpIcon name="jiantou" :rotate="180" />
<!-- 预设尺寸 -->
<XpIcon name="jiantou" size="small" />
<XpIcon name="jiantou" size="medium" />
<XpIcon name="jiantou" size="large" />
<!-- 自定义尺寸(数字按 px 处理) -->
<XpIcon name="jiantou" :size="32" />
<!-- 自定义颜色 -->
<XpIcon name="jiantou" color="#67c23a" />
<!-- 加载动画 -->
<XpIcon name="jiantou" spin color="var(--xp-color-primary)" />
<!-- name 不存在时显示占位符 -->
<XpIcon name="not-exist" />
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | string | '' | 图标名称,对应 icons/ 目录下 .svg 文件名(不含后缀) |
| size | string / number | medium | 尺寸:预设值 small(14) / medium(18) / large(24),或自定义数字(px) / 带单位字符串(如 48px) |
| color | string | '' | 图标颜色,值为任意合法 CSS 颜色 |
| spin | boolean | false | 是否持续旋转(加载动画) |
| rotate | number | 0 | 旋转角度(单位:度) |
Slots
| 插槽名 | 说明 |
|---|---|
| default | 自定义图标内容(传入则优先渲染插槽内容,而非 SVG) |
Button 按钮
按钮用于触发一个操作,如提交表单、打开对话框等。
基础用法
<template>
<XpButton>Default</XpButton>
<XpButton type="primary">Primary</XpButton>
<XpButton type="success">Success</XpButton>
<XpButton type="warning">Warning</XpButton>
<XpButton type="danger">Danger</XpButton>
<XpButton type="info">Info</XpButton>
<XpButton type="primary" plain>Plain Primary</XpButton>
<XpButton type="primary" text>Text Primary</XpButton>
<XpButton size="small" type="primary">Small</XpButton>
<XpButton size="medium" type="primary">Medium</XpButton>
<XpButton size="large" type="primary">Large</XpButton>
<XpButton type="primary" disabled>Disabled</XpButton>
<XpButton type="primary" loading @click="handleClick">Loading</XpButton>
</template>
<script setup>
const handleClick = () => {}
</script>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | string | default | 按钮类型,可选值:primary / default / success / warning / danger / info / ghost |
| size | string | medium | 按钮尺寸,可选值:small / medium / large |
| plain | boolean | false | 是否朴素按钮 |
| text | boolean | false | 是否文字按钮 |
| disabled | boolean | false | 是否禁用按钮 |
| loading | boolean | false | 是否显示加载状态 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| click | 点击按钮时触发 | event |
Input 输入框
用于收集用户输入信息。
基础用法
<template>
<XpInput v-model="inputValue" placeholder="请输入内容" showClear />
<XpInput placeholder="Small" size="small" />
<XpInput placeholder="Medium" size="medium" />
<XpInput placeholder="Large" size="large" />
<XpInput placeholder="用户名">
<template #prefix>👤</template>
</XpInput>
<XpInput placeholder="密码">
<template #suffix>🔒</template>
</XpInput>
<XpInput placeholder="禁用状态" disabled />
</template>
<script setup>
import { ref } from 'vue'
const inputValue = ref('')
</script>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | string / number | '' | 输入框的值,支持 v-model |
| id | string | '' | 原生 input 的 id,配合 label[for] 使用 |
| type | string | text | 输入框类型,可选值:text / password / number / email / tel / url |
| placeholder | string | '' | 占位符文本 |
| disabled | boolean | false | 是否禁用 |
| size | string | medium | 输入框尺寸,可选值:small / medium / large |
| maxlength | string / number | null | 最大输入长度 |
| showClear | boolean | false | 是否显示清空按钮 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 值改变时触发 | value |
| focus | 获得焦点时触发 | event |
| blur | 失去焦点时触发 | event |
| clear | 点击清空按钮时触发 | - |
Slots
| 插槽名 | 说明 |
|---|---|
| prefix | 前缀内容 |
| suffix | 后缀内容 |
实例方法
通过模板 ref 可调用内部原生 input 的方法(如自动聚焦 / 全选):
<template>
<XpInput ref="inputRef" v-model="value" placeholder="请输入" />
</template>
<script setup>
import { ref, nextTick } from 'vue'
const inputRef = ref(null)
nextTick(() => {
inputRef.value.focus()
inputRef.value.select()
})
</script>
| 方法 | 说明 |
|---|---|
| focus | 聚焦输入框 |
| select | 全选输入框内容 |
| blur | 失焦 |
Select 选择器
下拉选择器,使用 Teleport 渲染浮层。支持键盘操作(Enter/Space 打开、上下键切换、Esc 关闭)、清空、尺寸与禁用态。
基础用法
<template>
<XpSelect v-model="city" :options="options" placeholder="请选择城市" @change="onChange" />
</template>
<script setup>
import { ref } from 'vue'
const city = ref('shanghai')
const options = [
{ label: '北京', value: 'beijing' },
{ label: '上海', value: 'shanghai' },
{ label: '深圳', value: 'shenzhen' }
]
const onChange = (value) => console.log(value)
</script>
options兼容纯值数组(如['北京', '上海']),此时选中值即为该值本身。
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | string / number / boolean | '' | 当前选中值,支持 v-model |
| options | array | [] | 选项列表:{ label, value, disabled } 或纯值 |
| placeholder | string | 请选择 | 未选中时的占位文案 |
| size | string | medium | 尺寸:small / medium / large |
| disabled | boolean | false | 是否禁用 |
| clearable | boolean | false | 是否可清空 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 选中值变化(配合 v-model 使用) | (value) |
| change | 选择某一项后触发 | (value) |
| clear | 点击清空按钮时触发 | - |
| visible-change | 浮层显隐变化 | (visible: boolean) |
Switch 开关
表示两种相互对立的状态间的切换。
基础用法
<template>
<XpSwitch v-model="value" />
<XpSwitch v-model="value" size="small" />
<XpSwitch v-model="value" size="large" />
<XpSwitch :model-value="true" disabled />
</template>
<script setup>
import { ref } from 'vue'
const value = ref(false)
</script>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | boolean | false | 绑定值,支持 v-model |
| size | string | medium | 尺寸:small / medium / large |
| disabled | boolean | false | 是否禁用 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 值改变时触发 | value |
| change | 值改变时触发 | value |
Checkbox 多选框
一组备选项中进行多选。
基础用法
<template>
<XpCheckbox v-model="value1">同意协议</XpCheckbox>
<XpCheckbox v-model="value2">接收邮件</XpCheckbox>
<XpCheckbox :model-value="true" disabled>已勾选(禁用)</XpCheckbox>
<XpCheckbox :model-value="false" disabled>未勾选(禁用)</XpCheckbox>
</template>
<script setup>
import { ref } from 'vue'
const value1 = ref(true)
const value2 = ref(false)
</script>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | boolean | false | 绑定值,支持 v-model |
| disabled | boolean | false | 是否禁用 |
| indeterminate | boolean | false | 是否半选状态 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 值改变时触发 | value |
| change | 值改变时触发 | value |
Radio 单选框
在一组备选项中进行单选。
基础用法
<template>
<XpRadio v-model="value" value="A">选项 A</XpRadio>
<XpRadio v-model="value" value="B">选项 B</XpRadio>
<XpRadio v-model="value" value="C">选项 C</XpRadio>
</template>
<script setup>
import { ref } from 'vue'
const value = ref('A')
</script>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | string / number / boolean | '' | 绑定值 |
| value | string / number / boolean | '' | 选项的值 |
| disabled | boolean | false | 是否禁用 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 值改变时触发 | value |
| change | 值改变时触发 | value |
Tag 标签 & Badge 徽标
Tag 用于标记和分类;Badge 用于消息数量提示。
基础用法
<template>
<XpTag>Default</XpTag>
<XpTag type="primary">Primary</XpTag>
<XpTag type="success">Success</XpTag>
<XpTag type="warning">Warning</XpTag>
<XpTag type="danger">Danger</XpTag>
<XpTag type="info">Info</XpTag>
<XpTag round type="primary">Round</XpTag>
<XpTag size="small">Small</XpTag>
<XpTag size="large">Large</XpTag>
<XpTag closable>Closable</XpTag>
<XpBadge value="5">
<XpButton size="small">消息</XpButton>
</XpBadge>
<XpBadge value="120" type="warning">
<XpButton size="small">通知</XpButton>
</XpBadge>
<XpBadge isDot type="danger">
<XpButton size="small">新消息</XpButton>
</XpBadge>
</template>
Tag Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | string | default | 类型:primary / default / success / warning / danger / info |
| size | string | medium | 尺寸:small / medium / large |
| color | string | '' | 自定义颜色(#xxx 或 rgb) |
| plain | boolean | false | 是否朴素样式 |
| round | boolean | false | 是否圆角 |
| closable | boolean | false | 是否可关闭 |
Badge Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| value | string / number | 0 | 显示值 |
| max | number | 99 | 最大值,超出显示 {max}+ |
| type | string | danger | 类型:primary / success / warning / danger / info |
| isDot | boolean | false | 是否以小圆点显示 |
| hidden | boolean | false | 是否隐藏徽标 |
Alert 提示
用于页面中展示重要的提示信息。
基础用法
<template>
<XpAlert title="这是一条成功消息" type="success" />
<XpAlert title="这是一条警告消息" type="warning" />
<XpAlert title="这是一条错误消息" type="danger" />
<XpAlert title="这是一条提示消息" type="info" />
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string | '' | 标题 |
| type | string | info | 类型:success / warning / danger / info |
| effect | string | light | 样式:light / dark / plain |
| closable | boolean | true | 是否可关闭 |
| showIcon | boolean | true | 是否显示图标 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| close | 关闭 alert 时触发 | - |
Avatar 头像
用图标、图片或者字符展示用户头像。
基础用法
<template>
<XpAvatar size="small">Xp</XpAvatar>
<XpAvatar>User</XpAvatar>
<XpAvatar size="large">Demo</XpAvatar>
<XpAvatar shape="square">Sq</XpAvatar>
<XpAvatar shape="square" size="large">UI</XpAvatar>
<XpAvatar src="https://example.com/avatar.png" alt="Avatar" />
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| src | string | '' | 图片头像地址 |
| alt | string | '' | 替代文本 |
| shape | string | circle | 形状:circle / square |
| size | string | medium | 尺寸:small / medium / large |
| icon | string | '' | 图标 |
| colorful | boolean | false | 无图片时依据 alt 自动分配柔和主题色 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| error | 图片加载失败时触发 | - |
Progress 进度条 & Rate 评分
进度条用于展示操作进度;评分用于对事物进行评级操作。
基础用法
<template>
<XpProgress :percentage="30" style="width:240px" />
<XpProgress :percentage="60" status="warning" style="width:240px" />
<XpProgress :percentage="85" status="success" style="width:240px" />
<XpProgress :percentage="100" status="exception" style="width:240px" />
<XpRate v-model="rate" showText />
</template>
<script setup>
import { ref } from 'vue'
const rate = ref(3)
</script>
Progress Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| percentage | number | 0 | 百分比(0-100) |
| color | string | '' | 自定义进度条颜色 |
| showText | boolean | true | 是否显示百分比文字 |
| type | string | line | 类型:line / circle |
| status | string | '' | 状态:success / warning / exception |
| strokeWidth | number | 8 | line 模式下进度条高度(px),紧凑场景可设小值 |
Rate Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | number | 0 | 绑定值 |
| max | number | 5 | 最大分值 |
| color | string | #f7ba2a | 颜色 |
| disabled | boolean | false | 是否禁用 |
| showText | boolean | false | 是否显示文字 |
Tooltip 提示
常用于展示鼠标 hover 时的提示信息。
基础用法
<template>
<XpTooltip content="顶部提示">
<XpButton type="primary">Top</XpButton>
</XpTooltip>
<XpTooltip content="底部提示" placement="bottom">
<XpButton>Bottom</XpButton>
</XpTooltip>
<XpTooltip content="左侧提示" placement="left">
<XpButton type="success">Left</XpButton>
</XpTooltip>
<XpTooltip content="右侧提示" placement="right">
<XpButton type="warning">Right</XpButton>
</XpTooltip>
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| content | string | '' | 显示的内容 |
| placement | string | top | 出现位置:top / bottom / left / right |
| trigger | string | hover | 触发方式:hover / click / manual |
| disabled | boolean | false | 是否禁用 |
Dropdown 下拉菜单
将动作或菜单折叠到下拉菜单中。
基础用法
<template>
<XpDropdown :items="items" @select="handleSelect">
<XpButton type="primary">下拉菜单 ▾</XpButton>
</XpDropdown>
</template>
<script setup>
const items = [
{ label: '选项一', command: 'a' },
{ label: '选项二', command: 'b' },
{ label: '选项三(禁用)', command: 'c', disabled: true },
{ divider: true },
{ label: '选项四', command: 'd' }
]
const handleSelect = (item) => {
console.log('选中:', item)
}
</script>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| items | Array | [] | 菜单项:{ label, command, disabled, divider } |
| disabled | boolean | false | 是否禁用 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| select | 点击菜单项时触发 | item |
| command | 点击菜单项时触发(传 command) | command, item |
| open | 菜单打开时触发 | - |
| close | 菜单关闭时触发 | - |
Card 卡片
将信息聚合在卡片容器中展示。
基础用法
<template>
<XpCard title="卡片标题">
<p>这是卡片内容</p>
</XpCard>
<XpCard title="卡片标题">
<p>卡片内容区域</p>
<template #footer>
<div style="display:flex;justify-content:flex-end;gap:8px">
<XpButton size="small">取消</XpButton>
<XpButton size="small" type="primary">确定</XpButton>
</div>
</template>
</XpCard>
<XpCard title="卡片标题" shadow="hover">
<p>悬停出现阴影</p>
</XpCard>
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string | '' | 卡片标题 |
| shadow | string | always | 阴影效果:always / hover / never |
| bordered | boolean | true | 是否显示边框 |
| hover | boolean | false | 是否 hover 时上移 |
Slots
| 插槽名 | 说明 |
|---|---|
| header | 自定义头部内容 |
| default | 卡片主体内容 |
| footer | 底部内容 |
| extra | 头部右侧内容 |
Tabs 标签页
分隔内容上有关联但属于不同类别的数据集合。
基础用法
<template>
<XpTabs v-model="active">
<XpTabPane name="tab1" title="选项一">
<p>这是选项一的内容。</p>
</XpTabPane>
<XpTabPane name="tab2" title="选项二">
<p>这是选项二的内容。</p>
</XpTabPane>
<XpTabPane name="tab3" title="选项三">
<p>这是选项三的内容。</p>
</XpTabPane>
</XpTabs>
</template>
<script setup>
import { ref } from 'vue'
const active = ref('tab1')
</script>
Tabs Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | string / number | null | 当前激活的 name,支持 v-model |
TabPane Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | string / number | - | 必填,唯一标识 |
| title | string | '' | 标签标题 |
| disabled | boolean | false | 是否禁用 |
Tabs Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 切换时触发 | name |
| change | 切换时触发 | name |
Menu 菜单
为页面提供导航的菜单列表。支持目录(子菜单)嵌套目录,层级不限制;提供「组装模式」(通过 XpSubMenu / XpMenuItem 组合)与「数据驱动模式」(传入 items 树自动生成)两种用法。
基础用法
<template>
<XpMenu v-model="active" @select="handleSelect">
<XpMenuItem index="dashboard">仪表盘</XpMenuItem>
<XpSubMenu index="system">
<template #title>系统管理</template>
<XpMenuItem index="system-users">用户管理</XpMenuItem>
<XpMenuItem index="system-settings" disabled>系统设置</XpMenuItem>
</XpSubMenu>
</XpMenu>
</template>
<script setup>
import { ref } from 'vue'
const active = ref('dashboard')
const handleSelect = (index, item) => {
// index: 选中项完整 index;item: 数据驱动模式下对应的原始节点(组装模式为 null)
}
</script>
多级嵌套(不限制层级)
目录(XpSubMenu)内部可以继续放置目录,逐层展开:
<XpMenu v-model="active">
<XpSubMenu index="1">
<template #title>导航一</template>
<XpMenuItem index="1-1">选项一</XpMenuItem>
<XpSubMenu index="1-2">
<template #title>导航二(子目录)</template>
<XpSubMenu index="1-2-2">
<template #title>导航三(再嵌套)</template>
<XpMenuItem index="1-2-2-1">四级选项一</XpMenuItem>
</XpSubMenu>
</XpSubMenu>
</XpSubMenu>
</XpMenu>
数据驱动模式
传入 items 树自动递归生成菜单,children 可无限嵌套。节点结构:
{ index, label, icon?, children?, disabled? },其中 icon 传内联 SVG 字符串(图标内部颜色使用 currentColor 便于跟随主题着色)。节点还可携带任意扩展字段(如 path),经 select 事件随原节点一并透出。深度嵌套时依赖插件全局注册(app.use(XpUi) 或逐一注册 XpSubMenu / XpMenuItem)。
<template>
<XpMenu :items="items" v-model="active" @select="handleSelect"></XpMenu>
</template>
<script setup>
import { ref } from 'vue'
const active = ref('system.system-users')
const items = [
{ index: 'dashboard', label: '仪表盘' },
{
index: 'system',
label: '系统管理',
children: [
{ index: 'system-users', label: '用户管理' },
{
index: 'system-log',
label: '日志记录',
children: [
{ index: 'system-log-login', label: '登录日志' },
{ index: 'system-log-operation', label: '操作日志' }
]
}
]
}
]
</script>
激活项使用叶子节点的「完整 index」:数据驱动模式下 = 各级
index以.拼接(如system.system-log.system-log-login);组装模式下直接使用各组件上声明的index。
手风琴 & 多展开
uniqueOpen(默认 true)开启手风琴:同一分支下仅保留一个展开的目录,其余自动收起;设为 false 可同时展开多个。
分组标题
使用 XpMenuGroup 为相邻菜单添加分组说明(仅组装模式):
<XpMenu v-model="active">
<XpMenuGroup title="导航">
<XpMenuItem index="home">首页</XpMenuItem>
<XpMenuItem index="news">消息</XpMenuItem>
</XpMenuGroup>
</XpMenu>
横向 / 折叠
mode="horizontal":横向排列,一级目录悬停下拉弹出面板;collapse(仅 vertical 生效):折叠为仅图标模式,悬停一级目录在右侧弹出面板(面板内部仍支持无限嵌套)。折叠态下菜单宽度由外层容器控制(如 64px ~ 68px)。
<XpMenu mode="horizontal" :items="items" v-model="active"></XpMenu>
<XpMenu :items="items" collapse v-model="active"></XpMenu>
样式定制
菜单使用 --xp-menu-* 系列 CSS 变量(默认对齐全局主题),可在使用处按需覆盖,例如深色侧边栏:
.my-sidebar .xp-menu {
--xp-menu-text: rgba(226, 232, 255, 0.62);
--xp-menu-text-primary: rgba(236, 242, 255, 0.92);
--xp-menu-active: #fff;
--xp-menu-active-bg: rgba(64, 158, 255, 0.32);
--xp-menu-hover-bg: rgba(255, 255, 255, 0.04);
--xp-menu-panel-bg: #1c2333;
--xp-menu-disabled: rgba(226, 232, 255, 0.35);
}
Menu Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| modelValue | string / number | '' | 当前激活项(叶子节点)的完整 index,支持 v-model |
| mode | string | 'vertical' | 菜单方向:vertical / horizontal |
| items | array | [] | 数据驱动模式的菜单树,children 可无限嵌套 |
| collapse | boolean | false | 折叠为仅图标模式(仅 vertical 生效),悬停弹出面板 |
| uniqueOpen | boolean | true | 手风琴模式,同一分支下仅展开一个目录 |
| disabled | boolean | false | 是否禁用整个菜单 |
Menu Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:modelValue | 选择菜单项时触发 | index |
| select | 选择菜单项时触发 | (index, item):item 为数据驱动模式下选中的原始节点,组装模式为 null |
| open | 展开子菜单时触发 | index |
| close | 收起子菜单时触发 | index |
SubMenu Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| index | string / number | - | 必填,完整路径 index |
| label | string | '' | 数据驱动模式下的标题文案 |
| icon | string | '' | 数据驱动模式下的内联 SVG 图标 |
| items | array | [] | 数据驱动模式下子级菜单树 |
| disabled | boolean | false | 是否禁用 |
标题内容也支持
#title插槽,插槽优先级高于 label / icon。
MenuItem Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| index | string / number | - | 必填,叶子节点完整 index |
| disabled | boolean | false | 是否禁用 |
| item | object | null | 数据驱动模式下对应的原始节点(随 select 事件透传) |
MenuGroup Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string | '' | 分组标题 |
Slots
| 插槽名 | 说明 |
|---|---|
| default(Menu) | 组装模式:组合 XpSubMenu / XpMenuItem / XpMenuGroup |
| default(SubMenu) | 子级菜单内容 |
| title(SubMenu) | 子菜单标题内容 |
Dialog 对话框
在保留当前页面状态的情况下,告知用户并承载相关操作。
基础用法
<template>
<XpButton type="primary" @click="visible = true">打开对话框</XpButton>
<XpDialog
v-model:visible="visible"
title="提示"
@confirm="handleConfirm"
@cancel="handleCancel"
>
<p>这是一段弹窗内容</p>
</XpDialog>
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(false)
const handleConfirm = () => { visible.value = false }
const handleCancel = () => { visible.value = false }
</script>
自定义宽度
<template>
<XpDialog
v-model:visible="visible"
title="宽弹窗"
width="700px"
>
<p>弹窗内容</p>
</XpDialog>
</template>
自定义按钮文字
<template>
<XpDialog
v-model:visible="visible"
title="确认删除"
confirm-button-text="删除"
cancel-button-text="我再想想"
>
<p>确定要删除这条记录吗?</p>
</XpDialog>
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| visible | boolean | false | 是否显示,支持 v-model |
| title | string | '' | 弹窗标题 |
| width | string | 500px | 弹窗宽度 |
| confirmButtonText | string | 确定 | 确认按钮文字 |
| cancelButtonText | string | 取消 | 取消按钮文字 |
| closeOnClickModal | boolean | true | 点击遮罩层是否关闭弹窗 |
| showDefaultFooter | boolean | true | 是否显示默认底部按钮 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| confirm | 点击确认按钮时触发 | - |
| cancel | 点击取消按钮时触发 | - |
| close | 关闭弹窗时触发 | - |
| update:visible | 可见性改变时触发 | visible |
Slots
| 插槽名 | 说明 |
|---|---|
| default | 弹窗主体内容 |
| footer | 自定义底部按钮区域 |
按 Esc 键同样可以关闭弹窗。
Empty 空状态
用于列表、目录等数据为空时的占位展示。
基础用法
<template>
<XpEmpty title="目录为空" hint="拖入文件或点击工具栏上传" />
</template>
自定义图标与操作
<template>
<XpEmpty title="连接失败" hint="无法连接到服务器,请检查网络后重试">
<template #icon>
<div class="my-icon" />
</template>
<XpButton type="primary" size="small">重新加载</XpButton>
</XpEmpty>
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | string | 暂无内容 | 主文案 |
| hint | string | '' | 辅助说明文案 |
Slots
| 插槽名 | 说明 |
|---|---|
| icon | 自定义图标(默认是一个文件占位图形) |
| default | 底部操作区(如重试按钮) |
Pagination 分页
分页条组件,展示数据总数与页码序列(首尾 + 当前页前后 2 页,跳跃处用省略号)。total 为 0 时不渲染。通过 v-model:page 双向绑定当前页,翻页成功后触发 change 事件;传入 pageSizes 会额外渲染每页条数选择器,配合 v-model:page-size 使用。
基础用法
<template>
<XpPagination v-model:page="page" :total="128" :page-size="10" @change="fetchPage" />
</template>
<script setup>
import { ref } from 'vue'
const page = ref(1)
const fetchPage = (n) => {
// 依据新页码 page 向后端拉取数据
console.log('当前页', n)
}
</script>
每页条数选择
<template>
<XpPagination
v-model:page="page"
v-model:page-size="pageSize"
:total="186"
:page-sizes="[10, 20, 50]"
@change="fetchPage"
@size-change="fetchPage"
/>
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| total | number | 0 | 数据总条数(为 0 时不渲染分页条) |
| page | number | 1 | 当前页码,从 1 开始,支持 v-model:page |
| pageSize | number | 10 | 每页条数,支持 v-model:page-size |
| pageSizes | array | [] | 可选的每页条数列表;为空时不渲染条数选择器 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:page | 页码变化(配合 v-model:page 使用) | (page: number) |
| update:pageSize | 每页条数变化(配合 v-model:page-size 使用) | (pageSize: number) |
| change | 翻页成功 | (page: number) |
| size-change | 每页条数变化 | (pageSize: number) |
Table 表格
列式定义表格:XpTable 负责整体渲染(含加载遮罩、空状态),通过子组件 XpTableColumn 声明列。支持字段自动渲染、自定义单元格/表头、排序、多选、斑马纹与边框、固定列,适用于各数据列表页面。
基础用法
<template>
<XpTable :data="list" row-key="id">
<XpTableColumn prop="name" label="姓名" />
<XpTableColumn prop="email" label="邮箱" />
<XpTableColumn prop="active" label="状态" width="100">
<template #default="{ row }">
<XpTag size="small" :type="row.active ? 'success' : 'info'">
{{ row.active ? '启用' : '禁用' }}
</XpTag>
</template>
</XpTableColumn>
</XpTable>
</template>
XpTable Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| data | array | [] | 表格数据源 |
| loading | boolean | false | 加载中状态,为真时显示加载遮罩 |
| emptyTitle | string | 暂无数据 | 空状态标题 |
| emptyHint | string | '' | 空状态提示文案 |
| rowKey | string | id | 行唯一标识字段名,用于 :key 与多选标识;行数据缺失该字段时回退到行索引(多选/排序建议显式配置) |
| stripe | boolean | false | 斑马纹:奇偶行交替背景色 |
| border | boolean | false | 边框模式:单元格带纵向边框并整体加外框 |
XpTableColumn Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| prop | string | '' | 对应数据行字段名,自动渲染 row[prop];配合 formatter / #default 插槽时可不传 |
| label | string | '' | 表头文案 |
| type | string | default | 列类型:default 普通列 / selection 多选列 |
| width | number / string | '' | 列宽(number 为 px);固定列必须显式指定 |
| minWidth | number / string | '' | 最小列宽 |
| align | string | left | 对齐方式:left / center / right |
| sortable | boolean / string | false | 是否可排序:true 内置升/降序;'custom' 仅触发 sort-change 由外部处理 |
| fixed | string | '' | 固定列位置:'left' / 'right',需配合 width 使用 |
| formatter | function | - | 单元格格式化:(row, column, cellValue, index) => string |
| className | string | '' | 自定义列 class,同时作用于表头与单元格(如配合媒体查询隐藏整列) |
XpTable Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| sort-change | 排序状态变化 | { column, prop, order },order 为 ascending / descending / null |
| selection-change | 勾选集合变化 | (rows: array) 当前选中的行 |
| select | 单个行勾选状态变化 | (row, selected) |
| row-click | 点击行 | (row, index) |
XpTable 实例方法
| 方法名 | 说明 |
|---|---|
| clearSelection | 清空所有勾选 |
| toggleRowSelection(row, selected?) | 切换指定行勾选状态;selected 缺省时反转当前状态 |
XpTable Slots
| 插槽名 | 说明 |
|---|---|
| default | 放置若干 XpTableColumn(列定义) |
| empty | 自定义空状态内容(默认渲染 XpEmpty,由 emptyTitle / emptyHint 控制文案) |
XpTableColumn Slots
| 插槽名 | 说明 |
|---|---|
| default | 自定义单元格内容,作用域暴露 { row, index } |
| header | 自定义表头内容,作用域暴露 { column } |
排序
<XpTable :data="list" row-key="id" @sort-change="({ prop, order }) => onSort(prop, order)">
<XpTableColumn prop="name" label="姓名" sortable width="130" />
</XpTable>
sortable 为 true 时表格内置升/降序(重复点击在升序 → 降序 → 复原间切换);为 'custom' 时不排序,仅触发 sort-change,由外部维护排序后的数据。
多选
<template>
<XpTable ref="tableRef" :data="list" row-key="id" @selection-change="onSelectionChange">
<XpTableColumn type="selection" width="48" />
<XpTableColumn prop="name" label="姓名" />
</XpTable>
</template>
<script setup>
const tableRef = ref(null)
const onSelectionChange = (rows) => {
console.log(rows) // 选中的行数组
}
const clearAll = () => tableRef.value.clearSelection()
</script>
多选通过与 row-key 对应的字段标识行,请确保数据包含该字段。
固定列
<XpTable :data="list" row-key="id">
<XpTableColumn type="selection" fixed="left" width="48" />
<XpTableColumn prop="name" label="姓名" fixed="left" width="110" />
<XpTableColumn prop="email" label="邮箱" />
<XpTableColumn label="操作" fixed="right" width="140">
<template #default="{ row }">
<XpButton size="small" type="primary" @click="onEdit(row)">编辑</XpButton>
</template>
</XpTableColumn>
</XpTable>
fixed="left" / fixed="right" 将列冻结在容器两侧,表体可左右横向滚动(移动端同样生效)。固定列需显式传 width。
FileIcon 文件图标
按文件类型展示对应的矢量图标,内置文件夹与常见文件类型,适用于文件管理器等场景。
基础用法
<template>
<XpFileIcon type="folder" :size="34" />
<XpFileIcon type="pdf" :size="34" />
<XpFileIcon type="code" :size="20" />
</template>
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | string | file | 类型:folder / image / video / audio / pdf / doc / sheet / slide / archive / code / text / file |
| size | number | 20 | 图标边长(px) |
ContextMenu 右键菜单
在指定屏幕坐标位置弹出的菜单,常用于右键 / 长按等交互场景。
基础用法
<template>
<div @contextmenu.prevent="openMenu($event, items)">右键点击此区域</div>
<XpContextMenu :visible="menu.visible" :x="menu.x" :y="menu.y" :items="menu.items" @close="menu.visible = false" />
</template>
<script setup>
import { reactive } from 'vue'
const menu = reactive({ visible: false, x: 0, y: 0, items: [] })
const openMenu = (e, items) => {
menu.x = e.clientX
menu.y = e.clientY
menu.items = items
menu.visible = true
}
const items = [
{ label: '打开', command: 'open' },
{ label: '禁用项', disabled: true },
{ divider: true },
{ label: '删除', command: 'delete', danger: true }
]
</script>
菜单会自动进行视口边界校正;点击菜单项、遮罩或按 Esc 均会关闭菜单。
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| visible | boolean | false | 是否显示 |
| x | number | 0 | 菜单左上角 x 坐标(px) |
| y | number | 0 | 菜单左上角 y 坐标(px) |
| items | Array | [] | 菜单项:{ label, icon?, command?, danger?, disabled?, divider?, onClick? } |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| select | 点击菜单项时触发 | item |
| command | 点击菜单项时触发(传 command) | command, item |
| close | 菜单关闭时触发 | - |
兼容用法:菜单项里直接传
onClick函数也会在点击时被调用。
Toast 轻提示
轻量级的全局消息提示,需要配合 useToast() 组合式函数使用。
基础用法
<template>
<!-- 在应用根节点挂载一次即可 -->
<XpToast />
</template>
<script setup>
import { useToast } from '@cghbcf/xp-ui'
const { toast, success, error, warn, info } = useToast()
success('操作成功')
error('操作失败')
warn('请注意该操作的后果')
info('这是一条普通提示')
</script>
useToast API
| 方法 | 说明 | 参数 |
|---|---|---|
| toast | 触发提示,可自定义类型与时长 | (message, type?, duration?) |
| success | 成功提示(2200ms) | message |
| error | 错误提示(3600ms) | message |
| warn | 警告提示(2800ms) | message |
| info | 信息提示(2600ms) | message |
| toasts | 响应式提示列表(ref) | - |
toast 的 type 可选值:info / success / error / warn。
主题定制
Xp UI 内置完整的 浅色 / 暗黑双主题 系统,并支持 可视化颜色定制、配置导入导出,可在任意项目中按需使用。
主题色(Design Tokens)
组件库的所有颜色都通过 CSS 变量 控制,你可以在浏览器开发者工具中实时查看和调试:
| 变量名 | 说明 |
|---|---|
--xp-color-primary |
主色(品牌色) |
--xp-color-success |
成功色 |
--xp-color-warning |
警告色 |
--xp-color-danger |
危险色 |
--xp-color-info |
信息色 |
--xp-text-color-primary |
主要文字色 |
--xp-text-color-regular |
常规文字色 |
--xp-border-color |
边框色 |
--xp-bg-color |
背景色 |
--xp-fill-color |
填充色 |
--xp-shadow-sm / --xp-shadow-md / --xp-shadow-lg |
阴影 |
每个主题色还自动派生出
-light(30% 浅色)、-dark(20% 深色)、-bg(背景色)、-border(边框色),无需手动配置。
浅色 / 暗黑模式切换
在入口文件中调用 initTheme() 即可自动初始化主题(读取 localStorage 或跟随系统偏好):
import { useTheme } from '@cghbcf/xp-ui'
const { initTheme, toggleTheme, setTheme, theme } = useTheme()
// 1. 页面加载时初始化主题(读取 localStorage)
initTheme()
// 2. 切换主题按钮
const handleToggle = () => toggleTheme()
// 3. 强制切换到指定主题
setTheme('dark') // 或 'light'
// 4. 响应式读取当前主题
console.log(theme.value) // 'light' | 'dark'
Vue 组件中的示例用法:
<template>
<button @click="toggleTheme">
当前主题:{{ theme === 'dark' ? '🌙 暗黑' : '☀️ 浅色' }}
</button>
</template>
<script setup>
import { useTheme } from '@cghbcf/xp-ui'
const { theme, toggleTheme, initTheme } = useTheme()
initTheme() // 在页面挂载时调用
</script>
自定义主题色(applyThemeConfig)
通过 applyThemeConfig 可以一次性替换所有颜色变量,支持:
- 修改主色系(primary / success / warning / danger / info)
- 分别定制浅色和暗黑两套文本、边框、背景、填充、阴影
import { applyThemeConfig } from '@cghbcf/xp-ui'
// 修改主色为绿色 —— 只需一个颜色值,其他衍生色自动生成
applyThemeConfig({
primary: {
primary: '#22c55e', // 绿色主色
success: '#67c23a',
warning: '#e6a23c',
danger: '#f56c6c',
info: '#909399'
}
})
// 完整配置:浅色 + 暗黑各一套
applyThemeConfig({
primary: {
primary: '#22c55e',
success: '#67c23a',
warning: '#e6a23c',
danger: '#f56c6c',
info: '#909399'
},
light: {
text: { primary: '#303133', regular: '#606266', secondary: '#909399', placeholder: '#c0c4cc' },
border: { base: '#dcdfe6', light: '#ebeef5', lighter: '#f2f6fc' },
bg: { base: '#ffffff', page: '#f2f3f5', soft: '#f5f7fa', overlay: 'rgba(0, 0, 0, 0.5)' },
fill: { base: '#f0f2f5', light: '#f5f7fa', lighter: '#fafafa' },
shadow: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', md: '0 2px 12px 0 rgba(0, 0, 0, 0.1)', lg: '0 4px 20px 0 rgba(0, 0, 0, 0.15)', overlay: '0 8px 24px 0 rgba(0, 0, 0, 0.2)' }
},
dark: {
text: { primary: '#e5eaf3', regular: '#cfd3dc', secondary: '#a3a6ad', placeholder: '#8d9095' },
border: { base: '#4c4d4f', light: '#414243', lighter: '#363637' },
bg: { base: '#1d1e1f', page: '#141414', soft: '#2a2b2c', overlay: 'rgba(0, 0, 0, 0.7)' },
fill: { base: '#303030', light: '#262727', lighter: '#1d1d1d' },
shadow: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.4)', md: '0 2px 12px 0 rgba(0, 0, 0, 0.45)', lg: '0 4px 20px 0 rgba(0, 0, 0, 0.5)', overlay: '0 8px 24px 0 rgba(0, 0, 0, 0.6)' }
}
})
注意:
applyThemeConfig会自动做深合并——你只需传入要修改的部分,未传的部分保持默认。
导出主题配置(buildCssString)
可以将当前的主题配置导出为完整的 CSS 文件,放到其他项目(非 Vue 项目也可使用):
import { buildCssString, applyThemeConfig, getDefaultThemeConfig } from '@cghbcf/xp-ui'
// 1. 先应用你的自定义主题
applyThemeConfig({ primary: { primary: '#22c55e' } })
// 2. 生成 CSS(包含 :root 和 [data-theme="dark"] 两套规则)
const css = buildCssString({
primary: { primary: '#22c55e' },
light: { text: { primary: '#303133' } },
dark: { text: { primary: '#e5eaf3' } }
})
// 3. 写入文件(Node / Vite 构建时使用)
// fs.writeFileSync('dist/xp-ui-theme.css', css)
console.log(css)
也可以直接读取 themeConfig 响应式对象:
const { themeConfig } = useTheme()
console.log(JSON.stringify(themeConfig.value, null, 2))
// 输出完整的主题配置,供导入/导出
导出 JSON 配置文件
导出 JSON 后可以保存到项目配置文件中,下次启动时重新导入:
import { useTheme } from '@cghbcf/xp-ui'
const { themeConfig, applyThemeConfig, resetThemeConfig } = useTheme()
// 导出
const json = JSON.stringify(themeConfig.value, null, 2)
// 可以保存到 localStorage 或上传到服务器
// 导入(从文件 / localStorage / API 读取)
applyThemeConfig(JSON.parse(json))
// 恢复默认
resetThemeConfig()
主题 API 汇总
import { useTheme } from '@cghbcf/xp-ui'
const {
theme, // 响应式 ref: 当前主题 ('light' | 'dark')
themeConfig, // 响应式 ref: 完整主题配置对象
setTheme, // 设置主题: setTheme('dark' | 'light')
toggleTheme, // 在 light/dark 之间切换
initTheme, // 初始化:读取 localStorage + 跟随系统偏好
applyThemeConfig, // 应用自定义主题配置(CSS 变量会立即更新)
resetThemeConfig, // 恢复默认配置
getDefaultThemeConfig, // 获取默认配置对象
buildCssString, // 根据配置生成 CSS 字符串
buildCssVars // 根据配置生成 { '--xp-color-xxx': value } 映射
} = useTheme()
主题持久化
initTheme() 和 applyThemeConfig() 都会自动将设置写入 localStorage(键名 xp-ui-theme 和 xp-ui-theme-config),下次访问时自动恢复,无需额外处理。
浏览器支持
支持所有现代浏览器:
- Chrome(推荐)
- Firefox
- Safari
- Edge
License
MIT
Related Packages
Vue component library focused on usability, accessibility and developer experien...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.3.0 | ^3.4.0 | MIT | |
| 1.2.0 | ^3.4.0 | MIT | |
| 1.1.0 | ^3.4.0 | MIT | |
| 1.0.1 | ^3.4.0 | MIT | |
| 1.0.0 | ^3.4.0 | MIT |