Files
school-developer/src/components/DetailPopover/index.vue
guochunsi 5dd173d8d3 a
2026-01-23 18:00:43 +08:00

186 lines
4.4 KiB
Vue

<template>
<el-popover
:placement="placement"
:width="width"
:trigger="trigger"
:popper-class="popperClass">
<template #reference>
<slot name="reference"></slot>
</template>
<!-- 弹出内容 -->
<div class="detail-popover">
<!-- 标题 -->
<div v-if="title" class="detail-title">
<el-icon v-if="titleIcon" class="title-icon">
<component :is="titleIcon" />
</el-icon>
<span>{{ title }}</span>
</div>
<!-- 详情项列表 -->
<div
v-for="(item, index) in items"
:key="index"
:class="['detail-section', { 'horizontal': item.layout === 'horizontal' }]">
<div v-if="item.label" class="section-label">
<el-icon v-if="item.labelIcon" class="label-icon">
<component :is="item.labelIcon" />
</el-icon>
<span>{{ item.label }}</span>
</div>
<div class="section-content" :class="item.contentClass">
<!-- 使用插槽自定义内容 -->
<slot
v-if="$slots[`content-${index}`]"
:name="`content-${index}`"
:item="item">
</slot>
<!-- 默认显示文本内容 -->
<template v-else>
<component
v-if="item.component"
:is="item.component"
v-bind="item.componentProps || {}">
</component>
<span v-else :class="item.contentClass">{{ item.content }}</span>
</template>
</div>
</div>
<!-- 自定义内容插槽 -->
<slot name="custom-content"></slot>
</div>
</el-popover>
</template>
<script setup lang="ts">
import type { Component } from 'vue'
export interface DetailItem {
label?: string // 标签文本
content?: string | number // 内容文本
labelIcon?: Component // 标签图标
layout?: 'horizontal' | 'vertical' // 布局方向,默认 vertical
contentClass?: string // 内容区域的自定义类名
component?: Component // 自定义组件
componentProps?: Record<string, any> // 自定义组件的 props
}
interface Props {
title?: string // 标题
titleIcon?: Component // 标题图标
items?: DetailItem[] // 详情项列表
placement?: 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end'
width?: string | number // Popover 宽度
trigger?: 'click' | 'focus' | 'hover' | 'contextmenu' // 触发方式
popperClass?: string // Popover 自定义类名
}
withDefaults(defineProps<Props>(), {
title: '',
titleIcon: undefined,
items: () => [],
placement: 'right',
width: 300,
trigger: 'click',
popperClass: ''
})
</script>
<script lang="ts">
export default {
name: 'DetailPopover'
}
</script>
<style scoped lang="scss">
.detail-popover {
padding: 0;
.detail-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 2px solid #EBEEF5;
.title-icon {
color: var(--el-color-primary);
font-size: 18px;
}
}
.detail-section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
// 横向布局
&.horizontal {
display: flex;
align-items: center;
gap: 16px;
.section-label {
margin-bottom: 0;
white-space: nowrap;
display: flex;
align-items: center;
gap: 6px;
}
.section-content {
flex: 1;
}
}
// 纵向布局(默认)
&:not(.horizontal) {
.section-label {
margin-bottom: 10px;
}
}
.section-label {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #909399;
font-weight: 500;
.label-icon {
font-size: 14px;
color: var(--el-color-primary);
}
}
.section-content {
font-size: 14px;
color: #303133;
line-height: 1.6;
word-break: break-all;
// 新专业样式(蓝色高亮)
&.new-major {
color: var(--el-color-primary);
font-weight: 500;
}
// 支持嵌套的样式类
:deep(.new-major) {
color: var(--el-color-primary);
font-weight: 500;
}
}
}
}
</style>