Descriptions
数据展示组件,基于 Element Plus Descriptions 封装,支持字典、文件、富文本等多种展示类型。
基础用法
vue
<script setup lang="ts">
import { ref } from "vue";
import { Descriptions, defineDescriptionsConfig } from "vue-admin-kit";
const config = defineDescriptionsConfig([
{
title: "基本信息",
column: 3,
children: [
{ label: "客户名称", value: "customerName" },
{ label: "联系电话", value: "phone" },
{ label: "状态", value: "status", dictType: "sys_status" },
],
},
]);
const data = ref({
customerName: "张三",
phone: "13800138000",
status: "1",
});
</script>
<template>
<Descriptions :config="config" :data="data" />
</template>Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
config | DescriptionSectionConfig[] | - | 配置数组,定义展示的分组 |
data | Record<string, unknown> | - | 数据对象,包含要展示的数据 |
配置类型
DescriptionSectionConfig(分组配置)
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | string | - | 分组标题 |
column | number | 3 | 每行显示列数 |
children | DescriptionItemConfig[] | - | 子项配置数组 |
labelWidth | string | '200px' | 标签宽度 |
width | number | 500 | 内容宽度 |
align | 'left' | 'center' | 'right' | 'left' | 内容对齐方式 |
labelAlign | 'left' | 'center' | 'right' | 'left' | 标签对齐方式 |
style | Record<string, unknown> | - | 自定义样式 |
DescriptionItemConfig(子项配置)
| 属性 | 类型 | 说明 |
|---|---|---|
label | string | 标签文本 |
value | string | 数据字段名 |
span | number | 占用列数,默认 1 |
type | 'file' | 'richText' | 展示类型 |
dictType | string | 字典类型,自动转换为字典标签 |
options | DictDataOption[] | 自定义选项(优先于 dictType) |
format | (value, row) => string | 自定义格式化函数 |
labelWidth | string | 标签宽度(覆盖分组配置) |
align | 'left' | 'center' | 'right' | 内容对齐方式(覆盖分组配置) |
labelAlign | 'left' | 'center' | 'right' | 标签对齐方式(覆盖分组配置) |
展示类型
普通文本
默认展示类型,直接显示字段值:
typescript
const config = defineDescriptionsConfig([
{
title: "基本信息",
children: [
{ label: "名称", value: "name" },
{ label: "编码", value: "code" },
{ label: "描述", value: "description" },
],
},
]);字典类型
自动将字段值转换为字典标签文本:
typescript
const config = defineDescriptionsConfig([
{
title: "状态信息",
children: [
// 使用系统字典
{ label: "状态", value: "status", dictType: "sys_status" },
// 使用自定义选项
{
label: "类型",
value: "type",
options: [
{ label: "类型A", value: "a" },
{ label: "类型B", value: "b" },
],
},
],
},
]);文件类型
点击后弹出文件列表弹窗,支持预览和下载:
typescript
const config = defineDescriptionsConfig([
{
title: "附件信息",
children: [
{ label: "合同附件", value: "contractFiles", type: "file" },
{ label: "图片资料", value: "imageFiles", type: "file" },
],
},
]);
// 数据格式
const data = {
contractFiles: [
{ url: "https://example.com/file.pdf", originalName: "合同.pdf" },
{ url: "https://example.com/file2.docx", originalName: "协议.docx" },
],
imageFiles: [
{ url: "https://example.com/img.jpg", originalName: "图片.jpg" },
],
};文件数据格式:
typescript
interface FileItem {
url: string; // 文件 URL
name?: string; // 文件名(备选)
originalName?: string; // 原始文件名(优先)
}富文本类型
安全渲染 HTML 内容(使用 DOMPurify 过滤):
typescript
const config = defineDescriptionsConfig([
{
title: "详细描述",
column: 1,
children: [{ label: "内容描述", value: "content", type: "richText" }],
},
]);
const data = {
content: "<p>这是一段<strong>富文本</strong>内容</p>",
};自定义格式化
使用 format 函数自定义显示内容:
typescript
const config = defineDescriptionsConfig([
{
title: "金额信息",
children: [
// 简单格式化
{
label: "金额",
value: "amount",
format: (val) => `¥${Number(val).toFixed(2)}`,
},
// 访问整行数据
{
label: "全名",
value: "firstName",
format: (val, row) => `${val} ${row.lastName}`,
},
// 日期格式化
{
label: "创建时间",
value: "createTime",
format: (val) => {
if (!val) return "-";
return new Date(val).toLocaleString();
},
},
// 条件显示
{
label: "审核状态",
value: "auditStatus",
format: (val, row) => {
if (val === "1") return `已通过(${row.auditor})`;
if (val === "0") return "待审核";
return "已拒绝";
},
},
],
},
]);多分组配置
typescript
const config = defineDescriptionsConfig([
{
title: "基本信息",
column: 3,
children: [
{ label: "客户名称", value: "customerName" },
{ label: "联系电话", value: "phone" },
{ label: "状态", value: "status", dictType: "sys_status" },
{ label: "地址", value: "address", span: 2 },
{ label: "邮编", value: "zipCode" },
],
},
{
title: "业务信息",
column: 2,
labelWidth: "150px",
children: [
{ label: "合同金额", value: "contractAmount", format: (v) => `¥${v}` },
{ label: "签约日期", value: "signDate" },
{ label: "合同附件", value: "contractFiles", type: "file" },
{ label: "备注说明", value: "remark", type: "richText" },
],
},
{
title: "系统信息",
column: 4,
children: [
{ label: "创建人", value: "createBy" },
{ label: "创建时间", value: "createTime" },
{ label: "更新人", value: "updateBy" },
{ label: "更新时间", value: "updateTime" },
],
},
]);布局配置
列数配置
typescript
// 单列布局
{
title: "详细描述",
column: 1,
children: [
{ label: "描述", value: "description" },
],
}
// 两列布局
{
title: "基本信息",
column: 2,
children: [
{ label: "名称", value: "name" },
{ label: "编码", value: "code" },
],
}
// 四列布局
{
title: "系统信息",
column: 4,
children: [
{ label: "创建人", value: "createBy" },
{ label: "创建时间", value: "createTime" },
{ label: "更新人", value: "updateBy" },
{ label: "更新时间", value: "updateTime" },
],
}跨列配置
使用 span 属性让某项占用多列:
typescript
{
title: "基本信息",
column: 3,
children: [
{ label: "名称", value: "name" },
{ label: "编码", value: "code" },
{ label: "状态", value: "status" },
{ label: "详细地址", value: "address", span: 3 }, // 占满一行
{ label: "备注", value: "remark", span: 2 }, // 占两列
{ label: "创建时间", value: "createTime" },
],
}对齐方式
typescript
{
title: "金额信息",
column: 2,
align: "right", // 内容右对齐
labelAlign: "left", // 标签左对齐
children: [
{ label: "合同金额", value: "contractAmount" },
{ label: "已付金额", value: "paidAmount" },
{ label: "待付金额", value: "unpaidAmount", align: "center" }, // 单独设置
],
}宽度配置
typescript
{
title: "基本信息",
labelWidth: "120px", // 标签宽度
width: 400, // 内容宽度
children: [
{ label: "名称", value: "name" },
{ label: "详细描述", value: "description", labelWidth: "100px" }, // 单独设置
],
}加载状态
组件会在 data 为空时自动显示加载状态:
vue
<script setup>
import { ref, onMounted } from "vue";
const data = ref(null);
onMounted(async () => {
// 加载数据前 data 为 null,显示 loading
const res = await getDetail(id);
data.value = res.data;
});
</script>
<template>
<Descriptions :config="config" :data="data" />
</template>在详情弹窗中使用
vue
<script setup>
import {
useState,
PageTemplate,
Table,
Descriptions,
defineDescriptionsConfig,
} from "vue-admin-kit";
const detailConfig = defineDescriptionsConfig([
{
title: "用户信息",
column: 2,
children: [
{ label: "用户名", value: "userName" },
{ label: "昵称", value: "nickName" },
{ label: "手机号", value: "phone" },
{ label: "邮箱", value: "email" },
{ label: "状态", value: "status", dictType: "sys_status" },
{ label: "创建时间", value: "createTime" },
],
},
]);
useState({
api: { list: listUser, detail: getUser },
// ...
});
</script>
<template>
<PageTemplate ref="pageTemplateRef">
<template #table>
<Table />
</template>
<!-- 自定义详情内容 -->
<template #detail="{ detailData }">
<Descriptions :config="detailConfig" :data="detailData" />
</template>
</PageTemplate>
</template>完整示例
vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { Descriptions, defineDescriptionsConfig } from "vue-admin-kit";
import { getOrderDetail } from "@/api/order";
const config = defineDescriptionsConfig([
{
title: "订单信息",
column: 3,
children: [
{ label: "订单号", value: "orderNo" },
{ label: "订单状态", value: "status", dictType: "order_status" },
{ label: "下单时间", value: "createTime" },
{ label: "客户名称", value: "customerName", span: 2 },
{ label: "联系电话", value: "phone" },
],
},
{
title: "金额信息",
column: 4,
align: "right",
children: [
{
label: "商品金额",
value: "goodsAmount",
format: (v) => `¥${v?.toFixed(2)}`,
},
{ label: "运费", value: "freight", format: (v) => `¥${v?.toFixed(2)}` },
{
label: "优惠金额",
value: "discountAmount",
format: (v) => `-¥${v?.toFixed(2)}`,
},
{
label: "实付金额",
value: "payAmount",
format: (v) => `¥${v?.toFixed(2)}`,
},
],
},
{
title: "收货信息",
column: 1,
children: [
{ label: "收货人", value: "receiverName" },
{ label: "收货电话", value: "receiverPhone" },
{
label: "收货地址",
value: "province",
format: (_, row) =>
`${row.province}${row.city}${row.district}${row.address}`,
},
],
},
{
title: "附件信息",
column: 2,
children: [
{ label: "合同附件", value: "contractFiles", type: "file" },
{ label: "发票附件", value: "invoiceFiles", type: "file" },
],
},
{
title: "备注",
column: 1,
children: [{ label: "订单备注", value: "remark", type: "richText" }],
},
]);
const data = ref(null);
onMounted(async () => {
const res = await getOrderDetail(orderId);
data.value = res.data;
});
</script>
<template>
<div class="order-detail">
<Descriptions :config="config" :data="data" />
</div>
</template>