前端-一个通用详情框

效果图:

 代码:

公用组件部分

子组件1:page-detail.vue

<template>
  <a-space direction="vertical" :size="12" class="page-detail">
    <page-title v-if="title || $slots.title" class="page-detail__title">
      <slot name="title">{
   
   { title }}</slot>
    </page-title>
    <a-spin :spinning="loading">
      <slot>
        <a-row class="page-detail__list">
          <a-col
            v-for="(field, index) of _fields"
            :key="index"
            :offset="field.offset ?? offset"
            :span="field.span ?? span"
            class="page-detail__item"
          >
            <span
              class="page-detail__label"
              :style="{
                width: `${field.labelWidth ?? labelWidth}px`,
                ...(labelStyle ?? field.labelStyle)
              }"
            >
              <slot :name="field.labelSlot" :text="field.label">
                {
   
   { field.label }}:
              </slot>
            </span>
            <span
              class="page-detail__text"
              :style="textStyle ?? field.textStyle"
            >
              <slot :name="field.textSlot" :text="field.text">
                <ellipsis v-if="field.ellipsis">
                  {
   
   { field.text ?? '-' }}
                </ellipsis>
                <template v-else>
                  {
   
   { field.text ?? '-' }}
                </template>
              </slot>
            </span>
          </a-col>
        </a-row>
      </slot>
    </a-spin>
  </a-space>
</template>

<script setup>
  import { computed } from 'vue';
  import PageTitle from '../page-title/index.vue';
  import Ellipsis from '../ellipsis/index.vue';

  const props = defineProps({
    title: String,
    loading: Boolean,
    offset: {
      type: Number,
      default: 0
    },
    span: {
      type: Number,
      default: 8
    },
    labelWidth: {
      type: Number,
      default: 120
    },
    labelStyle: Object,
    textStyle: Object,
    fields: {
      type: Array,
      default: () => []
    }
  });

  const _fields = computed(() =>
    props.fields.map((field) => ({
      ...field,
      ellipsis: field.ellipsis === undefined ? true : !!field.ellipsis
    }))
  );
</script>

<style lang="less" scoped>
  .page-detail {
    width: 100%;
    .page-detail__list {
      padding: 20px 20px 0 10px;
      background: #f5f7fa;
      border-radius: 2px;
      .page-detail__item {
        display: flex;
        margin-bottom: 16px;
        .page-detail__label {
          flex: 0 0 auto;
          text-align: right;
          color: #9c9c9c;
        }
        .page-detail__text {
          flex: 1;
          width: 0;
          word-break: break-word;
        }
      }
    }
  }
</style>

子组件2: page-title.vue

<template>
  <div class="page-title">
    <slot>{
   
   { title }}</slot>
  </div>
</template>

<script setup>
  defineProps({
    title: String
  });
</script>

<style lang="less" scoped>
  .page-title {
    position: relative;
    padding-left: 12px;
    font-weight: 500;
    font-size: 16px;
    color: #000000d9;
    &::before {
      content: '';
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      border-left: 4px solid var(--primary-color);
      border-top: 2px solid transparent;
      border-bottom: 2px solid transparent;
      height: 18px;
    }
  }
</style>

子组件3: ellipsis.vue

<template>
  <div ref="ellipsisRef" class="ellipsis" @mouseenter="handleMouseEnter">
    <a-tooltip v-if="overflow" :align="{ offset }">
      <template #title>
        <slot></slot>
      </template>
      <slot></slot>
    </a-tooltip>
    <span v-else>
      <slot></slot>
    </span>
  </div>
</template>

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

  const ellipsisRef = ref();

  const overflow = ref(false);

  const offset = ref(0);

  const handleMouseEnter = () => {
    const width = ellipsisRef.value?.getBoundingClientRect().width;
    const contentWidth =
      ellipsisRef.value?.children[0].getBoundingClientRect().width;
    overflow.value = contentWidth > width;
    offset.value = (width - contentWidth) / 2;
  };
</script>

<style scoped>
  .ellipsis {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
</style>

父组件如何使用:

<page-detail title="这里是标题" :fields="fields">
      <template #fileList>
        <FileUpload disabled v-model:file-list="form.fileList" />
      </template>
      <empty v-if="!form" />
    </page-detail>


  const fields = computed(() => {
    const fields = [
      { label: '报告编号', text: form.value?.reportCode },
      {
        label: '结果',
        text: statusValueLabelReport.value?.[form.value?.result]
      },
      {
        label: '分析担当',
        text: form.value?.analysisDeptName
      },

      {
        label: '分析日期',
        text: form.value?.analysisDate
      },
      { label: '下次分析', text: form.value?.nextAnalysis },
      {
        label: '频率',
        text: statusValueLabelFrequency.value?.[form.value?.analysisFrequency]
      },

      { label: '分析报告', textSlot: 'fileList' }
    ];
    return fields;
  });