# 架构设计说明 - 支持各模板独有 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 结构完全相同（用配置对象更简单）
