动态组件和插槽

发布于:2025-07-12 ⋅ 阅读:(15) ⋅ 点赞:(0)

[Vue2]动态组件和插槽

动态组件和插槽来实现外部传入自定义渲染

组件

<template>
  <!-- 回复的处理进度 -->
  <div v-if="steps.length > 0" class="gain-box-header">
    <el-steps direction="vertical">
      <div class="load-but">
        <el-step v-for="item in steps" :key="item.id" :icon="getIcon(item)">
          <div slot="title" class="step_title" @click="item.isShow = !item.isShow">
            <div>{{ item.title || '加载中' }}</div>
            <div>
              <i v-if="item.isShow" class="el-icon-arrow-down"></i>
              <i v-else class="el-icon-arrow-up"></i>
            </div>
          </div>
          <template slot="description">
            <div v-show="item.isShow">
              <!-- 内置类型渲染 -->
              <template v-if="item.type === NormalSteps.Markdown">
                <MdRender v-if="item.content" :content="item.content" />
              </template>
              <template v-else-if="item.type === NormalSteps.Text">
                <div class="step_label">{{ item.content }}</div>
              </template>
              <template v-else-if="item.type === SpecialStep.RxtPolicyList">
                <div class="step_label">{{ item.content }}</div>
              </template>
              
              <!-- 自定义组件渲染 -->
              <template v-else-if="isCustomComponent(item.type)">
                <component 
                  :is="getCustomComponent(item.type)" 
                  :item="item" 
                  :content="item.content"
                  :meta="item.meta"
                  v-bind="item.props || {}"
                />
              </template>
              
              <!-- 自定义插槽渲染 -->
              <template v-else-if="isCustomSlot(item.type)">
                <slot 
                  :name="getSlotName(item.type)"
                  :item="item"
                  :content="item.content"
                  :meta="item.meta"
                >
                  <!-- 插槽默认内容 -->
                  <div class="step_label">{{ item.content }}</div>
                </slot>
              </template>
              
              <!-- 自定义渲染函数 -->
              <template v-else-if="isCustomRender(item.type)">
                <div v-html="getCustomRender(item.type, item)"></div>
              </template>
              
              <!-- 默认文本渲染 -->
              <template v-else>
                <div class="step_label">{{ item.content }}</div>
              </template>
            </div>
          </template>
        </el-step>
      </div>
    </el-steps>
  </div>
</template>

<script>
import MdRender from '@/components/MdRender/think'
import { NormalSteps, SpecialStep } from '@/dicts/DictSse.js'

export default {
  name: 'StepList',
  components: { MdRender },
  props: {
    steps: {
      type: Array,
      default: () => [
        // {
        //   id: '', // 唯一键
        //   type: 'md', // 类型
        //   title: '', // 标题
        //   content: '', // 内容
        //   isStop: false, // 是否结束
        //   isShow: true, // 是否展示
        //   isError: false, // 是否失败 消息停止时改步骤未结束,则标记为失败
        //   meta: {}, // 附加属性
        //   props: {}, // 传递给自定义组件的额外props
        // }
      ]
    },
    // 自定义组件映射 { 'custom-chart': ChartComponent }
    customComponents: {
      type: Object,
      default: () => ({})
    },
    // 自定义渲染函数映射 { 'custom-render': (item) => '<div>...</div>' }
    customRenders: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      NormalSteps,
      SpecialStep
    }
  },
  methods: {
    // 判断使用的icon
    getIcon(item) {
      if (!item.isStop) {
        return 'el-icon-loading'
      }
      if (item.isError) {
        return 'el-icon-error'
      }
      return 'el-icon-success'
    },
    
    // 判断是否为自定义组件
    isCustomComponent(type) {
      return type && type.startsWith('component:') && this.customComponents[type.replace('component:', '')]
    },
    
    // 获取自定义组件
    getCustomComponent(type) {
      const componentName = type.replace('component:', '')
      return this.customComponents[componentName]
    },
    
    // 判断是否为自定义插槽
    isCustomSlot(type) {
      return type && type.startsWith('slot:')
    },
    
    // 获取插槽名称
    getSlotName(type) {
      return type.replace('slot:', '')
    },
    
    // 判断是否为自定义渲染函数
    isCustomRender(type) {
      return type && type.startsWith('render:') && this.customRenders[type.replace('render:', '')]
    },
    
    // 获取自定义渲染结果
    getCustomRender(type, item) {
      const renderName = type.replace('render:', '')
      const renderFn = this.customRenders[renderName]
      return renderFn ? renderFn(item) : item.content
    }
  }
}
</script>

<style scoped>
.gain-box-header {
  width: 100%;
  display: flex;
  align-items: center;
}

.load-but {
  width: 100%;
  padding: 15px 10px;
  background: rgba(13, 62, 135, 0.06);
  border-radius: 17px;
  border: 1px solid rgba(1, 128, 255, 0.03);
}
.load-but > span {
  line-height: 29px;
  color: #625b88;
}

.step_title {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.step_label {
  white-space: pre-wrap;
  font-size: 0.75rem;
  color: #606266;
}

::v-deep .el-steps {
  width: 100%;
}

::v-deep .el-step {
  min-height: 50px;
}

::v-deep .el-step:last-child {
  min-height: 0;
}

::v-deep .el-step__icon.is-icon {
  background: transparent;
}

::v-deep .el-step.is-vertical .el-step__title {
  font-size: 14px;
  color: #222222;
}

::v-deep .el-step.is-vertical .el-step__line {
  top: 27px;
  bottom: 3px;
}

::v-deep .el-icon-success {
  color: #4281ed;
}

::v-deep .el-icon-error {
  color: #c0c4cc;
}

::v-deep .vuepress-markdown-body {
  background: transparent;
}
</style>

外部引用

<template>
  <div>
    <!-- 使用StepList组件 -->
    <StepList 
      :steps="steps" 
      :custom-components="customComponents"
      :custom-renders="customRenders"
    >
      <!-- 自定义插槽渲染 -->
      <template #custom-table="{ item, content, meta }">
        <el-table :data="content" size="mini" border>
          <el-table-column prop="name" label="名称" />
          <el-table-column prop="value" label="" />
        </el-table>
      </template>
      
      <template #custom-progress="{ item, meta }">
        <el-progress 
          :percentage="meta.progress" 
          :status="meta.status"
          :stroke-width="8"
        />
        <div style="margin-top: 8px; font-size: 12px; color: #666;">
          {{ meta.progressText }}
        </div>
      </template>
      
      <template #custom-image="{ item, content }">
        <div class="image-container">
          <img :src="content" :alt="item.title" style="max-width: 100%; height: auto;" />
        </div>
      </template>
    </StepList>
  </div>
</template>

<script>
import StepList from './StepList.vue'
import { NormalSteps, SpecialStep } from '@/dicts/DictSse.js'

// 自定义图表组件
const CustomChart = {
  props: ['item', 'content', 'meta'],
  template: `
    <div class="custom-chart">
      <div class="chart-title">{{ item.title }}</div>
      <div class="chart-content">
        <div v-for="(data, index) in content" :key="index" class="chart-bar">
          <span class="bar-label">{{ data.label }}</span>
          <div class="bar-container">
            <div class="bar-fill" :style="{ width: data.value + '%' }"></div>
          </div>
          <span class="bar-value">{{ data.value }}%</span>
        </div>
      </div>
    </div>
  `,
  style: `
    .custom-chart { padding: 10px; }
    .chart-title { font-weight: bold; margin-bottom: 10px; }
    .chart-bar { display: flex; align-items: center; margin-bottom: 8px; }
    .bar-label { width: 80px; font-size: 12px; }
    .bar-container { flex: 1; height: 20px; background: #f0f0f0; margin: 0 10px; position: relative; }
    .bar-fill { height: 100%; background: #409eff; transition: width 0.3s; }
    .bar-value { font-size: 12px; }
  `
}

// 自定义列表组件
const CustomList = {
  props: ['item', 'content', 'meta'],
  template: `
    <div class="custom-list">
      <div v-for="(listItem, index) in content" :key="index" class="list-item">
        <div class="list-icon">
          <i :class="listItem.icon || 'el-icon-check'"></i>
        </div>
        <div class="list-content">
          <div class="list-title">{{ listItem.title }}</div>
          <div class="list-desc">{{ listItem.description }}</div>
        </div>
      </div>
    </div>
  `,
  style: `
    .custom-list { padding: 10px 0; }
    .list-item { display: flex; align-items: flex-start; margin-bottom: 12px; }
    .list-icon { width: 20px; height: 20px; margin-right: 10px; display: flex; align-items: center; justify-content: center; }
    .list-content { flex: 1; }
    .list-title { font-weight: bold; margin-bottom: 4px; }
    .list-desc { font-size: 12px; color: #666; }
  `
}

export default {
  name: 'StepListExample',
  components: { StepList },
  data() {
    return {
      // 自定义组件映射
      customComponents: {
        'chart': CustomChart,
        'list': CustomList
      },
      
      // 自定义渲染函数映射
      customRenders: {
        'highlight': (item) => {
          return `<div style="background: #fff3cd; padding: 10px; border-radius: 4px; border-left: 4px solid #ffc107;">
            <strong>⚠️ 重要提示</strong><br/>
            ${item.content}
          </div>`
        },
        'code': (item) => {
          return `<pre style="background: #f8f9fa; padding: 12px; border-radius: 4px; overflow-x: auto;"><code>${item.content}</code></pre>`
        }
      },
      
      // 步骤数据
      steps: [
        // 内置类型 - Markdown
        {
          id: '1',
          type: NormalSteps.Markdown,
          title: '步骤1:分析数据',
          content: '## 数据分析结果\n\n- 处理了 **1000** 条记录\n- 发现 `5` 个异常值\n- 准确率达到 **95%**',
          isStop: true,
          isShow: true,
          isError: false,
          meta: {}
        },
        
        // 内置类型 - Text
        {
          id: '2',
          type: NormalSteps.Text,
          title: '步骤2:文本说明',
          content: '这是一个普通的文本说明内容,用于展示基本的文本渲染效果。',
          isStop: true,
          isShow: true,
          isError: false,
          meta: {}
        },
        
        // 自定义组件 - 图表
        {
          id: '3',
          type: 'component:chart',
          title: '步骤3:数据可视化',
          content: [
            { label: '成功率', value: 85 },
            { label: '处理速度', value: 92 },
            { label: '准确率', value: 78 }
          ],
          isStop: true,
          isShow: true,
          isError: false,
          meta: {},
          props: {} // 额外传递给组件的props
        },
        
        // 自定义组件 - 列表
        {
          id: '4',
          type: 'component:list',
          title: '步骤4:任务清单',
          content: [
            { 
              title: '数据预处理', 
              description: '清洗和格式化原始数据',
              icon: 'el-icon-check'
            },
            { 
              title: '模型训练', 
              description: '使用机器学习算法训练模型',
              icon: 'el-icon-loading'
            },
            { 
              title: '结果验证', 
              description: '验证模型的准确性和可靠性',
              icon: 'el-icon-time'
            }
          ],
          isStop: false,
          isShow: true,
          isError: false,
          meta: {}
        },
        
        // 自定义插槽 - 表格
        {
          id: '5',
          type: 'slot:custom-table',
          title: '步骤5:数据表格',
          content: [
            { name: '处理时间', value: '2.5秒' },
            { name: '内存使用', value: '128MB' },
            { name: 'CPU使用率', value: '45%' }
          ],
          isStop: true,
          isShow: true,
          isError: false,
          meta: {}
        },
        
        // 自定义插槽 - 进度条
        {
          id: '6',
          type: 'slot:custom-progress',
          title: '步骤6:处理进度',
          content: '',
          isStop: false,
          isShow: true,
          isError: false,
          meta: {
            progress: 67,
            status: 'active',
            progressText: '正在处理中... 67%'
          }
        },
        
        // 自定义插槽 - 图片
        {
          id: '7',
          type: 'slot:custom-image',
          title: '步骤7:结果展示',
          content: 'https://via.placeholder.com/300x200?text=Processing+Result',
          isStop: true,
          isShow: true,
          isError: false,
          meta: {}
        },
        
        // 自定义渲染函数 - 高亮提示
        {
          id: '8',
          type: 'render:highlight',
          title: '步骤8:重要提示',
          content: '请注意:此操作不可逆,请确保数据已备份!',
          isStop: true,
          isShow: true,
          isError: false,
          meta: {}
        },
        
        // 自定义渲染函数 - 代码块
        {
          id: '9',
          type: 'render:code',
          title: '步骤9:代码示例',
          content: `function processData(data) {
  return data.map(item => ({
    ...item,
    processed: true,
    timestamp: new Date().toISOString()
  }));
}`,
          isStop: true,
          isShow: true,
          isError: false,
          meta: {}
        }
      ]
    }
  }
}
</script>

<style scoped>
.image-container {
  text-align: center;
  padding: 10px;
}
</style>

网站公告

今日签到

点亮在社区的每一天
去签到