- 在src/main.js中引入组件
import components from './components'
/******----- 自定义组件 -----******/
for (let k in components) {
Vue.use(components[k]);
}
- 在src/components/index.js 中导出组件
import VantCalendar from './vantCalendar' //日期组件
let components = {
VantCalendar
}
export default components;
- 在src/components目录下新建文件夹 vantCalendar,里面新建两个文件index.js和VantCalendar.vue
- 在src/components/vantCalendar/index.js中注册组件
import vantCalendar from "./VantCalendar.vue";
const VantCalendar = {
install(Vue) {
Vue.component(vantCalendar.name, vantCalendar);
}
};
export default VantCalendar;
- 在src/components/vantCalendar/VantCalendar.vue中写组件封装的逻辑,使用v-bind="$attrs"和v-on="$listeners"实现在父组件调用时子组件可以使用原生组件带的属性和方法
注意: 遇到的坑 van-calendar的poppable的属性为true时,v-model的值只能是true或false,用来控制弹出层的显隐。这样,会影响父组件的v-model通过props传过来的值,在选择日期点击确定后会触发2次value的watch监听事件,第一次value是选中的日期,第2次是false了,因为第二次监听的value 就变成了当前子组件的v-model,此时弹出层要关闭了,所以是false。
刚开始没用van-popup在外层包裹,是直接在van-calendar上写,用了v-on="$listeners"就会有上面问题,但是不用的话在父组件中的子组件调用上调用原生组件带的方法就无效!解决方法就是poppable属性写死为false,这样它就没有v-model属性了,然后通过van-popup来控制弹出层的显隐,poppable为true才支持的那些属性van-popup也支持,所以抽出来做成配置项,但是poppable为false是平铺效果,不太友好,就自己通过控制css来将它手动调整成正常显示,所以加了style配置项。
这样解决的弊端就是该组件不支持平铺显示,要支持就还得再优化一下,写个判断条件渲染,因为感觉平铺效果一般不会用到,所以在这里没实现。上代码:
<template>
<div class="vantCal">
<!-- 只读 -->
<div v-if="readonly"></div>
<!-- 日期组件 -->
<van-popup
v-if="!readonly"
v-model="show"
:position="position"
:round="round"
:closeable="closeable"
:style="styleObjC"
>
<van-calendar
:type="type"
v-bind="$attrs"
v-on="$listeners"
:poppable="false"
@confirm="onConfirm"
/>
</van-popup>
</div>
</template>
<script>
export default {
name: "VantCalendar",
props: {
readonly: {
type: Boolean,
default: false
},
value: {
type: [String, Array, Number]
},
position: {
// 弹出方向 默认底部
type: String,
default: "bottom"
},
round: {
// 是否显示圆角
type: Boolean,
default: true
},
closeable: {
// 是否显示关闭图标
type: Boolean,
default: true
},
type: {
/*
single表示选择单个日期,
multiple表示选择多个日期,
range表示选择日期区间
*/
type: String,
default: "single"
},
styleObj: {
// 自定义样式
type: Object,
default() {
return {};
}
}
},
data() {
return {
// 当前值
date: this.value,
show: false,
styleObjC: this.styleObj
};
},
watch: {
position: {
immediate: true,
deep: true,
handler(val) {
if (val === "bottom") {
this.styleObjC = { height: "60%" };
} else if (val === "left" || val === "right") {
this.styleObjC = { height: "60%", top: "70%" };
}
}
},
styleObj: {
immediate: true,
deep: true,
handler(val) {
this.styleObjC = val;
}
},
value: {
immediate: true,
deep: true,
handler(val) {
this.date = val;
}
},
date: {
immediate: true,
deep: true,
handler(val) {
this.$emit("input", val);
}
}
},
methods: {
// 处理:类型不同,时间格式不同
handleTime(date) {
// console.log('date', date)
let time = "";
let arr = [];
switch (this.type) {
// 单个日期
case "single":
time = this.formatDate(date);
break;
// 多个日期
case "multiple":
date.map(item => {
arr.push(this.formatDate(item));
});
time = arr.join(",");
break;
// 日期区间 开始日期 - 结束日期
case "range":
time = `${this.formatDate(date[0])} - ${this.formatDate(date[1])}`;
break;
default:
break;
}
return time;
},
// 格式化中国标准时间
formatDate(date) {
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
},
onConfirm(value) {
this.show = false;
this.date = this.handleTime(value);
}
}
};
</script>
<style lang="less" scoped></style>
- 组件使用:举例说明
<template>
<div class="home">
<van-field
v-model="date"
name="履行日期"
label="履行日期"
clickable
placeholder="请选择日期"
@click="dateClick"
/>
<VantCalendar ref="myVantCalendar" v-model="date" type="single" color="#1989fa" :styleObj="{height: '70%'}" @select="changeTime">
</VantCalendar>
</div>
</template>
export default {
data() {
return {
date: '',
}
},
methods: {
dateClick() {
this.$refs.myVantCalendar.show = true
},
changeTime(value) {
console.log('父组件value111', value)
},
}
}