PrimeVue 完全指南
PrimeVue 是 PrimeTek 出品的 Vue 3 UI 组件库,提供 90+ 开箱即用的组件。v4 在 v3 基础上进行了重大架构调整:以 CSS 变量和设计令牌体系替代了 SASS 主题,并引入了 PassThrough(PT)API 实现完全无样式模式。 PrimeVue v4 提供三种开箱即用的主题预设: CSS 变量命名规则:--p-{组件}-{属性},如 --p-button-background。 在 main.ts 中引用: PassThrough API 允许将自定义属性、类名、样式注入到组件内部任意 DOM 节点,无需覆盖 C
官方文档:https://primevue.org/
适用版本:PrimeVue 4.x(2026-05-07 核实)
PrimeVue 是 PrimeTek 出品的 Vue 3 UI 组件库,提供 90+ 开箱即用的组件。v4 在 v3 基础上进行了重大架构调整:以 CSS 变量和设计令牌体系替代了 SASS 主题,并引入了 PassThrough(PT)API 实现完全无样式模式。
安装与配置
Vite 项目安装
npm install primevue @primevue/themes
# 图标库(可选,但常用)
npm install primeicons
main.ts 配置
import { createApp } from 'vue'
import PrimeVue from 'primevue/config'
import Aura from '@primevue/themes/aura'
import ToastService from 'primevue/toastservice'
import ConfirmationService from 'primevue/confirmationservice'
import App from './App.vue'
const app = createApp(App)
app.use(PrimeVue, {
theme: {
preset: Aura,
options: {
prefix: 'p', // CSS 变量前缀,默认 'p'
darkModeSelector: 'system', // 'system' | '.dark' | 'media' | false
cssLayer: false // 是否注入 @layer 以降低样式优先级(与 Tailwind 配合时设 true)
}
},
ripple: true, // 全局启用水波纹效果
inputVariant: 'outlined', // 'outlined' | 'filled',全局输入框变体
locale: { // 可选:本地化配置
accept: '确认',
reject: '取消',
// ... 其余语言包字段
}
})
app.use(ToastService)
app.use(ConfirmationService)
app.mount('#app')
配置参数总表
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
theme.preset |
Object | — | 主题预设对象(Aura/Lara/Nora 或自定义) |
theme.options.prefix |
string | 'p' |
CSS 变量前缀,改为其他值可避免命名冲突 |
theme.options.darkModeSelector |
string | false | 'system' |
暗色模式触发方式:'system'(媒体查询)、自定义 CSS 类(.dark)、false(禁用暗色) |
theme.options.cssLayer |
boolean | false |
将 PrimeVue 样式包裹在 CSS @layer 中,便于 Tailwind 覆盖 |
ripple |
boolean | false |
全局启用点击水波纹效果 |
inputVariant |
string | 'outlined' |
所有输入组件的默认变体 |
unstyled |
boolean | false |
完全去除内置样式,配合 PassThrough API 使用 |
pt |
Object | — | 全局 PassThrough 配置,对所有组件生效 |
ptOptions |
Object | — | PT 合并策略选项 |
locale |
Object | — | 国际化文本配置 |
nonce |
string | — | Content Security Policy nonce 值 |
主题系统
内置预设
PrimeVue v4 提供三种开箱即用的主题预设:
| 预设 | 安装路径 | 风格 |
|---|---|---|
| Aura | @primevue/themes/aura |
PrimeTek 自研现代风格,色调中性 |
| Lara | @primevue/themes/lara |
Bootstrap 风格衍生,亲切感强 |
| Nora | @primevue/themes/nora |
企业级扁平风格,结构感强 |
设计令牌三层架构
Primitive 令牌(基础色板)
└── 如 blue-500、surface-200
Semantic 令牌(语义映射)
└── 如 primary.color → {blue.500}
Component 令牌(组件专属)
└── 如 button.background → {primary.color}
CSS 变量命名规则:--p-{组件}-{属性},如 --p-button-background。
definePreset — 扩展自定义主题
import { definePreset } from '@primevue/themes'
import Aura from '@primevue/themes/aura'
const MyPreset = definePreset(Aura, {
// 覆盖 Semantic 令牌:将主色改为靛蓝色系
semantic: {
primary: {
50: '{indigo.50}',
100: '{indigo.100}',
200: '{indigo.200}',
300: '{indigo.300}',
400: '{indigo.400}',
500: '{indigo.500}',
600: '{indigo.600}',
700: '{indigo.700}',
800: '{indigo.800}',
900: '{indigo.900}',
950: '{indigo.950}'
},
colorScheme: {
light: {
primary: {
color: '{primary.500}',
contrastColor: '#ffffff',
hoverColor: '{primary.600}',
activeColor: '{primary.700}'
},
surface: {
0: '#ffffff',
50: '{zinc.50}',
100: '{zinc.100}',
200: '{zinc.200}',
300: '{zinc.300}',
400: '{zinc.400}',
500: '{zinc.500}',
600: '{zinc.600}',
700: '{zinc.700}',
800: '{zinc.800}',
900: '{zinc.900}',
950: '{zinc.950}'
}
},
dark: {
primary: {
color: '{primary.400}',
contrastColor: '{surface.900}',
hoverColor: '{primary.300}',
activeColor: '{primary.200}'
}
}
}
},
// 覆盖 Component 令牌:自定义按钮样式
components: {
button: {
colorScheme: {
light: {
root: {
background: '{primary.500}',
hoverBackground: '{primary.600}',
color: '#ffffff'
}
}
}
}
}
})
export default MyPreset
在 main.ts 中引用:
app.use(PrimeVue, {
theme: { preset: MyPreset }
})
运行时切换主题
import { usePreset, updatePreset } from '@primevue/themes'
import Lara from '@primevue/themes/lara'
// 切换到 Lara 预设
usePreset(Lara)
// 动态更新部分令牌(不切换预设)
updatePreset({
semantic: {
primary: {
500: '#22c55e'
}
}
})
暗色模式
// 方式一:跟随系统
app.use(PrimeVue, {
theme: { preset: Aura, options: { darkModeSelector: 'system' } }
})
// 方式二:手动切换 CSS 类
app.use(PrimeVue, {
theme: { preset: Aura, options: { darkModeSelector: '.dark' } }
})
// 在组件中切换
document.documentElement.classList.toggle('dark')
PassThrough (PT) API
PassThrough API 允许将自定义属性、类名、样式注入到组件内部任意 DOM 节点,无需覆盖 CSS 变量。
全局 PT 配置
app.use(PrimeVue, {
theme: { preset: Aura },
pt: {
button: {
root: { class: 'font-bold' },
label: { class: 'tracking-wide' }
},
datatable: {
root: { class: 'border rounded-lg' },
thead: { class: 'bg-gray-50' }
}
}
})
组件级 PT
<Button
label="提交"
:pt="{
root: { class: 'my-custom-btn', 'data-testid': 'submit-btn' },
label: { style: 'letter-spacing: 0.1em' }
}"
/>
PT 合并策略
当同时存在全局 PT 和组件级 PT 时,默认后者覆盖前者。可通过 ptOptions 配置合并策略:
app.use(PrimeVue, {
ptOptions: {
mergeSections: true, // 合并各 section 的类名(而非覆盖)
mergeProps: true // 合并属性(而非覆盖)
}
})
无样式(Unstyled)模式
完全移除 PrimeVue 内置样式,结合 Tailwind CSS 使用:
app.use(PrimeVue, {
unstyled: true,
pt: {
button: {
root: { class: 'px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors' }
}
}
})
Nuxt 3 集成
npm install primevue @primevue/themes
npm install -D @primevue/nuxt-module
// nuxt.config.ts
import Aura from '@primevue/themes/aura'
export default defineNuxtConfig({
modules: ['@primevue/nuxt-module'],
primevue: {
autoImport: true, // 自动注册所有组件(默认开启,支持 Tree-shaking)
options: {
theme: {
preset: Aura,
options: {
darkModeSelector: '.dark'
}
},
ripple: true
},
// 仅引入指定组件(autoImport: false 时生效)
components: {
include: ['Button', 'DataTable', 'Dialog', 'InputText', 'Select']
}
}
})
常用组件 API
Button
<Button
label="提交"
icon="pi pi-check"
icon-pos="right"
severity="primary"
:loading="isLoading"
@click="handleClick"
/>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label |
string | — | 按钮文字 |
icon |
string | — | PrimeIcons 图标类名,如 pi pi-check |
iconPos |
string | 'left' |
图标位置:'left' | 'right' | 'top' | 'bottom' |
severity |
string | 'primary' |
风格:'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger' | 'contrast' |
raised |
boolean | false |
添加阴影 |
rounded |
boolean | false |
圆角全圆 |
text |
boolean | false |
文字按钮,无背景 |
outlined |
boolean | false |
描边按钮 |
link |
boolean | false |
链接样式 |
size |
string | — | 'small' | 'large' |
loading |
boolean | false |
显示加载状态 |
loadingIcon |
string | — | 加载状态图标类名 |
disabled |
boolean | false |
禁用 |
fluid |
boolean | false |
撑满父容器宽度 |
badge |
string | — | 徽标内容 |
badgeSeverity |
string | — | 徽标风格 |
as |
string/Component | 'button' |
渲染元素类型,可传 'a' 或路由组件 |
asChild |
boolean | false |
将样式传给子元素(headless) |
| 插槽 | 说明 |
|---|---|
default |
自定义内容,替换 label |
icon |
自定义图标 |
loadingicon |
自定义加载图标 |
InputText
<InputText v-model="value" placeholder="请输入..." size="large" fluid />
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue |
string | — | v-model 绑定值 |
size |
string | — | 'small' | 'large' |
variant |
string | — | 'outlined' | 'filled',覆盖全局 inputVariant |
fluid |
boolean | false |
宽度 100% |
invalid |
boolean | false |
错误状态样式 |
disabled |
boolean | false |
禁用 |
| 事件 | 说明 |
|---|---|
update:modelValue |
值变化 |
Select(v3 的 Dropdown)
<Select
v-model="selected"
:options="cities"
option-label="name"
option-value="code"
placeholder="请选择城市"
filter
fluid
/>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue |
any | — | 选中值 |
options |
array | — | 选项数组 |
optionLabel |
string/Function | — | 选项显示字段名或取值函数 |
optionValue |
string/Function | — | 选项实际值字段名 |
optionDisabled |
string/Function | — | 禁用项字段名 |
optionGroupLabel |
string | — | 分组标签字段名 |
optionGroupChildren |
string | — | 分组子项字段名 |
placeholder |
string | — | 占位文字 |
filter |
boolean | false |
启用搜索过滤 |
filterPlaceholder |
string | — | 搜索框占位文字 |
loading |
boolean | false |
加载状态 |
disabled |
boolean | false |
禁用 |
editable |
boolean | false |
允许手动输入 |
showClear |
boolean | false |
显示清除按钮 |
fluid |
boolean | false |
宽度 100% |
invalid |
boolean | false |
错误状态样式 |
checkmark |
boolean | false |
选中项显示勾选标记 |
highlightOnSelect |
boolean | true |
选中项高亮 |
maxSelectedLabels |
number | 3 |
最多显示几个标签(MultiSelect) |
panelClass |
string | — | 下拉面板额外 CSS 类 |
appendTo |
string/Element | 'body' |
下拉挂载点 |
virtualScrollerOptions |
object | — | 虚拟滚动配置(大数据量) |
| 插槽 | 说明 |
|---|---|
value |
自定义选中值显示区域 |
option |
自定义选项渲染 |
optiongroup |
自定义分组标题 |
header |
下拉头部 |
footer |
下拉底部 |
empty |
无选项时的提示内容 |
| 事件 | 说明 |
|---|---|
change |
选项改变,payload: { value } |
before-show |
面板显示前 |
show |
面板显示后 |
before-hide |
面板隐藏前 |
hide |
面板隐藏后 |
filter |
搜索过滤时,payload: { value } |
DatePicker(v3 的 Calendar)
<DatePicker
v-model="date"
date-format="yy-mm-dd"
:min-date="minDate"
:max-date="maxDate"
show-icon
show-button-bar
/>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue |
Date/Date[]/object | — | 绑定值 |
selectionMode |
string | 'single' |
'single' | 'multiple' | 'range' |
dateFormat |
string | 'mm/dd/yy' |
日期格式字符串 |
showTime |
boolean | false |
显示时间选择 |
timeOnly |
boolean | false |
仅时间模式 |
showIcon |
boolean | false |
显示日历图标 |
showButtonBar |
boolean | false |
显示今天/清除按钮 |
minDate |
Date | — | 最小可选日期 |
maxDate |
Date | — | 最大可选日期 |
disabledDates |
Date[] | — | 禁用日期列表 |
disabledDays |
number[] | — | 禁用星期几(0=周日) |
numberOfMonths |
number | 1 |
同时显示月份数 |
inline |
boolean | false |
内联展示,不作为弹层 |
fluid |
boolean | false |
宽度 100% |
disabled |
boolean | false |
禁用 |
DataTable
DataTable 是功能最丰富的组件,支持排序、筛选、分页、行选择、行展开、懒加载、虚拟滚动等。
<DataTable
:value="products"
v-model:selection="selectedProducts"
v-model:filters="filters"
selection-mode="multiple"
data-key="id"
:paginator="true"
:rows="10"
:rows-per-page-options="[5, 10, 25, 50]"
sort-mode="multiple"
filter-display="row"
:loading="loading"
:lazy="true"
:total-records="totalRecords"
@page="onPage"
@sort="onSort"
@filter="onFilter"
>
<template #header>
<div class="flex justify-between">
<span>产品列表</span>
<InputText v-model="filters['global'].value" placeholder="全局搜索" />
</div>
</template>
<Column selection-mode="multiple" style="width: 3rem" />
<Column field="name" header="名称" sortable />
<Column field="price" header="价格" sortable>
<template #body="{ data }">
¥{{ data.price.toFixed(2) }}
</template>
</Column>
<Column field="status" header="状态" filter-field="status" :show-filter-menu="false">
<template #filter="{ filterModel, filterCallback }">
<Select
v-model="filterModel.value"
:options="statuses"
placeholder="筛选状态"
@change="filterCallback()"
/>
</template>
</Column>
</DataTable>
DataTable 属性(常用)
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value |
array | — | 数据源 |
dataKey |
string | — | 行唯一标识字段(选择/展开等功能必填) |
selection |
any | — | 选中行,配合 v-model:selection |
selectionMode |
string | — | 'single' | 'multiple' |
metaKeySelection |
boolean | false |
是否需要按 Meta 键多选 |
paginator |
boolean | false |
启用分页 |
rows |
number | — | 每页行数 |
rowsPerPageOptions |
number[] | — | 每页行数选项 |
paginatorPosition |
string | 'bottom' |
'top' | 'bottom' | 'both' |
currentPageReportTemplate |
string | — | 分页报告模板,如 '第 {currentPage} 页,共 {totalPages} 页' |
totalRecords |
number | — | 总记录数(懒加载必填) |
lazy |
boolean | false |
懒加载模式(分页/排序/筛选由外部处理) |
loading |
boolean | false |
加载状态 |
sortField |
string | — | 初始排序字段 |
sortOrder |
number | — | 初始排序方向:1 升序,-1 降序 |
sortMode |
string | 'single' |
'single' | 'multiple' |
removableSort |
boolean | false |
允许取消排序 |
multiSortMeta |
array | — | 多列排序元数据 |
filters |
object | — | 筛选对象,配合 v-model:filters |
filterDisplay |
string | — | 'menu' | 'row' |
globalFilterFields |
string[] | — | 全局搜索覆盖字段 |
expandedRows |
array | — | 展开的行,配合 v-model:expandedRows |
rowExpansionTemplate |
— | — | 行展开内容(使用插槽) |
frozenColumns |
number | — | 冻结左侧列数 |
scrollable |
boolean | false |
启用滚动 |
scrollHeight |
string | — | 内容区滚动高度,如 '400px' | 'flex' |
virtualScrollerOptions |
object | — | 虚拟滚动配置 |
editMode |
string | — | 'cell' | 'row' 行内编辑 |
size |
string | — | 'small' | 'large' |
showGridlines |
boolean | false |
显示表格线 |
stripedRows |
boolean | false |
隔行变色 |
tableStyle |
string | — | table 元素 style |
tableClass |
string | — | table 元素额外 CSS 类 |
DataTable 事件
| 事件 | 说明 |
|---|---|
page |
翻页,payload: PageState |
sort |
排序,payload: SortEvent |
filter |
筛选,payload: FilterEvent |
row-click |
点击行,payload: { originalEvent, data, index } |
row-dblclick |
双击行 |
row-select |
选中行 |
row-unselect |
取消选中行 |
row-expand |
展开行 |
row-collapse |
折叠行 |
cell-edit-complete |
单元格编辑完成 |
row-edit-save |
行编辑保存 |
row-edit-cancel |
行编辑取消 |
DataTable 插槽
| 插槽 | 说明 |
|---|---|
header |
表格头部 |
footer |
表格底部 |
loading |
加载状态内容 |
empty |
无数据时内容 |
paginatorstart |
分页器左侧内容 |
paginatorend |
分页器右侧内容 |
expansion |
行展开内容,接收 { data, index } |
groupheader |
行分组头部 |
groupfooter |
行分组尾部 |
Column 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
field |
string | — | 对应数据字段 |
header |
string | — | 列标题 |
sortable |
boolean | false |
允许排序 |
sortField |
string | — | 排序使用的字段(与 field 不同时) |
filter |
boolean | false |
启用该列过滤 |
filterField |
string | — | 过滤使用的字段 |
showFilterMenu |
boolean | true |
显示过滤菜单图标 |
filterMatchMode |
string | — | 默认匹配模式 |
selectionMode |
string | — | 'single' | 'multiple' 选择列 |
frozen |
boolean | false |
冻结该列 |
alignFrozen |
string | 'left' |
冻结位置:'left' | 'right' |
expander |
boolean | false |
行展开按钮列 |
rowspan |
number | — | 行合并 |
colspan |
number | — | 列合并 |
style |
string | — | 列样式 |
class |
string | — | 列 CSS 类 |
bodyStyle |
string | — | body 单元格样式 |
bodyClass |
string | — | body 单元格 CSS 类 |
headerStyle |
string | — | 表头单元格样式 |
headerClass |
string | — | 表头单元格 CSS 类 |
pt |
object | — | PassThrough 配置 |
Column 插槽
| 插槽 | 说明 |
|---|---|
body |
自定义单元格内容,接收 { data, field, index } |
header |
自定义列标题 |
footer |
自定义列底部 |
filter |
自定义过滤元素 |
filterclear |
清除过滤按钮内容 |
filterapply |
应用过滤按钮内容 |
editor |
行内编辑元素 |
筛选初始化示例
import { FilterMatchMode } from '@primevue/core/api'
const filters = ref({
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
status: { value: null, matchMode: FilterMatchMode.EQUALS },
price: { value: null, matchMode: FilterMatchMode.GREATER_THAN }
})
可用 FilterMatchMode 值:STARTS_WITH、CONTAINS、NOT_CONTAINS、ENDS_WITH、EQUALS、NOT_EQUALS、IN、LESS_THAN、LESS_THAN_OR_EQUAL_TO、GREATER_THAN、GREATER_THAN_OR_EQUAL_TO、BETWEEN、DATE_IS、DATE_IS_NOT、DATE_BEFORE、DATE_AFTER。
Dialog
<Button label="打开" @click="visible = true" />
<Dialog
v-model:visible="visible"
header="确认操作"
:modal="true"
:draggable="true"
:closable="true"
:style="{ width: '30rem' }"
:breakpoints="{ '960px': '75vw', '641px': '100vw' }"
>
<p>确认要执行此操作吗?</p>
<template #footer>
<Button label="取消" text @click="visible = false" />
<Button label="确认" @click="onConfirm" />
</template>
</Dialog>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
visible |
boolean | — | 显示状态,配合 v-model:visible |
header |
string | — | 标题文字 |
modal |
boolean | true |
是否显示遮罩 |
closable |
boolean | true |
显示关闭按钮 |
closeOnEscape |
boolean | true |
按 Esc 关闭 |
dismissableMask |
boolean | false |
点击遮罩关闭 |
draggable |
boolean | true |
允许拖拽 |
resizable |
boolean | true |
允许调整大小 |
maximizable |
boolean | false |
显示最大化按钮 |
position |
string | 'center' |
位置:'center' | 'top' | 'bottom' | 'left' | 'right' | 四角 |
style |
object | — | 对话框 style |
class |
string | — | 对话框额外 CSS 类 |
contentStyle |
object | — | 内容区 style |
contentClass |
string | — | 内容区 CSS 类 |
breakpoints |
object | — | 响应式宽度断点,如 { '960px': '75vw' } |
appendTo |
string/Element | 'body' |
挂载目标 |
blockScroll |
boolean | false |
打开时锁定 body 滚动 |
keepInViewport |
boolean | true |
拖拽时保持在视口内 |
| 插槽 | 说明 |
|---|---|
default |
对话框内容 |
header |
自定义标题区域 |
footer |
底部按钮区域 |
closeicon |
自定义关闭图标 |
maximizeicon |
自定义最大化图标 |
container |
完全接管对话框外层容器 |
| 事件 | 说明 |
|---|---|
show |
对话框显示后 |
hide |
对话框关闭后 |
after-hide |
关闭动画完成后 |
maximize |
最大化切换 |
dragend |
拖拽结束 |
Drawer(v3 的 Sidebar)
<Drawer v-model:visible="drawerVisible" header="菜单" position="left">
<nav>
<ul>
<li>首页</li>
<li>关于</li>
</ul>
</nav>
</Drawer>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
visible |
boolean | — | 显示状态 |
header |
string | — | 标题 |
position |
string | 'left' |
'left' | 'right' | 'top' | 'bottom' |
modal |
boolean | true |
显示遮罩 |
dismissable |
boolean | true |
点击遮罩关闭 |
showCloseIcon |
boolean | true |
显示关闭按钮 |
closeOnEscape |
boolean | true |
Esc 关闭 |
style |
object | — | 面板 style |
blockScroll |
boolean | false |
打开时锁定滚动 |
full-screen |
boolean | false |
全屏模式 |
| 插槽 | 说明 |
|---|---|
default |
内容 |
header |
自定义标题 |
container |
接管整个容器 |
Toast 服务
注册服务
// main.ts
import ToastService from 'primevue/toastservice'
app.use(ToastService)
放置 Toast 容器(通常在 App.vue)
<template>
<Toast />
<RouterView />
</template>
在组件中使用
import { useToast } from 'primevue/usetoast'
const toast = useToast()
// 各种类型
toast.add({ severity: 'success', summary: '成功', detail: '操作已完成', life: 3000 })
toast.add({ severity: 'info', summary: '提示', detail: '这是一条信息' })
toast.add({ severity: 'warn', summary: '警告', detail: '请注意此操作' })
toast.add({ severity: 'error', summary: '错误', detail: '操作失败,请重试', sticky: true })
toast.add({ severity: 'secondary', summary: '次要', detail: '次要消息' })
toast.add({ severity: 'contrast', summary: '对比', detail: '对比色消息' })
// 清除
toast.remove(toastInstance) // 移除指定
toast.removeGroup('myGroup') // 移除指定分组
toast.removeAllGroups() // 移除所有
Toast 消息选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
severity |
string | 'info' |
'success' | 'info' | 'warn' | 'error' | 'secondary' | 'contrast' |
summary |
string | — | 标题 |
detail |
string | — | 详细内容 |
life |
number | 3000 |
自动关闭时间(毫秒),0 或不设表示不自动关闭 |
sticky |
boolean | false |
持久显示,不自动关闭 |
closable |
boolean | true |
显示关闭按钮 |
group |
string | — | 分组名,配合 Toast 组件的 group 属性 |
icon |
string | — | 自定义图标类名 |
Toast 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
position |
string | 'top-right' |
'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'center' |
group |
string | — | 对应特定分组消息 |
baseZIndex |
number | 0 |
基础层叠 z-index |
breakpoints |
object | — | 响应式宽度断点 |
pt |
object | — | PassThrough 配置 |
ConfirmDialog / ConfirmPopup 服务
注册服务
// main.ts
import ConfirmationService from 'primevue/confirmationservice'
app.use(ConfirmationService)
放置容器
<ConfirmDialog />
<!-- 或气泡确认 -->
<ConfirmPopup />
在组件中使用
import { useConfirm } from 'primevue/useconfirm'
const confirm = useConfirm()
// 对话框确认
confirm.require({
message: '确定要删除吗?此操作不可逆。',
header: '删除确认',
icon: 'pi pi-exclamation-triangle',
rejectLabel: '取消',
acceptLabel: '确认删除',
rejectProps: { label: '取消', severity: 'secondary', outlined: true },
acceptProps: { label: '确认删除', severity: 'danger' },
accept: () => {
toast.add({ severity: 'info', summary: '已删除', life: 3000 })
},
reject: () => {
toast.add({ severity: 'warn', summary: '已取消', life: 3000 })
}
})
// 气泡确认(需要目标元素)
confirm.require({
target: event.currentTarget,
message: '确认删除?',
icon: 'pi pi-info-circle',
accept: () => doDelete()
})
Popover(v3 的 OverlayPanel)
<Button label="更多信息" @click="pop.toggle($event)" />
<Popover ref="pop">
<div class="p-4">
<p>这是弹出内容</p>
</div>
</Popover>
const pop = ref()
// 方法
pop.value.show(event) // 显示
pop.value.hide() // 隐藏
pop.value.toggle(event) // 切换
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dismissable |
boolean | true |
点击外部关闭 |
closeOnEscape |
boolean | true |
Esc 关闭 |
appendTo |
string/Element | 'body' |
挂载目标 |
style |
object | — | 面板 style |
class |
string | — | 面板额外 CSS 类 |
pt |
object | — | PassThrough 配置 |
Tabs(v3 的 TabView)
<Tabs value="0">
<TabList>
<Tab value="0">首页</Tab>
<Tab value="1">关于</Tab>
<Tab value="2">联系</Tab>
</TabList>
<TabPanels>
<TabPanel value="0">
<p>首页内容</p>
</TabPanel>
<TabPanel value="1">
<p>关于内容</p>
</TabPanel>
</TabPanels>
</Tabs>
| 属性(Tabs) | 类型 | 默认值 | 说明 |
|---|---|---|---|
value |
string/number | — | 当前激活 Tab 的值,配合 v-model:value |
lazy |
boolean | false |
懒渲染非激活 Tab 内容 |
scrollable |
boolean | false |
Tab 列表可横向滚动 |
selectOnFocus |
boolean | false |
键盘聚焦时即激活 |
Accordion
<Accordion value="0">
<AccordionPanel value="0">
<AccordionHeader>标题一</AccordionHeader>
<AccordionContent>
<p>内容一</p>
</AccordionContent>
</AccordionPanel>
<AccordionPanel value="1">
<AccordionHeader>标题二</AccordionHeader>
<AccordionContent>
<p>内容二</p>
</AccordionContent>
</AccordionPanel>
</Accordion>
| 属性(Accordion) | 类型 | 默认值 | 说明 |
|---|---|---|---|
value |
string/string[] | — | 展开项,multiple 为 true 时可传数组 |
multiple |
boolean | false |
允许同时展开多项 |
lazy |
boolean | false |
懒渲染折叠内容 |
collapseIcon |
string | — | 折叠状态图标 |
expandIcon |
string | — | 展开状态图标 |
InputNumber
<InputNumber
v-model="price"
prefix="¥"
:min="0"
:max="9999"
:min-fraction-digits="2"
:max-fraction-digits="2"
:use-grouping="true"
locale="zh-CN"
mode="currency"
currency="CNY"
fluid
/>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue |
number | — | v-model 绑定值 |
mode |
string | 'decimal' |
'decimal' | 'currency' |
locale |
string | — | 使用的区域设置 |
currency |
string | — | 货币代码,如 'CNY'(mode=currency 时) |
prefix |
string | — | 前缀文本 |
suffix |
string | — | 后缀文本 |
min |
number | — | 最小值 |
max |
number | — | 最大值 |
step |
number | 1 |
步进值 |
showButtons |
boolean | false |
显示加减按钮 |
buttonLayout |
string | 'stacked' |
'stacked' | 'horizontal' | 'vertical' |
useGrouping |
boolean | true |
使用千位分隔符 |
minFractionDigits |
number | — | 最少小数位数 |
maxFractionDigits |
number | — | 最多小数位数 |
disabled |
boolean | false |
禁用 |
readonly |
boolean | false |
只读 |
invalid |
boolean | false |
错误状态 |
fluid |
boolean | false |
宽度 100% |
AutoComplete
<AutoComplete
v-model="value"
:suggestions="items"
@complete="search"
option-label="name"
force-selection
:multiple="false"
fluid
>
<template #option="{ option }">
<div class="flex items-center gap-2">
<img :src="option.avatar" class="w-6 h-6 rounded-full" />
<span>{{ option.name }}</span>
</div>
</template>
</AutoComplete>
const search = (event: { query: string }) => {
items.value = allItems.filter(item =>
item.name.toLowerCase().includes(event.query.toLowerCase())
)
}
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue |
any | — | v-model 绑定值 |
suggestions |
array | — | 匹配建议列表 |
optionLabel |
string/Function | — | 显示字段 |
optionGroupLabel |
string | — | 分组标签字段 |
optionGroupChildren |
string | — | 分组子项字段 |
multiple |
boolean | false |
多选 tag 模式 |
typeahead |
boolean | true |
自动完成打字;设 false 可做纯 tag 输入 |
forceSelection |
boolean | false |
只允许从建议中选择 |
minLength |
number | 1 |
触发搜索的最少字符数 |
delay |
number | 300 |
输入到触发 complete 事件的延迟(毫秒) |
disabled |
boolean | false |
禁用 |
fluid |
boolean | false |
宽度 100% |
dropdown |
boolean | false |
显示下拉触发按钮 |
dropdownMode |
string | 'blank' |
点击下拉按钮的搜索模式 |
virtualScrollerOptions |
object | — | 大数据量虚拟滚动 |
| 事件 | 说明 |
|---|---|
complete |
输入时触发,接收 { query } |
item-select |
选中建议项,接收 { value } |
item-unselect |
取消选中(多选模式) |
change |
值变化 |
focus / blur |
聚焦/失焦 |
FileUpload
<FileUpload
name="file"
url="/api/upload"
:multiple="true"
accept="image/*"
:max-file-size="1000000"
:auto="false"
@upload="onUpload"
@before-upload="onBeforeUpload"
@error="onError"
/>
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name |
string | — | 表单字段名 |
url |
string | — | 上传接口地址 |
method |
string | 'POST' |
HTTP 方法 |
multiple |
boolean | false |
允许多选 |
accept |
string | — | 允许的文件类型,如 'image/*,.pdf' |
disabled |
boolean | false |
禁用 |
auto |
boolean | false |
选文件后自动上传 |
maxFileSize |
number | — | 最大文件大小(字节) |
fileLimit |
number | — | 最多文件数 |
withCredentials |
boolean | false |
携带凭证(Cookie) |
mode |
string | 'advanced' |
'advanced'(完整 UI)| 'basic'(简单按钮) |
customUpload |
boolean | false |
使用自定义上传逻辑 |
showUploadButton |
boolean | true |
显示上传按钮 |
showCancelButton |
boolean | true |
显示取消按钮 |
previewWidth |
number | 50 |
预览图宽度(px) |
chooseLabel |
string | '选择' |
选择按钮文字 |
uploadLabel |
string | '上传' |
上传按钮文字 |
cancelLabel |
string | '取消' |
取消按钮文字 |
| 事件 | 说明 |
|---|---|
before-upload |
上传前,可修改 XmlHttpRequest |
upload |
上传完成,接收 { files, xhr } |
error |
上传失败 |
clear |
文件列表清空 |
select |
文件选择,接收 { files, originalEvent } |
remove |
移除文件 |
uploader |
自定义上传处理(customUpload: true 时) |
Menu / MenuBar / TieredMenu / ContextMenu
<!-- Menu(弹出式) -->
<Button label="操作" @click="menu.toggle($event)" />
<Menu ref="menu" :model="menuItems" :popup="true" />
<!-- Menubar(顶部导航) -->
<Menubar :model="navItems">
<template #start>
<img src="/logo.svg" class="h-8" />
</template>
<template #end>
<Button label="登录" />
</template>
</Menubar>
<!-- ContextMenu(右键菜单) -->
<ContextMenu ref="cm" :model="contextItems" />
<div @contextmenu="cm.show($event)">右键点击此区域</div>
菜单 model 的数据结构:
const menuItems = ref([
{
label: '文件',
icon: 'pi pi-file',
items: [
{ label: '新建', icon: 'pi pi-plus', command: () => createFile() },
{ label: '打开', icon: 'pi pi-folder-open', command: () => openFile() },
{ separator: true },
{ label: '退出', icon: 'pi pi-sign-out', command: () => exit() }
]
},
{
label: '外部链接',
icon: 'pi pi-external-link',
url: 'https://example.com',
target: '_blank'
},
{
label: '路由',
icon: 'pi pi-home',
route: '/home' // 配合 vue-router 使用
}
])
菜单项字段:
| 字段 | 类型 | 说明 |
|---|---|---|
label |
string | 菜单项文字 |
icon |
string | 图标类名 |
command |
Function | 点击回调 |
url |
string | 外部链接地址 |
target |
string | 链接打开方式 |
route |
string/object | vue-router 路由路径或对象 |
items |
array | 子菜单(TieredMenu/Menubar 支持多层) |
separator |
boolean | 是否为分隔线 |
disabled |
boolean | 禁用该菜单项 |
visible |
boolean | 是否可见 |
badge |
string | 徽标文字 |
badgeSeverity |
string | 徽标风格 |
class |
string | 额外 CSS 类 |
style |
object | 内联样式 |
shortcut |
string | 快捷键提示文字(仅显示,不绑定) |
Stepper(v3 的 Steps)
<Stepper v-model:value="activeStep" linear>
<StepList>
<Step value="1">账户信息</Step>
<Step value="2">个人资料</Step>
<Step value="3">完成</Step>
</StepList>
<StepPanels>
<StepPanel value="1" v-slot="{ activateCallback }">
<div>步骤一内容</div>
<Button label="下一步" @click="activateCallback('2')" />
</StepPanel>
<StepPanel value="2" v-slot="{ activateCallback }">
<div>步骤二内容</div>
<Button label="上一步" @click="activateCallback('1')" />
<Button label="下一步" @click="activateCallback('3')" />
</StepPanel>
<StepPanel value="3">
<div>完成!</div>
</StepPanel>
</StepPanels>
</Stepper>
Chart
基于 Chart.js,需额外安装:
npm install chart.js
<Chart type="bar" :data="chartData" :options="chartOptions" style="height: 400px" />
const chartData = ref({
labels: ['一月', '二月', '三月', '四月'],
datasets: [
{
label: '销售额',
data: [65, 59, 80, 81],
backgroundColor: ['rgba(255, 99, 132, 0.2)', 'rgba(255, 159, 64, 0.2)']
}
]
})
const chartOptions = ref({
responsive: true,
plugins: {
legend: { position: 'top' }
}
})
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
type |
string | — | 图表类型:'bar' | 'line' | 'pie' | 'doughnut' | 'radar' | 'polarArea' | 'bubble' | 'scatter' |
data |
object | — | Chart.js data 对象 |
options |
object | — | Chart.js options 对象 |
plugins |
array | — | Chart.js 插件数组 |
width |
number | 300 |
画布宽度 |
height |
number | 150 |
画布高度 |
表单验证集成
与 VeeValidate + Zod 集成
npm install vee-validate zod @vee-validate/zod
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod'
const schema = toTypedSchema(z.object({
username: z.string().min(3, '用户名至少 3 位').max(20),
email: z.string().email('邮箱格式不正确'),
age: z.number({ required_error: '请填写年龄' }).min(18, '须年满 18 岁')
}))
const { handleSubmit, defineField, errors, isSubmitting } = useForm({
validationSchema: schema
})
const [username, usernameAttrs] = defineField('username')
const [email, emailAttrs] = defineField('email')
const [age, ageAttrs] = defineField('age')
const onSubmit = handleSubmit(async (values) => {
console.log('提交值:', values)
})
</script>
<template>
<form @submit.prevent="onSubmit">
<div class="flex flex-col gap-1 mb-4">
<label>用户名</label>
<InputText v-model="username" v-bind="usernameAttrs" :invalid="!!errors.username" />
<small class="text-red-500">{{ errors.username }}</small>
</div>
<div class="flex flex-col gap-1 mb-4">
<label>邮箱</label>
<InputText v-model="email" v-bind="emailAttrs" :invalid="!!errors.email" />
<small class="text-red-500">{{ errors.email }}</small>
</div>
<div class="flex flex-col gap-1 mb-4">
<label>年龄</label>
<InputNumber v-model="age" v-bind="ageAttrs" :invalid="!!errors.age" fluid />
<small class="text-red-500">{{ errors.age }}</small>
</div>
<Button type="submit" label="提交" :loading="isSubmitting" />
</form>
</template>
与 Tailwind CSS 配合
启用 cssLayer 使 Tailwind 的工具类可以覆盖 PrimeVue 样式:
// main.ts
app.use(PrimeVue, {
theme: {
preset: Aura,
options: {
cssLayer: {
name: 'primevue',
order: 'tailwind-base, primevue, tailwind-utilities'
}
}
}
})
/* tailwind.css */
@layer tailwind-base {
@tailwind base;
}
@layer tailwind-utilities {
@tailwind utilities;
}
组件完整清单
v3 → v4 更名对照
| v3 名称 | v4 名称 | 说明 |
|---|---|---|
Calendar |
DatePicker |
日期/时间选择器 |
Dropdown |
Select |
下拉选择 |
InputSwitch |
ToggleSwitch |
开关 |
OverlayPanel |
Popover |
弹出层 |
Sidebar |
Drawer |
侧边抽屉 |
TabView |
Tabs |
标签页 |
InlineMessage |
Message |
内联消息 |
Steps |
Stepper |
步骤条 |
TabMenu |
Tabs(无 Panel) |
标签导航 |
v4 废弃组件
| 废弃组件 | 替代方案 |
|---|---|
Chips |
AutoComplete(multiple: true,typeahead: false) |
TriStateCheckbox |
Checkbox(indeterminate 属性) |
DataViewLayoutOptions |
SelectButton |
按分类列举全部组件
表单
| 组件 | 说明 |
|---|---|
InputText |
单行文本输入 |
Textarea |
多行文本 |
InputNumber |
数字输入,支持格式化 |
Password |
密码输入,强度指示 |
InputMask |
格式掩码输入 |
InputOtp |
OTP 验证码输入 |
AutoComplete |
自动完成 |
Select |
下拉选择(原 Dropdown) |
MultiSelect |
多选下拉 |
DatePicker |
日期/时间选择(原 Calendar) |
CascadeSelect |
级联下拉 |
Checkbox |
复选框 |
RadioButton |
单选按钮 |
ToggleSwitch |
开关(原 InputSwitch) |
ToggleButton |
切换按钮 |
SelectButton |
多按钮单选/多选 |
Slider |
滑块 |
ColorPicker |
颜色选择器 |
Knob |
旋钮 |
Rating |
评分 |
TreeSelect |
树形选择 |
FileUpload |
文件上传 |
Editor |
富文本编辑器(基于 Quill) |
数据展示
| 组件 | 说明 |
|---|---|
DataTable |
数据表格 |
Column |
表格列(DataTable 子组件) |
ColumnGroup |
列分组 |
TreeTable |
树形表格 |
DataView |
数据视图(列表/网格切换) |
VirtualScroller |
虚拟列表 |
OrderList |
可排序列表 |
PickList |
穿梭框 |
Tree |
树形控件 |
Timeline |
时间线 |
Paginator |
分页器(独立组件) |
Chart |
图表(基于 Chart.js) |
面板
| 组件 | 说明 |
|---|---|
Panel |
可折叠面板容器 |
Fieldset |
字段集分组 |
Card |
卡片 |
Accordion |
手风琴 |
Tabs |
标签页(原 TabView) |
Divider |
分割线 |
Splitter |
可拖拽分割布局 |
ScrollPanel |
自定义滚动条面板 |
DeferredContent |
延迟渲染内容 |
Toolbar |
工具栏 |
覆盖层
| 组件 | 说明 |
|---|---|
Dialog |
对话框 |
Drawer |
侧边抽屉(原 Sidebar) |
Popover |
弹出层(原 OverlayPanel) |
Tooltip |
文字提示(指令) |
ConfirmDialog |
确认对话框 |
ConfirmPopup |
确认气泡 |
DynamicDialog |
动态对话框 |
消息
| 组件 | 说明 |
|---|---|
Toast |
轻提示 |
Message |
内联消息(原 InlineMessage) |
菜单
| 组件 | 说明 |
|---|---|
Menu |
简单菜单 |
Menubar |
顶部菜单栏 |
TieredMenu |
多级菜单 |
ContextMenu |
右键菜单 |
MegaMenu |
超级菜单 |
BreadCrumb |
面包屑 |
PanelMenu |
侧边多级菜单 |
Dock |
macOS 风格 Dock 栏 |
SpeedDial |
快速拨号浮动按钮组 |
SplitButton |
分裂按钮 |
Steps |
步骤导航(建议迁移到 Stepper) |
其他
| 组件/指令 | 说明 |
|---|---|
Avatar |
头像,支持文字/图标/图片 |
AvatarGroup |
头像组 |
Badge |
徽标数字 |
OverlayBadge |
覆盖徽标包装器(原 BadgeDirective) |
Tag |
标签 |
Chip |
芯片 |
ProgressBar |
进度条 |
ProgressSpinner |
加载旋转圈 |
Skeleton |
骨架屏 |
ScrollTop |
返回顶部按钮 |
Image |
图片(支持预览/旋转/缩放) |
Galleria |
图片画廊 |
Carousel |
轮播图 |
BlockUI |
区域遮罩 |
Inplace |
内联编辑 |
MeterGroup |
分段进度条 |
Terminal |
终端模拟 |
v-tooltip |
Tooltip 指令 |
v-badge |
Badge 指令 |
v-ripple |
水波纹指令 |
v-styleclass |
样式类切换指令 |
v-focustrap |
焦点捕获指令 |
v-animateonscroll |
滚动动画指令 |
无障碍(Accessibility)
PrimeVue v4 按照 WAI-ARIA 1.2 规范构建。
- 所有交互组件均内置
aria-*属性 - 支持键盘导航(Tab、方向键、Enter、Space、Esc)
- 提供
ariaLabel、ariaLabelledBy等属性供自定义 - 使用
<PrimeVuePassThrough>或 PT API 可进一步定制 aria 属性
常见踩坑
1. v4 不再引入旧版 CSS
// v3 写法(v4 中删除)
import 'primevue/resources/themes/lara-light-blue/theme.css'
import 'primevue/resources/primevue.min.css'
v4 通过 @primevue/themes 的 JS 对象统一管理主题,无需引入任何 CSS 文件。
2. API 路径变更
// v3
import { FilterMatchMode } from 'primevue/api'
// v4
import { FilterMatchMode } from '@primevue/core/api'
3. p-fluid 类废弃
v3 中包裹 <div class="p-fluid"> 可使内部组件撑满宽度;v4 中改为在组件上使用 fluid prop:
<!-- v3 -->
<div class="p-fluid">
<InputText v-model="val" />
</div>
<!-- v4 -->
<InputText v-model="val" fluid />
4. Dark Mode 与 SSR 水合不匹配
Nuxt 3 中使用 darkModeSelector: '.dark' 并配合 @nuxtjs/color-mode 时,确保服务端和客户端的初始类名一致,否则会出现 Hydration Mismatch。
5. DataTable 懒加载 totalRecords 必须提前设置
lazy: true 时,分页器需要 totalRecords 才能计算页数。初始加载时应在获取第一页数据的同时设置 totalRecords。
6. Tooltip 指令注册
使用 v-tooltip 前需注册:
import Tooltip from 'primevue/tooltip'
app.directive('tooltip', Tooltip)
// 或在 Nuxt 中通过 directives 配置自动注册
最佳实践
选用 Unstyled 模式 + Tailwind,而非 Styled 模式:PrimeVue v4 的 Unstyled 模式(unstyled: true)允许完全自定义样式,配合 tailwindcss-primeui 插件可以用 Tailwind 类名控制组件外观,避免主题覆盖战。Styled 模式适合快速原型,Unstyled 模式适合需要严格设计系统的项目。
使用 PassThrough API 为单个组件实例添加 class,而非全局覆盖 CSS:通过 :pt prop 可以精确控制组件内部 DOM 节点的类名,比写全局 CSS 选择器更可预期且不影响其他组件实例。
<DataTable :pt="{ root: { class: 'border rounded-lg' }, header: { class: 'bg-gray-50' } }">
DataTable 分页用受控模式(:rows + @page),而非非受控:非受控模式下,分页状态由组件内部管理,无法与 URL、路由或外部状态同步。受控模式将 first、rows 状态提升到外部,便于持久化和服务端分页。
表单组件与 Vee-Validate 或 Vue-Formkit 集成时,使用 v-bind 传递错误状态而非包装层:PrimeVue v4 的表单组件接受 invalid prop,直接绑定校验库的错误状态即可,无需额外包装组件。
<InputText v-model="email" :invalid="!!errors.email" />
<small class="text-red-500">{{ errors.email }}</small>
图标使用 primeicons 类名时检查版本兼容性:PrimeIcons 在大版本更新时会重命名部分图标(如 pi-home → pi-house),升级后应全量搜索项目中的 pi- 类名进行核对。
常见陷阱
陷阱:Styled 模式下自定义 CSS 被覆盖
现象: 通过全局 CSS 修改 DataTable 行高或颜色,在开发环境生效,生产构建后失效;或某些状态(hover、selected)样式无法覆盖。
原因: PrimeVue Styled 模式的主题 CSS 使用了较高特异性的选择器,全局 class 覆盖时特异性不够;生产构建中 CSS 加载顺序可能与开发环境不同。
解决: 升级到 Unstyled 模式使用 PT API 控制样式;或用 :deep() 穿透 Scoped CSS;或在主题 CSS 后引入自定义 CSS 以保证顺序。
<style scoped>
:deep(.p-datatable-row-selected) {
background: #e0f2fe !important;
}
</style>
陷阱:v-model 在 Dialog 内的组件数据不同步
现象: Dialog 打开后修改了表单数据,关闭再打开时,数据没有重置或显示了上次的值。
原因: Dialog 组件默认在关闭时不销毁内部 DOM(仅隐藏),Vue 组件状态保留,下次打开时仍是上次的值。
解决: 使用 :visible 控制显隐时,在 @hide 事件中手动重置表单数据;或使用 v-if 替代 v-show 控制 Dialog,每次打开都重新挂载组件。
<Dialog v-model:visible="showDialog" @hide="resetForm">
陷阱:DataTable 懒加载时 totalRecords 未同步导致分页异常
现象: 服务端分页时,分页器显示的总页数不对,或跳转到最后一页后无数据。
原因: lazy: true 时,DataTable 分页器完全依赖 totalRecords prop 计算总页数,若首次加载时未设置或设置了错误的值,分页计算会出错。
解决: 在获取第一页数据的同时从接口返回 total 并赋值给 totalRecords,确保与数据同步更新。
<DataTable :value="rows" :lazy="true" :totalRecords="total" @page="onPage">