一、设计优质vue业务组件
(一)基本原则:
1.组件需要复用到很多的地方,设计组件的核心就是可扩展性和贴合业务平衡。业务组件更偏向业务。基础组件更偏向扩展
2.尽量提供简便,满足大多数场景下0配置或极少配置可用,特殊场景也可以扩展自定义
(二)分解ui结构
A. 图片区域分析:
问题分析:
1.图片控制大小
2.统一兜底图片
3.图片地址
专门思考:
1.以后是否有图片上需要加上icon
2.图片的兜底图是否会有替换的可能
图片区方案:
1.图片大小默认按设计图,有特殊需要用css,组件css层级设计的短一点(意思是不要套太多的层级,以免未来调整图片大小的
时候不方便。比如可以是item-wraper myitem/img-container,不要套四五级那么多)–方便调节
2.统一兜底图,留出兜底图的自定义空间
3.默认显示一张图片,如果有特殊需要,支持插槽替换
4.地址不单独传入,直接传入整体的商品信息,组件内部提取地址,也预留props可以强制定义url地址
示例代码:
<script setup>
import { ref } from "vue";
//示例信息
let {shopItem={},errorImg,forceImageUrl}=defineProps(['shopItem','errorImg','forceImageUrl'])
let imgurl = ref(forceImageUrl || shopItem.imgurl || 'http://localhost:5173/logo.svg');
</script>
<template>
<div class="item-wraper">
<div class="img-container">
<slot name="imgslot">
<img src="imgurl" @error="()=>{
if (errorImg) {
imgurl=errorImg;
return;
}
imgurl='http://localhost:5173/logo.svg'
}">
</slot>
</div>
<div class="info-container">
</div>
</div>
</template>
<style>
/* 样式随意设置一下 */
</style>
B. 标题区分析
问题分析:
1.标题字体大小,字体样式是否固定
2.标题有时候两行省略,有时候一行省略该如何控制
3.标题的内容如何处理
标题区域方案:
1.按额外内容区有几条决定标题几行,等于四条就一行,小于等于三条显示两行,预留props可以强制指定几行
2.字体大小,样式固定,交由css处理
3.内容默认按组件传入的所有商品信息提取标题,支持强制指定
示例代码:
<script setup>
import { ref,computed } from "vue";
//示例信息
let {shopItem={},errorImg,forceImageUrl,forceTitle}=defineProps(['shopItem','errorImg','forceImageUrl','forceTitle'])
//标题区域显示一行还是两行需要根据额外信息区域来确定,因此此处先写成默认的。
let istwo = computed(()=>{
return false;
})
let imgurl = ref(forceImageUrl || shopItem.imgurl || 'http://localhost:5173/logo.svg');
</script>
<template>
<div class="item-wraper">
<div class="img-container">
<slot name="imgslot">
<img src="imgurl" @error="()=>{
if (errorImg) {
imgurl=errorImg;
return;
}
imgurl='http://localhost:5173/logo.svg'
}">
</slot>
</div>
<div class="info-container">
<h4 :class="{'info-title':true,'two-line':istwo}">
{{ forceTitle || shopItem.title || 默认标题}}
</h4>
</div>
</div>
</template>
<style>
/* 样式随意设置一下 */
</style>
C. 额外信息区
问题分析:
1.行数不固定
方案1:整个额外信息区都用插槽插入
(可扩展性、灵活性最高,但是使用者需要自己写整个信息区域的html和css,工作量较大)
方案2:分成4个插槽,按需插入
(扩展性不错,使用者不需要写区域的布局,减少工作量)
示例代码:
<!-- 这是组件文件 -->
<script setup>
import { ref, computed, useSlots } from "vue";
//示例信息
let { shopItem = {}, errorImg, forceImageUrl, forceTitle } = defineProps(['shopItem', 'errorImg', 'forceImageUrl', 'forceTitle'])
//使用了几个插槽
let slotObj = useSlots();
//标题区域显示一行还是两行需要根据额外信息区域来确定,因此此处先写成默认的。
let istwo = computed(() => {
let slotArr = Object.key(slotObj)
let slotrow = slotArr.filter((item) => {
if (item.indexOf('row') !== -1) {
return item;
}
})
if (slotrow.length === 4) {
return false;
} else {
return true;
}
})
let imgurl = ref(forceImageUrl || shopItem.imgurl || 'http://localhost:5173/logo.svg');
</script>
<template>
<div class="item-wraper">
<div class="img-container">
<slot name="imgslot">
<img src="imgurl" @error="() => {
if (errorImg) {
imgurl = errorImg;
return;
}
imgurl = 'http://localhost:5173/logo.svg'
}">
</slot>
</div>
<div class="info-container">
<h4 :class="{ 'info-title': true, 'two-line': istwo }">
{{ forceTitle || shopItem.title || 默认标题 }}
</h4>
<div class="info-extra">
<div class="info-row">
<slot name="row1"></slot>
</div>
<div class="info-row">
<slot name="row2"></slot>
</div>
<div class="info-row">
<slot name="row3"></slot>
</div>
<div class="info-row row-bottom">
<slot name="row4"></slot>
</div>
</div>
</div>
</div>
</template>
<style>
/* 样式随意设置一下 */
.info-container {
width: 0;
padding-left: 10px;
background-color: pink;
display: flex;
flex-direction: column;
flex-grow: 1;
.info-extra {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.info-row {
margin-bottom: 10px;
}
.row-bottom {
margin-top: auto;
/* 在底部显示 */
}
}
</style>
<!-- 这是页面文件 -->
<template>
<div class="wraper">
<baseItem :shop-item="{
imgurl: 'http://localhost:5173/echart1s.png'
}">
<template #row1>
123
</template>
<template #row2>
123
</template>
<template #row3>
123
</template>
<template #row4>
123
</template>
</baseItem>
</div>
</template>
<script setup>
import { ref } from "vue";
import baseItem from "./components"
</script>
<style>
</style>
D. 按钮区域
按钮区域基本无规律,直接插槽传入
添加的代码:
<div class="button-container">
<slot name="button">
</slot>
</div>
(三)行为角度
从行为角度来看,根据不同的点击,会产生不同的效果。而且商品都会有公共的
异常状态的点击效果。通过给代码添加点击事件@click="()=>{}"可以实现行为角
度的需求。
添加行为角度后的代码:
下面分别是页面文件和组件文件的代码。
在页面文件中使用该组件,并使用ClickShop监听,在这里可以对基本行为做出拓展。
在组件文件中,增加点击事件的方法,该方法中有点击跳转到目标链接、异常情况的处理,回调等功能。
<!-- 这是页面文件 -->
<template>
<div class="wraper">
<baseItem @ClickShop="()=>{
//父组件可以在此监听做拓展行为
}" :shop-item="{
imgurl: 'http://localhost:5173/echart1s.png'
}">
<template #row1>
123
</template>
<template #row2>
123
</template>
<template #row3>
123
</template>
<template #row4>
123
</template>
</baseItem>
</div>
</template>
<script setup>
import { ref } from "vue";
import baseItem from "./components"
</script>
<style>
</style>
<!-- 这是组件文件 -->
<template>
<div class="item-wraper">
<div class="img-container">
<slot name="imgslot">
<img src="imgurl" @error="() => {
if (errorImg) {
imgurl = errorImg;
return;
}
imgurl = 'http://localhost:5173/logo.svg'
}">
</slot>
</div>
<div class="info-container" @click="defaultClick">
<h4 :class="{ 'info-title': true, 'two-line': istwo }">
{{ forceTitle || shopItem.title || 默认标题 }}
</h4>
<div class="info-extra">
<div class="info-row">
<slot name="row1"></slot>
</div>
<div class="info-row">
<slot name="row2"></slot>
</div>
<div class="info-row">
<slot name="row3"></slot>
</div>
<div class="info-row row-bottom">
<slot name="row4"></slot>
</div>
</div>
</div>
<div class="button-container">
<slot name="button">
</slot>
</div>
</div>
</template>
<script setup>
import { ref, computed, useSlots } from "vue";
//示例信息
let { shopItem = {},
errorImg,
forceImageUrl,
forceTitle,
targeturl,
noerroStatue
} = defineProps(['shopItem',
'errorImg',
'forceImageUrl',
'forceTitle',
'targeturl',
'noerroStatue'
])
//定义回调
let emit = defineEmits('ClickShop','ClickError');
//使用了几个插槽
let slotObj = useSlots();
//标题区域显示一行还是两行需要根据额外信息区域来确定,因此此处先写成默认的。
let istwo = computed(() => {
let slotArr = Object.key(slotObj)
let slotrow = slotArr.filter((item) => {
if (item.indexOf('row') !== -1) {
return item;
}
})
if (slotrow.length === 4) {
return false;
} else {
return true;
}
})
let imgurl = ref(forceImageUrl || shopItem.imgurl || 'http://localhost:5173/logo.svg');
function defaultClick() {
if (targeturl) {
window.location.href = targeturl;
}
if (!noerroStatue) {
if (shopItem.errorArr.length!==0) {
let str = '';//拼接错误信息
shopItem.errorArr.forEach((mes) => {
str+=mes;
});
alert(str);//弹出错误信息
}
emit("ClickError")
}
emit("ClickShop")
}
</script>
<style>
/* 样式随意设置一下 */
.info-container {
width: 0;
padding-left: 10px;
background-color: pink;
display: flex;
flex-direction: column;
flex-grow: 1;
.info-extra {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.info-row {
margin-bottom: 10px;
}
.row-bottom {
margin-top: auto;
/* 在底部显示 */
}
}
</style>
(四)总结
分析ui+分析行为
注意:
1.尽量少的props和插槽
2.写的时候,思考是否可以更加贴近业务做的更加简便
3.留拓展接口
4.行为记得开关和回调
开关:可以让使用者决定不去做该事情,点击行为后面加上默认行为。
回调:触发父组件的某个监听,可以做对于基本行为的拓展
本文是我在学习b站这个博主https://space.bilibili.com/2114295304的组件设计课程的笔记和一些感悟吧(图片来源于该up的视频)总结下来便于复习也帮助大家学习!如有不正之处请指正,我会及时修改。