vuejs学习笔记,仅供参考,有错误欢迎评论指正

备注:笔记是从记事本复制过来的,有些格式有点问题,不影响看

1、在js中,要先定义一个Vue组件并定义组件里的属性:Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
再建立数据源:var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其他什么人吃的东西' }
]
}
})
2、在页面html中,用定义的组件显示数据源:
<div id="app-7">
<ol>
<todo-item //此处不直接用自定义组件,可以改写成<li is='todo-item',可避开潜在的浏览器解析错误
v-for="item in groceryList"
v-bind:todo="item" //此处将自定义组件的属性props绑定到数据源
v-bind:key="item.id">
</todo-item>
</ol>
</div>

3、在Vue实例的数据源中,可以加入钩子(类似内置函数),在Vue实例被创建后执行:
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"

4、计算属性:给数据域中的属性赋值
var vm = new Vue({
el: '#example',
data: {
firstName: 'li',
lastName:'lei',
fullName:'li lei'
},
computed: {
// 我们提供的函数用作计算属性的默认getter函数,也可以提供setter
fullName: function () {
return this.firstName + ' ' + this.lastName // `this` 指向 vm 实例
}
}
})

// ...
computed: {
fullName: {
// 默认的getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// 给出的setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// 当运行vm.fullName='xiao ming'时,setter会被调用,vm.firstName 和 vm.lastName 也会相应更新;同理vm.firstName 和 vm.lastName重新赋值时,vm.fullName
也会更新

5、监听属性与计算属性的比较
HTML:<div id="demo">{{ fullName }}</div>
监听属性:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
计算属性:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
//计算属性比监听属性更简单,推荐使用

6、给HTML标签绑定类class,传给v-bind:class一个对象
(1)<div v-bind:class="{ active: isActive }"></div>
(2)<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"> //v-bind:class与普通class属性共存
</div>

data: {
isActive: true,
hasError: false
}
//结果渲染为<div class="static active"></div>,数据属性变化时,class列表页相应更新
(3)<div v-bind:class="classObject"></div>

data: {
classObject: {
active: true,
'text-danger': false
}
}//1、2中都是将绑定的数据对象内联定义在模板里,如3,也可以分开定义在数据部分
(4)<div v-bind:class="classObject"></div>

data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
//绑定类属性在计算属性里(常用)

7、给HTML标签绑定类class,传给v-bind:class一个数组
<div v-bind:class="[activeClass, errorClass]"></div>

data: {
activeClass: 'active',
errorClass: 'text-danger'
}//渲染为<div class="active text-danger"></div>
//也可以用三元表达式根据条件切换列表中的class:<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
//数组语法中也可以使用对象语法:<div v-bind:class="[{ active: isActive }, errorClass]"></div>

8、给用户自定义的组件绑定类class
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})

<my-component v-bind:class="{ active: isActive }"></my-component>
//当isActive为真值时,HTML被渲染为<p class="foo bar active">Hi</p>

9、给HTML标签绑定内联样式,传给v-bind:style的语法跟绑定类一致
<div v-bind:style="styleObject"></div>

data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}

//结合返回对象的计算属性使用
data: {
color: 'red',
fontSize: '13px'
},
computed: {
styleObject: function () {
return {
color: this.color,
fontSize: this.fontSize
}
}
}

//绑定数组内联样式
<div v-bind:style="[baseStyles, overridingStyles]"></div>

10、条件渲染
(1)v-if: 当条件第一次为真时开始渲染条件块。因为 v-if 是一个指令,所以必须将它添加到一个元素上。可以把一个 <template> 元素当做不可见的包裹元素,并在
上面使用 v-if。最终的渲染结果将不包含 <template> 元素。给元素添加具有唯一值的key属性,条件中的元素是独立的,每次切换,输入框都将被重新渲染:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>

(2)v-show: 带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。v-show 不支持 <template> 元素,也不支持 v-
else.
//注:v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if
较好。

11、列表渲染
(1)v-for: v-for 指令根据一组数组的选项列表进行渲染。v-for 块中,我们拥有对父作用域属性的完全访问权限
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} -{{$index}} -{{ item.message }}
</li>
</ul>

var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})//index是可选参数
//结果:
Parent-0-0-Foo
Parent-1-1-Bar
(2)v-for也可以通过一个对象的属性来迭代:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>

new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})
//key和index是可选参数
//界外:1、js数组中的splice方法,参数(index,howmany,newValue),从数组删除或添加元素,直接修改数组:index是从哪个索引开始操作,howmany是删除的元素个
数,newValue是要添加的元素值,howmany为0时则添加元素;
2、js数组中的slice方法,参数(start,end):获取数组的部分元素返回新数组,不修改原数组。end不填时,返回start到最后的元素。end不为空时返回到end-1的位
置元素

(3)注意事项
由于JavaScript 的限制,Vue 不能检测以下变动的数组:
1当你利用索引直接设置一个项时:vm.items[indexOfItem] = newValue
2当你修改数组的长度时:vm.items.length = newLength
对于第1种,可以用vue.set(vm.items,indexOfItem,newValue)或vm.items.splice(indexOfItem,1,newValue)
对于第2种,用vm.items.splice(newLength)

同上,由于JavaScript 的限制,Vue 不能检测对象属性的添加或删除:
var vm = new Vue({
data: {
a: 1,
object:{
name:'lily'
}
}
})//vm.a现在是响应式的
vm.b=2 //vm.b不是响应式的
//对于已经创建的实例,vue不能动态添加根级别的响应式属性,但是可以用Vue.set(object, key, value) 方法向嵌套对象添加响应式属性:
vue.set(object,'age',21)

(4)不改变或重置原始数组时,可以返回数组的计算属性:
<li v-for="n in evenNumbers">{{ n }}</li>

data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
或当计算属性不适用时,使用method方法:
<li v-for="n in even(numbers)">{{ n }}</li>

data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}

(5)v-for与v-if
当它们处于同一节点,v-for 的优先级比 v-if 更高

(6)自定义组件里使用v-for,与普通元素一样使用,key是必须的:<my-component v-for="item in items" :key="item.id"></my-component>
自定义组件使用v-for的完整例子:
html:<div id="todo-list-example">
<input
v-model="newTodoText" //newTodoText对应Vue对象中的数据属性
v-on:keyup.enter="addNewTodo" //keyup是按键修饰符,enter键盘操作对应方法addNewTodo,在js中定义
placeholder="Add a todo"
>
<ul>
<li
is="todo-item" //此处使用is的实现效果与直接使用自定义组件todo-item相同,可以避开潜在浏览器解析错误
v-for="(todo, index) in todos"
v-bind:key="todo.id" //绑定key与ID
v-bind:title="todo.title" //绑定组件中的属性title与Vue对象数据域中的属性title
v-on:remove="todos.splice(index, 1)" //自定义组件用v-on指令监听remove方法,具体在自定义组件的js中调用
></li>
</ul>
</div>

js:
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">X</button>\ //使用自定义组件时的HTML效果,添加按钮并绑定点击事件,emit是触发、发送
</li>\
',
props: ['title'] //数据不会自动传递到组件里,要用props
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({ //给数组push进新元素
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = '' //将用户输入的内容push进数组后,清空input框的值
}
}
})

12、事件处理: v-on指令监听DOM事件,并在触发时运行js代码.若要在内联语句处理器中访问原始DOM事件,可以用特殊变量$event把它传入方法,如:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>

methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}
}
(1)事件修饰符:修饰符顺序很重要
方法是纯粹的数据逻辑,而不是去处理DOM事件细节,Vue.js为v-on提供了事件修饰符来解决这一问题。
.stop 阻止单击事件继续传播:<a v-on:click.stop="doThis"></a>
.prevent 提交事件不再重载页面:<form v-on:submit.prevent="onSubmit"></form>
修饰符可以串联使用<a v-on:click.stop.prevent="doThat"></a>
.capture 添加事件监听器时使用事件捕获模式,即内部元素触发的事件先在此处处理,然后才交由内部元素自身进行处理<div v-on:click.capture="doThis">...</div>
.self 只当在 event.target 是当前元素自身时触发处理函数 ,即事件不是从内部元素触发的<div v-on:click.self="doThat">...</div>
.once 点击事件将只会触发一次<a v-on:click.once="doThis"></a>,除.once以外,只能对原生DOM事件起作用,.once还能被用到自定义组件事件上。
(2)按键修饰符
Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:<input v-on:keyup.enter="submit">
(3)在HTML中监听事件的原因
Vue.js事件处理方法和表达式都绑定在当前视图的viewModel上。
易定位在js代码里对应的方法 / 不用在js里手动绑定事件 / 当viewModel被销毁时,事件处理器被自动删除

13、表单输入绑定
v-model 指令在表单控件元素上创建双向数据绑定,v-model 会忽略所有表单元素的 value、checked、selected 、picked特性的初始值
v-model绑定的就是Vue对象中的数据域,也是最后显示在页面上的数据
应用举例:
****************************************
单纯input输入框
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
****************************************
多行文本域
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<p style="white-space: pre-line;">{{ message }}</p>
****************************************
复选框,绑定到数组
<div id='example-3'>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> //v-model绑定Vue对象数据域中的数据属性
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span> //此处拿的是Vue对象数据域中的数据属性
</div>

new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
//每选中一个复选框,数据域数组checkedNames中就增加一个元素(双向数据绑定)
****************************************
单选按钮
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>

new Vue({
el: '#example-4',
data: {
picked: ''
}
})
****************************************
下拉框
<div id="example-5">
<select v-model="selected" multiple> //多选属性,绑定到一个数组数据域
<option disabled value="">请选择</option> //下拉框的默认选中值。v-model初始值不匹配任何选项时会渲染“未选中”状态,使得用户无法选择第一个选项,
而用disabled则去除了这一情况,值从下一个开始选
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>

new Vue({
el: 'example-5',
data: {
selected: []
}
})
还可用v-for动态绑定选项
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>

new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
}) //页面上显示的下拉框文本是one,two...,实际获取的值是A,B,C
****************************************
(1)v-model 绑定的 value 通常是静态字符串 (对于勾选框是逻辑值)
绑定value到Vue实例的动态属性上,可以用v-bind实现,且这个属性的值可以不是字符串
(2)修饰符
.lazy 在 "change" 而不是 "input" 事件中更新<input v-model.lazy="msg" >,(v-model 在 input 事件中同步输入框的值与数据)
.number 将用户的输入值转为 Number 类型 (如果原值的转换结果为 NaN 则返回原值)<input v-model.number="age" type="number">
.trim 过滤用户输入的首尾空格<input v-model.trim="msg">

14、组件:
一、使用组件
(1)注意确保在初始化根实例之前注册全局组件
// 注册
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 创建根实例
new Vue({
el: '#example'
})

HTML:
<div id="example">
<my-component></my-component>
</div>
(2)局部注册组件:通过某个 Vue 实例/组件的实例选项 components 注册仅在其作用域中可用的组件:
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// ...
components: {
// <my-component> 将只在父组件模板中可用
'my-component': Child
}
})
(3)DOM模板解析注意事项
使用 DOM 作为模板时,会受到 HTML 本身的一些限制。特定元素里包含的元素有限制,如table,select,自定义组件使用在这些元素之内,浏览器解析时会被当做无效
的内容,导致渲染错误,解决方法是使用is特性,前面也提到过:
<table>
<tr is="my-row"></tr>
</table>
(4)构造 Vue 实例时传入的各种选项大多数都可以在组件里使用。只有一个例外:data 必须是函数
(5)组件组合:父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过prop给子组件下发数据,子组件通过事件给父组件发送消息

二、Prop
(1)、使用prop传递数据:父组件的数据通过prop下发到子组件中,prop是单向绑定的,父组件属性变化会传递给子组件,反过来不会(单向数据流)
js:
vue.component('child',{
props:['msg'], //声明props
template:'<span>{{msg}}</span>' //跟data一样,prop也可以在模板中使用,也可以在VM实例中通过this.msg来使用
})
html: <child msg="info"></child> //实际结果页面上显示info
(2)、动态prop : 用v-bind动态将prop绑定到父组件的数据
js:
vue.component('child',{
props:['myMsg'],
template:'<span>{{myMsg}}</span>'
})
html:
<div>
<input v-model="inputParentMsg"><br>//父组件
<child v-bind:my-msg="inputParentMsg"></child>//当使用的不是字符串模板时,驼峰式命名的prop=‘myMsg’要转换为相应的短横线分隔式命名my-msg
</div>
//也可将一个对象的所有属性作为prop进行传递,如:
todo:{
id:'1',
text:'name'
}
<todo-item v-bind="todo"></todo-item>等价于<todo-item v-bind:id="todo.id" v-bind:text="todo.text"></todo-item>

***
注意:子组件中,prop的值不能随意改变,js中对象和数组是引用类型,指向同一内存空间,若prop是对象或数组,在子组件内改变它会影响父组件的状态
要把它当做局部数据使用或者处理成其他数据输出,要么定义一个局部变量并用prop的值初始化它:
props:['initCounter'],
data:function(){
return {counter: this.initCounter}
}
或者使用计算属性处理prop的值并返回:
props:['size'],
computed:{
initSize:function(){
return this.size.trim().toLowerCase()
}
}
****

三、非prop特性:直接传入组件,不需定义prop

替换/合并特性
传递给组件的特性值会覆盖组件本身设定的值,例如传递type=‘large’会覆盖type=‘date’,且可能会破坏该组件。但是对于class和style,会做特性值合并操作,不
会覆盖原特性值

四、自定义事件:自定义事件使得子组件与父组件通信
(1)、使用v-on绑定自定义事件:$on是监听组件释放的事件,$emit监听组件触发的事件
HTML:
<div id="count-example">
<span>{{total}}</span>
<button-count v-on:increment="incrementTotal"></button-count> //2、页面自定义组件将触发事件increment绑定到Vue对象方法incrementTotal
<button-count v-on:increment="incrementTotal"></button-count>
</div>

js:
Vue.conponent('button-count',{
template:'<button v-on:click="incrementCount">{{count}}</button>', //1、模板的点击事件触发自定义组件中的方法incrementCount
data:function(){
return {count:0} //定义count的初始值,避免页面两个button的值同时更新
},
methods:{
incrementCount:function(){
this.count+=1
this.$emit('increment') //自定义组件的方法里绑定了触发事件increment,这是自定义组件与外部父组件的纽带,也使
得子组件与外部解耦了
}
}
})
new Vue({
el:'#count-example',
data:{total:0},
methods:{
incrementTotal:function(){
this.total+=1
}
}
})

(2)、v-on使用修饰符.native在组件的根元素上监听一个原生事件<my-component v-on:click.native="doTheThing"></my-component>

(3)、.sync修饰符:Vue 1.x中,.sync对prop进行双向绑定,当子组件改变了一个带.sync的prop的值时,这个变化也会同步到父组件中所绑定的值(但这破坏了单向数
据流)。适用.sync需要让子组件改变父组件状态的代码更容易被区分,扩展为自动更新父组件属性的v-on监听器,如:
<comp :foo.sync="bar"></comp>被扩展为<comp :foo="bar" @update:foo="val"=>bar="val"></comp>
当子组件更新foo的值时,需要显式地触发更新事件this.$emit('update:foo',newValue)

(4)、使用自定义事件的表单输入组件:要让组件的v-model生效,需要接受一个value的prop特性并在有新的值时触发input事件并将新值作为参数
<currency-input v-model="price"></currency-input>

Vue.component('currency-input',{
template:'<span>$<input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)"></span>',//自定义input事件触发
updateValue
props:['value'],
methods:{
updateValue:function(value){ //此方法对输入值进行格式化和位数限制
var formatValue=value.trim().slice(0,value.indexOf('.')===-1?value.length:value.indexOf('.')+3) //两位小数
if(formatValue!==value){
this.$refs.input.value=formatValue //此处对应上模板中的ref
}
this.$emit('input',Number(formatValue))
}
}
})

(5)、自定义组件的v-model:
组件的v-model默认情况下会使用value prop和input事件。单选、复选框之类的输入类型可能会把value用作别的目的,而model选项可避免这样的冲突:
<my-checkbox v-model="foo" value="XXX"></my-checkbox>
Vue.component('my-checkbox',{
model:{
prop:'checked',//需要显式声明checked这个prop
event:'change'
},
props:{
checked:Boolean,
value:String //value这个prop可以用作其他目的了
},
template:'<input type="checkbox">'
})

五、使用插槽分发内容
内容分发,即混合父组件的内容与子组件的模板的过程。<slot>元素作为原始内容的插槽
(1)编译作用域:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译;

注:在父组件模板内将一个指令绑定到子组件的属性/方法,是错误的:<child-component v-show="someChildProperty"></child-component>
应该在子组件自己的模板里绑定子组件作用域内的指令到一个组件的根节点:
Vue.component('child-compent',{
template:'<div v-show="someChildProperty">child</div>',
data:function(){
return {someChildProperty:true}
}
})

(2)、单个插槽
a、子组件模板至少要包含一个<slot>插口否则父组件的内容会被丢弃。
b、当子组件模板只有一个没有属性的插槽<slot>时,父组件传入的整个内容片段将插入到插槽所在的DOM位置并替换掉插槽标签
c、<slot>中的内容作备用内容,当宿主元素为空且没有要插入的内容时显示备用内容
子组件my-component模板template:
<div>
<h2>子组件标题</h2>
<slot>没有属性的插槽且当没有要分发的内容时才显示</slot>
</div>
父组件模板template:
<div>
<h1>父组件标题</h1>
<my-component>
<p>severalContents</p>
<p>otherContents</p>
</my-component>
</div>
渲染结果为:
<div>
<h1>父组件标题</h1>
<div>
<h2>子组件标题</h2>
<p>severalContents</p> //若此处父组件中没有要插入的内容,则显示子组件<slot>中的内容
<p>otherContents</p>
</div>
</div>

(3)带有名字的插槽:具名插槽
特定插槽分发内容可以通过特性name来对应。具名插槽将匹配内容片段中有对应slot特性name的元素。组件中可以有一个无名的默认插槽,作为找不到匹配内容片段的备
用插槽,没有备用插槽,这些找不到匹配内容的片段会被抛弃:
子组件app-layuot的模板template:
<div class="container">
<header><slot name="header"></slot></header>
<main><slot></slot></main> //默认插槽
<footer><slot name="footer"></slot></footer>
</div>
父组件模板:
<app-layout>
<h1 slot="header">父组件标题</h1>
<p>severalContents</p>
<p>otherContents</p> //没有配置插槽的内容会放在默认插槽中
<p slot="footer">备注</p>
</app-layout>
渲染结果为:
<div class="container">
<header><h1>父组件标题</h1></slot></header>
<main><p>severalContents</p><p>otherContents</p> </main>
<footer><p>备注</p></footer>
</div>
//渲染结果即替换掉配置了插槽的内容,不匹配的内容放到默认插槽内

六、动态组件
使用组件的保留元素component,动态绑定他的is特性,让多个组件使用同一个挂载点,并动态切换:
var vm=new Vue({
el:'#example',
data:{
currentView:'home'
},
component:{
home:{/*...*/},
posts:{/*...*/}
}
})
<component v-bind:is="currnetView">
<!--组件在VM.currentView变化时改变-->
</component>
若要把切换出去的组件保留在内存中,可添加keep-alive指令参数:
<keep-alive>
<component v-bind:is="currnetView">
<!--非活动组件将被缓存-->
</component>
</keep-alive>

 

后注:以上笔记是本人开官网后的记录,目前只有这些,还不完全,待日后补上

posted on 2017-12-20 13:26  黑子菜园  阅读(71)  评论(0)    收藏  举报

导航