[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: {// 判斷使用的icongetIcon(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>