Writing code is sometimes like working with springs. A seemingly simple item may not be easy to process
1. Introduction
Recently, when I was working on a project, I saw that there were two requirements with the same functions but different interaction styles. In order to facilitate maintenance, I encapsulated the components. I found that a seemingly simple component, if it is to be encapsulated in general, there are also many things to consider.
This article is just an example of where you can start to increase the versatility of components. And provide some ideas for encapsulation. The components mentioned still have a lot to do with the requirements of the project, and are more or less custom developed for the project. They may not be available out of the box on other projects, and they will need to be modified if they are to be used.
2. Look at components first
This component looks simple enough to write in a moment
For the sake of space, CSS, and some unrelated JS code is not provided, need to see the source code can be moved: article example source :HandleButtonOld, project complete code: project code
HandleButtonOld.vue
<template>
<div class="ec-handle">
<div
class="ec-handle--item"
v-for="(item,index) in value"
:key="index"
:class="{'cur':nowClickIndex===index}"
@click="switchCur(item,index)"
>
<ec-text v-if="item.fileType==='text'" :src="item.fileUrl"></ec-text>
<video :src="item.fileUrl" v-if="item.fileType==='video'"></video>
<audio :src="item.fileUrl" controls="controls" v-if="item.fileType==='audio'"></audio>
<ec-image :src="item.fileUrl" v-if="item.fileType==='image'" />
<ul class="customer-form-view-action-box">
<li class="iconfont icon-icon-cus-edit" @click.stop="handleEvent('edit',index)"></li>
<li
class="iconfont icon-icon-cus-up"
@click.stop="handleEvent('up',index)"
v-if="index! = = 0"
></li>
<li
class="iconfont icon-icon-cus-down"
@click.stop="handleEvent('down',index)"
v-if="index! ==value.length-1"
></li>
<li class="iconfont icon-icon-cus-del" @click.stop="handleEvent('delete',index)"></li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'HandleButton',
componentName: 'HandleButton',
props: {
value: {
type: Array,
default () {
return[]}}},data () {
return {
nowClickIndex: ' '}},mounted () {},
methods: {
handleEvent (type, index) {
let _list = JSON.parse(JSON.stringify(this.value))
let _nowItem = _list[index]
switch (type) {
case 'up':
this.nowClickIndex--
_list.splice(index, 1)
_list.splice(index - 1, 0, _nowItem)
break
case 'down':
this.nowClickIndex++
_list.splice(index, 1)
_list.splice(index + 1, 0, _nowItem)
break
case 'delete':
_list.splice(index, 1)
}
this.$emit('input', _list)
this.$emit(type, _nowItem, index)
},
switchCur (item, index) {
this.nowClickIndex = index
}
}
}
</script>
<style lang="scss"Slightly scoped > / / < / style >Copy the code
3. Improvement and optimization
Components are also easy to use, with a single line of code
<handle-button-old v-model="sortData"/>
sortData
sortData: [
{
fileType: 'text',
content: 'Front-end development',
index: 2,
size: 12
},
{
fileNmae: '251bb6d882024b11a6051d604ac51fc3.jpeg',
fileType: 'image',
fileUrl:
'https://file-cdn-china.wechatify.net/marketing/sms/mms_material/53ce422f14e516af0eb9a5c7251cc1ca.jpeg',
index: 3,
size: 101109,
fileName: '53ce422f14e516af0eb9a5c7251cc1ca.jpeg'
},
{
fileType: 'text',
content: 'wait',
index: 5,
size: 12
}
]
Copy the code
But if there is a requirement on the page, the function is the same, the style is different, such as the following figure
The component is then unusable.
At this point, it’s definitely not copying a file, changing the style and writing another component, but changing the original component to be more generic and suitable for more needs.
Copying a file and writing a component is not recommended for such a requirement. The next time this happens, you have to copy another file and write another component. This can result in a large number of component files, affecting maintenance
To make components more generic and suitable for more needs, the main point is to extract frequently changing factors and give them to users to customize. What can be improved and optimized? Here are some simple examples
3-1. Supports custom content
Home page, see two requirements, typesetting style and display field is not the same. I don’t know what the third or fourth typesetting style will be, or what fields will be displayed. Therefore, it is better for him to operate the button to extract, as a component package, how to layout, display the field, the component does not care, just provide a slot for the user to define.
HandleButtonOld.vue
<template>
<div class="ec-handle">
<div
class="ec-handle--item"
v-for="(item,index) in value"
:key="index"
:class="{'cur':nowClickIndex===index}"
@click="switchCur(index)"> <! -- provide slot --> <slot :data="item"></slot>
<ul class="customer-form-view-action-box"> <! </div> </div> </template>Copy the code
Page calls
<handle-button-old v-model="sortData"> <! --> <div slot-scope="item" class="view-item">
<span v-if="item.data.fileType==='text'">{{item.data.content}}123</span>
<video :src="item.data.fileUrl" v-if="item.data.fileType==='video'"></video>
<audio :src="item.data.fileUrl" controls="controls" v-if="item.data.fileType==='audio'"></audio>
<img :src="item.data.fileUrl" v-if="item.data.fileType==='image'" />
</div>
</handle-button-old>
Copy the code
3-2. Support custom selected styles
So if I go over here and look at what I’ve selected,
In addition to displaying a few action buttons, there is also a blue border line, but depending on your needs, the effect of the selection may be different, such as a gray double solid line in one place, a white line in another, a 30px margin, etc. It is impossible to guess what style will be selected the next time you use this component. Therefore, the selected style cannot be written or determined inside the handle-button-old, and can only be customized by users. All we can provide is a field that tells the user which item is currently selected.
HandleButtonOld.vue
<template>
<div class="ec-handle">
<div
class="ec-handle--item"
v-for="(item,index) in value"
:key="index"
:class="{'cur':nowClickIndex===index}"
@click="switchCur(item,index)"> <! --> <slot :data="getItem(item,index)"</div> </div> </template> <script>exportMethods: {getItem (item, index) {$index) and whether currently selected ($select// Here is the way to pass the index in the past, is for the future need, not expand herereturn Object.assign({}, item, { $index: index, $select: this.nowClickIndex === index})} </script>Copy the code
Page calls
<! - according to$select<handle-button-old V-model ="sortData">
<div slot-scope="item" class="view-item" :class="{'cur':item.data.$select}"</div> </handle-button-old> <style lang= "box-sizing: border-box! Important"scss">
.view-item {
padding: 10px;
border:4px dashed transparent;
&.cur{
border:4px double #ccc;
}
}
</style>
Copy the code
This allows the user to customize the selected style
3-2. Set the display position and direction of the operation button
Look again at the styles of the two requirements
First, the position and orientation of the buttons are different. The position of the button can be given the default value, but also to allow the user to customize. To determine the location of a button, the handle-button-old component needs to provide top, right, bottom, and left parameters. In order to facilitate positioning, in addition to the specific pixel, percentage can be set, but also support users to enter’ center’, so that users can set vertical or horizontal center.
The direction of the button needs to provide the direction parameter, the user input horizontal vertical display, input vertical horizontal display
handle-button-old
<template>
<div class="ec-handle">
<div
class="ec-handle--item"
v-for="(item,index) in value"
:key="index"
:class="{'cur':nowClickIndex===index}"
@click="switchCur(item,index)"
>
<slot :data="getItem(item,index)"></slot> <! <ul class= > <ul class="customer-form-view-action-box"
:style="ulPosition"
:class="{'handle-vertical':direction==='vertical'}"</ul> </div> </div> </template> <script>exportDefault {// props: {// props: {type: [String, Number],
default: '0'
},
bottom: {
type: [String, Number],
default: 'auto'
},
left: {
type: [String, Number],
default: 'auto'
},
right: {
type: [String, Number],
default: '-26px'
},
direction: {
type: String,
default: 'horizontal'
}
},
computed: {
ulPosition () {
let obj = {
left: this.left,
right: this.right,
top: this.top,
bottom: this.bottom
}
let _x = '0'
let _y = '0'
if (this.top === 'center' || this.bottom === 'center') {
obj.top = '50%'
obj.bottom = 'auto'
_y = '50%'
obj.transform = `translate(${_x}.${_y})`
}
if (this.left === 'center' || this.right === 'center') {
obj.left = '50%'
obj.right = 'auto'
_x = '50%'
obj.transform = `translate(${_x}.${_y})`
}
return obj
}
}
}
</script>
<style lang="scss" scoped>
.ec-handle--item {
position: relative;
ul {
position: absolute;
right: -26px;
top: 0;
display: none;
line-height: 24px;
&.handle-vertical {
li {
display: inline-block;
vertical-align: top;
}
}
}
}
</style>
Copy the code
Page calls
<! --> <handle-button-old v-model="sortData"
direction="vertical"
right="6px"
bottom="center"
>
<div slot-scope="item" class="handle-item"</div> </handle-button-old>export default {
data () {
return {
iconByFileType: {
text: 'icon-wenben',
image: 'icon-tupian1',
video: 'icon-shipin',
audio: 'icon-yinpin',
link: 'icon-duanlian'}, {formatSize (val) {}, {formatSize (val) {if (val === 0) {
return '0B'
}
let sizeObj = {
MB: 1048576,
KB: 1024,
B: 1
}
val = +val
for (let key in sizeObj) {
if (val >= sizeObj[key]) {
return+(val/sizeObj[key]).tofixed (2) + key}} </script> <style lang="scss"Scoped > </style>Copy the code
And so the effect is achieved
3-3. Set the display mode of the operation button
You can already see the problem. [3-2] ended up with only one of the items having an action button, while [3-2] initially saw the requirement that all the results should be displayed. So what we’re going to do here is set a display property, set how the action button is going to look. Currently three values are provided: ‘default’- selected items are shown, ‘Visible ‘- all items are shown, and’ None ‘- none is shown.
handle-button-old
<template>
<div class="ec-handle">
<div
class="ec-handle--item"
v-for="(item,index) in value"
:key="index"
:class="{'cur':nowClickIndex===index || display==='visible'}"
@click="switchCur(item,index)"
>
<slot :data="getItem(item,index)"></slot>
<ul class="customer-form-view-action-box"
:style="ulPosition"
v-if="display! =='none'"
:class="{'handle-vertical':direction==='vertical'}"</ul> </div> </div> </template> <script>export default {
props: {
display: {
type: [String],
default: 'default'</script> </script>Copy the code
Page calls
<handle-button-old
v-model="sortData"
direction="vertical"
right="6px"
display="visible"
bottom="center"</handle-button-old>Copy the code
That’s how it works
3-4. Trigger action before clicking the operation button
Many people encounter the need to be careful with a pop-up or other notification before performing “dangerous actions” such as deleting or erasing. The action button of this component may also be “dangerous action”. So you need to allow the user to customize the callback before the operation.
Take the handle-button-old component mentioned in the article as an example. If the “delete” button is required, it needs to remind the popup window, and other buttons can be operated directly.
handle-button-old
<template> <! </template> <script>export default {
props: {
beforeDelete: {
type: Function
},
beforeUp: {
type: Function
},
beforeDown: {
type: Function
},
beforeEdit: {
type: Function
}
},
data () {
return {
nowClickIndex: ' ',
eventType: ' ',
curHandleIndex: ' '}}, methods: {/** * @description Execution event */handle () {
let _list = this.value
let _nowItem = _list[this.curHandleIndex]
switch (this.eventType) {
case 'up':
this.nowClickIndex--
_list.splice(this.curHandleIndex, 1)
_list.splice(this.curHandleIndex - 1, 0, _nowItem)
break
case 'down':
this.nowClickIndex++
_list.splice(this.curHandleIndex, 1)
_list.splice(this.curHandleIndex + 1, 0, _nowItem)
break
case 'delete':
_list.splice(this.curHandleIndex, 1)
}
this.$emit('input', _list)
this.$forceUpdate()
this.$emit(this.eventType, _nowItem, this.curhandleIndex)}, /** * @description handles events */ handleEvent (eventType, item, this.curhandleIndex) EventType = eventType // Record the index of the current operation item this.curHandleIndex = indexlet _type = eventType.substr(0, 1).toUpperCase() + eventType.substr(1)
if (typeof this[`before${_type}`] = = ='function') {// Pass the current operation function, current item, index as arguments to the calling function this[' before${_type}`](this.handle, item, index)
} else{this.handle()},} </script>Copy the code
Page calls
<template>
<handle-button-old
v-model="sortData"
direction="vertical"
right="6px"
display="visible"
bottom="center"
:beforeDelete="handleBefore"> <! </handle-button-old> </template> <script> methods: {/** * @description * @augmentsdone- Used to perform the operation * @augments item - current item * @Augments index - current index */ handleBefore (done, item, index) {// Click ok to proceed, click cancel to do not process this.$confirm('Are you sure you want to delete? ')
.then(() => {
done()
})
.catch(() => {})
}
}
</script>
Copy the code
3-5. Switch the trigger action of the selected item
For example, if there is a demand, click to switch the selected item, you need to take the data of the current item as the parameter of the request. To achieve this requirement, a custom event needs to be provided in the handle-button-old component
handle-button-old
Methods :{switchCur (item, index) {this.nowClickIndex = index // Trigger the custom event this.$emit('change', item, index)
}
}
Copy the code
Page calls
<handle-button-old
style="margin-bottom:500px;"
v-model="sortData"
direction="vertical"
right="6px"
display="visible"
bottom="center"
@change="handleChange"
>
</handle-button-old>
Copy the code
3-6. Deselect
You may have noticed this problem earlier in the day, but if you select one of these terms, the following happens.
But what if the requirement is to deselect? Then we can’t do it. From the logic of the code, as long as it is selected, it has to be selected, there is no way to cancel it. Therefore, the switchCur function 3-5 needs to check and deselect the current item if it is clicked
handle-button-old
methods:{
switchCur (item, index) {
if (this.display === 'visible') {
return} // If the current item is clicked, deselect this.nowClickIndex = this.nowClickIndex! == index ? index :' '
this.$emit('change', item, index)
}
}
Copy the code
3-7. Button folding display
As you can see in the image above, the buttons are arranged either horizontally or vertically. If one day the button occupies too much space and needs to fold the display button, this is also easy to be compatible. Add a type parameter to handle-button-old to determine the display mode.
handle-button-old
<template>
<div class="ec-handle">
<div
class="ec-handle--item"
v-for="(item,index) in value"
:key="index"
:class="{'cur':nowClickIndex===index || display==='visible'}"
@click="switchCur(item,index)"
>
<slot :data="getItem(item,index)"></slot> <! -- If not dropdown and dispaly is not none--> <ul class="customer-form-view-action-box"
:style="ulPosition"
v-if="type! =='dropdown'&&display! =='none'"
:class="{'handle-vertical':direction==='vertical'}"> <! </ul> <! -- if the type is dropdown --> <el-dropdown v-else-if="type==='dropdown'" class="customer-form-view-action-box" :style="ulPosition" style="position:absolute;">
<span class="el-dropdown-link"> operation < I class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu>
<el-dropdown-item><div @click.stop="handleEvent('edit',item,index)"> Edit </div></el-dropdown-item> <el-dropdown-item v-if="index! = = 0"><div @click.stop="handleEvent('up',item,index)"</div></el-dropdown-item> <el-dropdown-item v-if="index! ==value.length-1"><div @click.stop="handleEvent('down',item,index)"> scroll down </div></el-dropdown-item> <el-dropdown-item><div @click.stop="handleEvent('delete',item,index)"Delete > < / div > < / el - dropdown can - item > < / el - dropdown can - menu > < / el - dropdown can > < / div > < / div > < / template >export default {
name: 'HandleButton',
componentName: 'HandleButton',
props: {
type: {
type: String} // code omitted} // code omitted}Copy the code
Page calls
The direction parameter is invalid after type=”dropdown”
<! --type="dropdown"< span style= "max-width: 100%; clear: both; min-height: 1em"margin-bottom:500px;"
v-model="sortData"
direction="vertical"
type="dropdown"
right="6px"
display="visible"
bottom="center"
:beforeDelete="handleBefore"
>
<div slot-scope="item" class="handle-item">
<div class="message-item___box">
<span class="message-item___icon iconfont" :class="iconByFileType[item.data.fileType]"></span>
</div>
<div class="message-item___info">
<p v-if="item.data.fileType==='text'">
<span>{{item.data.content}}</span>
</p>
<p v-else>{{item.data.fileName}}</p>
<span class="message-item___info_size">{{formatSize(item.data.size)}}</span>
</div>
</div>
</handle-button-old>
Copy the code
3-8. Button display mode
Going back to this scenario, you might have thought during development that in order for an action button to appear, you have to click on something to appear. But a lot of times the need, need to mouse on the time to display the operation button, do not need to click. To do this, just add a trigger parameter. Triggle defaults to ‘click’- click to appear, and ‘hover’- mouse to appear
<template>
<div class="ec-handle">
<div
class="ec-handle--item"
v-for="(item,index) in value"
:key="index"
:class="{'cur':nowClickIndex===index || display==='visible'}"
@click="switchCur(item,index,'click')"
@mouseenter="switchCur(item,index,'hover')"
@mouseleave="handleMouseLeave"> <! </div> </div> </template> <script>exportDefault {props: {// props: {type: String,
default: 'click'}}, methods: {switchCur (item, index, eventType) {if (this.display === 'visible') {
return} // If the current triggered event is different from triggle, no action is performedif(eventType ! == this.triggle) {return} this.nowClickIndex = this.nowClickIndex ! == index ? index :' '
this.$emit('change', item, index)
},
handleMouseLeave() {// If triggle is hover, uncheck the current item when the mouse is removedif (this.triggle === 'hover') {
this.nowClickIndex = ' '} </script>Copy the code
Page calls
<handle-button-old v-model="sortData" triggle="hover"> <! </handle-button-old>Copy the code
3-9. Regarding others
For the first time, the handle-button-old component is used as an example to list some improved and optimized functions. There are plenty of features you can fiddle with if you want, such as button style (icon color, shape, background color, size, etc.), spacing, customization buttons, etc. As for whether to toss, it depends on the demand is necessary, specific circumstances, specific analysis. This is a very simple component. If it is a complex component, there may be more points to optimize.
4. Summary
When encapsulating components, if the requirements for components are simple at the beginning or the time is urgent, you can also encapsulate components with basic functions that can meet the requirements. Later, if the component does not meet the business requirements, it can be improved and optimized.
— — — — — — — — — — — — — — — — — — — — — — — — — gorgeous line — — — — — — — — — — — — — — — — — — — —
To learn more and communicate with me, please add me on wechat. Or follow my wechat public number: Waiting book Pavilion