Component development practice - digital input box and label page components

1. Digital input box component

  • index.html entry page
  • input-number.js digital input box component
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>数字输入框组件</title>
	</head>
	<body>
		<div id="app">
			<!-- 引入数字输入框组件,设置一个默认值,最大值,最小值 -->
			<input-number v-model="value" :max="10" :min="0" :step="10"></input-number>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
		<script src="input-number.js"></script>
		<script>
		    // 根实例
		    var app = new Vue({
		    	el:'#app',
		    	data:{
		    		value:5
		    	}
		    })
		</script>
		
	</body>
</html>
// 数字输入框组件
Vue.component('input-number', {
	template:'\
	        <div class="input-number">\
	        <input \
	            type="text"\
	            @focus="keyControl"\
	            :value="currentValue"\
				 :step=this.$parent.step\
	            @change="handleChange">\
	        <button \
	            @click="handleReduce" \
	            :disabled = "currentValue <= min">-</button>\
	        <button \
	            @click="handleIncrease" \
	            :disabled = "currentValue >= max">+</button>\
	        </div>',
	props: {
		max: {
			type: Number,
			default: Infinity
		},
		min: {
			type: Number,
			default: -Infinity
		},
		value: {
			type: Number,
			default: 0
		},
		step:{
			type:Number,
			default:1
		}
	},
	// 给组件声明一个data,默认使用value的值,然后在组件内部维护这个data
	data: function() {
		return {
			currentValue: this.value,
			stepNum:this.step, //增加一个步伐控制,比如设为10,点击加号按钮,一次增加10
		}
	},
	// 监听某个prop或data改变
	watch: {
		// 当currentValue改变时,更新value
		currentValue: function(val) {
			this.$emit('input', val);
			this.$emit('on-change', val);
		},
		// 知晓父组件修改了value
		value: function(val) {
			this.updateValue(val);
		}
	},
	methods: {
		updateValue: function(val) {
			if (val > this.max) val = this.max;
			if (val < this.min) val = this.min;
			this.currentValue = val;
		},
		handleChange: function(event) {
			var val = event.target.value.trim();
			var max = this.max;
			var min = this.min;
			if (isValueNumber(val)) {
				val = Number(val);
				this.currentValue = val;
				if (val > max) {
					this.currentValue = max;
				} else if (val < min) {
					this.currentValue = min;
				}
			} else {
				// 如果输入的不是数字,内容重置为之前的值
				event.target.value = this.currentValue;
			}

		},
		handleReduce: function() {
			if (this.currentValue <= this.min) return;
			this.currentValue -= this.stepNum;
		},
		handleIncrease: function() {
			if (this.currentValue >= this.max) return;
			this.currentValue += this.stepNum;
		},
		// 当输入框聚焦时,增加对键盘上下键的支持
		keyControl: function() {
			var _this = this;
			document.onkeydown = function(e) {
				if (document.getElementsByTagName('input')) {
					if (e.keyCode == 38) {
						_this.handleIncrease();
					} else if (e.keyCode == 40) {
						_this.handleReduce();
					}
				}
			}
		},
	},
	// 生命周期钩子mounted也调用了更新value方法,因为在第一次初始化时也对value进行了过滤
	mounted: function() {
		this.updateValue(this.value);
	}

})

2. Tab component

  • index.html entry page
  • style.js style sheet
  • tabs.js The component tabs of the outer layer of the tab page
  • pane.js tab nested component pane
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>标签页组件</title>
		<link rel="stylesheet" type="text/css"  href="style.css"/>
	</head>
	<body>
		<div id="app" v-cloak>
			<tabs v-model="activeKey">
				<pane lable="标签一" name="1">标签一的内容</pane>
				<pane lable="标签二" name="2">标签二的内容</pane>
				<pane lable="标签三" name="3">标签三的内容</pane>
			</tabs>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
		<script src="pane.js"></script>
		<script src="tabs.js"></script>
		<script type="text/javascript">
		    var app = new Vue({
				el:'#app',
				data:{
					activeKey:'1'
				}
			})
		</script>
	</body>
</html>
[v-cloak]{
	display:none;
}
.tabs{
	font-size: 14px;
	color: #657180;
}
.tabs-bar:after{
	content: '';
	display: block;
	width: 100%;
	height: 1px;
	background: #d7dde4;
	margin-top: -1px;
}
.tabs-tab{
	display: inline-block;
	padding: 4px 16px;
	margin-right: 6px;
	background: #fff;
	border:1px solid #d7dde4;
	cursor: pointer;
	position: relative;
}
.tabs-tab-active{
	color: #3399ff;
	border-top: 1px solid #3399ff;
	border-bottom: 1px solid #fff;
}
.tabs-tab-active:before{
	content: '';
	display: block;
	height: 1px;
	background: #3399ff;
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
}
.tabs.content{
	padding: 8px 0;
}
// 标签页外层组件
Vue.component('tabs',{
	template:'<div class="tabs">\
	               <div class="tabs-bar">\
				       <div :class = "tabCls(item)"\
					        v-for="(item,index) in navList"\
							@click="handleChange(index)">\
							{
   
   {item.lable}}\
						</div>\
				   </div>\
				   <div class="tabs-container">\
				        <slot></slot>\
					</div>\
	          </div>',
	data:function(){
		return {
			// 用于渲染tabs的标题
			navList:[],
			currentValue:this.value
		}
	},
	props:{
		value:{
			type:[String,Number]
		}
	},
	methods:{
		tabCls:function(item){
			return [
				'tabs-tab',
				{
					// 给当前选中的tab加一个class
					'tabs-tab-active':item.name === this.currentValue
				}
			]
		},
		handleChange:function(index){
			var nav = this.navList[index];
			var name= nav.name;
			// 改变当前选中的nav触发下面的watch
			this.currentValue = name;
			// 更新value
			this.$emit('input',name);
			// 触发一个自定义事件,供父级使用
			this.$emit('on-click',name);
		},
		getTabs(){
			// 通过遍历子组件,得到所有的pane组件
			return this.$children.filter(function(item){
				return item.$options.name ==='pane';
			})
		},
		updateNav(){
			this.navList = [];
			// 设置对this的引用,在function回调里,this的指向并不是Vue实例
			var _this = this;
			this.getTabs().forEach(function(pane,index){
				_this.navList.push({
					lable:pane.lable,
					name:pane.name||index
				});
				// 如果没有给pane设置那么,默认设置它的索引
				if(!pane.name) pane.name = index;
				// 设置当前选中的tab的索引
				if(index ===0){
					if(!_this.currentValue){
						_this.currentValue = pane.name||index;
					}
				}
			});
			this.updateStatus();
		},
		updateStatus(){
			var tabs = this.getTabs();
			var _this =this;
			//显示当前选中的tab对应的pane组件,隐藏没有选中的
			tabs.forEach(function(tab){
				return tab.show = tab.name ===_this.currentValue;
			})
		}
		
	},
	watch:{
		value:function(val){
			this.currentValue = val;
		},
		currentValue:function(){
			this.updateStatus();
		}
	}
})
// 标签页嵌套组件
Vue.component('pane',{
	name:'pane',
	template:'<div class="pane" v-show="show">\
	             <slot></slot>\
			  </div>',
	data:function(){
		return {
			show:true //pane需要控制标签页内容的显示与隐藏
		}
	},
	props:{
		name:{
			type:String //需要点击对应标签页标题按钮,应该有一个值来表示这个pane
		},
		lable:{ //设置标签页标题,tabs组件需要将它显示在标签页标题里
			type:String,
			default:''
		}
	},
	methods:{
		updateNav(){
			this.$parent.updateNav();   //直接调用父组件的方法更新lable
		}
	},
	watch:{
		lable(){
			this.updateNav();
		}
	},
	mounted(){
		this.updateNav();
	}
			  
})

Guess you like

Origin blog.csdn.net/acx0000/article/details/126180754