press-plus
Version:
637 lines (520 loc) • 16.4 kB
Markdown
# 架构设计说明 - 支持各模板独有 Props
## 📊 核心问题
**场景:** 多个模板组件需要:
1. ✅ **共享相同的逻辑**(props、events、methods)
2. ✅ **有不同的 DOM 结构**(布局、元素顺序、装饰元素)
3. ✅ **各自有独有的 Props**(如 `cctGoodsDetailBarcode`、`bctGoodsDetailBarcode`)
**解决方案:Mixin(共享) + 独立 Props(特有)**
---
## 🏗️ 架构设计
### 文件结构
```
press-convert-exchange-template/
├── press-convert-exchange-template.vue # 主组件(定义所有 props,自动透传)
├── template-mixin.js # 公共逻辑 Mixin(共享 props)
├── a.vue # 模板A(独有 props + 独立 DOM)
├── b.vue # 模板B(独有 props + 独立 DOM)
├── c.vue # 模板C(独有 props + 独立 DOM)
├── demo.vue
├── README.md
└── ARCHITECTURE.md
```
### 架构原理图
```
┌──────────────────────────────────────────────────────┐
│ 主组件 (press-convert-exchange-template.vue) │
│ │
│ 职责: │
│ 1. 定义所有 props(共享 + 各模板独有) │
│ 2. 使用 v-bind="$props" 自动透传所有 props │
│ 3. 使用 v-on="$listeners" 自动透传所有事件 │
│ │
│ Props: │
│ - type (模板类型) │
│ - exchangeList (共享) │
│ - pointIcon (共享) │
│ - cctGoodsDetailBarcode (模板A独有) ⭐ │
│ - templateATitle (模板A独有) ⭐ │
│ - bctGoodsDetailBarcode (模板B独有) ⭐ │
│ - templateBDescription (模板B独有) ⭐ │
│ - templateCDecorImage (模板C独有) ⭐ │
│ - templateCThemeColor (模板C独有) ⭐ │
│ │
│ <component :is="currentTemplate" │
│ v-bind="$props" │
│ v-on="$listeners" /> │
└──────────────────┬───────────────────────────────────┘
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│a.vue│ │b.vue│ │c.vue│
└─────┘ └─────┘ └─────┘
│ │ │
├──────────┼──────────┤
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Mixin │ │ 独立 Props │
│ (共享逻辑) │ │ (各自特有) │
│ │ │ │
│ - props │ │ a.vue: │
│ - methods │ │ - cctGoods.. │
│ - emits │ │ - templateA..│
│ - components │ │ │
│ │ │ b.vue: │
│ │ │ - bctGoods.. │
│ │ │ - templateB..│
│ │ │ │
│ │ │ c.vue: │
│ │ │ - templateC..│
└──────────────┘ └──────────────┘
```
---
## 💡 核心实现
### 1. 主组件 - 定义所有 Props
```vue
<!-- press-convert-exchange-template.vue -->
<template>
<component
:is="currentTemplate"
v-bind="$props" <!-- 自动透传所有 props -->
v-on="$listeners" <!-- 自动透传所有事件 -->
/>
</template>
<script>
import TemplateA from './a.vue';
import TemplateB from './b.vue';
import TemplateC from './c.vue';
export default {
name: 'PressExchangeTemplate',
components: { TemplateA, TemplateB, TemplateC },
props: {
type: {
type: String,
default: 'a',
validator: value => ['a', 'b', 'c'].includes(value),
},
// ========== 共享 Props ==========
exchangeList: { type: Array, default: () => [] },
pointIcon: { type: String, default: '' },
pointInfo: { type: Object, default: () => ({}) },
// ... 其他共享 props
// ========== 模板A 独有 Props ==========
cctGoodsDetailBarcode: { type: String, default: '' },
templateATitle: { type: String, default: '' },
// ========== 模板B 独有 Props ==========
bctGoodsDetailBarcode: { type: String, default: '' },
templateBDescription: { type: String, default: '' },
// ========== 模板C 独有 Props ==========
templateCDecorImage: { type: String, default: '' },
templateCThemeColor: { type: String, default: '' },
},
computed: {
currentTemplate() {
const map = { a: 'TemplateA', b: 'TemplateB', c: 'TemplateC' };
return map[this.type] || 'TemplateA';
},
},
};
</script>
```
**关键点:**
- ✅ 主组件定义**所有** props(共享 + 各模板独有)
- ✅ 使用 `v-bind="$props"` 自动透传,无需手动一个个传递
- ✅ 各模板只接收自己需要的 props
### 2. Mixin - 共享逻辑
```javascript
// template-mixin.js
import exchangeList from 'src/packages/press-convert-exchange-list/press-convert-exchange-list.vue';
import pointCost from 'src/packages/press-convert-point-cost/press-convert-point-cost.vue';
export default {
components: { exchangeList, pointCost },
// 只定义共享的 props
props: {
exchangeList: { type: Array, default: () => [] },
pointIcon: { type: String, default: '' },
pointInfo: { type: Object, default: () => ({}) },
// ... 其他共享 props
},
emits: ['exchangeClick', 'showRewardExplain', 'refresh'],
methods: {
handleExchangeClick(item) {
this.$emit('exchangeClick', item);
},
// ... 其他共享方法
},
};
```
**关键点:**
- ✅ 只定义**共享**的 props、methods、emits
- ✅ 所有模板都会继承这些逻辑
- ✅ 一次修改,所有模板生效
### 3. 模板组件 - 独有 Props + 独立 DOM
#### 模板 A
```vue
<!-- a.vue -->
<template>
<div class="template-a">
<!-- 模板A独有:条形码 -->
<div v-if="cctGoodsDetailBarcode">
{{ cctGoodsDetailBarcode }}
</div>
<!-- 模板A独有:标题 -->
<h2 v-if="templateATitle">
{{ templateATitle }}
</h2>
<!-- 共享组件 -->
<exchange-list
:list="exchangeList"
:point-icon="pointIcon"
:point-info="pointInfo"
@exchange-click="handleExchangeClick"
/>
<point-cost
:point-icon="pointIcon"
:point-info="pointInfo"
/>
</div>
</template>
<script>
import templateMixin from './template-mixin';
export default {
name: 'PressExchangeTemplateA',
mixins: [templateMixin], // 继承共享逻辑
// 定义模板A独有的 props
props: {
cctGoodsDetailBarcode: { type: String, default: '' },
templateATitle: { type: String, default: '' },
},
};
</script>
```
#### 模板 B
```vue
<!-- b.vue -->
<template>
<div class="template-b">
<!-- 反向布局 -->
<point-cost
:point-icon="pointIcon"
:point-info="pointInfo"
/>
<!-- 模板B独有:条形码 -->
<div v-if="bctGoodsDetailBarcode">
{{ bctGoodsDetailBarcode }}
</div>
<!-- 模板B独有:描述 -->
<p v-if="templateBDescription">
{{ templateBDescription }}
</p>
<exchange-list
:list="exchangeList"
:point-icon="pointIcon"
:point-info="pointInfo"
@exchange-click="handleExchangeClick"
/>
</div>
</template>
<script>
import templateMixin from './template-mixin';
export default {
name: 'PressExchangeTemplateB',
mixins: [templateMixin],
// 定义模板B独有的 props
props: {
bctGoodsDetailBarcode: { type: String, default: '' },
templateBDescription: { type: String, default: '' },
},
};
</script>
```
#### 模板 C
```vue
<!-- c.vue -->
<template>
<div class="template-c" :style="{ '--theme-color': templateCThemeColor }">
<!-- 模板C独有:装饰图片 -->
<img v-if="templateCDecorImage" :src="templateCDecorImage" />
<exchange-list
:list="exchangeList"
:point-icon="pointIcon"
:point-info="pointInfo"
@exchange-click="handleExchangeClick"
/>
<point-cost
:point-icon="pointIcon"
:point-info="pointInfo"
/>
</div>
</template>
<script>
import templateMixin from './template-mixin';
export default {
name: 'PressExchangeTemplateC',
mixins: [templateMixin],
// 定义模板C独有的 props
props: {
templateCDecorImage: { type: String, default: '' },
templateCThemeColor: { type: String, default: '#4ecb73' },
},
};
</script>
```
---
## 🎯 Props 流转过程
### 使用示例
```vue
<PressExchangeTemplate
type="a"
:exchange-list="list"
cct-goods-detail-barcode="1234567890123"
template-a-title="限时兑换"
@exchangeClick="handleClick"
/>
```
### 流转过程
```
1. 用户传入 props
↓
2. 主组件接收所有 props
{
type: 'a',
exchangeList: [...],
cctGoodsDetailBarcode: '1234567890123',
templateATitle: '限时兑换',
bctGoodsDetailBarcode: '', // 未传入,使用默认值
...
}
↓
3. v-bind="$props" 自动透传所有 props
↓
4. 模板A (a.vue) 接收 props
- 从 mixin 继承:exchangeList, pointIcon, ...
- 自己定义:cctGoodsDetailBarcode, templateATitle
- 忽略其他模板的 props(bctGoodsDetailBarcode 等)
↓
5. 模板A 使用 props 渲染
```
---
## ✅ 方案优势
### 1. 避免代码重复
| 内容 | 无 Mixin | Mixin 方案 |
|------|---------|-----------|
| 共享 props 定义 | 重复 3 次 | 定义 1 次 |
| 共享 methods 定义 | 重复 3 次 | 定义 1 次 |
| 代码行数 | ~400 行 | ~200 行 |
### 2. 支持独有 Props
```vue
<!-- 模板A 使用独有 props -->
<PressExchangeTemplate
type="a"
cct-goods-detail-barcode="123"
template-a-title="标题"
/>
<!-- 模板B 使用独有 props -->
<PressExchangeTemplate
type="b"
bct-goods-detail-barcode="456"
template-b-description="描述"
/>
```
### 3. Props 自动透传
```vue
<!-- 主组件 -->
<component
:is="currentTemplate"
v-bind="$props" <!-- 自动传递所有 props -->
/>
```
**优势:**
- ✅ 无需手动一个个传递 props
- ✅ 添加新 props 无需修改透传代码
- ✅ 各模板自动接收需要的 props
### 4. 支持不同 DOM 结构
- 模板A:标准布局 + 条形码 + 标题
- 模板B:反向布局 + 条形码 + 描述
- 模板C:装饰布局 + 图片 + 主题色
---
## 📈 对比分析
### 方案对比
| 方案 | 代码重复 | 独有 Props | DOM 灵活性 | 维护成本 |
|------|---------|-----------|-----------|---------|
| **无 Mixin** | ❌ 高 | ✅ 支持 | ✅ 支持 | ❌ 高 |
| **Mixin 方案** | ✅ 无 | ✅ 支持 | ✅ 支持 | ✅ 低 |
### Props 管理对比
#### 方案一:各模板独立定义所有 props(❌ 不推荐)
```javascript
// a.vue - 重复定义
props: {
exchangeList: { ... }, // 重复
pointIcon: { ... }, // 重复
cctGoodsDetailBarcode: { ... }, // 独有
}
// b.vue - 重复定义
props: {
exchangeList: { ... }, // 重复
pointIcon: { ... }, // 重复
bctGoodsDetailBarcode: { ... }, // 独有
}
```
**问题:**
- ❌ 共享 props 重复定义 3 次
- ❌ 修改共享 props 需要同步 3 个文件
#### 方案二:Mixin + 独有 Props(✅ 推荐)
```javascript
// template-mixin.js - 共享 props
props: {
exchangeList: { ... },
pointIcon: { ... },
}
// a.vue - 只定义独有 props
props: {
cctGoodsDetailBarcode: { ... },
templateATitle: { ... },
}
// b.vue - 只定义独有 props
props: {
bctGoodsDetailBarcode: { ... },
templateBDescription: { ... },
}
```
**优势:**
- ✅ 共享 props 只定义 1 次
- ✅ 各模板只定义独有 props
- ✅ 修改共享 props 只需改 mixin
---
## 🔧 扩展新模板
### 步骤 1:在主组件添加新模板的独有 props
```javascript
// press-convert-exchange-template.vue
props: {
// ... 现有 props
// ========== 模板D 独有 Props ==========
templateDSpecialField: {
type: String,
default: '',
},
},
```
### 步骤 2:创建新模板文件
```vue
<!-- d.vue -->
<template>
<div class="template-d">
<!-- 使用独有 props -->
<div v-if="templateDSpecialField">
{{ templateDSpecialField }}
</div>
<!-- 使用共享组件 -->
<exchange-list
:list="exchangeList"
:point-icon="pointIcon"
:point-info="pointInfo"
@exchange-click="handleExchangeClick"
/>
<point-cost
:point-icon="pointIcon"
:point-info="pointInfo"
/>
</div>
</template>
<script>
import templateMixin from './template-mixin';
export default {
name: 'PressExchangeTemplateD',
mixins: [templateMixin], // 继承共享逻辑
// 定义独有 props
props: {
templateDSpecialField: {
type: String,
default: '',
},
},
};
</script>
```
### 步骤 3:在主组件注册
```javascript
// press-convert-exchange-template.vue
import TemplateD from './d.vue';
export default {
name: 'PressExchangeTemplate',
components: { TemplateA, TemplateB, TemplateC, TemplateD },
props: {
type: {
type: String,
default: 'a',
validator: value => ['a', 'b', 'c', 'd'].includes(value),
},
// ... 现有 props
// ========== 模板D 独有 Props ==========
templateDSpecialField: {
type: String,
default: '',
},
},
computed: {
currentTemplate() {
const map = {
a: 'TemplateA',
b: 'TemplateB',
c: 'TemplateC',
d: 'TemplateD',
};
return map[this.type] || 'TemplateA';
},
},
};
```
---
## 📝 最佳实践
### 1. Props 命名规范
```javascript
// ✅ 好的命名
cctGoodsDetailBarcode // 清晰表明是模板A的条形码
templateATitle // 清晰表明是模板A的标题
bctGoodsDetailBarcode // 清晰表明是模板B的条形码
// ❌ 不好的命名
barcode // 不清楚是哪个模板的
title // 容易与共享 props 混淆
```
### 2. Mixin 中放什么?
✅ **应该放:**
- 所有模板都需要的 props
- 所有模板都需要的 methods
- 所有模板都需要的 emits
- 公共的子组件注册
❌ **不应该放:**
- 某个模板独有的 props
- 某个模板独有的 methods
- Template(每个模板独立)
### 3. 主组件的职责
✅ **应该做:**
- 定义所有 props(共享 + 各模板独有)
- 使用 `v-bind="$props"` 自动透传
- 根据 type 动态渲染对应模板
❌ **不应该做:**
- 处理业务逻辑
- 直接操作 DOM
- 定义 methods(应该在 mixin 或各模板中)
---
## 🏆 总结
**Mixin + 独有 Props 方案完美解决了"共享逻辑 + 不同结构 + 独有属性"的场景!**
### 核心优势
1. ✅ **避免代码重复** - 共享逻辑在 mixin 中,减少 60% 代码
2. ✅ **支持独有 Props** - 各模板可以定义自己的 props
3. ✅ **支持不同 DOM** - 各模板可以有完全不同的布局
4. ✅ **Props 自动透传** - 使用 `v-bind="$props"`,无需手动传递
5. ✅ **易于维护** - 修改共享逻辑只需改 mixin
6. ✅ **易于扩展** - 添加新模板只需 3 步
### 适用场景
✅ **适合:**
- 多个模板共享大部分逻辑
- 但各模板有独有的 props
- 且各模板有不同的 DOM 结构
❌ **不适合:**
- 模板之间逻辑差异很大
- 不需要独有 props
- DOM 结构完全相同(用配置对象更简单)