表格列配置项
通过 option.column 描述字段的显示方式。先设置稳定的行主键,再按内容选择列宽、格式化、排序和显隐;这些配置可以组合使用。
| 需求 | 主要配置 |
|---|---|
| 识别同一条记录 | 表格的 rowKey,默认 id |
| 固定 / 自适应列宽 | 列的 width / minWidth |
| 控制长文本 | 列的 overHidden |
| 拖动列顺序 | 表格的 columnSort |
| 刷新后恢复列状态 | 表格的 columnStateKey |
主键
选择、展开、树形表格、拖拽等操作依赖稳定的主键。本例使用 rowKey: 'userId'。勾选记录后查看下方 ID,再点击「倒序显示」,可以确认选择仍对应同一条记录。
<template>
<div class="row-key-example">
<el-button @click="reverseRows">倒序显示</el-button>
<avue-crud :data="data" :option="option" @selection-change="selectionChange" />
<p class="row-key-result" aria-live="polite">已选 userId:{{ selectedIds || '暂无' }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const selectedIds = ref('');
const data = ref([
{ userId: 'U1001', name: '张三', department: '研发部' },
{ userId: 'U1002', name: '李四', department: '产品部' },
{ userId: 'U1003', name: '王五', department: '运营部' }
]);
const option = ref({
rowKey: 'userId',
selection: true,
reserveSelection: true,
addBtn: false,
menu: false,
refreshBtn: false,
columnBtn: false,
column: [
{ label: '业务主键', prop: 'userId', width: 120 },
{ label: '姓名', prop: 'name' },
{ label: '部门', prop: 'department' }
]
});
function reverseRows() {
data.value = data.value.slice().reverse();
}
function selectionChange(rows) {
selectedIds.value = rows.map(row => row.userId).join('、');
}
</script>
<style scoped>
.row-key-example > .el-button { margin-bottom: 16px; }
.row-key-result {
margin: 12px 0 0;
padding: 12px 16px;
border-radius: 8px;
background: var(--el-fill-color-light);
overflow-wrap: anywhere;
}
</style>
主键在数据集中必须唯一,并且在刷新、分页和排序前后保持不变。不要使用数组下标作为记录主键。
宽度
width 指定列宽,minWidth 保留最小宽度并分配剩余空间。开启 border 后,拖动表头分隔线改变宽度,下方显示 header-dragend 返回的新旧宽度。
<template>
<div>
<avue-crud :data="data" :option="option" @header-dragend="headerDragend" />
<p class="width-result" aria-live="polite">{{ resizeResult }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const resizeResult = ref('拖动「姓名」或「说明」表头的分隔线,查看列宽变化。');
const data = ref([
{ id: 1, name: '张三', description: '姓名设置固定宽度;说明列随剩余空间伸缩。' },
{ id: 2, name: '李四', description: '拖动分隔线后,也可以选中这些文字进行复制。' }
]);
const option = ref({
rowKey: 'id',
border: true,
addBtn: false,
menu: false,
refreshBtn: false,
columnBtn: false,
column: [
{ label: 'ID', prop: 'id', width: 70, resizable: false },
{ label: '姓名', prop: 'name', width: 140 },
{ label: '说明', prop: 'description', minWidth: 220 }
]
});
function headerDragend(newWidth, oldWidth, column) {
resizeResult.value = column.label + ':' + oldWidth + ' px → ' + newWidth + ' px';
}
</script>
<style scoped>
.width-result {
margin: 12px 0 0;
padding: 12px 16px;
border-radius: 8px;
background: var(--el-fill-color-light);
line-height: 1.7;
overflow-wrap: anywhere;
}
</style>
姓名等短字段适合固定宽度,描述字段适合 minWidth。resizable: false 可以禁止某一列调整宽度;若希望刷新页面后保留宽度,请同时配置下面的 columnStateKey。
索引
设index属性为true即可,indexLabel设置表格的序号的标题,默认为#
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
// 定义响应式数据
const data = ref([
{
name: '张三',
sex: '男'
},
{
name: '李四',
sex: '女'
}
]);
const option = ref({
index: true,
indexLabel: '序号',
column: [
{
label: '姓名',
prop: 'name'
},
{
label: '性别',
prop: 'sex'
}
]
});
</script>
自定义索引
<template>
<avue-crud :data="data"
:option="option">
<template #index="{row,index}">
<el-tag>{{index+1}}</el-tag>
</template>
</avue-crud>
</template>
<script>
export default {
data () {
return {
data: [
{
name: '张三',
sex: '男'
}, {
name: '李四',
sex: '女'
}
],
option: {
column: [{
label: '序号',
prop: 'index',
fixed: true
},
{
label: '姓名',
prop: 'name'
}, {
label: '性别',
prop: 'sex'
}
]
},
};
}
}
</script>内容超出隐藏
overHidden设置true即可超出列表宽度的内容以省略号显示
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
// 定义响应式数据
const data = ref([
{
name: '张三的名字是一个很长很长的内容',
sex: '男'
},
{
name: '李四',
sex: '女'
}
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
overHidden: true,
width: 80,
},
{
label: '性别',
prop: 'sex'
}
]
});
</script>
内容格式化
formatter方法格式化内容
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
// 定义响应式数据
const data = ref([
{
name: '张三',
sex: '男',
select: '110000'
},
{
name: '李四',
sex: '女',
select: '120000'
}
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
formatter: (val, value, label) => {
return val.name + '格式化内容';
}
},
{
label: '性别',
prop: 'sex'
},
{
label: '字典',
prop: 'select',
type: 'select',
formatter: (val, value, label) => {
return `${label}(${value})`;
},
props: {
label: 'name',
value: 'code'
},
dicUrl: 'https://api.avuejs.com/area/getProvince'
}
]
});
</script>
支持 html 转译
配置html设置为true即可支持formatter转义html代码
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
// 定义响应式数据
const data = ref([
{
name: '张三',
sex: '男'
},
{
name: '李四',
sex: '女'
}
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
html: true,
formatter: (val) => {
return `<b style="color:red">${val.name}格式化内容</b>`;
}
},
{
label: '性别',
prop: 'sex'
}
]
});
</script>
列隐藏
一共是有 4 列的hide和showColumn可以达到控制列显隐控制
<template>
<avue-crud :option="option"
:data="data"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
// 定义响应式数据
const data = ref([
{
text1: '内容1-1',
text2: '内容1-2',
text3: '内容1-3',
text4: '内容1-4'
},
{
text1: '内容2-1',
text2: '内容2-2',
text3: '内容2-3',
text4: '内容2-4'
}
]);
const option = ref({
align: 'center',
headerAlign: 'center',
column: [
{
label: '列内容1',
prop: 'text1',
},
{
label: '列内容2',
prop: 'text2',
},
{
label: '列内容3',
prop: 'text3',
hide: true
},
{
label: '列内容4',
prop: 'text4',
showColumn: false,
}
]
});
</script>
持久化存储
提示
如果有远程字典类的配置或者加载错误情况,需要初始化下组件CRUD 初始化
<template>
<avue-crud ref="crud"
:key="reload"
:option="option"
:data="data">
<template #menu-left="{size}">
<el-button @click="saveOption"
type="danger"
:size="size">保存配置</el-button>
</template>
</avue-crud>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
// 设置常量
const key = 'avue-option';
// 定义响应式数据
const reload = ref(Math.random());
const data = ref([
{ text1: '内容1-1', text2: '内容2-1', text3: '110000' },
{ text1: '内容1-2', text2: '内容2-2', text3: '120000' },
{ text1: '内容1-3', text2: '内容2-3' },
{ text1: '内容1-4', text2: '内容2-4' },
{ text1: '内容1-5', text2: '内容2-5' }
]);
const option = ref({
sortable: true,
addBtn: false,
menu: false,
border: true,
align: 'center',
column: [
{ label: '列内容1', prop: 'text1' },
{ label: '列内容2', prop: 'text2' },
{ label: '列内容3', prop: 'text3', type: 'select', props: { label: 'name', value: 'code' }, dicUrl: 'https://api.avuejs.com/area/getProvince' }
]
});
// 在组件挂载后读取本地存储
onMounted(() => {
const storedOption = localStorage.getItem(key);
if (storedOption) {
option.value = JSON.parse(storedOption);
reload.value = Math.random();
}
});
// 保存配置到本地存储
const saveOption = () => {
localStorage.setItem(key, JSON.stringify(option.value));
ElMessage.success('配置保存成功');
};
</script>
列状态持久化
提示
3.9.2+
配置 columnStateKey 后,列顺序、显隐、冻结、筛选和排序开关、列宽会自动保存。先打开「列设置」隐藏或冻结一列,再拖动列宽,点击「读取本地状态」检查结果。刷新页面后仍会恢复;「重置列状态」调用 resetColumnState 恢复默认配置。
<template>
<div>
<div class="demo-actions">
<el-button type="primary"
@click="openColumnDialog">列设置</el-button>
<el-button @click="resetColumnState">重置列状态</el-button>
<el-button @click="readState">读取本地状态</el-button>
<span class="demo-key">key:{{ stateKey }}</span>
</div>
<avue-crud ref="crud"
v-model="form"
v-model:page="page"
:option="option"
:data="data"
@column-state-change="handleColumnStateChange"
@column-state-reset="handleColumnStateReset"></avue-crud>
<pre class="state-preview">{{ stateText }}</pre>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { ElMessage } from 'element-plus';
const crud = ref(null);
const form = ref({});
const stateKey = 'crud-column-state-doc';
const stateText = ref('');
const page = ref({
total: 4,
currentPage: 1,
pageSize: 10
});
const option = ref({
columnStateKey: stateKey,
border: true,
stripe: true,
index: true,
selection: true,
columnBtn: true,
align: 'center',
menuWidth: 160,
column: [
{
label: '姓名',
prop: 'name',
width: 120,
search: true,
rules: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
},
{
label: '部门',
prop: 'dept',
width: 140,
type: 'select',
dicData: [
{ label: '研发部', value: 'dev' },
{ label: '产品部', value: 'product' },
{ label: '运营部', value: 'ops' }
]
},
{
label: '岗位',
prop: 'post',
width: 140
},
{
label: '状态',
prop: 'status',
type: 'radio',
width: 120,
dicData: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 }
]
},
{
label: '创建时间',
prop: 'createTime',
type: 'datetime',
width: 170
},
{
label: '备注',
prop: 'remark',
minWidth: 180,
overHidden: true
}
]
});
const data = ref([
{
id: 1,
name: '张三',
dept: 'dev',
post: '前端工程师',
status: 1,
createTime: '2026-06-01 09:00:00',
remark: '可在列设置中隐藏或冻结本列'
},
{
id: 2,
name: '李四',
dept: 'product',
post: '产品经理',
status: 1,
createTime: '2026-06-02 10:30:00',
remark: '调整列宽后会写入 localStorage'
},
{
id: 3,
name: '王五',
dept: 'ops',
post: '运营专员',
status: 0,
createTime: '2026-06-03 14:20:00',
remark: '刷新页面后恢复列顺序和显隐'
},
{
id: 4,
name: '赵六',
dept: 'dev',
post: '测试工程师',
status: 1,
createTime: '2026-06-04 16:45:00',
remark: '点击重置会清理当前 key 的列状态'
}
]);
const getStorageKey = () => 'AVUE_COLUMN_STATE:' + stateKey;
const readState = () => {
stateText.value = localStorage.getItem(getStorageKey()) || '暂无列状态';
};
const openColumnDialog = () => {
const dialog = crud.value?.$refs?.dialogColumn;
if (dialog?.handleShow) dialog.handleShow();
};
const resetColumnState = () => {
crud.value.resetColumnState(() => {
readState();
ElMessage.success('列状态已重置');
});
};
const handleColumnStateChange = (state, reason) => {
stateText.value = JSON.stringify({ reason, state }, null, 2);
};
const handleColumnStateReset = () => {
readState();
};
onMounted(readState);
</script>
<style scoped>
.demo-actions {
margin-bottom: 12px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.demo-key {
color: #606266;
font-size: 13px;
}
.state-preview {
margin-top: 12px;
max-height: 220px;
padding: 12px;
overflow: auto;
color: #e5e7eb;
background: #111827;
font-family: Consolas, 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
}
</style>
为每个独立业务表格设置不同的 key,避免相互覆盖。状态保存在当前浏览器的 localStorage,不会自动同步到其他浏览器或设备。
这里保存的是列的 filters/sortable 配置,不包含当前选中的筛选值或升降序结果。自定义 columnStateLoad(key, option) 必须同步返回状态;需要远程读取时,先获取状态再挂载表格。
筛选
设置filters为true,字典用法和普通用法一致,filterMethod为自定义的筛选逻辑,filter-multiple筛选的数据为多选还是单选,默认为 true
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{
name: '张三',
sex: '男'
},
{
name: '李四',
sex: '女'
}
]);
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
formatter: (row, value, label, column) => {
return row.name + '自定义';
}
},
{
label: '性别',
prop: 'sex',
filters: true,
dicData: [
{ label: '男', value: '男' },
{ label: '女', value: '女' }
],
filterMethod: (value, row, column) => {
return row.sex === value;
}
}
]
});
</script>
默认排序
设置defaultSort一个属性接受prop排序的字段和order排序的方式俩个属性,初始化的时候根据设置默认排序,order中接受 ascending 表示升序,descending 表示降序,回调sort-change方法返回排序参数
<template>
<avue-crud :data="data"
:option="option1"
@sort-change="sortChange"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
const data = ref([
{
name: '张三',
sex: '男'
},
{
name: '李四',
sex: '女'
}
]);
const option1 = ref({
defaultSort: {
prop: 'name',
order: 'descending'
},
border: true,
column: [
{
sortable: true,
label: '姓名',
prop: 'name'
},
{
label: '性别',
prop: 'sex'
}
]
});
function sortChange (val) {
ElMessage.success(JSON.stringify(val));
}
</script>
冻结列
提示
配置fixed为true即可实现冻结列
配置 indexFixed、selectionFixed、expandFixed 可以控制序号、多选、展开列是否冻结;对应宽度配置为 indexWidth、selectionWidth、expandWidth。
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{
id: 1,
name: '张三',
sex: '男'
},
{
id: 2,
name: '李四',
sex: '女'
}
]);
const option = ref({
index: true,
indexFixed: false,
indexWidth: 100,
selection: true,
selectionWidth: 100,
selectionFixed: false,
expand: true,
expandWidth: 100,
expandFixed: false,
column: [
{
label: '姓名',
fixed: true,
prop: 'name'
},
{
width: 500,
label: '性别',
prop: 'sex'
}
]
});
</script>
单元格和表头样式
提示
配置className和labelClassName配置单元格和表头样式名称
配置indexClassName,selectionClassName,expandClassName可以配置序号,多选,面板列单元格的样式名称,配置indexLabelClassName,selectionLabelClassName,expandLabelClassName表头样式名称
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref } from 'vue';
const data = ref([
{
id: 1,
name: '张三',
sex: '男'
},
{
id: 2,
name: '李四',
sex: '女'
}
]);
const option = ref({
index: true,
indexClassName: 'indexClassName',
indexLabelClassName: 'indexLabelClassName',
selection: true,
selectionClassName: 'selectionClassName',
selectionLabelClassName: 'selectionLabelClassName',
expand: true,
expandClassName: 'expandClassName',
expandLabelClassName: 'expandLabelClassName',
column: [
{
label: '姓名',
prop: 'name',
className: 'className',
labelClassName: 'labelClassName'
},
{
width: 500,
label: '性别',
prop: 'sex'
}
]
});
</script>
列拖拽排序
提示
3.5.2+ 支持 column-sortable-change;3.9.5 改善了列拖拽与列宽调整同时使用时的交互。
// 在客户端入口、表格挂载前加载;本页已加载。
import Sortable from 'sortablejs';
window.Sortable = Sortable;设置 columnSort: true 开启列拖拽。按住表头文字左右移动可调整列顺序;拖动表头分隔线可调整列宽。完成后试着选中表格中的文字,观察下方两类事件各自的结果。
<template>
<div>
<avue-crud :option="option"
:data="data"
@column-sortable-change="columnSortableChange"
@header-dragend="headerDragend" />
<div class="column-sort-result" aria-live="polite">
<p><strong>列顺序:</strong>{{ sortResult }}</p>
<p><strong>列宽:</strong>{{ widthResult }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const sortResult = ref('按住表头文字左右拖动。');
const widthResult = ref('把鼠标移到表头分隔线,再按住调整。');
const data = ref([
{ id: 1, name: '文档示例', owner: '张三', description: '调整列宽后,仍然可以选中并复制这段文字。' },
{ id: 2, name: '交互验证', owner: '李四', description: '拖动表头文字调整列顺序,拖动分隔线调整列宽。' },
{ id: 3, name: '版本发布', owner: '王五', description: '两种操作分别显示在下方,便于核对。' }
]);
const option = ref({
rowKey: 'id',
columnSort: true,
border: true,
addBtn: false,
menu: false,
refreshBtn: false,
columnBtn: false,
column: [
{ label: '任务', prop: 'name', width: 150 },
{ label: '负责人', prop: 'owner', width: 110 },
{ label: '说明', prop: 'description', minWidth: 220 }
]
});
function columnSortableChange(event, newIndex) {
// 兼容下标参数与包含 oldIndex / newIndex 的事件对象。
const oldIndex = typeof event === 'number' ? event : event.oldIndex;
const nextIndex = typeof event === 'number' ? newIndex : event.newIndex;
sortResult.value = '第 ' + (oldIndex + 1) + ' 列 → 第 ' + (nextIndex + 1) + ' 列';
}
function headerDragend(newWidth, oldWidth, column) {
widthResult.value = column.label + ':' + oldWidth + ' px → ' + newWidth + ' px';
}
</script>
<style scoped>
.column-sort-result {
margin-top: 16px;
padding: 12px 16px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
background: var(--el-fill-color-light);
line-height: 1.7;
overflow-wrap: anywhere;
}
.column-sort-result p { margin: 4px 0; }
</style>
列顺序由组件更新,回调可用于记录操作;不需要再次手动移动列。调整列宽时不会把分隔线拖动当成列排序。若需要持久保存顺序和宽度,可同时设置 columnStateKey。
当前表头拖拽还会兼容触发一次旧式下标回调。若回调要保存到接口,可只处理对象参数,避免一次拖拽提交两次:
function columnSortableChange(event) {
if (typeof event !== 'object') return;
const { oldIndex, newIndex, column } = event;
// 使用项目请求方法保存变化。
}自定义列
设置列的属性slot为true时,在卡槽中用prop作为卡槽的名字即可
<template>
<avue-crud :data="data"
:option="option">
<template #name="scope">
<el-tag>{{scope}}</el-tag>
</template>
</avue-crud>
</template>
<script setup>
import { ref } from 'vue'
const data = ref([
{
name: '张三',
sex: '男'
}, {
name: '李四',
sex: '女'
}
])
const option = ref({
column: [
{
label: '姓名',
prop: 'name'
},
{
label: '性别',
prop: 'sex'
}
]
})
</script>render 渲染
提示
3.4.2+
<template>
<avue-crud :data="data"
:option="option"></avue-crud>
</template>
<script setup>
import { ref, h } from 'vue'
const data = ref([
{
name: '张三',
sex: '男'
}, {
name: '李四',
sex: '女'
}
])
const option = ref({
column: [
{
label: '姓名',
prop: 'name',
render: ({ row }) => {
return h('p',
{
attrs: { id: 'box' },
class: { 'demo': true },
style: { color: 'pink', lineHeight: '30px' },
}, row.name + 'Hello World Avue')
}
},
{
label: '性别',
prop: 'sex'
}
]
},)
</script>列合并
提示
如果数据不确定参考动态数据行和列合并
通过给传入spanMethod方法可以实现合并行或列,方法的参数是一个对象,里面包含当前行row、当前列column、当前行号rowIndex、当前列号columnIndex四个属性。该函数可以返回一个包含两个元素的数组,第一个元素代表rowspan,第二个元素代表colspan。 也可以返回一个键名为rowspan和colspan的对象
<template>
<avue-crud :data="data"
:option="option"
:span-method="spanMethod"></avue-crud>
</template>
<script setup>
import { ref } from 'vue'
const data = ref([
{
id: '12987122',
name: '王小虎',
amount1: '234',
amount2: '3.2',
amount3: 10
},
{
id: '12987123',
name: '王小虎',
amount1: '165',
amount2: '4.43',
amount3: 12
},
{
id: '12987124',
name: '王小虎',
amount1: '324',
amount2: '1.9',
amount3: 9
},
{
id: '12987125',
name: '王小虎',
amount1: '621',
amount2: '2.2',
amount3: 17
},
{
id: '12987126',
name: '王小虎',
amount1: '539',
amount2: '4.1',
amount3: 15
}
])
const option = ref({
border: true,
menu: false,
column: [
{
label: 'ID',
prop: 'id'
},
{
label: '姓名',
prop: 'name'
},
{
label: '数值 1',
prop: 'amount1'
},
{
label: '数值 2',
prop: 'amount2'
},
{
label: '数值 3',
prop: 'amount3'
}
]
})
function spanMethod ({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
if (rowIndex % 2 === 0) {
return {
rowspan: 2,
colspan: 1
}
} else {
return {
rowspan: 0,
colspan: 0
}
}
}
}
</script>