拓展某个组件
情景
所谓拓展某个组件这里是说在某个原有组件的基础上增加一些它没有的新功能。
下面要说的做法可能不够高明,但也是一种解决方式。权当写下来供大家参考。
iView的自己的组件select,我们现在需要将其拓展,在它的下拉框下添加一个提示条,当搜索的用户有权限,且远程搜索不到结果时我们要展示一个提示条在下拉框的最底部,是否要将新的搜索内容添加到进去。此外,该提示条还有两个按钮"是"和"否",当点击"是"时,将输入框里的数据通过事件传给上级组件然后发送给后端添加到数据库中再返回前端展示出来并关闭提示条;当点击"否"时,关闭提示条,将输入框里的数据通过事件传给上级组件处理,并关闭提示条。
自定义自己的select
首先要读懂iView的select的源码,在此基础上,我们在开始定义自己的组件。
结构使用iView里的结构,在此基础上,添加提示条的结构到其中:
iView在项目里找到node_modules\iview\src\components\select\select.vue路径下的select的源码,这里我们只取结构select组件的结构部分,js部分太长,不方便展示,大家可以自己去看:
<template>
<div
:class="classes"
v-click-outside.capture="onClickOutside"
v-click-outside:mousedown.capture="onClickOutside"
>
<div
ref="reference"
:class="selectionCls"
:tabindex="selectTabindex"
@blur="toggleHeaderFocus"
@focus="toggleHeaderFocus"
@click="toggleMenu"
@keydown.esc="handleKeydown"
@keydown.enter="handleKeydown"
@keydown.up.prevent="handleKeydown"
@keydown.down.prevent="handleKeydown"
@keydown.tab="handleKeydown"
@keydown.delete="handleKeydown"
@mouseenter="hasMouseHoverHead = true"
@mouseleave="hasMouseHoverHead = false"
>
<slot name="input">
<input type="hidden" :name="name" :value="publicValue">
<select-head
:filterable="filterable"
:multiple="multiple"
:values="values"
:clearable="canBeCleared"
:disabled="disabled"
:remote="remote"
:input-element-id="elementId"
:initial-label="initialLabel"
:placeholder="placeholder"
:query-prop="query"
@on-query-change="onQueryChange"
@on-input-focus="isFocused = true"
@on-input-blur="isFocused = false"
@on-clear="clearSingleSelect"
/>
</slot>
</div>
<transition name="transition-drop">
<Drop
:class="dropdownCls"
v-show="dropVisible"
:placement="placement"
ref="dropdown"
:data-transfer="transfer"
v-transfer-dom
>
<ul v-show="showNotFoundLabel" :class="[prefixCls + '-not-found']"><li>{{ localeNotFoundText }}</li></ul>
<ul :class="prefixCls + '-dropdown-list'">
<functional-options
v-if="(!remote) || (remote && !loading)"
:options="selectOptions"
:slot-update-hook="updateSlotOptions"
:slot-options="slotOptions"
></functional-options>
</ul>
<ul v-show="loading" :class="[prefixCls + '-loading']">{{ localeLoadingText }}</ul>
</Drop>
</transition>
</div>
</template>
在iView的HTML结构中插入我们要添加的结构(其中showNotFoundLabel是iView里找不到搜索结果的标志,其它绑定的事件和属性大家可以先忽略,下面有详细的解释):
<div class="alert" v-show="hideCaseType && showNotFoundLabel && switchCaseType">
<Alert show-icon>
是否把 "{{query}}" 添加为新的案件类型?
<span style="float:right;margin-top:-4.2px;">
<Button @click="confirm" :loading="buLoading" type="primary" size="small" style="margin-right:10px;">是</Button>
<Button @click="cancel" size="small" style="margin-right:-30px;">否</Button>
</span>
</Alert>
</div>
最终定义出我们的select.vue就是:
<style scoped>
.alert {
width:100%;
position:absolute;
bottom:0px;
margin-bottom:0px;
}
.alert div{
margin-bottom:0px;
}
</style>
<template>
<div
:class="classes"
v-click-outside.capture="onClickOutside"
v-click-outside:mousedown.capture="onClickOutside"
>
<div
ref="reference"
:class="selectionCls"
:tabindex="selectTabindex"
@blur="toggleHeaderFocus"
@focus="toggleHeaderFocus"
@click="toggleMenu"
@keydown.esc="handleKeydown"
@keydown.enter="handleKeydown"
@keydown.up.prevent="handleKeydown"
@keydown.down.prevent="handleKeydown"
@keydown.tab="handleKeydown"
@keydown.delete="handleKeydown"
@mouseenter="hasMouseHoverHead = true"
@mouseleave="hasMouseHoverHead = false"
>
<slot name="input">
<input type="hidden" :name="name" :value="publicValue">
<select-head
:filterable="filterable"
:multiple="multiple"
:values="values"
:clearable="canBeCleared"
:disabled="disabled"
:remote="remote"
:input-element-id="elementId"
:initial-label="initialLabel"
:placeholder="placeholder"
:query-prop="query"
@on-query-change="onQueryChange"
@on-input-focus="isFocused = true"
@on-input-blur="isFocused = false"
@on-clear="clearSingleSelect"
/>
</slot>
</div>
<transition name="transition-drop">
<Drop
:class="dropdownCls"
v-show="dropVisible"
:placement="placement"
ref="dropdown"
:data-transfer="transfer"
v-transfer-dom
>
<ul v-show="showNotFoundLabel" :class="[prefixCls + '-not-found']">
<li>{{ localeNotFoundText }}</li>
</ul>
<!-- 三个开关,其中hideCaseType是外部传入的权限控制开关;showNotFoundLabel是iView组件搜索远程数据有无匹配的标志用来做我们的第二个开关;switchCaseType是本组件内部button按钮控制提示条显示与否的第三个开关 -->
<div class="alert" v-show="hideCaseType && showNotFoundLabel && switchCaseType">
<Alert show-icon>
是否把 "{{query}}" 添加为新的案件类型?
<span style="float:right;margin-top:-4.2px;">
<Button @click="confirm" :loading="buLoading" type="primary" size="small" style="margin-right:10px;">是</Button>
<Button @click="cancel" size="small" style="margin-right:-30px;">否</Button>
</span>
</Alert>
</div>
<ul :class="prefixCls + '-dropdown-list'">
<functional-options
v-if="(!remote) || (remote && !loading)"
:options="selectOptions"
:slot-update-hook="updateSlotOptions"
:slot-options="slotOptions"
></functional-options>
</ul>
<ul v-show="loading" :class="[prefixCls + '-loading']">{{ localeLoadingText }}</ul>
</Drop>
</transition>
</div>
</template>
<script>
import {Select} from 'iview';
import SelectHead from './SelectHead.vue';
export default {
name: 'iSelect',
data() {
return {
// 组件内部点击提示条是或否时的控制开关
switchCaseType: this.showNotFoundLabel,
// 对应props里的buLoading值,因为props不能被改变,为了confirm()里能改变这个值,从而定义了这个data。
realBuLoading: false
}
},
props: {
// 根据权限外部传入的属性控制是否显示控制条的开关
hideCaseType: {
type: Boolean,
default: true
},
// 外部发送添加数据成功时传入的控制button是否loading的开关
buLoading: {
type: Boolean,
default: false
}
},
watch: {
// 观察远程搜索结果有无匹配数据的标志showNotFoundLabel(iView组件本身显示"无匹配数据"也用的是这个标志来确定。)来相应的控制是否显示控制条显示的开关。
showNotFoundLabel(val) {
this.switchCaseType = val;
}
},
methods: {
// 点击button“是”时的触发的回调,触发并通过confirm事件传入相应的输入框里的数据到外部
confirm() {
this.realBuLoading = true;
this.$emit('confirm', this.query);
},
// 点击button“否”时的回调,控制内部开关switchCaseType关闭提示条,触发并通过cancel事件传输入框的值到外部
cancel() {
this.switchCaseType = false;
this.$emit('cancel', this.query)
},
},
// 继承Select组件
extends: Select,
};
</script>
假设某个父级组件xxx需要使用我们自定义的组件:
<Select2
v-model="inputVal"
filterable
:buLoading="ButtonLoading"
:hideCaseType="operateAuth"
@confirm="handleConfirm"
@cancel="handleCancel">
<Option v-for="item in searchResult" :value="item.id" :key="item.id" :label="item.name"></Option>
</Select2>
// 引入组件
import Select2 from '../select-components/select.vue';
// 引入请求接口
import Api from '@/server-api/case';
export default {
name: 'xxx',
data () {
return {
inputVal:'',
searchResult: [],
buttonLoading: false,
// 发送添加案件类型成功时需要传入的控制button是否loading的开关
ButtonLoading: false,
}
},
computed: {
operateAuth () {
// return this.$store.getters.resources.includes('XXX_ADD');
return true;
},
},
methods: {
// 添加案件下拉框提示条被点击"是"时触发confirm事件的回调
handleConfirm(newVal) {
let that = this;
// console.log(newVal);
this.inputVal = newVal;
let item = {
newName: newVal,
}
this.ButtonLoading = true;
// 发送post请求的api
Api.addNewData(item).then((data) => {
that.ButtonLoading = false;
that.$Message.success('添加新数据成功!');
// 刷新数据列表,method里自己定义
that.getDataList();
},
(err) => {
that.ButtonLoading = false;
});
},
// 添加案件下拉框提示条被点击"否"时触发cancel事件的回调
handleCancel(newVal) {
// console.log(newVal);
this.inputVal = newVal;
},
getDataList () {
let that = this;
Api.getData().then((data) => {
let newDataList = data.payLoad.data;
that.searchResult = newDataList;
});
}
},
components: {
// 注册组件
Select2
}
}