精华内容
下载资源
问答
  • vue技术栈

    2020-04-23 21:49:11
    一个更加方便的,例如组件不需要注册 export default { install(Vue, options) { const components = require.context(’@/components’, true, /index.(vue)$/) console.log(components.keys()) components.keys()....

    一个更加方便的,例如组件不需要注册

    export default {
    install(Vue, options) {
    const components = require.context(’@/components’, true, /index.(vue)$/)
    console.log(components.keys())
    components.keys().forEach(item => {
    const component = components(item).default;
    if(component.name){
    Vue.component(component.name,component)
    }else{
    this.installGroupComponent(Vue,component)
    }
    })
    },
    installGroupComponent(Vue,component){
    Object.keys(components).forEach(key => {
    Vue.component(key,components[key])
    })
    }
    }

    需要引入的地方

    在这里插入图片描述

    展开全文
  • 我们学习知识并不只是为了应付面试那种程度,对于面试应该作为我们的最低要求。这里就体现了数据安全思想,前端程序员或许考虑的没有Java程序员多,甚至没有为变量想过某个变量设置不可...让自己的职业生涯更加辉煌!

    写在开头

    学习完了ES 6基础,推荐阅读:ECMAScript 6 全套学习目录 整理 完结

    现在开始逐步深入Vue 技术栈,想了想,技术栈专栏的主要内容包括:

    1、Vue源码分析
    2、手把手教 保姆级 撸代码
    3、无惧面试,学以致用,继承创新
    4、谈谈前端发展与学习心得
    5、手写源码技术栈,附上详细注释
    6、从源码中学习设计模式,一举两得
    7、编程思想的提升及代码质量的提高
    8、通过分析源码学习架构,看看优秀的框架
    9、项目实战开发
    10、面试准备,完善个人简历


    暂时想到的就这么多,把这列举的10点做好了,我觉得也OK了,欢迎一起学习,觉得不错的话,可以关注博主,专栏会不断更新,可以关注一下,传送门~

    学习目录

    为了方便自己查阅与最后整合,还是打算整个目录,关于Vue技术栈前面的几篇优秀的文章:

    正文

    Vue 2的响应式原理


    提到Vue2的响应式原理,或许你就会想到Object.defineProperty(),但Object.defineProperty()严格来说的话,并不是来做响应式的。

    什么是defineProperty( )

    推荐阅读:Vue 中 数据劫持 Object.defineProperty()

    • defineProperty其实是定义对象的属性,或者你可以认为是对象的属性标签
    defineProperty其实并不是核心的为一个对象做数据双向绑定,而是去给对象做属性标签,只不过属性里的get和set实现了响应式
    属性名 默认值
    value undefined
    get undefined
    set undefined
    writalbe true
    enumerable true
    configurable true

    下面我们来详细了解一下:

    var obj={
        a:1,
        b:2
    }
    //参数说明:1.对象 2.对象的某个属性 3.对于属性的配置
    Object.defineProperty(obj,'a',{
    	writable:false
    });
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));
    

    打开浏览器,按F12,将以上代码粘贴过去,查看控制台内容:


    上述,打印的就是我们obj对象中a属性的一系列标签,权限方面可以看到默认的话为true

    那么,我们刚刚设置了 writalbe为false,即设置了a属性不可写,进行简单测试一下:

    发现我们无法对a属性进行value的修改,因为将writalbe设置了为false

    当然,我们可以设置其他权限标签,例如:

    var obj={
        a:1,
        b:2
    }
    //参数说明:1.对象 2.对象的某个属性 3.对于属性的配置
    Object.defineProperty(obj,'a',{
    	writable:false,
    	enumerable:false,
    	configurable:false
    });
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));
    

    因此,承接上文所述,defineProperty并不是来做响应式的,而是给对象中某个属性设置权限操作,是否可写,是否可以for in,是否可delete


    get和set的使用

    Vue中实现双向绑定,其实就是与get和set有很大关系

    举个栗子,请看如下代码:

    var obj={
        a:1,
        b:2
    }
    //参数说明:1.对象 2.对象的某个属性 3.对于属性的配置
    Object.defineProperty(obj,'a',{
    	get:function(){
    		console.log('a is be get!');
    	},
    	set:function(){
    		console.log('a is be set!');
    	}
    });
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));
    

    我们在控制台,简单测试一下:

    问题来了,细心的伙伴,应该发现了上图的问题,当我们get的时候,我们返回的是一个undefined,而且我们set一个值之后,也是获取不到新值,依旧是undefined,如下:

    原因呢,其实就是我们的get函数是有返回值的,如果你不return的话,就会默认返回undefined,不管你怎么set都没用,那么如何解决这个问题呢,请看下面代码:

    var obj={
        a:1,
        b:2
    }
    //借助外部变量存储值
    let _value=obj.a;
    //参数说明:1.对象 2.对象的某个属性 3.对于属性的配置
    Object.defineProperty(obj,'a',{
    	get:function(){
    		console.log('a is be get!');
    		return _value;
    	},
    	set:function(newVal){
    		console.log('a is be set!');
    		_value=newVal;
    		return _value;
    	}
    });
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));
    

    可以看到,我们必须借助一个外部变量,也就是中转站一样,才能达到我们的get和set效果,这也是vue2 中不太优雅的地方

    然后,查看控制台,解决了上述问题

    Vue中从改变一个数据到发生改变的过程

    手写 Vue 2 中响应式原理

    基于上述流程图,我们可以手写一个简单版的Vue2.0实现双向绑定的例子:

    这里我就只实现逻辑,不具体去弄视图渲染了

    文件名:2.js

    //Vue响应式手写实现
    function vue(){
    	this.$data={a:1};
    	this.el=document.getElementById('app');
    	this.virtualdom="";
    	this.observer(this.$data)
    	this.render();
    }
    //注册get和set监听
    vue.prototype.observer=function(obj){
    	var value; //借助外部变量
    	var self=this; //缓存this
    	
    	/*下面代码 a可能是data里的某个对象,不是属性
    	因此在vue2.0中需要for in循环找到属性*/
    	//Object.defineProperty(obj,'a') 
    	
    	for(var key in obj){
    		value=obj[key];
    		//判断是否为对象
    		if(typeof value === 'object'){
    			this.observer(value);
    		}else{
    			Object.defineProperty(this.$data,key,{
    				get:function(){
    					//进行依赖收集
    					return value;
    				},
    				set:function(newVal){
    					value=newVal;
    					//视图渲染
    					self.render();
    				}
    			}) 
    		}
    	}
    }
    //更新渲染部分
    vue.prototype.render=function(){
    	this.virtualdom="i am "+this.$data.a;
    	this.el.innerHTML=this.virtualdom;
    }
    

    文件名:index.html

    <!DOCTYPE html>
    <html>
    	<head>
    		<meta charset="utf-8">
    		<title>手写Vue响应式原理</title>
    	</head>
    	<body>
    		<div id='app'></div>
    		<script type="text/javascript" src="./2.js"></script>
    		<script type="text/javascript">
    			var vm = new vue();
    			//设置set定时器
    			setTimeout(function(){
    				console.log('2秒后将值改为123');
    				console.log(vm.$data);
    				vm.$data.a=123;
    			},2000)
    		</script>
    	</body>
    </html>
    

    查看页面,就会有如下效果:

    那么,以后面试如果遇到手写响应式原理,把上述js代码写上去就ok了

    源码分析:响应式原理中的依赖收集

    手写的代码里面对于依赖收集这一块我们进行了省略,下面我们从源码的角度去看依赖收集到底是什么玩意:

    /**
       * Define a reactive property on an Object.
       */
      function defineReactive$$1 (
        obj,
        key,
        val,
        customSetter,
        shallow
      ) {
        //依赖收集
        var dep = new Dep();
    
        var property = Object.getOwnPropertyDescriptor(obj, key);
        if (property && property.configurable === false) {
          return
        }
    
        // cater for pre-defined getter/setters
        var getter = property && property.get;
        var setter = property && property.set;
        if ((!getter || setter) && arguments.length === 2) {
          val = obj[key];
        }
    
        var childOb = !shallow && observe(val);
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get: function reactiveGetter () {
            var value = getter ? getter.call(obj) : val;
            if (Dep.target) {
    		  //进行依赖收集
              dep.depend();
    		  
    		  /*采用依赖收集的原因:*/
    		  
              //1.data里面的数据并不是所有地方都要用到
              //2.如果我们直接更新整个视图,会造成资源浪费
    		  //3.将依赖于某个变量的组件收集起来
    		  
              if (childOb) {
                childOb.dep.depend();
                if (Array.isArray(value)) {
                  dependArray(value);
                }
              }
            }
            return value
          },
          set: function reactiveSetter (newVal) {
            var value = getter ? getter.call(obj) : val;
            /* eslint-disable no-self-compare */
            if (newVal === value || (newVal !== newVal && value !== value)) {
              return
            }
            /* eslint-enable no-self-compare */
            if (customSetter) {
              customSetter();
            }
            // #7981: for accessor properties without setter
            if (getter && !setter) { return }
            if (setter) {
              setter.call(obj, newVal);
            } else {
              val = newVal;
            }
            childOb = !shallow && observe(newVal);
            dep.notify();
          }
        });
      }
    

    对依赖收集的总结

    在初次渲染时,会触发一次get函数,为了提高效率,节省资源,采用依赖收集,这里以之前手写的为例,get部分,我们就会对this.$data里的每一个属性(即key值)进行收集,看在哪些组件里进行了调用,以此提高效率。

    而在set部分,就会更新我们收集到的依赖

    	Object.defineProperty(this.$data,key,{
    				get:function(){
    					//进行依赖收集
    					return value;
    				},
    				set:function(newVal){
    					value=newVal;
    					//视图渲染
    					self.render();
    				}
    			}) 
    

    额外注意——关于数组的监听(探索设计模式)

    从前文我们可以了解到,defineProperty定义的ger和set是对象的属性,那么数组该怎么办呢?

    对于数组呢,在Vue中,你是没有办法像C/C++、Java等语言那样直接通过操作下标来触发更新,只能通过push、pop等方法来触发数据更新

    var arr=[1,2,3];
    arr.push(4);
    arr.pop();
    arr.shift();
    

    这里 特别重要!

    关于数组这一块里面巧妙运用到了一个设计模式——装饰者模式

    //装饰者模式
    
    //先取出原型
    var arraypro=Array.prototype;
    //拷贝一份,原因:避免影响到了原来的原型链
    var arrob=Object.create(arraypro);
    //定义一个需要装饰的方法的数组,这里只例举以下三个
    var arr=['push','pop','shift'];
    //设置重写方法(装饰者模式)
    arr.forEach(function(methods,index){
    	arrob[method]=function(){
    		//先调用原来的方法
    		var res=arraypro[method].apply(this,arguments);
    		//触发视图更新
    		dep.notify();
    	}
    })
    //接下来将数组的prototype替换到data上的prototype(此处省略)
    //这样的话,例如我们push方法,既能push又能触发视图更新了
    

    对于设计模式呢,其实并不是很难,常说难懂,很难学,可能你学设计模式,你看了书,看到的可能就是简单事例,只是一个用法,没有训练思维,正确的做法是:

    • 提高我们的思维,提高代码质量
    • 先学透,记住一些定义和一些具体使用,然后去看,去探索
    • 非常好的一种方式就是结合源码,例如上文我们从Vue数组的监听原理里面剖析出来了装饰者模式
    • 学以致用

    Vue 3的响应式原理


    对于2.0响应式原理,我们暂告一段落,接下来,我们讨论Vue 3中的技巧,众所周知,Vue 3将defineProperty替换成了proxy

    什么是proxy

    用于定义基本操作的自定义行为

    和defineProperty类似,功能几乎一样,只不过用法上有所不同

    和上文一样,我们依旧写一个响应式,不过下面的代码是有问题的,读者可以先思考一下。

    var obj={
        a:1,
        b:2
    }
    //无需借助外部变量
    
    new Proxy(obj,{
    	get(target,key,receiver){
    		console.log(target,key,receiver);
    		return target[key];
    	},
    	set(target,key,value,receiver){
    		return Reflect.set(target,key,value);
    		//return target[key]=value;
    		/*上面注释的代码和上一行意思相同*/
    	}
    })
    

    我们在控制台跑一下上述代码,发现它并没有输出console.log的内容,因此是有问题的

    正确代码如下:

    var obj={
        a:1,
        b:2
    }
    //无需借助外部变量
    //对于vue 2,提高效率,无需for in 遍历找属性
    //不会污染原对象,会返回一个新的代理对象,原对象依旧是原对象
    //也是软件工程里的重要知识,尽量不要"污染"原对象,不用给原对象做任何操作
    //只需对代理对象进行操作
    var objChildren=new Proxy(obj,{
    	get(target,key,receiver){
    		console.log(target,key,receiver);
    		return target[key];
    	},
    	set(target,key,value,receiver){
    		return Reflect.set(target,key,value);
    		//return target[key]=value;
    		/*上面注释的代码和上一行意思相同*/
    	}
    })
    

    总结:为什么Vue 3中使用proxy

    • defineProperty只能监听某个属性,不能对全对象进行监听
    • 可以省去for in遍历找对象中的属性,提高效率,省去很多代码
    • 可以监听数组,不用再去单独的对数组进行特异性操作
    • 不会污染原对象,会返回一个新的代理对象,原对象依旧是原对象
    • 只需对代理对象进行操作

    手写 Vue 3 中响应式原理

    下面代码,是在上文手写 Vue 2 响应式原理基础上修改的,通过对比,可以发现,我们省去了好多代码,不需要进行for in循环比较复杂、耗时间的操作了

    //Vue响应式手写实现
    function vue(){
    	this.$data={a:1};
    	this.el=document.getElementById('app');
    	this.virtualdom="";
    	this.observer(this.$data)
    	this.render();
    }
    //注册get和set监听
    vue.prototype.observer=function(obj){
    	var self=this;
    	this.$data=new Proxy(this.$data,{
    		get(target,key){
    			return target[key];
    		},
    		set(target,key,value){
    			target[key]=value;
    			self.render();
    		}
    	})
    }
    //更新渲染部分
    vue.prototype.render=function(){
    	this.virtualdom="i am "+this.$data.a;
    	//this.el.innerHTML=this.virtualdom;
    	this.el.innerHTML=this.virtualdom;
    }
    

    查看页面,就会有如下效果:

    proxy这么好用,还能做什么呢?(再遇设计模式)

    我们学习知识并不只是为了应付面试那种程度,对于面试应该作为我们的最低要求,接下来,我们接着去深度研究proxy还能干什么呢?

    在 Vue 3 基本上已经不兼容IE8了,这里简单提及一下

    • 类型验证

    这里我们就自定义一个实例:创建一个成人的对象,拥有name和age两个属性

    要求:name必须是中文,age必须是数字,并且大于18

    如果用纯原生js做验证的话,可想有多难去验证上述需求,或许你想到的是在构造函数里面去实现,但也不会简单,那么我们看看proxy怎么实现的:

    //类型验证
    //外部定义一个验证器对象
    var validator={
    	name:function(value){
    		var reg=/^[\u4E00-\u9FA5]+$/;
    		if(typeof value=='string'&&reg.test(value)){
    			return true;
    		}
    		return false;
    	},
    	age:function(value){
    		if(typeof value=='number'&&value>=18){
    			return true;
    		}
    		return false;
    	}
    }
    
    function person(name,age){
    	this.name=name;
    	this.age=age;
    	return new Proxy(this,{
    		get(target,key){
    			return target[key];
    		},
    		set(target,key,value){
    			if(validator[key](value)){
    				return Reflect.set(target,key,value);
    			}else{
    				throw new Error(key+' is not right!');
    			}
    		}
    	})
    }
    

    这里 特别重要!

    关于类型验证这一块里面又巧妙运用到了一个设计模式——策略模式

    关于设计模式这一块,此专栏不会细讲,但会在探索源码时发现了好的实例,会提出来一下。

    上述用到了一个正则表达式,关于这个可能面试会问到,这是之前ES 6 里的内容,大家可以看看这篇简单易懂的文章:

    推荐阅读:ES6 面试题:你能说出浏览器上到此支持多少个中文字吗?

    • 私有变量

    关于私有变量这一块,我们就拿 vue-router 源码来进行分析:

    //vue-router源码分析
    
    Object.defineProperty(this,'$router',{//Router的实例
    	get(){
    		return this._root._router;
    	}
    });
    Object.defineProperty(this,'$route',{
    	get(){
    		return{
    			//当前路由所在的状态
    			current:this._root._router.history.current
    		}
    	}
    })
    

    通过查看源码,提出疑问:为什么要为$router写get方法呢,而且没做什么操作,只是一个return?

    原因:这样可以使得$router不可修改。避免程序员通过set修改了路由,导致路由失效的情况。这里就体现了数据安全思想,前端程序员或许考虑的没有Java程序员多,甚至没有为变量想过某个变量设置不可修改。由于工作的需要,我们也要努力提升自己的代码质量!让自己的职业生涯更加辉煌!

    virtual dom 和 diff算法


    关于diff算法和虚拟dom,也是面试常见的问题,平常容易忽视,这里我也就深入研究了一下:

    虚拟dom

    所谓虚拟dom,如字面意思,它是虚拟的,只在概念里面存在,并不真的存在,在vue中是ast语法树,关于这个语法树本文就不详细介绍了,有兴趣的读者可以深入研究一下。

    下面代码,是一个简单vue template模板,那么解析成虚拟dom是怎样的呢?

    <template>
    	<div id='dd'>
    		<p>{{msg}}</p>
    		<p>abc</p>
    		<p>123</p>
    	</div>
    </template>
    

    解析成虚拟dom:

    diff <div>
    	props:{
    		id:dd
    	},
    	children:[
    		diff <p>
    		props:
    		children:[
    			
    		],
    		text:xxx,
    	]
    

    上述代码就是概念上的介绍,如果懂一点算法知识的应该就明白了,就是不断地嵌套,但为了让更多伙伴读懂学会虚拟dom,下面来手写一个对象的形式:

    <template>
    	<div id='dd'>
    		<p><span></span></p>
    		<p>abc</p>
    		<p>123</p>
    	</div>
    </template>
    
    var virtual=
    {
    	dom:'div',
    	props:{
    		id:dd
    	},
    	children:[
    		{
    			dom:'p',
    			children:[
    				dom:'span',
    				children:[]
    			]
    		},
    		{
    			dom:'p',
    			children:[
    			]
    		},
    		{
    			dom:'p',
    			children:[
    			]
    		}
    	]
    }
    

    上述代码应该就很清晰了,简单来说,就是将最上面的dom结构,解析成下面用js解析成的对象,每一个对象都有一个基础的结构:

    • dom元素标签
    • props记录挂载了哪些属性
    • children记录有哪些子元素(子元素拥有和父元素相同的结构)

    diff算法的比对机制

    下面部分采用了伪代码形式介绍diff算法的比对机制,已经给出了详细的注释说明:

    //diff算法匹配机制
    patchVnode(oldVnode,vnode){
    	//先拿到真实的dom
    	const el=vnode.el=oldVnode.el;
    	//分别拿出旧节点和新节点的子元素
    	let i,oldCh=oldVnode.children,ch=vnode.children;
    	//如果新旧节点相同,直接return
    	if(oldVnode==vnode) return;
    	/*分四种情况讨论*/
    	//1.只有文字节点不同的情况
    	if(oldVnode.text!==null&&vnode.text!==null&&oldVnode.text!==vnode.text){
    		api.setTextContent(el,vnode.text);
    	}else{
    		updateEle();
    		//2.如果新旧节点的子元素都存在,那么发生的是子元素变动
    		if(oldCh&&ch&&oldCh!==ch){
    			updateChildren();
    		//3.如果只有新节点有子元素,那么发生的是新增子元素
    		}else if(ch){
    			createEl(vnode);
    		//4.如果只有旧节点有子元素,那么发生的是新节点删除了子元素
    		}else if(oldCh){
    			api.removeChildren(el);
    		}
    	}
    }
    

    总结

    学如逆水行舟,不进则退
    
    展开全文
  • 写此篇文章是为了今后能对项目整体有个把握。 记录自己做项目的心路历程。 同时更加巩固所学的技术

    花了差不多半个多月跟着网上的视频终于完成了这个项目。完结撒花。
    下面先来展示一下整个项目的界面及功能模块。
    项目源码在码云上托管,有兴趣可以去下载看看。

    https://gitee.com/missq1111/vue_shop_manage

    在这里插入图片描述
    在这里插入图片描述
    接下来进入正题。
    先梳理一下此项目需要的模块:

    1. 登录/退出功能。
    2. 主页布局。
    3. 用户管理、权限管理、分类管理、参数管理、商品列表、订单管理、数据统计模块
      在这里插入图片描述

    技术栈:

    • vue
    • vue-router
    • Element-UI
    • Axios
    • Echarts

    要点分析:

    下面所有代码为不完整的代码,仅为了说明具体用法。
    1.登录界面的绘制

    //登录表单
    <el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules">
    <el-form-item prop="username">
    <el-input v-model="loginForm.username" prefix-icon="iconfont icon-user"></el-input>
    </el-form-item>
    </el-form>
    
    data() {
        return {
    // 这是登录表单的数据绑定对象
          loginForm: {
            username: 'admin',
            password: '123456'
          },
          loginFormRules: {
            username: [
              { required: true, message: '请输入用户名称', trigger: 'blur' },
              { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
            ]
          }
         }
        }
    
    this.$refs.loginFormRef.resetFields(); //重置表单数据
    

    对以上代码进行简短分析,el-form为一个表单区域,此表单需要用到loginForm作为双向绑定的数据对象,loginFormRules为表单项的输入验证规则,需要在data中进行定义。el-form-item为具体的表单项,prop为要绑定的数据。表单项里面有一个输入框,要用data中定义的loginForm的具体项作进行数据绑定。el-form组件中的方法resetFields可以重置表单。比如点击按钮重置操作。
    整个项目梳理:
    1、所有页面的设计及逻辑,实现数据的添加、删除、修改功能。 2、使用Element-UI实现所有子页面内容区域顶部的面包屑导航栏,卡片视图区域及分页区域。 3、使用Element-UI完成了添加/修改信息等的对话框渲染及进行逻辑部分的双向数据绑定设计。 4、利用axios来配置默认请求数据根路径,通过修改原型链来更方便的在组件中请求数据。为了实现页面加载时显示进度条的效果,可以搭NProgress和axios设置request拦截器展示进度条和response拦截器隐藏进度条。 5、利用作用域插槽来接收操作列的数据、渲染级联选择器、富文本编辑器、树形控件与后台数据的交互。 6、利用Echarts来渲染数据折线图,同时利用lodash将参数合并实现options数据的完整性。

    (持续更新。。。)

    展开全文
  • 那不是一个全栈攻城狮的自我修养,虽然VUE出3.0了,但是入门还是建议VUE2.0 + Element UI,毕竟3.0还要等养肥了在学,现在教程太少,学习2.0之后在学3.0也能更加理解为什么要这么去改进 VUE是啥?简单来说就是一个JS...

    目录

    前端技术栈:5分钟入门VUE+Element UI

    前言

    2021了,VUE都出3.0了,还不开始学习VUE?那不是一个全栈攻城狮的自我修养,虽然VUE出3.0了,但是入门还是建议VUE2.0 + Element UI,毕竟3.0还要等养肥了在学,现在教程太少,学习2.0之后在学3.0也能更加理解为什么要这么去改进

    VUE是啥?简单来说就是一个JS框架

    Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

    此处可以看VUE入门视频:https://learning.dcloud.io/#/?vid=0

    本教程是给有一点基础的人进行VUE的快速入门,搭建一个单页面的增删改查。

    效果演示

    [图片上传失败…(image-56de9f-1612435593826)]

    Nodejs

    现在的前端都是模块化开发了,需要Nodejs的支持,到官网下载Nodejs一路Next即可安装,官网地址:https://nodejs.org/en/

    安装好后打开cmd命令行窗口,输出node -v,如果有版本号说明安装成功,同样输入npm -V如果有版本号说明npm安装成功(npm为nodejs的包管理器)

    json-server

    在实战VUE之前先了解下json-server,顾名思义json服务器,在我们的入门实战中,不想引入太过于复杂的后端环境但是又想体验模块化的前端整个HTTP请求的完整过程,所幸Nodejs提供了json-server工具,方便快速的搭建简易服务器。

    安装json-server

    npm i json-server -g
    
    • i表示install,即安装
    • -g表示global,即全局安装

    运行json-server

    随便找个目录,创建一个db.json文件,内容如下

    {
      "data": []
    }
    

    然后在该目录下打开命令行窗口,执行

    json-server --watch db.json
    

    回显如下说明成功

      \{^_^}/ hi!
    
      Loading db.json
      Done
    
      Resources
      http://localhost:3000/data
    
      Home
      http://localhost:3000
    
      Type s + enter at any time to create a snapshot of the database
      Watching...
    
    

    测试json-server

    http://localhost:3000/data是接口地址,直接用curl发送POST,DELETE,PATCH,GET请求模拟增删改查,只要测试一个通过了即可,其他的感兴趣可以测试下。

    增(POST)

    curl "http://127.0.0.1:3000/data" -H "Content-Type: application/json" -d "{\"name\":\"zhangsan\"}" -X POST
    
    • -H表示增加请求头
    • -d后面是数据
    • -X POST表示用POST请求

    返回值如下

    {
      "name": "zhangsan",
      "id": 2
    }
    

    再看一下我们刚才创建的db.json文件,里面多了我们刚才POST的数据。实际上就是存在了这个文件里面。

    改(PATCH)

    注意改的时候url后面跟上id

    curl "http://127.0.0.1:3000/data/2"  -H "Content-Type: application/json" -d "{\"name\":\"lisi\"}"  -X PATCH
    

    返回如下

    {
      "name": "lisi",
      "id": 2
    }
    

    说明修改成功

    查(GET)

    curl "http://127.0.0.1:3000/data"  -X GET
    

    直接查全部

    [
      {
        "name": "lisi",
        "id": 2
      }
    ]
    

    删(DELETE)

    删除id为2的数据

    curl "http://127.0.0.1:3000/data/2" -X DELETE
    

    返回如下

    {}
    

    VUE实战

    全局安装vue-cli

    vue-cli是vue的脚手架,所谓脚手架说白了就是快速创建模板化的项目,不用每次创建项目都去重复那些固定化的操作,注意我们安装的是vue2.0的脚手架

    npm install -g vue-cli
    

    不要和vue3.0的混淆,vue3.0是npm install -g @vue/cli,此处不需要安装3.0,否则会冲突

    全局安装webpack

    webpack可以将vue项目打包成静态文件

    npm install webpack -g
    

    vue 2.0 + Element UI 项目搭建

    基础版本搭建

    随便找个目录输入如下命令

    # 初始化一个叫做vue2_elementui的项目,使用webpack模板
    vue init webpack-simple vue2_elementui
    
    # 进入项目目录
    cd vue2_elementui
    
    # 构建
    npm install
    
    # 运行
    npm run dev
    

    经过上面的操作,打开http://localhost:8080/如果能看到下面的页面说明搭建成功了

    前端技术栈:5分钟入门VUE+Element UI

    引入Element UI

    上面的先CTRL + C退出,下面安装Element UI依赖

    # 安装Element UI
    npm install element-ui -S
    
    # 顺便安装vue-router 和 vue-resource,前者是路由,后者是执行Ajax请求用到的依赖
    npm install vue-router vue-resource --S
    
    # 安装moment,事件选择组件
    npm install moment -S
    
    • -S表示只在该项目下安装,不是全局安装

    打开main.js,引入上面安装的组件

    import Vue from 'vue'
    import App from './App.vue'
    
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    Vue.use(ElementUI)
    
    import axios from 'axios'
    import moment  from 'moment'
    
    Vue.filter('moment', function (value, formatString) {
      formatString = formatString || 'YYYY-MM-DD HH:mm:ss';
      return moment(value).format("YYYY-MM-DD"); // value可以是普通日期 20170723
    });
    
    Vue.prototype.$axios = axios
    
    new Vue({
      el: '#app',
      render: h => h(App)
    })
    
    

    打开APP.vue,修改如下

    <template>
      <div id="app">
         <img src="./assets/logo.png">
        <div>
          <el-button @click="startHacking">element ui 安装成功</el-button>
        </div>
      </div>
    </template>
    <script>
    export default {
      methods: {
        startHacking () {
          this.$notify({
            title: 'It works!',
            type: 'success',
            message: 'We\'ve laid the ground work for you. It\'s time for you to build something epic!',
            duration: 5000
          })
        }
      },
      components:{
        UserInfo
      }
    }
    </script>
    
    <style>
    #app {
      font-family: Helvetica, sans-serif;
      text-align: center;
    }
    </style>
    
    

    最后再次运行

    npm run dev
    

    如果出现下面的页面,而且点击页面会出现右边的提示框,说明Element UI安装成功

    前端技术栈:5分钟入门VUE+Element UI

    简单增删改查页面

    用户信息组件UserInfo.vue

    <template>
      <div class="info">
        <h1>用户信息管理界面</h1>
        <el-row>
          <el-col :span="20" :push='2'>
            <div>
              <el-form :inline="true">
                <el-form-item style="float: left" label="查询用户信息:">
                  <el-input v-model="keyUser" placeholder="查询所需要的内容......"></el-input>
                </el-form-item>
                <el-form-item style="float: right">
                  <el-button type="primary" size="small" icon="el-icon-edit-outline" @click="hanldeAdd()">添加</el-button>
                </el-form-item>
              </el-form>
            </div>
            <div class="table">
              <el-table
                :data="searchUserinfo(keyUser)"
                border
                style="width: 100%">
                <el-table-column
                  type="index"
                  label="序号"
                  align="center"
                  width="60">
                </el-table-column>
                <el-table-column
                  label="日期"
                  align="center"
                  width="120">
                  <template slot-scope="scope">
                    <span>{{ scope.row.date | moment}}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="姓名"
                  align="center"
                  width="100">
                  <template slot-scope="scope">
                    <span>{{ scope.row.name }}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="邮箱"
                  align="center"
                  width="160">
                  <template slot-scope="scope">
                    <span>{{ scope.row.email }}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="标题"
                  align="center"
                  width="160">
                  <template slot-scope="scope">
                    <span>{{ scope.row.title }}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="评价"
                  align="center"
                  width="200">
                  <template slot-scope="scope">
                    <span>{{ scope.row.evaluate }}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="状态"
                  align="center"
                  width="160">
                  <template slot-scope="scope">
                    <span>{{ scope.row.state }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="操作" fixed="right">
                  <template slot-scope="scope">
                    <el-button
                      size="mini"
                      @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
                    <el-button
                      size="mini"
                      type="danger"
                      @click="handleDelete(scope.$index, scope.row)">删除</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-col>
        </el-row>
        <AddUser :dialogAdd="dialogAdd" @update="getUserInfo"></AddUser>
        <EditUser :dialogEdit="dialogEdit" :form="form" @updateEdit="getUserInfo"></EditUser>
      </div>
    </template>
    
    <script>
    import AddUser from './AddUser'
    import EditUser from './EditUser'
    export default {
      name: 'info',
      data () {
        return {
          tableData:[],
          dialogEdit:{
            show:false,
          },
          dialogAdd:{
            show:false
          },
          keyUser:"",
          form:{    //编辑信息
            date:'',
            name:'',
            email:'',
            title:'',
            evaluate:'',
            state:''
          },
        }
      },
      methods:{
        getUserInfo() {
          this.$axios.get('http://localhost:3000/data').then(res => {
            console.log(res)
            this.tableData = res.data
          })
        },
        hanldeAdd(){  //添加
          this.dialogAdd.show = true;
        },
        handleEdit(index,row){  //编辑
          this.dialogEdit.show = true; //显示弹
          this.form = {
            date:row.date,
            name:row.name,
            email:row.email,
            title:row.title,
            evaluate:row.evaluate,
            state:row.state,
            id:row.id
          }
          console.log(row)
        },
        handleDelete(index,row) {
          // 删除用户信息
          this.$axios.delete(`http://localhost:3000/data/${row.id}`).then(res =>{
            this.$message({
              type:"success",
              message:"删除信息成功"
            })
            this.getUserInfo()    //删除数据,更新视图
          })
        },
        searchUserinfo(keyUser) {
          return this.tableData.filter((user) => {
            if(user.name.includes(keyUser)) {
              return user
            }
          })
        }
      },
      created(){
        this.getUserInfo()
      },
      components:{
        AddUser,
        EditUser
      }
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
    h1{
      font-size: 30px;
      color: #333;
      text-align: center;
      margin: 0 auto;
      padding-bottom: 5px;
      border-bottom: 2px solid #409EFF;
      width: 300px
    }
    </style>
    
    

    这是列表主组件,查询和展示用的,长下面这样

    [图片上传失败…(image-e10941-1612435593825)]

    其中引入了AddUser组件和EditUser组件

    添加用户组件AddUser.vue

    <template>
      <div class="hello">
        <el-dialog title="添加用户信息" :visible.sync="dialogAdd.show">
          <el-form :model="formDate" ref="formdong" label-width="100px" :rules="formrules">
            <el-form-item label="日期" prop="date">
              <el-date-picker
                v-model="formDate.date"
                type="date"
                placeholder="选择日期">
              </el-date-picker>
            </el-form-item>
            <el-form-item label="姓名" prop="name">
              <el-input v-model="formDate.name"></el-input>
            </el-form-item>
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="formDate.email"></el-input>
            </el-form-item>
            <el-form-item label="标题" prop="title">
              <el-input v-model="formDate.title"></el-input>
            </el-form-item>
            <el-form-item label="评价" prop="evaluate">
              <el-input v-model="formDate.evaluate"></el-input>
            </el-form-item>
            <el-form-item label="状态" prop="state">
              <el-input v-model="formDate.state"></el-input>
            </el-form-item>
          </el-form>
          <div slot="footer" class="dialog-footer">
            <el-button @click="dialogAdd.show = false">取 消</el-button>
            <el-button type="primary" @click="dialogFormAdd('formdong')">确 定</el-button>
          </div>
        </el-dialog>
      </div>
    </template>
    
    <script>
    export default {
      name: 'AddUser',
      props:{
        dialogAdd:Object
      },
      data () {
        return {
          formDate:{
            date:'',
            name:'',
            email:'',
            title:'',
            evaluate:'',
            state:''
          },
          formrules:{
            date:[{required:true,message:"日期不能为空",trigger:"blur"}],
            name:[{required:true,message:"用户名不能为空",trigger:"blur"}],
            email:[{required:true,message:"邮箱不能为空",trigger:"blur"}],
          }
        }
      },
      methods:{
        dialogFormAdd(formdong) {
          this.$refs[formdong].validate((valid) => {
            if (valid) {
              this.$axios.post('http://localhost:3000/data',this.formDate).then(res => {
                this.$message({
                  type:"success",
                  message:"添加信息成功"
                })
                this.dialogAdd.show = false;
                this.$emit('update');
    
              })
              this.formDate  = ""
            } else {
              console.log('error submit!!');
              return false;
            }
          })
        }
      }
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
    
    </style>
    
    

    这是添加用户信息组件

    前端技术栈:5分钟入门VUE+Element UI

    编辑用户组件EditUser.vue

    <template>
      <div class="hello">
        <el-dialog title="编辑用户信息" :visible.sync="dialogEdit.show">
          <el-form :model="form" ref="formEdit" label-width="100px" :rules="formrules">
            <el-form-item label="日期" prop="date">
              <el-date-picker
                v-model="form.date"
                type="date"
                placeholder="选择日期">
              </el-date-picker>
            </el-form-item>
            <el-form-item label="姓名" prop="name">
              <el-input v-model="form.name"></el-input>
            </el-form-item>
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="form.email"></el-input>
            </el-form-item>
            <el-form-item label="标题" prop="title">
              <el-input v-model="form.title"></el-input>
            </el-form-item>
            <el-form-item label="评价" prop="evaluate">
              <el-input v-model="form.evaluate"></el-input>
            </el-form-item>
            <el-form-item label="状态" prop="state">
              <el-input v-model="form.state"></el-input>
            </el-form-item>
          </el-form>
          <div slot="footer" class="dialog-footer">
            <el-button @click="dialogEdit.show = false">取 消</el-button>
            <el-button type="primary" @click="dialogFormEdit('formEdit')">确 定</el-button>
          </div>
        </el-dialog>
      </div>
    </template>
    
    <script>
    export default {
      name: 'HelloWorld',
      props:{
        dialogEdit:Object,
        form:Object
      },
      data () {
        return {
          formrules:{
            date:[{required:true,message:"日期不能为空",trigger:"blur"}],
            name:[{required:true,message:"用户名不能为空",trigger:"blur"}],
            email:[{required:true,message:"邮箱不能为空",trigger:"blur"}],
          }
        }
      },
      methods:{
        dialogFormEdit(formEdit) {
          this.$refs[formEdit].validate((valid) => {
            if (valid) {
              this.$axios.put(`http://localhost:3000/data/${this.form.id}`,this.form).then(res => {
                this.$message({
                  type:"success",
                  message:"编辑信息成功"
                })
                console.log(res)
                this.dialogEdit.show = false;
                this.$emit('updateEdit')
              })
            } else {
              console.log('error submit!!');
              return false;
            }
          })
        }
      }
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
    
    </style>
    
    

    编辑和新增类似

    在APP.vue中引入组件

    APP.vue修改如下,其中import UserInfo from './UserInfo’表示引入组件

    表示调用该组件

    <template>
      <div id="app">
         <img src="./assets/logo.png">
        <div>
          <el-button @click="startHacking">element ui 安装成功</el-button>
        </div>
        <hr>
        <UserInfo></UserInfo>
      </div>
    </template>
    
    <script>
    import UserInfo from './UserInfo'
    export default {
      methods: {
        startHacking () {
          this.$notify({
            title: 'It works!',
            type: 'success',
            message: 'We\'ve laid the ground work for you. It\'s time for you to build something epic!',
            duration: 5000
          })
        }
      },
      components:{
        UserInfo
      }
    }
    </script>
    
    <style>
    #app {
      font-family: Helvetica, sans-serif;
      text-align: center;
    }
    </style>
    
    

    最后打开页面,看看是否能增删改查

    前端技术栈:5分钟入门VUE+Element UI

    此时,项目结构如下图所示

    [图片上传失败…(image-ae5dd1-1612435593825)]

    总结

    此处假设你已经看过VUE基础视频了:https://learning.dcloud.io/#/?vid=0

    main.js

    main.js很重要,里面定义了入口组件APP.vue;引入了项目所需要的包

    组件三件套

    分别是template,script,style,新组件就这样来定义

    <template>
     //定义该组件的模板
    </template>
    
    <script>
    import com1 from 'XXX'
    import com2 from 'XXX'
    export default {
      name: '组件名字',
      data () {
        return {
            //定义组件数据
          },
        }
      },
      methods:{
        //定义组件方法
      },
      created(){
        this.getUserInfo()
      },
      components:{
        com1,com2//定义改组件引用的其他组件
      }
    }
    </script>
    
    <style>
    //定义该组件用到的样式
    </style>
    
    

    组件的属性

    在AddUser.vue组件中,有一个键叫props,里面有一个dialogAdd

    <script>
    export default {
      name: 'AddUser',
      props:{
        dialogAdd:Object
      }
    }
    </script>
    

    这表示该组件可以用到的属性,在UserInfo.vue里面这么引用AddUser组件的

    <AddUser :dialogAdd="dialogAdd" @update="getUserInfo"></AddUser>
    

    在AddUser上面绑定了一个dialogAdd属性,这会传到AddUser.vue组件里面控制AddUser的显示与隐藏,有点像父子组件之前的通信

    $emit触发自定义事件

    在AddUser.vue组件中,有一个自定义事件

    this.$emit('update');
    

    这会给父组件UserInfo触发一个update事件,父组件自然会调用@update中定义的方法getUserInfo重新查询数据并加载页面,总结两句话就是:

    • 父组件可以使用 props 把数据传给子组件
    • 子组件可以使用 $emit 触发父组件的自定义事件

    Element UI

    ElementUI相关问题可以参考官方文档:https://element.eleme.cn/#/zh-CN/component/installation

    推荐

    推荐一个通用后端项目:https://panjiachen.github.io/vue-element-admin-site/zh/guide/

    vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。

    展开全文
  • 当年 MVC 技术热度不减现在的 react 和 vue。几年过去了,前后端分离,各种前端框架的出现,使得部分老项目更加少人维护了,毕竟 MVC 项目的语法对前端实在是太不友好了 然而最近有一个项目就是 java web 的项目,...
  • Vue SSR之 服务端渲染

    2019-10-20 16:45:14
    序言 SSR 是解决目前一些对SEO, 首屏渲染速度问题的优选方案。 目前VUE SSR 的实现由... 而NUXT 提供了平滑的开箱即用的体验,它建立在同等的Vue技术栈之上, 但抽象出很多模板,并提供了一些额外的功能。 例如: 静...
  • Vue入门项目实战

    2019-08-29 17:03:58
    前端Vue.js + 后端Spring boot项目实战开发环境主要技术栈项目的功能已经完成的界面结语: 开发环境 前端 node,js +vue 2.9+vue-cli 后端 Springboot+maven+mybatis+mysql IDE 前端 Vscode 后端Spring tool suite ...
  • vue双向绑定原理解析

    2019-07-18 23:26:20
    最近项目中大量用到vue技术栈的东西,让我这个对底层原理很感兴趣的菜鸟(这不是找虐吗,哈哈哈哈!)好奇vue的双绑定具体是怎么实现的,于是研究了一哈vue的源码(虽然过程略艰辛),收获很大,为了能更加深入的体会...
  • [Vue.js+Socket.io 智能聊天室 microzz.com](https://dn-mhke0kuv.qbox.me/a5abbff92a442fa2d356.png)## 源代码 现在已经开源: https://github.com/microzz/vue-chat 欢迎star和提出宝贵意见## 技术栈* Vue2.0:...
  • 技术栈 【前端】 Vue:用于重建用户界面的MVVM框架。它的核心是响应的数据绑定和组系统件 vue-router:为单页面应用提供的路由系统,项目上线前使用了 vuex:Vue集中状态管理,在多个组件共享某些状态时非常方便 ...
  • 技术栈 vue2 + vuex + vue-router + webpack + ES6/7 + fetch + sass + flex + svg 项目运行 注意:由于涉及大量的 ES6/7 等新属性,node 需要 6.0 以上版本 git clone ...
  • vue 单文件探索

    2018-09-04 08:37:04
    vue 作为开发技术栈的前端开发者,往往会配合前端构建工具,进行项目的工程化管理。比如,大家常用的 vue 全家桶 + webpack 的方案进行一些中大型前端项目的开发。配合 webpack 后,vue 的组件化优势更加明显,...
  • aVue与Vue

    2020-10-15 13:31:22
    Avue.js是基于现有的element-ui库进行的二次封装,...Avue是一个后台快速集成解决方案(curd和form快速开发组件),它基于 Vue全家桶 和 element-ui,使用了最新的前端技术栈,支持权限验证,第三方网站嵌套等功能。 ...
  • 之前用的技术栈都是yeoman上找的webpack+react的脚手架,第一次看vue项目的源码。感觉一个vue文件里包含html模板、对应JS和样式的组合方式,使得组件化更加明显,也降低了平时项目中的小文件数量。相比于react的JSX...
  • 此项目是基于Vue全家桶+ TypeScript + Element-UI技术栈的简洁时尚博客网站。 项目详情请猛戳该文章: TypeScript具有类型系统,且是JavaScript的超集,TypeScript在2018年势头迅猛,可谓遍地开花。 Vue3.0将使用...
  • 注:由于公众号文章推送规则的改变,所以为了大家能够准时收到我们的文章推送,请记得将公众号:JAVA编码进化论设为星标~这样就不会错过每一篇...不得不说这些技术栈使得开发速度提高一个层次了,但是还应当更加深...
  • 因为官网是单人开发技术栈都是自己定,还是选择了老baby Vue,之前一直使用的是element框架,这次换成了iView,两者相比较,(以下仅为个人意见,不服憋着):一些小众组件上各有所长整体 iView 更加丰富,通过使用频率...
  • vue+vuex仿telegram app的单页应用,实现分组功能消息发送 前言 vue2仿telegram这个项目我把自己所学的vue大部分知识都用上了,可以说覆盖了vue和vuex的大部分知识,通过这项目对vue...技术栈 脚手架工具 vue-cli@4.4.
  • vue.js是什么

    2020-05-14 17:48:52
    对于不懂编程的朋友来说,vue.js或许会显得十分陌生,而对于程序员们来说,它却是一个非常常用的框架。...简单小巧的核心,渐进式技术栈,足以应付任何规模的应用。 3、性能 20kb min+gzip 运行大小、超快虚拟
  • 最近在学vue,所以就自己搭建了一个springboot和vue项目, springboot项目和vue项目 架构如下,简单实现登录,注册,拦截,权限...项目中的技术栈 mysql redis mybatis 等 因为刚学的vue,所以都是自己手敲的代码 ...
  • 0. 技术栈 主要功能:登录后跳转到书籍管理页面,能够对书籍信息进行增删改查。 后端: Spring Boot Mybatis Plus MySQL 前端: Vue Axios Element-UI 1. 前端 关于怎么安装Vue以及构建Vue脚手架的部分,可见...
  • Vue.js+express建站

    2019-09-28 16:18:11
    技术栈Vue.js: 前端开发框架:响应式数据绑定和组件化开发(单页应用,用户管理系统) 响应式实现页面数据和代码数据的自动同步更新,使开发可以简化为操作业务数据,跟视图层分离组件化开发将页面不同功能的...
  • 此外,在过去的几年中,Vue.js 变得非常流行,许多企业将其添加到他们的技术栈中。该框架的优点是它具有内置的数据绑定和MVC模型(模型、视图、控制器),与Angular.js和React.js相比,它使配置更加容易。另外,Vue....
  • 同时对于前端人员来说,可以直接使用javascript得标准来编写桌面程序,无需使用微软的技术栈(C#写的我脑瓜子疼…)。使得javascript的用途更加广泛了。好,废话不多说… 2,构建程序框架所需环境 Nodejs(是不是发现...
  • 技术栈 初步 Spring Boot 2.0.5。发布 Hibernate5 四郎1.4.0 活动5.25.2 招摇2.8.0 前端 vue2 威克斯 Vue路由器 轴距 元素用户界面 网络包 文件 更多项目的文档请参考 初步开发 前端开发 常用VUE组件 生产环境...
  • 背景:最近在学习Vue.js,于是决定借...不得不说这些技术栈使得开发速度提高一个层次了,但是还应当更加深入研究啊。 源码地址:后台:https://github.com/yangyuscript/appapidemo.git 前台:https://github.com/y...
  • 互联网行业发展至今出现了很多技术,技术都在与时俱进的更新中,如从前的前端技术栈(html4+js+css+jquery)到现在vue+axios。随着更多优秀的框架出现,让我们从重复的基础代码编写中解救出来了,我们将更加专注业务...
  • 本文主要对以下技术要点...建议结合源码一起阅读,效果更好(这个 DEMO 使用的是 Vue 技术栈)。 1. 编辑器 先来看一下页面的整体结构。 这一节要讲的编辑器其实就是中间的画布。它的作用是:当从左边组件列表拖拽出一

空空如也

空空如也

1 2 3 4
收藏数 67
精华内容 26
关键字:

vue技术栈更加

vue 订阅