Vue.js

开发模式:

  • CDN

  • NPM

    • 在用 Vue 构建大型应用时推荐使用 NPM 。NPM 能很好地和诸如 webpack 或 Browserify 模块打包器配合使用。同时 Vue 也提供配套工具来开发单文件组件

学习开发工具:Visual Studio Code

工具下载网址:Visual Studio Code下载地址

  • 装好软件后装几个插件:
    1、Live Server
    为静态和动态页面启动带有实时重新加载功能的开发本地服务器

2、Vetur
用于VS代码的Vue工具

3、Vue.js with TypeScript Snippets for VSCode
使用SFC或基于类的API (vscode)的TypeScript收集Vue.js的代码片段

  • 设置一下工具:
  • 加上(可以格式化代码):
    “editor.formatOnType”: true,
    “editor.formatOnSave”: true,
    在这里插入图片描述

Vue-CDN的使用

  • 创建项目(创建一个文件夹:vue-basic-cdn)
  • 创建三个资源:index.html、app.js、styles.css
    app.js:
//实列化vue对象
new Vue({
    el: '#vue-app', //element(容器)返回的数据只能在本容器中使用
    data() {
        return {
            name: "米罗",
            age: "18"
        };
    }
})

index.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue CDN</title>
</head>
<body>
    <div id="vue-app">
        <h1>欢迎:{{name}}</h1>
        <p>年龄:{{age}}</p>
    </div>
</body>
<script src="app.js"></script>

</html>

使用vue中的方法(Methods)和指令(v-bind):

指令还有很多可以看官网的使用方法

  • 页面:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue CDN</title>
</head>

<body>
    <div id="vue-app">
        <h1>欢迎:{{name}}</h1>
        <p>年龄:{{age}}</p>
        <h1>methods:{{green()}}</h1>
        <h1>methods2:{{green2('2018-5-8')}}</h1>
        <a v-bind:href="url">百度</a>
        <a :href="url">百度</a>
        <p v-html="a"></p>
    </div>
</body>
<script src="app.js"></script>

</html>
  • js:
//实列化vue对象;this指的就是这个实例
new Vue({
    el: '#vue-app', //element(容器)
    data() {
        return {
            name: "米罗",
            age: "18",
            url: "https:www.baidu.com",
            a: "<a href='https:www.taobao.com'>淘宝</a>"
        };
    },
    methods: {
        // green: function () {
        //     return 'Good night' + this.name;
        // }
        green() {
            return `Good night ${this.name}`;
        },
        green2(time) {
            return `Good night ${time} ${this.name}`;//这里是反引号这主键盘去的1后面
        }
    }
})

vue鼠标事件的使用

  • 常见的事件类型

    click:鼠标单击某元素时触发,相当于mousedown和mouseup的组合
    dblclick:双击事件
    mouseover:鼠标放在某元素上触发
    mouseout:鼠标移出某元素时触发
    mousemove:鼠标移动时触发
    mousedown:鼠标按钮被按下时触发
    mouseup:鼠标按钮被松开时触发
    dblclick:鼠标双击时触发

  • 属性修饰符:

    .stop - 调用 event.stopPropagation()。
    .prevent - 调用 event.preventDefault()。
    .capture - 添加事件侦听器时使用 capture 模式。
    .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
    .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
    .native - 监听组件根元素的原生事件。
    .once - 只触发一次回调。
    .left - (2.2.0) 只当点击鼠标左键时触发。
    .right - (2.2.0) 只当点击鼠标右键时触发。
    .middle - (2.2.0) 只当点击鼠标中键时触发。
    .passive - (2.3.0) 以 { passive: true } 模式添加侦听器

  • 实例:
    页面:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="styles.css">
    <title>Vue CDN</title>
</head>

<body>
    <div id="vue-app">
        <!-- 加不加()都没问题,它会先找methods中的方法 -->
        <button id="but1" v-on:click="num++">单击+1</button>
        <button id="but2" @click="num--">单击-1</button>
        <button id="but1" v-on:click="numon(1)">单击+1</button>
        <button id="but2" @click="numup(1)">单击-1</button>
        <button id="but1" v-on:dblclick="numon(10)">双击+10</button>
        <button id="but2" @dblclick="numup(10)">双击-10</button>
        <p>当前值:{{num}}</p>
        <!-- mousemove event(鼠标移动时触发) -->
        <div id="mouse" v-on:mousemove="updateXY">
            {{x}} , {{y}}
        </div>
        <!-- 属性修饰符 -->
        <a @click.prevent="onmethod" href="https://www.baidu.com">百度</a>
    </div>

</body>
<script src="app.js"></script>

</html>

js:

//实列化vue对象;this指的就是这个实例
new Vue({
    el: '#vue-app', //element(容器)
    data() {
        return {
            num: 50,
            x: 0,
            y: 0
        };
    },
    methods: {
        numon(a) {
            this.num += a;
        },
        numup(a) {
            this.num -= a;
        },
        updateXY(event) {
            // console.log(event);
            this.x = event.offsetX;
            this.y = event.offsetY;
        }
    }
})

vue键盘事件的使用

更多键盘事件链接

        <label>姓名:</label>
        <!-- 当按Enter键才会触发 -->
        <input type="text" v-on:keydown.enter="logName" />
        <label>年龄:</label>
        <input type="text" v-on:keyup.alt.enter="logAge" />

双向数据绑定

修饰符:

.lazy - 取代 input 监听 change 事件
.number - 输入字符串转为有效的数字
.trim - 输入首尾空格过滤

用法:

在表单控件或者组件上创建双向绑定。细节请看下面的教程链接。

	//js
		    data() {
		        return {
		            name: '',
		            age: null
		        };
		    },
	//html
        <!-- 双向数据绑定 -->
        <label>姓名:</label>
        <!-- lazy相当于懒加载就是输入框失去焦点时才改值 -->
        <input type="text" v-model.lazy="name" />
        <span>{{name}}</span>
        <label>年龄:</label>
        <input type="text" v-model="age" />
        <span>{{age}}</span>
ref 的使用:

可以使用$refs获取ref的父级:
一般用ref获取容器(相当于FindById)
js:

        getname() {
            this.name = this.$refs.name.value;
        },
        getage() {
            this.age = this.$refs.age.value;
        }

html:

        <label>姓名:</label>
        <input type="text" ref="name" @keyup="getname" />
        <span>{{name}}</span>
        <label>年龄:</label>
        <input type="text" ref="age" @keyup="getage" />
        <span>{{age}}</span>

使用watch调式:

一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个 property。

//实列化vue对象;this指的就是这个实例
new Vue({
    el: '#vue-app', //element(容器)
    data() {
        return {
            name: '',
            age: null
        };
    },
    methods: {
    },
    watch: {
        //val:新值;oldVal:旧值
        name(val, oldVal) {
            console.log(val, oldVal);
        }
    }
})

computer计算属性的使用:

使用methods中的方法时,实用其中一个方法,实际上其他方法也会被运行(耗费性能)。这样可以使用计算属性。

js定义:

new Vue({
    el: '#vue-app',
    data() {
        return {
            a: 0,
            b: 0,
        };
    },
    methods: {
    },
    //computer中的方法必须有返回值
    computed: {
        numplus() {
            return this.a + this.b;
        }
    }
})

html使用:

    <div id="vue-app">
        <!-- computer -->
        <button id="but1" v-on:click="a++">单击+1</button>
        <button id="but2" @click="b++">单击-1</button>
        <p>当前值a:{{a}}</p>
        <p>当前值b:{{b}}</p>
        <!-- 调用computer时不能加括号 -->
        <p>a+b={{numplus}}</p>
    </div>

动态CSS(v-bind:class):

html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="styles.css">
    <title>Vue CDN</title>
</head>

<body>
    <div id="vue-app">
        <h1>动态绑定样式,两种方式</h1>
        <!-- <h2>示例1 属性绑定</h2>
        <div @click="mrChangeColor=!mrChangeColor" v-bind:class="{add:mrChangeColor}">
            <span>Hello</span>
        </div> -->
        <h2>示例2 计算属性绑定</h2>
        <div @click="michangcolor=!michangcolor" v-bind:class="addclass">
            <span>Hello</span>
        </div>
    </div>

</body>
<script src="app.js"></script>

</html>

js:

new Vue({
    el: '#vue-app',
    data() {
        return {
            mrChangeColor: true,
            michangcolor: true
        };
    },
    methods: {
    },
    //computer中的方法必须有返回值
    computed: {
        addclass() {
            return {
                add: this.mrChangeColor,
                adder: this.michangcolor
            };
        }
    }
});

css:

div span{
    background: red;
    display: inline=block;
    color: white;
    margin: 10px 0;
}
.add span{ 
    background: rgb(37, 36, 33);
}
.adder span::after{ 
    content: '效果';
    margin-left: 10px;
}

在这里插入图片描述

v-if、v-else、v-else-if、v-show:

js:

    data() {
        return {
            error: false,
            success: false
        };

html:

    <div id="vue-app">
        <h1>v-if</h1>
        <button id="fontclass" @click="error=!error">显示error</button>
        <button id="fontclass" @click="success=!success">显示success</button>
        <!-- <p v-if="error">error:连接错误404</p>
        <p v-else-if="success">success:连接成功:200</p>
        <p v-else="success">Other</p> -->
        <!-- show -->
        <!-- 和v-if的区别在于show会加一个display: none;属性 -->
        <p v-show="error">success:连接成功:200</p>
        <p v-show="success">Other</p>
    </div>

v-for:

html:

<div id="vue-app">
        <h1>v-for指令</h1>
        <!-- {{names[0]}},
        {{names[1]}},
        {{names[2]}},
        {{names[3]}}, -->
        <!-- <ul>
            <li v-for="names in names">{{names}}</li>
        </ul> -->
        <!-- <template>
            <li v-for="(names,item) in names">{{item}}.{{names}}</li>
        </template> -->
        <template>
            <li v-for="(names,item) in users">{{names.name}}.{{names.age}}</li>
        </template>
        <template v-for="(user,item) in users">
            <li v-for="(val,key) in user">
                <p>{{key}}——{{val}}</p>
            </li>
        </template>
    </div>

js:

new Vue({
    el: '#vue-app',
    data() {
        return {
            names: ["小猪佩奇", "西奥潘西", "小鸡莉莉", "小项雍熙"],
            users: [
                { name: "小猪佩奇", sex: "男", age: 3 },
                { name: "西奥潘西", sex: "男", age: 2 },
                { name: "小鸡莉莉", sex: "女", age: 1 },
                { name: "小项雍熙", sex: "男", age: 5 },
            ]
        };
    },
    methods: {
    },
});

练习

html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="styles.css">
    <title>Vue CDN</title>
</head>

<body>
    <div id="vue-app">
        <div id="image" :class="{burst:start}"></div>
        <div id="ku">
            <div id="tiao" :style="{width:health+'%'}"></div>
        </div>
        <div id="con">
            <button id="butup" @click="da" v-show="!start">打击</button>
            <button id="butnew" @click="nostart">重新开始</button>
        </div>
    </div>

</body>
<script src="app.js"></script>

</html>

js

new Vue({
    el: '#vue-app',
    data() {
        return {
            start: false,
            health: 100
        };
    },
    methods: {
        da() {
            this.health -= 10;
            if (this.health <= 0) {
                this.start = true;
                console.log("da");
            }
        },
        nostart() {
            this.health = 100;
            if (this.health == 100) {
                this.start = false;
            }
        }
    },
});

css

#image{
    width: 200px;
    height: 200px;
    margin: 0 auto;
    background: url(image/01.jpg) center no-repeat;
    background-size: 100%;
}
#image.burst{
    background: url(image/04.jpg) center no-repeat;
    background-size: 100%;
}
#ku{
    width: 200px;
    border: 2px solid black;
    margin: 0 auto 20px auto;
}
#tiao{
    height: 20px;
    background-color: red;
}
#con{
    width: 120px;
    margin: 0 auto;
}

多个Vue实例(const ):

//多个Vue实例
const method1 = new Vue({
    el: '#vue-app',
    data() {
    },
});
const method2=new Vue({
    el: '#vue-app',
    data() {
    },
});

全局组件(component):

js:

//定义全局变量
let data = {
    name: '小罗',
    wechat: '2510116656'
};

//创建全局组件(Greeting是自己定义的名称)
Vue.component("Greeting", {
    //html模版
    template: `
    <p>这是一个全局变量,
    可以在任何一个Vue实例中使用
    姓名:{{name}}
    微信:{{wechat}}
    <button @click="chanageName">改名</button>
    </p>
    `,
    //属性、方法等
    data() {
        return data;

    },
    methods: {
        chanageName() {
            this.name = "小罗在线";
        }
    }
})

//多个Vue实例
const app = new Vue({
    el: '#vue-app'
});
const app2 = new Vue({
    el: '#vue-app2'
});

html:

    <div id="vue-app">
        <Greeting />
    </div>
    <div id="vue-app2">
        <Greeting></Greeting>
    </div>

Vue Fatch和Axios请求

我这使用的接口是:测试接口

实例:

html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="styles.css">
    <title>Vue CDN</title>
</head>

<body>
    <h1>Fetch请求</h1>
    <div id="vue-app">

        <form @submit.prevent="onSubmit">
            <input type="text" v-model="todo.title">
            <input type="checkbox" v-model="todo.completed">
            <input type="submit" value="提交">
        </form>

        <ul>
            <li v-for="todo in todos">
                <h1>{{todo.title}}</h1>
                <p v-if="todo.completed">{{todo.completed}}</p>
            </li>
        </ul>
    </div>
    <div id="vue-app2">
    </div>
</body>
<script src="app.js"></script>

</html>

js:

new Vue({
    el: '#vue-app',
    data() {
        return {
            todos: [],
            todo: {
                title: "",
                completed: false
            }
        };
    },
    // mounted是个函数会自动执行
    mounted() {
        //fetch api请求接口
        // fetch('http://jsonplaceholder.typicode.com/todos').then(res => {
        //     //console.log(res.json());
        //     return res.json();
        // })
        //     .then(todos => {
        //         this.todos = todos;
        //     });
        //axios get请求(这种常用一点)
        axios.get('http://jsonplaceholder.typicode.com/todos').then(
            res => {
                this.todos = res.data;
            }
        )
    },
    methods: {
        onSubmit() {
            //axios post请求
            axios.post("http://jsonplaceholder.typicode.com/todos",
                this.todo).then(res => {
                    //console.log(res);
                    //将结果写到对象中
                    this.todos.unshift(res.data);
                })

            // fetch('http://jsonplaceholder.typicode.com/todos', {
            //     //请求方式
            //     method: "POST",
            //     //转换成json
            //     body: JSON.stringify(this.todo),
            //     headers: {
            //         'Content-Type': 'application/json'
            //     }
            // }).then(res => {
            //     return res.json();
            // })
            //     .then(todo => {
            //         console.log(todo);
            //         this.todos.unshift(todo);
            //     });
        }
    }
});

Vue Cli(脚手架)开发

一般公司不会用CDN,而是使用Vue Cli进行开发。

  • 脚手架需要的环境:

    • Node.js运行环境(这个环境可以让js独立运行)做前端的可以去学一下Node.js
    • npm(node package manage)依赖包(包含很多组件等,国外)
  • 脚手架的好处:

    • 1.脚手架可以大大的提高开发系效率;
    • 2.可以使用最主流的ECMAScript语法;
    • 3.通过Webpack实现编译查看效果(非浏览器编译);前端需要去学一下Webpack
    • 4.自动更新,可实时查看最新效果等;
  • 安装 Node.js运行环境:

    • 先下载node.js;下载地址:node.js官网
    • 安装一直下一步就OK了
    • 测试环境(CMD):node -v\npm -v
  • npm依赖

  • 安装Vue Cli(脚手架)

    • 安装Vue Cli可以直接看官网:安装Vue Cli官网
    • 在 CMD中输入:npm install -g @vue/cli(-g表示所有)
    • 测试:vue --version
      成功如下图:
      注意脚手架的版本不一定跟vue的版本一样。
      在这里插入图片描述
      -这样配置的npm下载插件是国外的;可以访问cnpm:https://developer.aliyun.com/mirror/NPM?from=tnpm;这是淘宝提供的服务器。配置cnpm在CMD中的输入:npm install -g cnpm --registry=https://registry.npm.taobao.org
      使用:cnpm --version查看
  • 注意:在其他系统使用这些安装命令时需要加sudo(超级管理员) ,windows不用。

创建vue cil并运行:

1、配置项目存放路径:cd 路径
2、创建项目可以用vue --help查看命令;created project vuecil-demo创建项目名称为vuecil-demo
3、创建完之后在选中创建好的项目:cd vuecil-demo
4、启动脚手架npm run serve(关闭服务ctrl+c(可以多按几次))
打开多个服务,只需重新打开一个cmd然后npm run serve,端口号会不一样

使用Visual Studio Code打开项目:

将项目拖入即可
查看终端如下(点击加号开启多个,按住ctrl点击地址,可以跳转到浏览器):
在这里插入图片描述
vue项目目录说明可见:vue项目目录说明

组件传值:

只有父组件传值给子组件。
注意:属性传值:值有两种情况,1.传值 2.传引用(对象、数组)引用的是地址,改变值时,会一起变

在这里插入图片描述
在这里插入图片描述

子组件想要改变父组件的值时,需要用到事件

子组件:

  methods: {
    updatetitle() {
      //注册事件 参数1:事件名称,参数2:;值
      this.$emit("updatetitle", "拉布拉多犬title修改后的值");
    }
  }

父组件:
在这里插入图片描述

vue生命线:

在这里插入图片描述

父组件传标签到子组件(slot:占位符):

父组件:

<h1 slot="text">这是一个文本</h1>

子组件:

 <slot name="text" />

总结项目

父组件:

<template>
  <div id="app">
    <!-- 3、调用组件 -->
    <Header />
    <!-- 属性传值:值有两种情况,1.传值 2.传引用(对象、数组) -->
    <Users :users="users">
      <h1 slot="text">这是一个文本</h1>
    </Users>
    <p>{{title}}</p>
    <foorder @updatetitle="updatetitle" :title="title" />
  </div>
</template>

<script>
//1、局部组件的调用
import Users from "./components/Users";
import Header from "./components/Header";
import Foorder from "./components/Foorder";

export default {
  name: "App",
  data() {
    return {
      users: [
        { name: "小猪佩奇", wechat: "2510116656", show: true },
        { name: "小猪佩奇", wechat: "2510116656", show: true },
        { name: "小猪佩奇", wechat: "2510116656", show: true },
        { name: "小猪佩奇2", wechat: "2510116656", show: true },
        { name: "小羊苏西", wechat: "2510116656", show: true },
        { name: "小猪佩奇3", wechat: "2510116656", show: true },
        { name: "小猪佩奇4", wechat: "2510116656", show: true },
        { name: "小猪佩奇5", wechat: "2510116656", show: true },
        { name: "小猪佩奇6", wechat: "2510116656", show: true }
      ],
      title: "拉布拉多犬",
      text: "<a>这是一个文本</a>"
    };
  },
  methods: {
    updatetitle(updatetitle) {
      //console.log(updatetitle);
      this.title = updatetitle;
    }
  },
  components: {
    //2、注册组件
    Users,
    //sers:Users,
    // "mr-users": Users
    Header,
    foorder: Foorder
  }
};
</script>
//scoped相当于作用域
<style>
h1 {
  color: purple;
}
</style>

子组件:

<template>
  <div class="foorder">
    <footer>
      <p>{{copyright}}</p>
      <p @click="updatetitle()">{{title}}</p>
    </footer>
  </div>
</template>
<script>
export default {
  props: {
    title: {
      type: String
    }
  },
  data() {
    return {
      copyright: "copyright 2020 Vue Demo"
    };
  },
  methods: {
    updatetitle() {
      //注册事件 参数1:事件名称,参数2:;值
      this.$emit("updatetitle", "拉布拉多犬title修改后的值");
    }
  }
};
</script>
<style scoped>
.foorder {
  background: black;
  padding: 10px;
}
p {
  color: white;
  text-align: center;
}
</style>

子组件:

<template>
  <div class="users">
    <h1>Users.vue</h1>
    <slot name="text" />
    <ul>
      <!-- 在使用脚手架的for时必须给一个key值一定时唯一的值 -->
      <li @click="user.show=!use.show" v-for="(use,index) in users" :key="index">
        <h2>{{use.name}}</h2>
        <h2 v-show="use.show">{{use.wechat}}</h2>
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  //   props: ["users"],
  props: {
    users: {
      type: Array,
      required: true
    }
  },
  data() {
    return {};
  },
  methods: {
    test() {
      console.log(this.users);
    }
  }
};
</script>
<style scoped>
h1 {
  color: green;
}
</style>

子组件:

<template>
  <div class="foorder">
    <footer>
      <p>{{copyright}}</p>
      <p @click="updatetitle()">{{title}}</p>
    </footer>
  </div>
</template>
<script>
export default {
  props: {
    title: {
      type: String
    }
  },
  data() {
    return {
      copyright: "copyright 2020 Vue Demo"
    };
  },
  methods: {
    updatetitle() {
      //注册事件 参数1:事件名称,参数2:;值
      this.$emit("updatetitle", "拉布拉多犬title修改后的值");
    }
  }
};
</script>
<style scoped>
.foorder {
  background: black;
  padding: 10px;
}
p {
  color: white;
  text-align: center;
}
</style>

动态组件和缓存(Component和keep-allve):

在这里插入图片描述

使用VS Code创建vue项目:

跟cmd是一样的。
可能会出现脚本拦截解决方法可见:拦截解决方法

添加插件:

1、 vue add ·需要添加的插件·
2、npm install ·npm install·(3.0后的版本)

vue的环境变量:

创建一个.env文件,定义变量;使用时需要先在data中拿到它(url: process.env.VUE_APP_URL)。
开发环境变量:创建一个.env.development文件,定义,使用都一样。

Vue 3.0后的新特性(可视化):

可以独立运行.vue文件:

  • 输入命令:vue serve holler.vue(holler.vue是文件)这里它会提示让你装一个全局的组件:npm install -g @vue/cli-service-global

图形页面构建项目(GUi)

  • 终端中输入vue ui,创建跟终端创建时一样的

vue配置文件(默认路径,静态文件等):

注意文件名必须是(vue.config.js),这样vue才会自动加载这个文件。
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐