赞
踩
效果图以及分页效果:
一、创建步骤:通过创建vue.cli搭建脚手架,全局安装npm:npm i-g@vue/cli,并且利用IDE模板创建项目lesson8(vue-cli)默认项目。也可以用过cmd中的vue ui进入通过界面来创建
二、安装vue.cli项目时,名为vue.cli.service命令默认在preset的package.json运行起来,npm run serve使用热更新在浏览器中打开,vue config配置安装、vue.config.js可选的配置文件,被@vue/cli-service自动加载
结构展示
三、关键代码:
(1)在App.vue中设置header和footer以及样式设置,在components中创建top-menu.vue和bottom-menu.vue的全局组件,顶部和底部的菜单。使用v-for循环定义模块,传入变量:index==menuIdx样式和事件,在父组件APP.vue中添加function,引入后进行注册,menuIdx默认为0
(2)插槽slot,利用父组件进行扩展。并且使用具名插槽使得左右两边各有内容,再使用作用域插槽,绑定数据属性,子组件绑定数据提供给父组件访问,访问绑定的子组件数据或替代显示。最终显示点赞数
(3)动态组件,使用v-if和v-show实现功能,进行动态加载,通过component动态绑定。
”currentComponent :list=”imglist,利用两个图片组件,使用图片模拟加载,并引入注册ImgList和ImgdetailList。在菜单切换时使用原来的top-menu中的menu-click事件进行捕获,传入参数进行接收方法:如果为0则使用组件ImgList否则使用组件ImgdetailList组件。根据currentComp属性来设置对应组件。
(4)分页效果:导入image-pagelist1和image-pagelist2.vue,注册并且添加标签,在窗口路径设置为老师的源代码E:\Web-3\实验文档\lesson7\jsonServer,在命令行输入mode.index获取端口号3000,访问image-pagelist1,取第一页的8张图片,在image-pagelist1.vue中调用了getCallback,取出渲染后再打印。
以下是代码:
mock-login-login.db.js:
let Mock = require('mockjs');
var Random = Mock.Random
function buildData() {
let data = [{
code: 0,
msg: '登录成功!',
data: Mock.mock({
'role|1':['ROLE_STUDENT', 'ROLE_TEACHER', 'ROLE_ADMIN'],
'userInfo': {
'userId': /5101[0-9]{3}/,
'userName': '@cname'
}
})
},
{
code: 300,
msg: '登录失败!',
data: {}
},
]
let i = Random.integer(0, 1)
return data[i];
}
module.exports = {
name: '/api/login',
data: buildData()
};
mock-stdent-studentlist.db.js
let Mock = require('mockjs');
var Random = Mock.Random
function buildData() {
let data=[];
for(var i = 1; i <= 15; i++) {
data.push(
Mock.mock({
'id': i,
'studentId': /5187101[0-9]{3}/,
'name':'@cname',
'pic':Random.image('200x200',Random.color()),
'gentle|1': ['男', '女'],
'classname': /2018级软件工程[1-5]{1}班/,
'address': '@province' + '@city' + '@county'
})
);
}
return data;
}
module.exports = {
name: '/api/students',
data: buildData()
};
mock-mockData.js
const path = require('path');
const glob = require('glob'); //匹配规则
var v = path.resolve(__dirname, "./*/*.db.js");
var files = glob.sync(v, {
nodir: true
});
let data = {};
for (var i = 0; i < files.length; i++) {
let api = require(files[i]);
data[api.name] = api.data;
}
module.exports = function(app) {
app.get("/api/students", function(req, res) {
res.jsonp(data["/api/students"]);
});
app.post("/api/login", function(req, res) {
res.jsonp(data["/api/login"]);
})
}
public中保存图片和图标
src-assets保存图片
src-components
bottom-menu.vue:
<template>
<div class="menu-wrap-active">
<!-- <div v-for="(item,index) in menus"
@click="menuClick(index)" :key="index"
:class="menuIdx==index?'item-active1':'item-img'">
{{item}}
</div> -->
<img v-for="(item,index) in menus" @click="menuClick(index)" :key="index"
:src="item" :class="menuIdx==index?'item-active1':'item-img'" />
</div>
</template>
<script>
export default {
name: "bottomMenu",
props:['menus'],
data:function(){
return{
menuIdx:0
}
},
methods:{
menuClick(e){
this.menuIdx=e;
}
}
};
</script>
<style>
.menu-wrap-active{
width:100%;
display:flex;
flex-direction:row;
justify-content:space-around;
margin-top:5px;
}
.item-img {
height: 28px;
width: 28px;
opacity: 0.3;
}
.item-active1 {
width: 32px;
height: 32px;
opacity: 1;
}
</style>
image-list.vue
<template>
<div class="content">
<div v-for="(item,index) in list" :key="index" class="item">
<div>
<img :src="item.imgurl" />
<div class="name-text">{{item.name}}</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['list']
};
</script>
<style scoped="scoped">
.content {
width: 100%;
height: 100%;
display: flex;
flex-flow: row wrap;
flex: 2;
}
.item {
width: 150px;
margin-bottom: 20px;
}
.name-text {
margin-top: -20px;
color:red;
}
img{
width: 150px;
}
</style>
image-pagelist1.vue
<template>
<div class="content">
<div v-for="item in list" :key="item.id" class="item">
<div>
<img :src="item.pic" />
<div class="name-text">{{item.name}}</div>
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
list: [],
pageIndex: 1,
pageSize: 8,
firstTouchY: 0,
isMove:false,
more: true, //是否还有数据
}
},
methods: {
getStudents() {
let getstr = `?_page=${this.pageIndex}&_limit=${this.pageSize}`
console.log('getstu')
this.axios.get("/students" + getstr)
.then((res) => {
console.log(res.data)
this.getCallback(res.data);
})
},
getCallback(res) {
this.list = this.list.concat(res.data);
console.log(res.len)
this.isMove=false;
let pageTotal = res.len / this.pageSize;
if (res.len % this.pageSize != 0)
pageTotal = pageTotal + 1;
if (this.pageIndex >= pageTotal) this.more = false;
},
onScroll() {
let innerHeight = document.querySelector('.content').clientHeight
let outerHeight = document.documentElement.clientHeight
let scrollTop = document.documentElement.scrollTop
console.log(innerHeight,outerHeight,scrollTop)
if (outerHeight + scrollTop >= innerHeight + 100) {
console.log(this.more,this.isMove)
if(this.more&&(!this.isMove)){
console.log(this.list)
this.pageIndex++;
this.isMove=true;
this.getStudents();
}
}
}
},
created: function() {
this.getStudents();
window.addEventListener('scroll', this.onScroll)
}
};
</script>
<style scoped="scoped">
.content {
width: 100%;
height: 100%;
display: flex;
flex-flow: row wrap;
flex: 2;
overflow-x: hidden;
overflow-y: scroll;
}
.item {
width: 150px;
margin-bottom: 20px;
}
.name-text {
margin-top: -20px;
color: red;
}
img {
width: 150px;
}
</style>
image-pagelist2.vue
<template>
<div class="content" @touchstart="touchStart"
@touchmove="touchMove" @touchend="touchEnd">
<div v-for="item in list" :key="item.id" class="item">
<div>
<img :src="item.pic" />
<div class="name-text">{{item.name}}</div>
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
list: [],
pageIndex: 1,
pageSize: 6,
firstTouchY: 0,
more: true, //是否还有数据
isBottom: false,
isMoved: false,
changeY:0
}
},
methods: {
getStudents() {
let getstr = `?_page=${this.pageIndex}&_limit=${this.pageSize}`
this.axios.get("/students" + getstr)
.then(res => {
this.getCallback(res.data);
})
},
getCallback(res) {
this.list=this.list.concat(res.data);
console.log(this.list);
let pageTotal = res.len / this.pageSize;
if (res.len % this.pageSize != 0)
pageTotal = pageTotal + 1;
if (this.pageIndex >= pageTotal) this.more = false;
},
scrollY() {
return window.pageYOffset ||
window.document.documentElement.scrollTop
},
touchStart(ev) {
if (!this.more) return
console.log("scollY="+this.scrollY());
console.log("document.body.scrollHeight="+document.body.scrollHeight);
console.log("document.documentElement.clientHeight="+document.documentElement.clientHeight)
this.firstTouchY = parseInt(ev.changedTouches[0].clientY)
this.isBottom = this.scrollY() >= (document.body.scrollHeight - document.documentElement.clientHeight-100)
},
touchMove(ev) {
if (!this.isBottom ) return
this.changeY = parseInt(ev.changedTouches[0].clientY) - this.firstTouchY
if (this.changeY < 0 && this.more) {
this.isMoved = true
}
},
touchEnd(ev) {
if (!this.isBottom || !this.isMoved || !this.more) return
this.pageIndex++;
this.getStudents();
this.isMoved = false;
},
},
created: function() {
this.getStudents();
}
};
</script>
<style scoped="scoped">
.content {
width: 100%;
height: 100%;
display: flex;
flex-flow: row wrap;
flex: 2;
overflow-x: hidden;
overflow-y: scroll;
}
.item {
width: 150px;
margin-bottom: 20px;
}
.name-text {
margin-top: -20px;
color: red;
}
img {
width: 150px;
}
</style>
imagedetail-list.vue
<template>
<div class="content">
<div v-for="(item,index) in list" :key="index" class="item">
<div>
<img :src="item.imgurl" />
<div class="name-text">{{item.name}}</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['list'],
methods: {
// 滚动
scroll() {
let clientHeight = document.documentElement.clientHeight || document.body.clientHeight
// 设备/屏幕高度
let scrollObj = document.querySelector('.content') // 滚动区域
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
let scrollHeight = scrollObj.scrollHeight // 滚动条的总高度
console.log("window.pageYOffset="+window.pageYOffset);
console.log("document.documentElement.scrollTop="+document.documentElement.scrollTop)
console.log("document.body.scrollTop="+document.body.scrollTop)
// if (scrollTop + clientHeight === scrollHeight) {
// // div 到头部的距离 + 屏幕高度 = 可滚动的总高度
// // 滚动条到底部的条件
// if (this.hasMore && !this.isLoading) {
// this.isLoading = true
// // 请求后台数据
// let getstr = `?_page=${this.pageIndex}&_limit=${this.pageSize}`
// this.axios.get("/students" + getstr)
// .then(res => {
// // 请求成功调用 getMoreCallback 方法
// this.getMoreCallback(res.data);
// })
// }
// }
}
},
created: function() {
window.addEventListener('scroll', this.scroll)
}
};
</script>
<style scoped="scoped">
.content {
width: 100%;
height: 100%;
}
.item {
width: 100%;
margin-bottom: 50px;
}
.name-text {
color: red;
}
img {
max-width: 350px;
object-fit: contain;
justify-items: center;
}
</style>
search-input.vue
<template>
<div>
<div class="header-warp">
<slot name="left">
<!-- <img src="../../public/imgs/房子.png" class="myicon"/> -->
</slot>
<div class="search-box">
<img src="../../public/imgs/baseline-search-px.png" class="myicon"/>
<input class="input-wrap" type="search" placeholder="请输入需要搜索的内容">
</div>
<slot name="right" :heartNum="heartNum">
<div>{{heartNum}}</div>
</slot>
</div>
</div>
</template>
<script>
export default{
data:function(){
return{
heartNum:5
}
}
}
</script>
<style scoped="scoped">
.header-warp{
width: 100%;
display: flex;
flex-direction: row;
height: 30px;
margin-left: 20px;
margin-top: 20px;
}
.search-box{
width: 60%;
display: flex;
flex-direction: row;
align-items: center;
vertical-align: middle;
background: white;
border-radius: 15px;
}
.myicon{
width: 24px;
height: 24px;
}
.input-wrap{
border:none;
outline:none;
}
</style>
student.vue
<template>
<div>
<img :src="imgurl" />
<div class="name-text">{{name}}</div>
</div>
</template>
<script>
export default {
props: ['imgurl','name']
};
</script>
<style scoped="scoped">
.name-text{
margin-top: -80px;
}
</style>
student-list.vue
<template>
<div class="content">
<div v-for="(item,index) in list" :key="index" class="item">
<student :imgurl="item.pic" :name="item.name"></student>
</div>
</div>
</template>
<script>
import student from "./student.vue"
export default {
components:{
student
},
props: ['list']
};
</script>
<style scoped="scoped">
.content{
width: 100%;
height: 100%;
display: flex;
flex-flow: row wrap;
flex: 2;
}
.item{
width: 200px;
margin-bottom: 80px;
}
</style>
top-menu.vue
<template>
<div class="menu-wrap">
<div v-for="(item,index) in menus"
@click="menuClick(index)" :key="index"
:class="menuIdx==index?'item-active':'item'">
{{item}}
</div>
</div>
</template>
<script>
export default {
name: "topMenu",
props:['menus'],
data:function(){
return{
menuIdx:0
}
},
methods:{
menuClick(e){
this.menuIdx=e;
this.$emit("menu-click",e);
}
}
};
</script>
<style>
.menu-wrap{
width: 100%;
display:flex;
flex-direction:row;
justify-content: space-around;
}
.item{
font-size: 18px;
line-height: 18px;
margin-left: 5px;
margin-top: 10px;
}
.item-active{
font-size: 24px;
line-height: 24px;
margin-left: 15px;
margin-top: 10px;
padding-bottom: 2px;
border-bottom: 1px solid;
}
</style>
vcode-input.vue
<!-- 输入验证码组件 -->
<template>
<div class="content">
<div class="vcode-wrap">
<div class="item">
<input type="number" maxlength="1"
@blur="focusState[0]=false"
v-focus="focusState[0]"
@input="ChangeHandler(0)"
v-model="vcodes0">
</div>
<div class="item">
<input type="number" maxlength="1" @blur="focusState[1]=false" v-focus="focusState[1]" @input="ChangeHandler(1)" v-model="vcodes1">
</div>
<div class="item">
<input type="number" maxlength="1" @blur="focusState[2]=false" v-focus="focusState[2]" @input="ChangeHandler(2)" v-model="vcodes2">
</div>
<div class="item">
<input type="number" maxlength="1" @blur="focusState[3]=false" v-focus="focusState[3]" @input="ChangeHandler(3)" v-model="vcodes3">
</div>
<div class="item">
<input type="number" maxlength="1" @blur="focusState[4]=false" v-focus="focusState[4]" @input="ChangeHandler(4)" v-model="vcodes4">
</div>
<div class="item">
<input type="number" maxlength="1" @blur="focusState[5]=false" v-focus="focusState[5]" @input="ChangeHandler(5)" v-model="vcodes5">
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
vcodes0: '',
vcodes1: '',
vcodes2: '',
vcodes3: '',
vcodes4: '',
vcodes5: '',
focusState:[false,false,false,false,false,false]
};
},
computed: {
vcodeStr: function() {
if ((this.vcode0 != ' ') && (this.vcode0 != '') &&
(this.vcode1 != ' ') && (this.vcode1 != '') &&
(this.vcode2 != ' ') && (this.vcode2 != '') &&
(this.vcode3 != ' ') && (this.vcode3 != '') &&
(this.vcode4 != ' ') && (this.vcode4 != '') &&
(this.vcode5 != ' ') && (this.vcode5 != '')) {
let res = this.vcodes0 + this.vcodes1 +
this.vcodes2 + this.vcodes3 +
this.vcodes4 + this.vcodes5;
return res;
}
}
},
methods: {
ChangeHandler(e) {
switch (e) {
case 0:
if ((this.vcodes0 != '') && (this.vcode0 != ' ')) {
for (let i = 0; i < 6; i++)
this.focusState[i] = false;
this.focusState[1] = true;
}
break;
case 1:
if ((this.vcodes1 != '') && (this.vcode1 != ' ')) {
for (let i = 0; i < 6; i++) this.focusState[i] = false;
this.focusState[2] = true;
}
break;
case 2:
if ((this.vcodes2 != '') && (this.vcode2 != ' ')) {
for (let i = 0; i < 6; i++) this.focusState[i] = false;
this.focusState[3] = true;
}
break;
case 3:
if ((this.vcodes3 != '') && (this.vcode3 != ' ')) {
for (let i = 0; i < 6; i++) this.focusState[i] = false;
this.focusState[4] = true;
}
break;
case 4:
if ((this.vcodes4 != '') && (this.vcode4 != ' ')) {
for (let i = 0; i < 6; i++) this.focusState[i] = false;
this.focusState[5] = true;
}
break;
case 5:
if ((this.vcodes5 != '') && (this.vcode5 != ' ')) {
for (let i = 0; i < 6; i++) this.focusState[i] = false;
this.focusState[0] = true;
}
break;
}
this.$emit('input', this.vcodeStr);
}
},
created:function(){
this.focusState[0]=true;
},
directives: {
focus: {
//根据focusState的状态改变是否聚焦focus
update: function(el, {value})
{ //第二个参数传进来的是个json
if (value) {
el.focus()
}
}
}
}
}
</script>
<style scoped="scoped">
.content {
/* position: relative; */
width: 100%;
}
.vcode-wrap {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-around;
}
.item {
width: 40px;
height: 40px;
border: #E5E5E5 1px solid;
}
input {
font-size: 24px;
line-height: 38px;
text-align: center;
width: 38px;
border:none;
}
</style>
App.vue
<template>
<div id="app">
<div class="header">
<search-input class="search-wrap">
<template v-slot:left>
<img src="../public/imgs/登陆.png" class="myicon"/>
<!-- @click="RegHandler"/> -->
</template>
<template v-slot:right="pro" >
<img src="../public/imgs/heart1.png" class="myicon"/>
<div v-show="pro.heartNum!=0" style="color: yellow;">
</div>
{{pro.heartNum}}
</template>
</search-input>
<top-menu :menus="mymenus" @menu-click="MenuHandler" class="menu-wrap"></top-menu>
</div>
<div class="main">
<!-- <div v-show="showReg" class="body-content">
<span>输入手机号码:</span>
<input type="tel" v-model="mobile"/>
<div>{{leftTime}}</div>
<vcode-input v-model="vcodeStr"></vcode-input>
<hr>
<div>{{mobile}}</div>
<div>{{vcodeStr}}</div>
</div> -->
<div class="body-content">
<!-- <component :is="currentComp"
:list="imglist"></component> -->
<image-page-list1></image-page-list1>
<!-- <image-page-list2></image-page-list2> -->
</div>
</div>
<div class="footer">
<bottom-menu :menus="imgs"></bottom-menu>
</div>
</div>
</template>
<!--注册引用登记注册-->
<!--父组件参数返回对象属性-->
<script>
import SearchInput from './components/search-input.vue'
import TopMenu from "./components/top-menu.vue"
import BottomMenu from "./components/bottom-menu.vue"
import StundentList from "./components/student-list.vue"
//import VcodeInput from "./components/vcode-input.vue"
import ImgList from "./components/image-list.vue"
import ImgdetailList from "./components/imgdetail-list.vue"
import ImagePageList1 from "./components/image-pagelist1.vue"
import ImagePageList2 from "./components/image-pagelist2.vue"
export default {
name: 'app',
components: {
TopMenu,
BottomMenu,
SearchInput,
// VcodeInput
StundentList,
ImgList,
ImgdetailList,
ImagePageList1,
ImagePageList2
},
data:function(){
return{
mymenus:['动态','推荐','热门','精选'],
imgs:['./imgs/房子.png',
'./imgs/购物.png',
'./imgs/钱包.png',
'./imgs/相机.png',
'./imgs/问答.png'],
list:[ ],
// mobile:'',
// showReg:false,
// timer:null,
// leftTime:60,
// vcodeStr:''
currentComp:'ImgList',
imglist: [{
imgurl: require('./assets/images/5.jpg'),
name: '白雪公主'
},
{
imgurl: require('./assets/images/6.jpg'),
name: '辛德瑞拉'
},
{
imgurl: require('./assets/images/7.jpg'),
name: '爱丽儿'
},
{
imgurl: require('./assets/images/8.jpg'),
name: '贝尔'
}
]
}
},
methods:{
// RegHandler(){
// this.showReg=true;
// this.timer=setInterval(()=>{
// if(this.leftTime>0)
// this.leftTime--;
// else{
// clearInterval(this.timer);
// this.leftTime=60;
// }
// },1000)
// }
MenuHandler(e){
this.menuIdx=e;
if(this.menuIdx==0)
this.currentComp='ImgList'
else
this.currentComp='ImgdetailList'
}
},
created(){
}
}
</script>
<!-- // created(){
// // 直接发送请求,因为已经挂载axios
// this.axios.baseURL="http://localhost:8080"
// this.axios.get("/api/students")
// .then((res)=>{
// // console.log(res.data)
// this.list=res.data;
// })
// let p={
// uesr:'a11',
// passwd:'aaaa'
// }
// this.axios.post("/api/login",p)
// .then((res)=>{
// console.log(res.data)
// }) -->
<style scoped="scoped">
.header{
width: 100%;
height: 90px;
position: fixed;
top: 0;
left: 0;
background: #708090;
}
.footer{
width: 100%;
height: 40px;
position: fixed;
bottom: 0;
left: 0;
background: #708090;
}
.main{
/* width: 100%;
margin-top: 80px;
background: url(./assets/images/bg.jpg) no-repeat; */
position:fixed;
top: 0;
left: 0;
width:100%;
height:100%;
min-width: 300px;
z-index:-10;
zoom: 1;
background-color: #fff;
background: url(./assets/images/bg.jpg);
background-repeat: no-repeat;
background-size: cover;
background-position: center 0;
}
.myicon{
width: 28px;
height: 24px;
}
.body-content{
margin-top: 100px;
}
</style>
main.js
import Vue from 'vue';
import App from './App.vue';
import axios from "axios";
import VueAxios from "vue-axios";
Vue.config.productionTip = false
Vue.use(VueAxios,axios)
axios.defaults.baseURL="http://localhost:3000"
new Vue({
render: h => h(App),
}).$mount('#app')
vue.config.js
const path = require('path')
module.exports = {
publicPath: '/', //部署应用包时的基本 URL
outputDir: 'dist', //生成的生产环境构建文件的目录
lintOnSave: true, //eslint-loader 是否在保存的时候检查
runtimeCompiler: false, //是否使用包含运行时编译器的 Vue 构建版本
productionSourceMap: false, //生产环境是否生成 sourceMap 文件
integrity: false, //生成的 HTML 中的 <link rel="stylesheet"> 和 <script> 标签上启用 Subresource Integrity (SRI)
// webpack相关配置
chainWebpack: (config) => {
config.resolve.alias
.set('vue$', 'vue/dist/vue.esm.js')
.set('@', path.resolve(__dirname, './src'))
},
configureWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
config.mode = 'production' // 生产环境
} else {
config.mode = 'development' // 开发环境
}
},
// css相关配置
// css: {
// extract: true, // 是否分离css(插件ExtractTextPlugin)
// sourceMap: false, // 是否开启 CSS source maps
// loaderOptions: {}, // css预设器配置项
// requireModuleExtension: false // 是否启用 CSS modules for all css / pre-processor files.
// },
parallel: require('os').cpus().length > 1, // 是否使用 thread-loader
pwa: {}, //PWA 插件相关配置
// webpack-dev-server 相关配置
devServer: {
open: true,
host: 'localhost',
port: 8080,
https: false,
hotOnly: false,
// http 代理配置
proxy: {
'/api': {
target: 'http://127.0.0.1:8080/api',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
before:require("./mock/mockData")
},
// 第三方插件配置
pluginOptions: {
}
}
四.总结
1.Vue中的axios前端请求数据:axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:(1)从浏览器中创建 XMLHttpRequest(2)从 node.js 发出 http 请求(3)支持 Promise API(4)拦截请求和响应(5)转换请求和响应数据,取消请求(6)自动转换JSON数据(7)客户端支持防止 CSRF/XSRF(8)体量相对较小。Axios改写为Vue的原型属性,在主入口main.js中引用,之后挂载在vue在原型链上:
2.在组件中使用get数据和post请求:根据实验8参考代码,其实 Vue 和 axios 可以在一起配合的事情不只是访问和展示一个 API。也可以和 Serverless Function 通信,向一个有写权限的 API 发送发布/编辑/删除请求等等。
3.组件的分类:
(1)页面级别的组件:页面级别的组件,通常是pages目录下的.vue组件,是组成整个项目的一个大的页面。一般不会有对外的接口。我们通常开发时,主要就是编写这种组件。
(2)业务上可以复用的基础组件:这一类组件通常是在业务中被各个页面复用的组件,这一类组件通常都写到components目录下,然后通过import在各个页面中使用。这一类组件通常是实现某个功能,比如外卖中各个页面都会使用到的评分系统。这个部分就可以实现评分功能,可以写成一个独立的业务组件。
(3)与业务无关的独立组件:这一类组件通常是与业务功能无关的独立组件。这类组件通常是作为基础组件,在各个业务组件或者页面组件中被使用。目前市面上比较流行的ElementUI和iview等中包含的组件都是独立组件。
4.组件的编写(博客园资料):首先一个组件最重要的两个一定是数据和事件。另外,组件开发要考虑可扩展性,在vue中组件扩展通过slot来实现。
(1)数据主要是指:data和prop。其中data主要是用于组件内部的数据展示,通常是一个函数。而prop是接收外部数据,涉及到数据的校验,数据的扩展等,是非常重要的一个API(2)事件:组件的事件(event)不同于在普通元素身上绑定事件。组件事件应该如何触发,是在父组件中触发还是在组件内部元素身上触发。(3)slot:主要用于组件的扩展。同样是组件开发非常重要的API。综上所述:组件开发中有三个非常重要的API,可以戏称为组件开发三要素:prop,event和slot。接下来我们将从组件开发的角度来分别讲述这三个API的使用(4)prop属性定义了组件可以接收哪些可配置的属性。主要是用来接收父组件传递的数据。props接收属性时可以是数组形式,也可以是对象形式。如果不涉及到类型校验或者其他校验可以直接使用数组形式,如果涉及到校验最好使用对象形式。
数组形式:props:[‘name’,’age’]
对象形式: 使用对象的形式,可以对数据的类型,是否必填,以及其他特征进行校验。这对于组件化开发非常有利。Child.vue:
Parent.vue使用组件
定义组件时,name是String类型且是必填的,age是number类型非必填的。type是必须是success,warning和primary中的某一个。
(5)自定义组件学习:我们定义的Child组件添加点击事件,这时候我们一般是通过在组件内部的button上通过
e
m
i
t
、
触
发
事
件
,
然
后
在
父
组
件
中
监
听
。
在
组
件
中
通
过
emit、触发事件,然后在父组件中监听。在组件中通过
emit、触发事件,然后在父组件中监听。在组件中通过emit定义事件:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。