vue2-elm组件库开发:封装可复用的Element UI扩展组件
在Vue.js项目开发中,组件复用是提升效率和保证代码一致性的关键。本文将以vue2-elm项目为例,详细介绍如何基于Element UI组件库封装可复用的扩展组件,涵盖购物车交互、提示弹窗、评分组件等实战场景,帮助开发者掌握组件设计的核心思路和实现技巧。
组件封装基础:Mixin与样式抽象
组件封装的第一步是建立基础复用机制。vue2-elm项目通过Mixin实现跨组件逻辑复用,通过SCSS变量和混合宏实现样式统一管理,为后续组件开发奠定基础。
逻辑复用:通用功能抽象
项目中的src/components/common/mixin.js定义了两个核心Mixin:
loadMore:实现滚动加载功能,通过自定义指令监听滚动事件,当滚动到底部时触发加载回调getImgPath:统一处理图片路径,支持根据环境变量切换图片CDN地址
// 图片路径处理Mixin示例
export const getImgPath = {
methods: {
getImgPath(path) {
let suffix;
if (!path) {
return '//elm.cangdu.org/img/default.jpg'
}
if (path.indexOf('jpeg') !== -1) {
suffix = '.jpeg'
} else {
suffix = '.png'
}
let url = '/' + path.substr(0, 1) + '/' + path.substr(1, 2) + '/' + path.substr(3) + suffix;
return 'https://fuss10.elemecdn.com' + url
}
}
}
样式复用:SCSS混合宏设计
src/style/mixin.scss定义了一系列样式混合宏,实现样式代码复用:
// 宽高定义混合宏
@mixin wh($width, $height){
width: $width;
height: $height;
}
// 字体大小与颜色混合宏
@mixin sc($size, $color){
font-size: $size;
color: $color;
}
// Flex布局混合宏
@mixin fj($type: space-between){
display: flex;
justify-content: $type;
}
这些基础工具为后续组件开发提供了统一的技术规范,确保不同组件间的风格一致性和逻辑复用性。
核心业务组件:购物车交互实现
购物车组件是电商类应用的核心交互模块,vue2-elm项目中的src/components/common/buyCart.vue实现了完整的购物车加减、规格选择、动画效果等功能,是组件封装的典型案例。
组件结构设计
购物车组件采用条件渲染处理普通商品和多规格商品的不同交互逻辑:
<template>
<section class="cart_module">
<!-- 普通商品 - 直接显示加减按钮 -->
<section v-if="!foods.specifications.length" class="cart_button">
<transition name="showReduce">
<span @click="removeOutCart(...)">
<svg><use xlink:href="#cart-minus"></use></svg>
</span>
</transition>
<transition name="fade">
<span class="cart_num" v-if="foodNum">{{foodNum}}</span>
</transition>
<svg class="add_icon" @click="addToCart(...)">
<use xlink:href="#cart-add"></use>
</svg>
</section>
<!-- 多规格商品 - 显示规格选择按钮 -->
<section v-else class="choose_specification">
<section class="choose_icon_container">
<transition name="showReduce">
<svg class="specs_reduce_icon" v-if="foodNum" @click="showReduceTip">
<use xlink:href="#cart-minus"></use>
</svg>
</transition>
<transition name="fade">
<span class="cart_num" v-if="foodNum">{{foodNum}}</span>
</transition>
<span class="show_chooselist" @click="showChooseList(foods)">选规格</span>
</section>
</section>
</section>
</template>
状态管理与事件通信
组件通过Vuex管理购物车数据,通过计算属性实时获取商品数量:
computed: {
...mapState(['cartList']),
shopCart() {
return Object.assign({}, this.cartList[this.shopId]);
},
foodNum() {
let category_id = this.foods.category_id;
let item_id = this.foods.item_id;
if (this.shopCart && this.shopCart[category_id] && this.shopCart[category_id][item_id]) {
let num = 0;
Object.values(this.shopCart[category_id][item_id]).forEach(item => {
num += item.num;
});
return num;
} else {
return 0;
}
}
}
通过自定义事件与父组件通信,实现跨组件交互:
methods: {
...mapMutations(['ADD_CART', 'REDUCE_CART']),
addToCart(...) {
this.ADD_CART({shopid: this.shopId, ...});
this.$emit('showMoveDot', this.showMoveDot, elLeft, elBottom);
},
showChooseList(foodScroll) {
this.$emit('showChooseList', foodScroll);
}
}
动画效果实现
组件使用Vue的transition组件实现数字变化和按钮显隐动画:
.showReduce-enter-active, .showReduce-leave-active {
transition: all .3s ease-out;
}
.showReduce-enter, .showReduce-leave-active {
opacity: 0;
transform: translateX(1rem);
}
.fade-enter-active, .fade-leave-active {
transition: all .3s;
}
.fade-enter, .fade-leave-active {
opacity: 0;
}
购物车加减按钮的交互效果如图所示:
反馈组件:AlertTip与Loading实现
用户交互反馈是提升体验的重要环节,vue2-elm项目封装了提示弹窗和加载动画组件,实现统一的反馈机制。
提示弹窗组件:alertTip.vue
src/components/common/alertTip.vue实现了操作结果提示功能,包含自定义图标和确认按钮:
<template>
<div class="alet_container">
<section class="tip_text_container">
<div class="tip_icon">
<span></span>
<span></span>
</div>
<p class="tip_text">{{alertText}}</p>
<div class="confrim" @click="closeTip">确认</div>
</section>
</div>
</template>
<style lang="scss" scoped>
@import '../../style/mixin';
@keyframes tipMove {
0% { transform: scale(1) }
35% { transform: scale(.8) }
70% { transform: scale(1.1) }
100% { transform: scale(1) }
}
.tip_text_container {
position: absolute;
top: 50%;
left: 50%;
margin-top: -6rem;
margin-left: -6rem;
width: 12rem;
animation: tipMove .4s ;
background-color: rgba(255,255,255,1);
border-radius: 0.25rem;
padding-top: .6rem;
display: flex;
flex-direction: column;
align-items: center;
.tip_icon {
@include wh(3rem, 3rem);
border: 0.15rem solid #f8cb86;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
span:nth-of-type(1) {
@include wh(.12rem, 1.5rem);
background-color: #f8cb86;
}
span:nth-of-type(2) {
@include wh(.2rem, .2rem);
border-radius: 50%;
margin-top: .2rem;
background-color: #f8cb86;
}
}
.tip_text {
@include sc(.7rem, #333);
line-height: .9rem;
text-align: center;
margin-top: .8rem;
padding: 0 .4rem;
}
.confrim {
@include sc(.8rem, #fff);
font-weight: bold;
margin-top: .8rem;
background-color: #4cd964;
width: 100%;
text-align: center;
line-height: 1.8rem;
border-bottom-left-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
}
}
</style>
加载动画组件:loading.vue
src/components/common/loading.vue实现了页面加载状态提示,使用CSS动画和定时器控制加载效果:
<template>
<div class="loading_container">
<div class="load_img" :style="{backgroundPositionY: -(positionY%7)*2.5 + 'rem'}"></div>
<svg class="load_ellipse" xmlns="http://www.w3.org/2000/svg" version="1.1">
<ellipse cx="26" cy="10" rx="26" ry="10" style="fill:#ddd;stroke:none;"></ellipse>
</svg>
</div>
</template>
<script>
export default {
data() {
return {
positionY: 0,
timer: null
};
},
mounted() {
this.timer = setInterval(() => {
this.positionY++;
}, 600);
},
beforeDestroy() {
clearInterval(this.timer);
}
};
</script>
<style lang="scss" scoped>
@import '../../style/mixin';
@keyframes load {
0% {transform: translateY(0px);}
50% {transform: translateY(-50px);}
100% {transform: translateY(0px);}
}
@keyframes ellipse {
0% {transform: scale(1);}
50% {transform: scale(0.3);}
100% {transform: scale(1);}
}
.loading_container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
@include wh(2.5rem, 2.5rem);
}
.load_img {
@include wh(100%, 100%);
background: url(../../images/icon_loading.png) no-repeat 0 0;
background-size: 2.5rem auto;
animation: load .6s infinite ease-in-out;
}
.load_ellipse {
position: absolute;
@include wh(2.6rem, 2rem);
top: 2.2rem;
left: 0.2rem;
animation: ellipse .6s infinite ease-in-out;
}
</style>
加载动画效果如图所示:
业务展示组件:RatingStar评分组件
评分展示是电商应用的常见功能,src/components/common/ratingStar.vue实现了一个灵活的星级评分组件,支持任意评分值的展示。
组件实现原理
组件通过两层SVG星星叠加,通过控制上层星星容器的宽度实现评分效果:
<template>
<div class="rating_container">
<!-- 灰色星星底层 -->
<section class="star_container">
<svg class="grey_fill" v-for="num in 5" :key="num">
<use xlink:href="#star"></use>
</svg>
</section>
<!-- 橙色星星上层,通过width控制显示宽度 -->
<div :style="'width:' + rating*2/5 + 'rem'" class="star_overflow">
<section class="star_container">
<svg class="orange_fill" v-for="num in 5" :key="num">
<use xlink:href="#star"></use>
</svg>
</section>
</div>
</div>
</template>
<style lang="scss" scoped>
@import '../../style/mixin';
.rating_container {
position: relative;
top: .2rem;
@include wh(2rem, .4rem);
.star_overflow {
overflow: hidden;
position: relative;
height: 100%;
}
.star_container {
position: absolute;
display: flex;
width: 2rem;
height: 0.4rem;
top: -0.02rem;
left: -0.02rem;
.grey_fill {
fill: #d1d1d1;
}
.orange_fill {
fill: #ff9a0d;
}
}
}
</style>
使用方式与效果
组件通过props接收评分值,实现灵活复用:
export default {
props: ['rating']
};
在父组件中使用:
<rating-star :rating="4.5"></rating-star>
评分组件效果如图所示:
组件封装最佳实践总结
通过分析vue2-elm项目的组件设计,我们可以总结出以下组件封装最佳实践:
接口设计原则
- 单一职责:每个组件专注于单一功能,如购物车组件专注于购物车操作,评分组件专注于评分展示
- Props设计:明确组件输入输出,通过props接收数据,通过自定义事件传递操作结果
- 状态管理:区分本地状态和全局状态,局部状态使用组件data,全局状态使用Vuex
代码组织规范
- 目录结构:按功能划分组件目录,公共组件放在src/components/common/目录
- 样式隔离:使用scoped属性和BEM命名规范避免样式冲突
- 逻辑复用:提取通用逻辑到Mixin,如src/components/common/mixin.js
- 样式复用:使用SCSS变量和混合宏统一样式风格,如src/style/mixin.scss
性能优化要点
- 条件渲染:使用v-if/v-else减少DOM节点,如购物车组件区分普通商品和多规格商品
- 事件委托:通过事件冒泡减少事件监听,如使用事件委托处理列表项点击
- 动画优化:使用CSS动画代替JS动画,减少重排重绘
- 销毁清理:及时清理定时器和事件监听,如loading组件在beforeDestroy钩子中清除定时器
总结与展望
本文通过分析vue2-elm项目的组件实现,详细介绍了购物车、提示弹窗、评分组件等常见业务组件的封装方法,展示了如何基于Element UI扩展开发符合业务需求的复用组件。这些组件不仅满足了项目功能需求,还通过精心设计的接口和良好的代码组织实现了高度复用。
组件化开发是Vue.js的核心优势之一,合理的组件设计可以显著提升开发效率和代码质量。未来可以进一步完善组件文档,添加单元测试,将组件发布为npm包,实现跨项目复用,构建企业级组件库。
更多组件实现细节可参考项目源代码:
- 项目主页:README.md
- 组件目录:src/components/
- 样式资源:src/style/
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考






