UNPKG

press-plus

Version:
637 lines (520 loc) 16.4 kB
# 架构设计说明 - 支持各模板独有 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 结构完全相同(用配置对象更简单)