<template>
|
<el-dialog v-model="descriptionsVisible" :title="title" width="60%" :close-on-click-modal="false">
|
<el-card>
|
<el-descriptions
|
class="margin-top"
|
:title="descriptionsTitle"
|
:column="column"
|
:size="size"
|
:border="border"
|
>
|
<template v-if="showOperations" #extra>
|
<slot name="extra">
|
<el-button type="primary" @click="handleEdit" v-if="!isViewOnly">编辑</el-button>
|
</slot>
|
</template> <!-- 动态渲染所有数据项 -->
|
<el-descriptions-item
|
v-for="(item, key) in filteredData"
|
:key="`desc-item-${key}`"
|
:label="getFieldLabel(key)"
|
:span="getFieldSpan(key)"
|
>
|
{{ formatValue(item, key) }}
|
</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="handleClose">关闭</el-button>
|
<el-button v-if="!isViewOnly" type="primary" @click="handleEdit">编辑</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { defineProps, computed } from "vue";
|
|
const props = defineProps({
|
// 弹窗标题
|
title: {
|
type: String,
|
default: "详情信息"
|
},
|
// 数据对象
|
formData: {
|
type: Object,
|
default: () => ({})
|
},
|
// 是否为查看模式
|
isViewOnly: {
|
type: Boolean,
|
default: true
|
},
|
// 描述组件标题
|
descriptionsTitle: {
|
type: String,
|
default: ""
|
},
|
// 列数
|
column: {
|
type: Number,
|
default: 2
|
},
|
// 尺寸
|
size: {
|
type: String,
|
default: "default",
|
validator: (value) => ["large", "default", "small"].includes(value)
|
},
|
// 是否显示边框
|
border: {
|
type: Boolean,
|
default: true
|
},
|
// 是否显示操作按钮
|
showOperations: {
|
type: Boolean,
|
default: true
|
},
|
// 字段映射配置 { key: { label: '显示名称', span?: 跨列数, formatter?: 格式化函数 } }
|
fieldConfig: {
|
type: Object,
|
default: () => ({})
|
},
|
// 需要排除显示的字段
|
excludeFields: {
|
type: Array,
|
default: () => ['id', 'createTime', 'updateTime', 'deleted']
|
}, // 需要包含显示的字段(如果设置了,则只显示这些字段)
|
includeFields: {
|
type: Array,
|
default: () => []
|
},
|
// 字段标签映射表 { key: '显示名称' }
|
fieldLabels: {
|
type: Object,
|
default: () => ({})
|
},
|
// 字段显示顺序
|
fieldOrder: {
|
type: Array,
|
default: () => []
|
}
|
});
|
|
const descriptionsVisible = defineModel("descriptionsVisible", {
|
type: Boolean,
|
default: false,
|
});
|
|
const emit = defineEmits(["update:descriptionsVisible", "edit", "close"]);
|
|
// 过滤后的数据
|
const filteredData = computed(() => {
|
if (!props.formData || typeof props.formData !== 'object') {
|
return {};
|
}
|
|
const data = { ...props.formData };
|
let filteredResult = {};
|
|
// 如果指定了包含字段,则只显示这些字段
|
if (props.includeFields.length > 0) {
|
props.includeFields.forEach(field => {
|
if (data.hasOwnProperty(field)) {
|
filteredResult[field] = data[field];
|
}
|
});
|
} else {
|
// 否则排除指定字段
|
Object.keys(data).forEach(key => {
|
if (!props.excludeFields.includes(key)) {
|
filteredResult[key] = data[key];
|
}
|
});
|
}
|
|
// 如果指定了字段顺序,则按顺序重新组织数据
|
if (props.fieldOrder.length > 0) {
|
const orderedResult = {};
|
|
// 先按指定顺序添加字段
|
props.fieldOrder.forEach(field => {
|
if (filteredResult.hasOwnProperty(field)) {
|
orderedResult[field] = filteredResult[field];
|
}
|
});
|
|
// 再添加未在顺序中指定的其他字段
|
Object.keys(filteredResult).forEach(key => {
|
if (!props.fieldOrder.includes(key)) {
|
orderedResult[key] = filteredResult[key];
|
}
|
});
|
|
return orderedResult;
|
}
|
|
return filteredResult;
|
});
|
|
// 获取字段显示标签
|
const getFieldLabel = (key) => {
|
// 1. 优先使用 fieldConfig 中的标签配置
|
if (props.fieldConfig[key]?.label) {
|
return props.fieldConfig[key].label;
|
}
|
|
// 2. 其次使用传入的字段映射表
|
if (props.fieldLabels[key]) {
|
return props.fieldLabels[key];
|
}
|
|
// 3. 最后使用默认的字段名转换(驼峰转空格)
|
return key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
|
};
|
|
// 获取字段跨列数
|
const getFieldSpan = (key) => {
|
return props.fieldConfig[key]?.span || 1;
|
};
|
|
// 格式化值
|
const formatValue = (value, key) => {
|
// 优先使用配置的格式化函数
|
if (props.fieldConfig[key]?.formatter && typeof props.fieldConfig[key].formatter === 'function') {
|
return props.fieldConfig[key].formatter(value);
|
}
|
|
// 默认格式化
|
if (value === null || value === undefined || value === '') {
|
return '--';
|
}
|
|
// 数组格式化
|
if (Array.isArray(value)) {
|
return value.join(', ');
|
}
|
|
// 对象格式化
|
if (typeof value === 'object') {
|
return JSON.stringify(value);
|
}
|
|
// 日期格式化
|
if (key.toLowerCase().includes('time') || key.toLowerCase().includes('date')) {
|
try {
|
const date = new Date(value);
|
if (!isNaN(date.getTime())) {
|
return date.toLocaleString('zh-CN');
|
}
|
} catch (e) {
|
// 如果不是有效日期,返回原值
|
}
|
}
|
|
return String(value);
|
};
|
|
// 处理编辑按钮点击
|
const handleEdit = () => {
|
emit('edit', props.formData);
|
};
|
|
// 处理关闭
|
const handleClose = () => {
|
descriptionsVisible.value = false;
|
emit('close');
|
};
|
</script>
|
|
<style scoped>
|
.margin-top {
|
margin-top: 20px;
|
}
|
|
.dialog-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 12px;
|
}
|
|
.cell-item {
|
display: flex;
|
align-items: center;
|
}
|
|
:deep(.el-descriptions__label) {
|
font-weight: 600;
|
}
|
|
:deep(.el-descriptions__content) {
|
word-break: break-word;
|
}
|
</style>
|