首页
封装头部子组件,需要用到插槽
子组件:
<div class="top">
<slot name="left"></slot>
<slot name="center"></slot>
<slot name="right"></slot>
</div>
<style lang="stylus" scoped>
@import '~styles/minxins.styl';
.top{
align-items center
height 1rem
width 100%
display flex
color #fff
bg()
.left{
width .8rem
text-align center
}
.con{
flex 1
}
.right{
padding 0 .2rem
}
}
</style>
父组件引入使用
<script>
import mytop from '@/components/mytop'
components: {
mytop,
},
</script>
<mytop class="mtop">
<template v-slot:left>
<span class="left iconfont">

</span>
</template>
<template v-slot:center>
<div class="con">
<i class="iconfont search">

</i>
<input type="text" placeholder="输入城市/景点/游玩主题">
</div>
</template>
<template v-slot:right>
<div class="right" @click="gocity">
{{cityval}}
<i class="iconfont icon-jiantouxia"></i>
</div>
</template>
</mytop>
.mtop{
.left{
font-size .6rem
}
.con{
// flex 1
color #ccc
position relative
.search{
position absolute
font-size .4rem
left .2rem
margin-top .1rem
}
input{
padding-left .8rem
height .6rem
width 100%
border-radius .1rem
box-sizing border-box
font-size .28rem
}
}
}
封装swipers子组件
子组件:
<template>
<div>
<div class="warp">
<swiper :options="swiperOption">
<swiper-slide v-for="item in swiperList" :key="item.id">
<img :src="item.imgUrl" alt="">
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</div>
</div>
</template>
<script>
export default {
props: ["swiperList"],
data() {
return {
swiperOption: {
autoplay: 5000,
initialSlide: 1,
pagination: ".swiper-pagination",
observeParents: true,
observer: true
}
};
},
methods: {},
created() {},
mounted() {},
components: {},
computed: {},
watch: {}
};
</script>
<style lang='stylus' scoped>
.warp {
overflow: hidden;
width: 100%;
height: 0;
padding-bottom: 26.67%;
background: pink;
}
.warp>>>img {
width: 100%;
height: 2rem;
}
</style>
封装iconlist子组件
子组件:
<template>
<div>
<div class="icons">
<swiper >
<swiper-slide v-for="(page,index) in pages" :key="index">
<div class="icon" v-for="item in page" :key="item.id">
<div class="icon-img">
<img class="icon-img-content" :src="item.imgUrl" >
</div>
<p class="icon-desc">{{item.desc}}</p>
</div>
</swiper-slide>
</swiper>
</div>
</div>
</template>
<script>
export default {
data() {
return {
swiperOption: {
autoplay: 5000,
initialSlide: 1,
pagination: ".swiper-pagination",
observeParents: true,
observer: true
},
iconList:[]
};
},
methods: {},
created() {
this.$axios.get("/static/index.json").then(res => {
console.log(res);
this.iconList = res.data.data.iconList;
});
},
mounted() {},
components: {},
computed: {
pages() {
const pages = [];
this.iconList.forEach((item, index) => {
const page = Math.floor(index / 8);
if (!pages[page]) {
pages[page] = [];
}
pages[page].push(item);
});
return pages;
}
},
watch: {}
};
</script>
<style lang='stylus' scoped>
.icons >>> .swiper-container {
height: 0;
padding-bottom: 50%;
}
.icon {
position: relative;
overflow: hidden;
float: left;
width: 25%;
height: 0;
padding-bottom: 25%;
.icon-img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0.44rem;
// background :blue
box-sizing: border-box;
padding: 0.05rem;
.icon-img-content {
display: block;
margin: 0 auto;
height: 100%;
}
}
.icon-desc {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 0.44rem;
line-height: 0.44rem;
text-align: center;
color: #333;
}
}
</style>
父组件:
<script>
import iconlist from '@/components/iconlist/iconlist.vue'
components: {
mytop,
iconlist
},
</script>
父传子
需要给子组件标签定义自定义属性绑定需要传递的数据进行传递,在子组件中通过props接收
子传父
需要给子组件标签定义自定义事件,子组件中通过this.$emit(‘父组件中自定义事件名’,需要传递的值)传递,父组件中接收到的数据在自定义事件的参数中 : 方法名(子组件传过来的参数){}
详情页
详情页主页
<template>
<div>
<banner :gallaryImgs="gallaryImgs"></banner>
<myheader ></myheader>
<div class="conter">
<detailList :categoryList="categoryList"></detailList>
</div>
</div>
</template>
<script>
import banner from "@/components/detail/Banner";
import myheader from "@/components/detail/myheader";
import detailList from "@/components/detail/detailList";
export default {
data() {
return {
info: {},
flag: false,
gallaryImgs: [],
categoryList:[]
};
},
methods: {
},
created() {
this.$axios.get("/static/detail.json").then(res => {
console.log(res);
this.info = res.data.data;
this.gallaryImgs = res.data.data.gallaryImgs;
this.categoryList=res.data.data.categoryList
});
},
mounted() {
},
components: {
banner,
myheader,
detailList
},
computed: {},
watch: {}
};
</script>
<style lang='stylus' scoped>
.conter {
height: 50rem;
}
</style>
myheader子组件
<template>
<div >
<div>
<router-link to="/" class="top" v-show="!flag">
<div class="iconfont con"></div>
</router-link>
<div class="header-fixed" v-show="flag" :style="opacityStyle">
<router-link to="/">
<div class="iconfont header-fixed-back"></div>
</router-link>
景点详情
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
opacityStyle: {
opacity: 0
}
};
},
methods: {
handleScroll() {
const top = window.pageYOffset;
if (top > 50) {
let opacity = top / 140;
opacity = opacity > 1 ? 1 : opacity;
this.opacityStyle = { opacity };
this.flag = true;
} else {
this.flag = false;
}
}
},
created() {},
mounted() {
window.addEventListener("scroll", this.handleScroll);
},
components: {},
computed: {},
watch: {}
};
</script>
<style lang='stylus' scoped>
@import '~styles/minxins.styl';
.top {
position: absolute;
left: 0.2rem;
top: 0.2rem;
width: 0.8rem;
line-height: 0.8rem;
border-radius: 0.4rem;
text-align: center;
background: rgba(0, 0, 0, 0.8);
.con {
color: #fff;
font-size: 0.4rem;
}
}
.header-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
text-align: center;
color: #fff;
background: $bgColor;
font-size: 0.32rem;
height: 1rem;
line-height: 1rem;
bg();
.header-fixed-back {
position: absolute;
top: 0;
left: 0;
width: 0.64rem;
text-align: center;
font-size: 0.4rem;
color: #fff;
}
}
</style>
banner子组件
<template>
<div class="banner">
<img @click="flag=true" class="banner-img" :src="gallaryImgs[0]" >
<div class="banner-con">
<div class="banner-title">
大连圣亚海洋世界(AAAA景区)
</div>
<div class="banner-number">
<span class="iconfont" ></span>
{{gallaryImgs.length}}
</div>
</div>
<gallary v-if="flag" :gallaryImgs="gallaryImgs" @ShutDown="ShutDown"></gallary>
</div>
</template>
<script>
import gallary from "@/components/detail/Gallary";
export default {
props: ["gallaryImgs"],
data() {
return {
flag:false
};
},
methods: {
ShutDown(){
this.flag=false
}
},
created() {},
mounted() {},
components: {
gallary
},
computed: {},
watch: {}
};
</script>
<style lang='stylus' scoped>
.banner {
position: relative;
overflow: hidden;
height: 0;
padding-bottom: 55%;
.banner-img {
width: 100%;
margin-top: -1.4rem;
height: 5.6rem;
}
.banner-con {
display: flex;
align-items center
justify-content space-between
position: absolute;
left: 0;
right: 0;
bottom: 0;
line-height: 0.6rem;
color: #fff;
background-image: linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8));
.banner-tittle {
flex: 1;
font-size: 0.32rem;
padding: 0 0.2rem;
}
.banner-number {
height: .4rem;
display flex
align-items center
padding: 0 0.4rem;
border-radius: 0.2rem;
background: rgba(0, 0, 0, 0.8);
font-size: 0.24rem;
.banner-icon {
font-size: 0.24rem;
}
}
}
}
</style>
Gallary子组件
<template>
<div>
<div class="gallary">
<div @click="ShutDown" class="wrap">
<swiper :options="swiperOption">
<swiper-slide v-for="(item, index) in gallaryImgs" :key="index">
<img class="swiper-img" :src="item" >
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</div>
</div>
</div>
</template>
<script>
export default {
props: ["gallaryImgs"],
data() {
return {
swiperOption: {
autoplay: 5000,
initialSlide: 1,
paginationType: 'fraction',
pagination: ".swiper-pagination",
observeParents: true,
observer: true
}
};
},
methods: {
ShutDown() {
this.$emit("ShutDown");
},
},
created() {},
mounted() {
},
components: {},
computed: {},
watch: {}
};
</script>
<style lang='stylus' scoped>
.gallary {
display: flex;
flex-direction: column;
justify-content: center;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #000;
z-index 1;
.wrap {
overflow: hidden;
height: 0;
width: 100%;
padding-bottom: 100%;
.swiper-img {
width: 100%;
}
}
}
</style>
detailList子组件
<template>
<div>
<ul class="categoryList">
<li class="categoryList-item" v-for="(item, index) in categoryList" :key="index">
<p class="item.item"> <i class="iconfont"></i>{{item.title}}</p>
<ul class="item-children" v-if="item.children">
<li class="children-ele" v-for="(ele, i) in item.children" :key="i">
<p class="ele-title"> <i class="iconfont"></i>{{ele.title}}</p>
<ul class="ele-children" v-if="ele.children">
<li class="children-con" v-for="(con, ind) in ele.children" :key="ind">
<p class="con-title"> <i class="iconfont"></i>{{con.title}}</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["categoryList"],
data() {
return {};
},
methods: {},
created() {},
mounted() {},
components: {},
computed: {},
watch: {}
};
</script>
<style lang='stylus' scoped>
ul {
padding: 0 0.2rem;
}
p {
border-bottom: 0.02rem solid #ccc;
line-height: 1rem;
font-size: 0.32rem;
i {
margin-right: 0.2rem;
color: #05b9d2;
}
}
</style>
城市页
主页:
<template>
<div>
<search class="header" @changeflag="changeflag" :cities="cities"></search>
<div class="wrap" v-if="flag">
<!--
<div class="header">
<div class="top">
<i class="iconfont"> </i>
<span>城市选择</span>
<i></i>
</div>
<div class="bottom">
<input type="text" placeholder="输入城市名或拼音">
</div>
</div> -->
<p class="title">当前城市</p>
<div class="city">
<span @click="gohome(city)"> {{city}}</span>
</div>
<p class="title">热门城市</p>
<ul class="hotCities">
<li @click="gohome(item.name)" v-for="(item, index) in hotCities" :key="index">
{{item.name}}
</li>
</ul>
<div :ref="key" v-for="(value, key,) in cities" :key="key">
<cityAll :list="value" :title="key"></cityAll>
</div>
<ul class="right">
<li @click="change(key)" v-for="(value, key,) in cities" :key="key">
{{key}}
</li>
</ul>
</div>
</div>
</template>
<script>
import Bscroll from "better-scroll";
import cityAll from "./cityAll.vue";
import search from "./search.vue";
export default {
data() {
return {
hotCities: [],
cities: [],
innerText: "",
city: this.$route.query.city,
startY: 0,
flag: true,
touchStatus: false
};
},
methods: {
// handleTouchStart() {
// this.touchStatus = true;
// },
// handleTouchMove(e) {
// if (this.touchStatus) {
// console.log(e);
// const startY = this.$refs[e.target.innerText][0].offsetTop;
// console.log(startY);
// this.innerText = e.target.innerText;
// // document.documentElement.scrollTop=startY-100
// },
// handleTouchEnd(e) {
// this.touchStatus = false;
// console.log(e);
// },
gohome(name) {
this.$router.push({
path: "/",
query: {
name
}
});
},
changeflag(flag) {
this.flag = flag;
},
change(key) {
console.log(key);
document.documentElement.scrollTop = this.$refs[key][0].offsetTop - 100;
}
},
updated() {
this.startY = this.$refs["A"][0].offsetTop;
console.log(this.startY);
},
created() {
this.$axios.get("/static/city.json").then(res => {
console.log(res);
this.hotCities = res.data.data.hotCities;
this.cities = res.data.data.cities;
});
},
mounted() {},
components: {
cityAll,
search
},
computed: {},
watch: {}
};
</script>
<style lang='stylus' scoped>
@import '~styles/minxins.styl';
.header {
width: 100%;
bg();
height: 1.6rem;
position: sticky;
top: 0;
.top {
height: 55%;
align-items: center;
font-size: 0.32rem;
width: 100%;
padding: 0 0.2rem;
display: flex;
box-sizing: border-box;
i {
font-size: 0.7rem;
width: 0.8rem;
color: #fff;
height: 100%;
line-height: 1.1rem;
}
span {
flex: 1;
text-align: center;
color: #fff;
}
}
.bottom {
height: 45%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
input {
width: 95%;
border-radius: 0.2rem;
height: 80%;
text-align: center;
}
}
.title {
line-height: 0.6rem;
font-size: 0.3rem;
padding-left: 0.2rem;
box-sizing: border-box;
background-color: #f0f0f0;
}
.city {
height: 1rem;
background-color: #fff;
display: flex;
align-items: center;
span {
margin-left: 0.2rem;
border-radius: 0.1rem;
padding: 0.08rem 0.5rem;
border: 1px solid #ccc;
}
}
.hotCities {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
padding-right: 0.7rem;
box-sizing: border-box;
padding-bottom: 0.2rem;
li {
width: 28%;
text-align: center;
border: 0.02rem solid #ccc;
line-height: 0.6rem;
margin-top: 0.2rem;
border: 1px solid #ccc;
border-radius: 0.1rem;
}
}
.list {
position: fixed;
right: 0.04rem;
top: 2rem;
li {
text-align: center;
line-height: 0.4rem;
color: #00c6c3;
}
}
.right {
position: fixed;
right: 0.1rem;
top: 3rem;
li {
line-height: 0.4rem;
color: #01c5c3;
text-align: center;
}
}
</style>
搜索
<template>
<div>
<div class="header">
<div class="top">
<i @click="$router.go(-1)" class="iconfont"> </i>
<span>城市选择</span>
<i></i>
</div>
<div class="bottom">
<input v-model="keyword" @keydown.enter="changeinp" type="text" placeholder="输入城市名或拼音">
</div>
</div>
<div class="search" v-if="flag">
<p v-if="result.length==0">暂无数据</p>
<ul v-else>
<li @click="gohome(item)" v-for="(item, index) in result" :key="index">
{{item}}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: ["cities"],
data() {
return {
keyword: "",
result: [],
flag: false
};
},
methods: {
gohome(name){
this.$router.push({
path: '/',
query: {
// url的参数, 类似get请求的传参
name
},
})
},
changeinp() {
if (this.keyword == "") {
this.flag = false;
this.$emit("changeflag", true);
return;
}
this.flag = true;
this.$emit("changeflag", !this.flag);
this.result = [];
console.log(this.keyword);
console.log();
for (let key in this.cities) {
this.cities[key].forEach(item => {
if (
item.spell.indexOf(this.keyword) > -1 ||
item.name.indexOf(this.keyword) > -1
) {
this.result.push(item.name);
}
});
}
}
},
created() {},
mounted() {},
components: {},
computed: {
list() {
let arr = Array.from(this.cities);
return arr;
}
},
watch: {}
};
</script>
<style lang='stylus' scoped>
@import '~styles/minxins.styl';
.header {
width: 100%;
bg();
height: 1.6rem;
position: sticky;
top: 0;
.top {
height: 55%;
align-items: center;
font-size: 0.32rem;
width: 100%;
padding: 0 0.2rem;
display: flex;
box-sizing: border-box;
i {
font-size: 0.7rem;
width: 0.8rem;
color: #fff;
height: 100%;
line-height: 1.1rem;
}
span {
flex: 1;
text-align: center;
color: #fff;
}
}
.bottom {
height: 45%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
input {
width: 95%;
border-radius: 0.2rem;
height: 80%;
text-align: center;
}
}
.search {
ul {
li {
line-height: 0.8rem;
line-height: 0.8rem;
width: 100%;
padding-left: 0.2rem;
border-bottom: 0.02rem solid #ccc;
}
}
}
</style>
城市列表
<template>
<div>
<div class="list" @touchstart = "handleTouchStart"
@touchmove = "handleTouchMove"
@touchend = "handleTouchEnd">
<div>
<div v-for="(value, key) in cities" :key="key" class="area">
<div class="title" :ref="key" >
{{key}}
</div>
<div class="item-list" v-for="(item, index) in value" :key="index">
<div class="item">{{item.name}}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Bscroll from "better-scroll";
export default {
props: ["cities", "letter"],
data() {
return {
touchStatus: false,
timer: null
};
},
methods: {
handleTouchStart(e) {
this.touchStatus = true;
},
handleTouchMove(e) {
if (this.touchStatus) {
// if (this.timer) {
// clearInterval(this.timer);
// }
// this.timer = setInterval(() => {
let documentTOP = document.documentElement.scrollTop + 80;
console.log(documentTOP);
this.lists.forEach(item => {
let startY = this.$refs[item][0].offsetTop;
console.log(startY);
if (startY == documentTOP) {
this.$emit("changeItem", item);
}
});
// }, 500);
}
},
handleTouchEnd() {
this.touchStatus = false;
}
},
created() {},
mounted() {
// this.scroll = new Bscroll(this.$refs.wrapper)
// console.log(new Bscroll(this.$refs.wrapper));
},
components: {},
computed: {
lists() {
let lists = [];
for (const key in this.cities) {
lists.push(key);
}
return lists;
}
},
watch: {
letter() {
console.log(this.letter);
if (this.letter) {
const element = this.$refs[this.letter][0];
console.log(element);
document.documentElement.scrollTop =
this.$refs[this.letter][0].offsetTop - 80;
// this.scroll.scrollToElement(element)
}
}
}
};
</script>
<style lang='stylus' scoped>
.list {
width: 100%;
.area {
width: 100%;
.title {
background-color: #f0f0f0;
line-height: 0.6rem;
}
.item {
line-height: 0.6rem;
border-bottom: 1px solid #ccc;
padding-left: 0.2rem;
}
}
}
</style>
let documentTOP = document.documentElement.scrollTop + 80;
//获取页面卷曲出去的距离
console.log(documentTOP);
this.lists.forEach(item => {
let startY = this.$refs[item][0].offsetTop;
console.log(startY);
判断title ABCD..距离顶部的距离 如果相等把 item传递给父组件
if (startY == documentTOP) {
this.$emit("changeItem", item);
}
});
右边悬浮字母表
<template>
<div>
<ul class="list">
<li
@touchstart = "handleTouchStart"
@touchmove = "handleTouchMove"
@touchend = "handleTouchEnd"
@click = "handleLetterClick"
class="item" :class="{active:i==index}" v-for="(item, index) in cities"
:ref="index" :key="index">
{{index}}
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["cities", "letter"],
data() {
return {
touchStatus: false,
i: "A",
timer: null,
key: ""
};
},
methods: {
handleLetterClick(e) {
this.i = e.target.innerText;
this.$emit("change", e.target.innerText); //发送
},
handleTouchStart(e) {
this.touchStatus = true;
},
handleTouchMove(e) {
if (this.touchStatus) {
// this.timer = setInterval(() => {
const startY = this.$refs["A"][0].offsetTop;
const touchY = e.touches[0].clientY - 80;
console.log(touchY);
// index触控位置的下标 20量为字符的高度
const index = Math.floor((touchY - startY) / 20);
if (index >= 0 && index < this.letters.length) {
this.i = this.letters[index];
this.$emit("change", this.letters[index]);
}
// }, 15);
//A字符距离顶部的距离
//所触控区距离最顶部距离e.touches[0].clientY 79为量出来量出来header高43+search高为36
}
},
handleTouchEnd() {
this.touchStatus = false;
}
},
created() {},
mounted() {},
components: {},
computed: {
letters() {
const letters = [];
for (let i in this.cities) {
letters.push(i);
}
return letters;
}
},
watch: {
letter: {
// deep: true,
immediate: true,
handler(newVal, oldVal) {
this.i = newVal;
}
}
}
};
</script>
<style lang='stylus' scoped>
.list {
display: flex;
flex-direction: column;
justify-content: center;
position: fixed;
top: 1.58rem;
right: 0;
bottom: 0;
width: 0.6rem;
z-index: 99;
// background :red
.item {
margin: 0 auto;
line-height: 0.4rem;
text-align: center;
color: #01c5c3;
width: 20px;
}
.active {
background-color: #01c5c3;
color: #fff;
}
}
</style>