AvueAvue
首页
  • 开发指南
  • Skill开发
  • 在线测试工具
  • Form组件
  • Crud组件
  • Default组件
  • Data组件
  • Component组件
产品
工作台
授权
联系
2.x文档
个人支付接口
首页
  • 开发指南
  • Skill开发
  • 在线测试工具
  • Form组件
  • Crud组件
  • Default组件
  • Data组件
  • Component组件
产品
工作台
授权
联系
2.x文档
个人支付接口
  • CRUD API
  • Object对象用法
  • 表格虚拟化
  • 分页
  • 搜索
  • 表头配置
  • 表格行配置项
  • 表格列配置项
  • 数据字典
  • 操作栏配置
  • 增删改查方法
  • 按钮文案和图标
  • 按钮自定义
  • 弹窗表单配置
  • 深层结构数据
  • 卡片模式
  • 统计合计
  • 导入导出
  • 表格树
  • 父子表
  • 行编辑
  • 权限控制
  • 动态行列合并
  • 空状态
  • 等待加载
  • 拖拽排序
  • 其它类型
  • 表格高级用法
  • 大表哥(宇宙最强表格)
  • CRUD模块封装
  • CRUD极简增删改查封装

卡片模式

卡片适合成员、资产、商品等以单条信息为中心的列表。字段、字典和插槽沿用 CRUD 配置,使用 grid: true 切换为卡片布局。

版本

卡片模式从 3.4.0 起支持;grid-status-change 从 3.4.7 起支持;下面的跨页保留选择示例请使用 3.9.5 及以上版本。

基本用法

效果预览可直接操作下方示例

grid 指定初始显示方式,gridBtn 控制卡片 / 表格切换按钮。点击右上方的切换按钮,下方状态会通过 grid-status-change 更新。

正在加载示例…
<template>
  <div>
    <avue-crud :option="option"
               :data="data"
               @grid-status-change="gridStatusChange" />
    <p class="grid-status" aria-live="polite">当前显示:{{ grid ? '卡片模式' : '表格模式' }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const grid = ref(true);
const data = ref([
  { id: 1, name: '张三', department: '研发部', role: '前端工程师', province: '110000' },
  { id: 2, name: '李四', department: '产品部', role: '产品经理', province: '130000' }
]);
const option = ref({
  rowKey: 'id',
  grid: true,
  gridBtn: true,
  gridSpan: 12,
  gridXsSpan: 24,
  addBtn: false,
  menu: false,
  refreshBtn: false,
  columnBtn: false,
  column: [
    { label: '姓名', prop: 'name', gridRow: true },
    { label: '部门', prop: 'department' },
    { label: '岗位', prop: 'role', gridRow: true },
    {
      label: '所在地',
      prop: 'province',
      type: 'select',
      dicData: [
        { label: '北京市', value: '110000' },
        { label: '河北省', value: '130000' }
      ]
    }
  ]
});
function gridStatusChange(value) {
  grid.value = value;
}
</script>

<style scoped>
.grid-status { margin: 12px 0 0; color: var(--el-text-color-secondary); }
</style>

跨页保留选择

效果预览可直接操作下方示例

同时配置 selection: true、reserveSelection: true 和稳定的 rowKey。在第 1 页勾选一张卡片,切换到第 2 页再选择一张,然后回到第 1 页查看状态。「重新加载当前页」会创建新的行对象,但相同 ID 的选中状态仍保留。

正在加载示例…
<template>
  <div class="grid-selection-example">
    <div class="grid-selection-actions">
      <el-button @click="loadPage(page)">重新加载当前页</el-button>
      <el-button :disabled="!selected.length" @click="clearSelection">清空全部选择</el-button>
    </div>
    <avue-crud ref="crud"
               v-model:page="page"
               :option="option"
               :data="data"
               @on-load="loadPage"
               @selection-change="selectionChange" />
    <div class="grid-selection-result" aria-live="polite">
      <strong>已选择 {{ selected.length }} 条</strong>
      <p v-if="!selected.length">请勾选卡片,再翻页选择其他记录。</p>
      <div v-else class="grid-selection-tags">
        <el-tag v-for="row in selected" :key="row.id">#{{ row.id }} {{ row.name }}</el-tag>
      </div>
      <small>当前第 {{ page.currentPage }} 页 · 已加载 {{ loadCount }} 次</small>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const crud = ref(null);
const selected = ref([]);
const data = ref([]);
const loadCount = ref(0);
const rows = [
  { id: 101, name: '张三', department: '研发部' },
  { id: 102, name: '李四', department: '产品部' },
  { id: 103, name: '王五', department: '运营部' },
  { id: 104, name: '赵六', department: '测试部' },
  { id: 105, name: '周七', department: '设计部' },
  { id: 106, name: '吴八', department: '研发部' }
];
const page = ref({
  currentPage: 1,
  pageSize: 2,
  pageSizes: [2],
  total: rows.length,
  layout: 'total, prev, pager, next'
});
const option = ref({
  rowKey: 'id',
  grid: true,
  gridBtn: false,
  gridSpan: 12,
  gridXsSpan: 24,
  selection: true,
  reserveSelection: true,
  addBtn: false,
  menu: false,
  refreshBtn: false,
  columnBtn: false,
  column: [
    { label: '编号', prop: 'id', gridRow: true },
    { label: '姓名', prop: 'name', gridRow: true },
    { label: '部门', prop: 'department', gridRow: true }
  ]
});

function loadPage(currentPage) {
  const start = (currentPage.currentPage - 1) * currentPage.pageSize;
  // 模拟每次接口返回新对象;选中状态由稳定 id 保持。
  data.value = rows.slice(start, start + currentPage.pageSize).map(row => ({ ...row }));
  loadCount.value += 1;
}
function selectionChange(rows) {
  selected.value = rows;
}
function clearSelection() {
  crud.value.clearSelection();
}
</script>

<style scoped>
.grid-selection-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.grid-selection-actions .el-button { margin-left: 0; }
.grid-selection-result {
  margin-top: 16px;
  padding: 16px;
  border-radius: 8px;
  background: var(--el-fill-color-light);
  line-height: 1.7;
}
.grid-selection-result p { margin: 8px 0; }
.grid-selection-result small { display: block; margin-top: 10px; color: var(--el-text-color-secondary); }
.grid-selection-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
</style>

selection-change 返回保留的已选记录,下方列表会显示所有已选 ID,而不只是当前页。取消当前页某张卡片不会取消其他页的选择;「清空全部选择」调用 clearSelection()。

主键必须在全部分页数据中唯一。不要用页内下标、每次请求重新生成的随机值,或姓名等可能重复的字段。若不需要跨页保留,关闭 reserveSelection;分页和重新加载后只保留当前页面应有的选择状态。

示例固定使用卡片模式,便于验证分页。若业务需要在表格和卡片之间切换并继续保留选中项,请在业务层保存已选主键,并在切换后恢复选择。

布局与颜色

效果预览可直接操作下方示例

gridSpan 按 24 栅格控制桌面卡片宽度,gridXsSpan 控制窄屏宽度,gridRow 让字段独占一行。本例桌面两列、窄屏一列,使用浅色背景突出内容。

正在加载示例…
<template>
  <avue-crud :option="option" :data="data" />
</template>

<script setup>
import { ref } from 'vue';

const data = ref([
  { id: 1, name: '文档完善', owner: '张三', status: '进行中', description: '补充可交互示例、操作步骤和常见问题。' },
  { id: 2, name: '版本验证', owner: '李四', status: '待开始', description: '确认桌面与手机布局,以及本地演示数据。' }
]);
const option = ref({
  rowKey: 'id',
  grid: true,
  gridBtn: false,
  gridSpan: 12,
  gridXsSpan: 24,
  gridBackground: 'linear-gradient(135deg, #eff6ff, #f8fafc)',
  addBtn: false,
  menu: false,
  refreshBtn: false,
  columnBtn: false,
  column: [
    { label: '任务', prop: 'name', gridRow: true },
    { label: '负责人', prop: 'owner' },
    { label: '状态', prop: 'status' },
    { label: '说明', prop: 'description', gridRow: true }
  ]
});
</script>

字段较多时,优先把标题、描述设置为独占一行。背景图通过 gridBackgroundImage 配置;请保证文字和背景有足够对比度。

按状态设置背景

效果预览可直接操作下方示例

gridBackground(row, index) 返回当前卡片的背景值。本例根据记录状态显示不同背景,颜色与状态文字同时出现,避免只靠颜色传达含义。

正在加载示例…
<template>
  <avue-crud :option="option" :data="data" />
</template>

<script setup>
import { ref } from 'vue';

const data = ref([
  { id: 1, name: '示例检查', owner: '张三', status: 'done' },
  { id: 2, name: '发布检查', owner: '李四', status: 'pending' }
]);
const option = ref({
  rowKey: 'id',
  grid: true,
  gridBtn: false,
  gridSpan: 12,
  gridXsSpan: 24,
  addBtn: false,
  menu: false,
  refreshBtn: false,
  columnBtn: false,
  gridBackground: row => row.status === 'done'
    ? 'linear-gradient(135deg, #ecfdf5, #f8fafc)'
    : 'linear-gradient(135deg, #fff7ed, #f8fafc)',
  column: [
    { label: '任务', prop: 'name', gridRow: true },
    { label: '负责人', prop: 'owner', gridRow: true },
    {
      label: '状态',
      prop: 'status',
      gridRow: true,
      type: 'select',
      dicData: [
        { label: '已完成', value: 'done' },
        { label: '待处理', value: 'pending' }
      ]
    }
  ]
});
</script>

自定义内容

效果预览可直接操作下方示例

使用与字段 prop 同名的插槽展示自定义内容。本例把头像绘制为姓名首字,不需要远程图片;样式限定在当前示例中,窄屏仍按单列显示。

正在加载示例…
<template>
  <div class="grid-slot-example">
    <avue-crud :option="option" :data="data">
      <template #profile="{ row }">
        <div class="grid-profile">
          <span class="grid-avatar" aria-hidden="true">{{ row.name.slice(0, 1) }}</span>
          <div>
            <strong>{{ row.name }}</strong>
            <span>{{ row.role }}</span>
          </div>
        </div>
      </template>
    </avue-crud>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const data = ref([
  { id: 1, name: '张三', role: '前端工程师', department: '研发部', location: '北京' },
  { id: 2, name: '李四', role: '产品经理', department: '产品部', location: '石家庄' }
]);
const option = ref({
  rowKey: 'id',
  grid: true,
  gridBtn: false,
  gridSpan: 12,
  gridXsSpan: 24,
  addBtn: false,
  menu: false,
  refreshBtn: false,
  columnBtn: false,
  column: [
    { label: '成员', prop: 'profile', gridRow: true, slot: true },
    { label: '部门', prop: 'department', gridRow: true },
    { label: '所在地', prop: 'location', gridRow: true }
  ]
});
</script>

<style scoped>
.grid-profile { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
.grid-avatar {
  display: inline-flex;
  width: 42px;
  height: 42px;
  flex: 0 0 42px;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  background: #dbeafe;
  color: #1d4ed8;
  font-size: 20px;
  font-weight: 600;
}
.grid-profile strong, .grid-profile div > span { display: block; overflow-wrap: anywhere; }
.grid-profile div > span { margin-top: 4px; color: var(--el-text-color-secondary); font-size: 13px; }
</style>

插槽中的 row 是当前记录,常规文本字段无需额外声明插槽。自定义图片时应提供替代文字和固定显示尺寸,以免图片加载前后造成布局跳动。

最后更新:
贡献者: smallwei
Prev
深层结构数据
Next
统计合计