需求描述
场景:现有很多类表单,进入表单详情后需要使用按钮提供表单的相关审批操作,需封装一个通用的按钮组件以满足不同表单不同需求操作
原型图如下:
思路:既然是在移动端,那我们是需要用到vant移动端组件库了。首先我们需要在项目中引入vant,然后绘制出原型图中的页面,最后考虑将其封装为一个通用组件。
步骤
引入vant
安装 npm install vant --save
引入:
1、 在main.js文件中引入
// 全局引入
import Vant from 'vant'
Vue.use(Vant)
2、按需引入
// 第一步--安装插件
npm i babel-plugin-import -D // 简写
npm install babel-plugin-import --save-dev // 完整写法
// 第二步--配置插件--在.babelrc 或 babel.config.js 中添加配置
"plugins": [
[
"import",
{
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}
]
]
// 第三步--按需引入vant组件--此处只作简单举例
import { Button } from 'vant;
Vue.use(Button)
使用
// 可直接在vue文件中使用啦
<van-button type="info">{{ 666 }}</van-button>
设计按钮组件
通过分析原型图,我们可以确定,我们需要实现的按钮组件主要分为两部分:一个主按钮和其他按钮,其他按钮包含其他操作。关于布局我们可以采用栅栏布局,左右两边各自占有12列(一行共24列),按钮绘制代码如下:
// btnOptions为存放按钮参数的数组--此处给出一个模拟数组
/*
btnOptions: [{
text: '提交',
value: 'submit',
buttonCode: 'submit'
}, {
text: '同意',
value: 'Approve',
buttonCode: 'Approve'
}, {
text: '拒绝',
value: 'Reject',
buttonCode: 'Reject'
}, {
text: '返回',
value: 'goBackPage',
buttonCode: 'goBackPage'
}]
*/
<van-row v-if="btnOptions.length > 0">
<van-col span="12">
<van-dropdown-menu
v-if="option.length > 1"
direction="up"
active-color="#1989fa"
>
<van-dropdown-item ref="other" title="其他">
<van-button
v-for="item in option"
:key="item.value"
plain
hairline
type="default"
block
@click="btnClick(item)"
>{{ item.text }}</van-button>
</van-dropdown-item>
</van-dropdown-menu>
<template v-else>
<van-button
v-if="option.length > 0"
type="default"
block
@click="btnClick(option[0])"
>{{ option[0].text }}</van-button>
</template>
</van-col>
<van-col span="12">
<van-button type="info" block @click="btnClick(mainBtn)">{{ mainBtn.text }}</van-button>
</van-col>
</van-row>
到这一步,我们就已经绘制出了如原型图中的按钮样式,效果如图:
我们发现,此时按钮展示效果已经有了,但是我们还需要将它固定在页面底部,我们给它一个样式class,定义为class="buttons"
,css代码具体如下:
<style lang="less" scoped>
.buttons {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
.info {
padding: 10px;
}
}
</style>
效果如图:
接下来,我们要为这些按钮事件写一下通用的代码逻辑,在前面绘制展示按钮时,我们已经定义了一个按钮点击方法btnClick(msg)
,具体代码如下:
// 按钮点击
btnClick(msg) {
if (msg.value === 'goBackPage') { // 返回
this.$router.go(-1)
return
}
this.loading = false // 控制加载动画
this.btnType = msg
if (this.$refs.other) {
this.$refs.other.toggle(false)
}
if (msg.notDialog) {
// 不显示弹窗
this.deliverInfo()
} else {
this.show = true // 点击按钮弹出交互弹窗
// 赋值提示信息
if (msg.dialogInfo) {
this.dialogInfo = msg.dialogInfo
}
if (msg.buttonCode === 'Reject' || msg.buttonCode === 'Approve') {
this.dialogInfo = {
title: '审批意见',
type: 'input',
info: ''
}
} else if (msg.buttonCode === 'approvalHistory' || msg.buttonCode === 'saveTemplate') {
this.show = false
this.deliverInfo()
} else {
this.dialogInfo = {
title: '提示',
type: 'text',
info: '确认进行该操作吗?'
}
}
}
},
// 传递信息
deliverInfo() {
if (this.loading) {
return
}
if (this.btnType.buttonCode !== 'approvalHistory' && this.btnType.buttonCode !== 'saveTemplate') {
this.loading = true
Toast.loading({
message: '加载中...',
forbidClick: true,
overlay: true,
duration: 30000 // 默认加载时间--可用作超时处理
})
this.loading = true
}
this.$emit('btnClick', { ...this.dialogInfo, ...this.btnType })
}
添加弹窗组件,代码如下:
<van-dialog
v-model="show"
width="80%"
:title="dialogInfo.title"
show-cancel-button
confirm-button-color="#1989fa"
@cancel="cancel"
@confirm="confirm"
>
<div v-if="dialogInfo.type === 'text'" class="info">
{{ dialogInfo.info }}
</div>
<van-field
v-if="dialogInfo.type === 'input'"
v-model="dialogInfo.info"
rows="4"
autosize
type="textarea"
maxlength="300"
show-word-limit
/>
</van-dialog>
点击提交按钮与同意效果如下(左:‘提交’,右:‘同意’):
加载效果如图:
刚刚我们在上面的代码中提到了加载效果的默认加载时间,但是在实际业务需求中,我们应该是需要通过业务逻辑来控制加载效果关闭的,如接口调用完毕,页面跳转加载完毕后再关闭加载效果,防止按钮重复点击造成接口多次调用等问题。我们需要再写一个方法来控制加载效果的关闭。具体代码如下:
// 按钮事件结束的回调
cancelLoad(time = 1000) {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
this.timer = setTimeout(() => {
this.loading = false
clearTimeout(this.timer)
this.timer = null
Toast.clear()
}, time)
}
当需要结束加载效果时,调用这个方法即可。
到这里,我们已经基本完成了按钮组件的开发工作。但是,这个组件还不能被其他组件所使用,我们还需要稍微改造一下数据源—btnOptions
,这个数组是存放按钮参数的。所以我们要封装按钮组件,就需要将这个数组作为接收数组,其他组件调用我们的按钮组件时就需要传递存放了按钮参数的数组。代码如下:
// 父组件
<buttons ref="btn" :btnOptions="btnList" @btnClick="btnClick"></buttons>
import Buttons from '按钮组件路径'
components: {
Buttons
},
data() {
return {
// 此数组来源可通过任意方式,如自定义、接口返回等
btnList: [{
text: '提交',
value: 'submit',
buttonCode: 'submit'
}, {
text: '同意',
value: 'Approve',
buttonCode: 'Approve'
}, {
text: '拒绝',
value: 'Reject',
buttonCode: 'Reject'
}, {
text: '返回',
value: 'goBackPage',
buttonCode: 'goBackPage'
}]
}
},
// 按钮组件
props: {
btnOptions: {
type: Array,
default: function() {
return []
}
}
},
当其他组件想要调用按钮组件内的方法如关闭加载效果方法cancelLoad()
时,可以通过this.$refs.btn.cancelLoad()
来调用此方法。或者通过公共事件$bus
也可以。
到这里,一个完整的按钮组件就开发好啦,能够完美契合文章开头的需求场景。完整代码如下:
<!-- 此为按钮组件 -->
<template>
<div class="buttons" safe-area-inset-bottom>
<van-row v-if="btnOptions.length > 0">
<van-col span="12">
<van-dropdown-menu
v-if="option.length > 1"
direction="up"
active-color="#1989fa"
>
<van-dropdown-item title="其他" ref="other">
<van-button
v-for="item in option"
:key="item.value"
plain
hairline
type="default"
block
@click="btnClick(item)"
>{{ item.text }}</van-button
>
</van-dropdown-item>
</van-dropdown-menu>
<template v-else>
<van-button
v-if="option.length > 0"
type="default"
block
@click="btnClick(option[0])"
>{{ option[0].text }}</van-button
>
</template>
</van-col>
<van-col span="12">
<van-button type="info" block @click="btnClick(mainBtn)">{{
mainBtn.text
}}</van-button>
</van-col>
</van-row>
<van-dialog
width="80%"
v-model="show"
:title="dialogInfo.title"
show-cancel-button
confirm-button-color="#1989fa"
@cancel="cancel"
@confirm="confirm"
>
<div class="info" v-if="dialogInfo.type === 'text'">
{{ dialogInfo.info }}
</div>
<van-field
v-if="dialogInfo.type === 'input'"
v-model="dialogInfo.info"
rows="4"
autosize
type="textarea"
maxlength="300"
show-word-limit
/>
</van-dialog>
</div>
</template>
<script>
import { Toast } from 'vant'
export default {
props: {
btnOptions: {
type: Array,
default: function() {
return []
}
}
},
data() {
return {
loading: false, // 加载中
show: false, // 弹窗提示
timer: null, // 定时器
dialogInfo: {},
btnType: {} // 当前点击的按钮类型
}
},
created() {
},
computed: {
mainBtn() {
const [main, ...surplus] = this.btnOptions
return main
},
option() {
const [main, ...surplus] = this.btnOptions
return surplus
},
value() {
return this.option[0].value
}
},
mounted() {
// 此处可用公共事件监听加载效果关闭方法
this.$bus.$on('closeLoading', res => {
if (res === true) {
this.cancelLoad()
}
})
},
methods: {
// 按钮点击
btnClick(msg) {
if (msg.value === 'goBackPage') {
this.$router.go(-1)
return
}
this.loading = false
this.btnType = msg
if (this.$refs.other) {
this.$refs.other.toggle(false)
}
if (msg.notDialog) {
// 不显示弹窗
this.deliverInfo()
} else {
this.show = true
// 赋值提示信息
if (msg.dialogInfo) {
this.dialogInfo = msg.dialogInfo
}
if (msg.buttonCode === 'Reject' || msg.buttonCode === 'Approve') {
this.dialogInfo = {
title: '审批意见',
type: 'input',
info: ''
}
} else if (msg.buttonCode === 'approvalHistory' || msg.buttonCode === 'saveTemplate') {
this.show = false
this.deliverInfo()
} else {
this.dialogInfo = {
title: '提示',
type: 'text',
info: '确认进行该操作吗?'
}
}
}
},
// 弹窗取消事件
cancel() {
this.show = false
if (this.dialogInfo.type === 'input') {
// 清空输入框内容
this.dialogInfo.info = ''
}
},
// 弹窗确认事件
confirm() {
this.show = false
this.deliverInfo()
},
// 传递信息
deliverInfo() {
if (this.loading) {
return
}
if (this.btnType.buttonCode !== 'approvalHistory' && this.btnType.buttonCode !== 'saveTemplate') {
this.loading = true
Toast.loading({
message: '加载中...',
forbidClick: true,
overlay: true,
duration: 30000
})
this.loading = true
}
this.$emit('btnClick', { ...this.dialogInfo, ...this.btnType })
},
// 按钮事件结束的回调
cancelLoad(time = 1000) {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
this.timer = setTimeout(() => {
this.loading = false
clearTimeout(this.timer)
this.timer = null
Toast.clear()
}, time)
}
}
}
</script>
<style lang="less" scoped>
.buttons {
position: fixed;
bottom: -4px;
left: 0;
width: 100%;
.info {
padding: 10px;
}
}
</style>
<!-- 此为父组件 -->
<template>
<div>
<buttons :btnOptions="btnOption" />
</div>
</template>
<script>
import Buttons from '@/components/buttons/buttons.vue'
export default {
name: '',
components: {
Buttons
},
data() {
return {
btnOption: [{
text: '提交',
value: 'submit',
buttonCode: 'submit'
}, {
text: '同意',
value: 'Approve',
buttonCode: 'Approve'
}, {
text: '拒绝',
value: 'Reject',
buttonCode: 'Reject'
}, {
text: '返回',
value: 'goBackPage',
buttonCode: 'goBackPage'
}]
}
},
computed: {},
watch: {},
mounted() {},
created() {},
methods: {}
}
</script>