vuevue-znly

Posted smart-girl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vuevue-znly相关的知识,希望对你有一定的参考价值。

老规矩,放下博主的项目地址:https://github.com/wohaiwo/vue-znly
我一直在想给那些开源者取什么名字比较好,怎样才对得起他们开源项目的精神,后来想想,还是叫博主吧。有的人用语言表达技术,有的人用代码表达技术。
接下来我们还是来看项目效果吧
技术图片

我们可以看到这个项目内容还是挺多的,里面缺少一些内容,但是不影响我们研究这个项目
我们看index.html可以发现,这个项目用到了vue和jquery结合做的。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, height=device-height, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
    <meta name="keywords" content="今世缘 景区">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-touch-fullscreen" content="yes">
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <title>今世缘景区欢迎您</title>
    <link rel="shortcut icon" href="/static/logo/favicon.ico" type="image/x-icon" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <meta name="format-detection"content="telephone=no, email=no" />
    <meta http-equiv="refresh" content="2000;url=http://www.baidu.com" />
  </head>
  <body>
    <div id="app">
        <router-view></router-view>
    </div>
    <script type="text/javascript" src="http://api.map.baidu.com/api?v=1.4"></script>
    <script type="text/javascript" src="../static/lib/js/jquery-3.2.1.slim.min.js"></script>
  </body>
</html>

main.js中引入入口文件App.vue

//main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import App from './App';
import VueRouter from 'vue-router';
import routes from './router/router.js';
import axios from 'axios';
// 解决30秒延迟问题
import FastClick from 'FastClick';

Vue.use(VueRouter);     // 加载vue-router插件
Vue.prototype.$http = axios;

FastClick.attach(document.body);


// 创建 router 实例,然后传 `routes` 配置
const router = new VueRouter( 
    mode: 'hash',
    routes
);

// 创建和挂载根实例 
var vm =  new Vue(
    router,
    components:  App 
).$mount('#app')
//app.vue中还加入了动画
<template>
    <div>
        <transition name="router-fade" mode="out-in">
            <router-view></router-view>
        </transition>
    </div> 
</template>

<script> 
    import './static/lib/css/main.css'
    import './static/lib/css/reset.css'
    export default 
    

</script>

<style lang="scss">
    .router-fade-enter-active, .router-fade-leave-active 
        transition: opacity .3s;
    
    .router-fade-enter, .router-fade-leave-active 
        opacity: 0;
    

    .router-slid-enter-active, .router-slid-leave-active 
        transition: all .4s;
    
    .router-slid-enter, .router-slid-leave-to 
        transform: translate3d(-100px, 0, 0);
        opacity: 0;
    
</style>

router中也是使用懒加载的模式,可以看到首先加载定向的是我们的home组件在app.vue中渲染出来

//router.js
import App from '../App.vue';

// Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
const home = resolve => require(['../page/home.vue'], resolve);     // 首页
const introduction = resolve => require(['../page/scenicIntroduction.vue'], resolve);
const listDetail = resolve => require(['../components/listDetail.vue'], resolve);
const travelBox = resolve => require(['../page/travelBox.vue'], resolve);
const externalMap = resolve => require(['../page/externalMap.vue'], resolve);
const service = resolve => require(['../page/service.vue'], resolve);
const dropBox = resolve => require(['../components/dropBox.vue'], resolve);

// 定义路由
const routes = [
    
        path: '/',
        component: App,
        children: [
            
                path: '/',
                redirect:  name: 'home' 
            ,
            
                path: '/home',
                name: 'home',
                component: home
            , 
                path: '/scenic/introduction', 
                name: 'introduction', 
                component: introduction
            , 
                path: '/scenic/detail/:id/:type/:identifier',
                name: 'listDetail', 
                component: listDetail
            , 
                path: '/travelBox',
                name: 'travelBox', 
                component: travelBox
            , 
                path: '/externalMap',
                name: 'externalMap',
                component: externalMap
            , 
                path: '/service/:type',
                name: 'service',
                component: service
            , 
                path: '/dropBox/:url/:title',
                name: 'dropBox',
                component: dropBox
            
        ]
    
]


export default routes;

我们来看home组件,里面的代码挺有意思的,
先看header组件

//header.vue
<template>
    <header>
        <slot name="logo"></slot>
        <span class="left-icon" v-if="goBack" @click="$router.go(-1)">
            <i title="返回" class="iconfont">&#xe679;</i>
        </span>
        <span class="left-icon side-bar" v-if="sideBar" @click="showSideBar">
            <i title="主菜单" class="iconfont">&#xe602;</i>
        </span>
        <span class="title-text" v-if="headTitle">headTitle</span>
        <transition name="slide-fade">
        <nav v-show="isShowSideBar">
            <router-link to="/scenic/introduction"><i class="iconfont">&#xe641;</i>景区介绍</router-link>
            <router-link :to="name: 'service', params: type: 3"><i class="iconfont">&#xe64c;</i>景区公告</router-link>
            <router-link :to="name: 'service', params: type: 15"><i class="iconfont">&#xe69b;</i>景区服务</router-link>
            <router-link :to="name: 'service', params: type: 13"><i class="iconfont">&#xe6b2;</i>预订门票</router-link>
            <router-link :to="name: 'service', params: type: 14"><i class="iconfont">&#xe6af;</i>特色购物</router-link>
            <router-link to="/travelBox"><i class="iconfont">&#xe603;</i>旅行百宝箱</router-link>
            <router-link :to="name: 'dropBox', params: url: vrUrl, title: vrTitle"><i class="iconfont">&#xe73d;</i>虚拟旅游</router-link>
             <router-link :to="name: 'service', params: type: 6"><i class="iconfont">&#xe7f1;</i>餐饮住宿</router-link>
        </nav>
        </transition>
    </header>
</template>
<script>
    export default 
        data() 
            return 
            
        ,
        props: ['goBack', 'headTitle', 'sideBar', 'isShowSideBar', 'vrUrl', 'vrTitle'],
        methods: 
            // 子组件通过emit向父组件传递事件的函数名
            showSideBar() 
                this.$emit('breadcrumb');
            
        
    
</script>
<style scoped lang="scss">
    $nav-color: #e60012;
    header 
        position: fixed;
        left: 0;
        top: 0;
        z-index: 100;
        width: 100%;
        height: 40px;
        line-height: 40px;
        color: $nav-color;
        background: #fff ;
        text-align: center;
        border-bottom: 2px solid #ededed;
        box-sizing: border-box;
        span 
            font-size: 18px;
            font-weight: bold;  
               
        .left-icon 
            position: absolute;
            left: 0;
            top: 50%;
            width: 50px;
            transform: translateY(-50%);
            i 
                color: $nav-color;
            
        
        .side-bar 
            left: 0;
            width: 10%;
            background: $nav-color;
            i 
                color: #fff;
            
        
        nav 
            position: fixed;
            left: 0;
            right: 0;
            top: 40px;
            bottom: 50px;
            z-index: 20;
            width: 140px;
            background: #fff;
            a 
                display: block;
                height: 50px;
                line-height: 50px;
                text-align: left;
                padding-left: 8%;
                box-sizing: border-box;
                color: #000;
                &:not(:last-child) 
                    border-bottom: 1px solid #e6e6e6;
                
                i 
                    color: $nav-color;
                    margin-right: 20px;
                
            
        
    
    .slide-fade-enter-active, .slide-fade-leave-active  
        transition: all 0.3s ease-in;
    
    .slide-fade-enter, .slide-fade-leave-to
        opacity: 0;
        transform: translate3d(-150px, 0, 0);
    
    // 适配一体机样式
    @media screen and  (min-width: 1000px) 
        $header-height: 100px;
        i 
            font-size: 36px;
        
        header 
            font-size: 32px;
            height: $header-height;
            line-height: $header-height;
            border-bottom: 4px solid #ededed;
            span 
                font-size: 45px;
                font-weight: bold;  
            
            .left-icon 
                width: $header-height;
            
            nav 
                top: $header-height;
                bottom: $header-height;
                width: 300px;
                a 
                    height: $header-height;
                    line-height: $header-height;
                    border-bottom: 3px solid #e6e6e6;
                
            
      

    
</style>

userCount.vue不知道表达什么意思?

//userCount.vue
//src\\components\\userCount.vue
 <template>
    <div>
        <p> msg </p>
         
    </div>
</template>

<script>
    export default 
        data() 
            return 
                msg: ''
            
        ,
        created() 
            let url = '/JSY_H5/h5/statistics';
            this.$http.get(url).then((response) => 
                this.$data.msg = response.data.data.msg;
            , (response) => 
                console.log('oops, data is error');
            );
        
    
</script>

<style scoped lang="scss">
    div 
        position: relative;
        width: 100%;
        height:  20px;
        padding: 0 4%;
        margin-top: 40px;
        font-size: 14px;
        color: #ddd;
        background: rgba(0, 0, 0, .4);
        overflow: hidden;
        z-index: 40;
        user-select: none;
        box-sizing: border-box;
        p 
            left: 100%;
            position: absolute;
            z-index: 40;
            white-space: nowrap;
            animation-delay: 1s;
            animation-name: slide;
            animation-duration: 45s;
            animation-iteration-count: infinite;
        
    
    @media screen and (min-width: 1000px) 
        div
            height:  60px;
            margin-top: 100px;
            font-size: 32px;
            p 
                line-height: 60px;
            
        
    
    @keyframes slide 
        0%  left: 100%; 
        100%  left: -120%; 
    
</style>
//footer.vue
<template>
    <footer>
        <ul>
            <li>
                <router-link :to="name: 'service', params: type: 15" :class=" pathName == navUrl[0] ? 'active' : ''">
                    <i class="iconfont">&#xe69b;</i><span>景区服务</span>
                </router-link>
            </li>
            <li>
                <router-link to="/home" :class=" pathName == navUrl[1] ? 'active' : ''">
                    <i class="iconfont">&#xe6b8;</i><span>主页</span>
                </router-link>
            </li>
            <li>
                <router-link :to="name: 'service', params: type: 6" :class=" pathName == navUrl[2] ? 'active' : ''">
                    <i class="iconfont">&#xe7f1;</i><span>餐饮住宿</span>
                </router-link>
            </li>
        </ul>
    </footer>
</template>

<script>
    export default 
        data() 
            return 
                isShow: false,
                navUrl : [0, 1, 2]
            
        ,
        props: ['pathName'],
        created() 
        ,
        methods: 
            showNav(state) 
                this.$data.isShow = state ? false : true;
            
        
    
</script>

<style scoped lang="scss">
    footer 
        display: block;
        width: 100%;
        height: 50px;
        position: fixed;
        left: 0;
        bottom: 0;
        z-index: 100;
        color: #000;
        background: #fff;           
        ul 
            height: 100%;
            overflow: hidden;
            li 
                display: inline-block;
                position: relative;
                float: left;
                width: 33.33%;
                height: 100%;
                box-sizing: border-box;
                .iconfont 
                    display: block;
                    margin-top: 4px;
                    font-size: 20px;
                
            
        
        // 菜单栏选中点击样式
        .active 
            i, span 
                color: #e60012;
            
        
        a 
            display: block;
            width: 100%;
            height: 100%;
            font-size: 14px;
            color: #5D656B;
            text-align: center;
        
    

    .slide-fade-enter-active 
      transition: all .3s ease;
    
    .slide-fade-leave-active 
      transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
    
    .slide-fade-enter, .slide-fade-leave-to 
      transform: translate3d(0, 100%, 0);
      opacity: 0;
    

    @media screen and  (min-width: 1000px) 
        footer 
            height: 100px;
            ul 
                li 
                    .iconfont 
                        font-size: 48px;
                    
                
            
            a 
                font-size: 24px;
            
        
    
</style>

技术图片

home.vue引入了这几个组件

//home.vue
<template>
    <div id="main">
        <v-header sideBar="true" :isShowSideBar="isShowSideBar" :vrUrl="vRinfo.jumpUrl" :vrTitle="vRinfo.title" @breadcrumb="showSideBar">
            <span class="header-logo" slot="logo"><img class="logo" :src="logoImgUrl" alt="logo-title"></span>
        </v-header>
        <user-count></user-count>
        <!-- 首页滚动banner -->
        <div class="banner">
            <div class="swiper-container" @click="closeSideBar">
                <div class="swiper-wrapper">
                    <!-- 从后端取数据进行渲染的 -->
                    <div class="swiper-slide" v-for="item in imageDataArr">
                       <img :src="item.INFO_IMAGE_URL" :alt="item.INFO_TITLE">
                    </div>
                </div>
                <!-- 如果需要分页器 -->
                <div class="swiper-pagination swiper-pagination-white"></div>
            </div>
            <nav class="right-side">
                <router-link :to="name: 'service', params: type: 13"><span>预订</span><span>门票</span></router-link>
                <router-link v-if="isApp" :to="name: 'dropBox', params: url: vRinfo.jumpUrl, title: vRinfo.title"><span>虚拟</span><span>旅游</span></router-link>
                <a v-if="!isApp" target="_blank" :href = "vRinfo.jumpUrl"><span>虚拟</span><span>旅游</span></a>
                <a @click="showSideBar"><span>更多</span><span>功能</span></a>
            </nav>
        </div>
        <v-footer :pathName="1"></v-footer>
    </div>
</template>

<script>
import Vue from 'vue';
import vHeader from '../components/header'
import userCount from '../components/userCount'
import vFooter from '../components/footer.vue'
import '../static/lib/js/swiper.min.js'
import '../static/lib/css/swiper.min.css'

export default 
    data() 
        return 
            isApp: false,           // 是否是园区一体机
            isShowSideBar: false,
            imageDataArr: [],       // 首页轮播图
            vRinfo:                // 虚拟旅游
                title: '',
                jumpUrl: ''
            ,
            logoImgUrl: ''
        
    ,
    components: 
        vHeader, userCount,  vFooter
    ,
    created() 

    ,
    mounted() 
        let isApp = window.localStorage ? localStorage.getItem('isApp') : Cookie.read('isApp');
        // 浏览器本地存储是否是一体机
        // 判断本地缓存里面是否已经存在isApp
        if(isApp == 'true') 
            this.logoImgUrl = '../static/logo/logo-red-pc.png';
            this.isApp = true;
         else 
            // 判断是否是第一次进来首页,如果是,则获取params的参数
            this.isApp = this.$route.query && this.$route.query.app;
            if(this.isApp == 'true') 
                // 保存到全局变量中
                if(window.localStorage) 
                    localStorage.setItem('isApp', this.isApp);
                 else 
                    Cookie.wirte('isApp', this.isApp);
                
                this.logoImgUrl = '../static/logo/logo-red-pc.png';
             else 
                this.logoImgUrl = '../static/logo/logo-red-h5.png';
            
        
        this.initPage();
        this.getVRTravel();
    ,
    methods: 
        initPage() 
              let url = `/JSY_H5/h5/queryServiceList?type=1`;
            this.$http.get(url).then((response) => 
                this.imageDataArr = response.data.rows;
                // vue.nextTick在页面初始挂载就要渲染好轮播
                Vue.nextTick(function() 
                    new Swiper('.swiper-container', 
                        autoplay: 10000, 
                        pagination: '.swiper-pagination',
                        loop: true
                    );
                );
            , (response) => 
                console.log('oops, data is not found');                
            );
        ,
        getVRTravel() 
            let url = '/JSY_H5/h5/queryServiceList?type=16';
            this.$http.get(url).then((response) => 
                // 遍历数据,改变数据结构,套用同一天模板listTpl
                this.$data.vRinfo['title'] = response.data.rows[0]['INFO_TITLE'];
                this.$data.vRinfo['jumpUrl'] = response.data.rows[0]['JUMP_URL'];
            );
        ,  
        showSideBar() 
            this.isShowSideBar = !this.isShowSideBar;
            console.log("this.isShowBar",this.isShowSideBar)
        ,
        closeSideBar() 
            this.isShowSideBar = false;
        
    

</script>

<style scoped lang="scss">
    #main 
        font-family: "Microsoft Yahei", 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
    
    .header-logo 
        display: inline-block;
        width: 100%;
        height: 40px;
        box-sizing: border-box;
        .logo 
            height: 100%;
        
    
    .banner 
        $right-side-size: 50px;
        .swiper-container 
            position: fixed;
            left: 0;
            right: 0;
            top: 40px;
            bottom: 50px;
            z-index: 10;
            overflow: hidden;
            .swiper-slide img 
                width: 100%;
                height: 100%;
            
        
        .right-side 
            position: fixed;
            right: 4%;
            bottom: 70px;
            z-index: 10;
            width: $right-side-size;
            a 
                display: inline-block;
                width: $right-side-size;
                height: $right-side-size;
                color: #fff;
                background: #e60012;
                border: 2px solid #fff;
                padding: 5px;
                border-radius: 50%;
                box-shadow: 0 0 10px 0 rgba(0, 0, 0, .5);
                box-sizing: border-box;
                span 
                    display: block;
                    font-size: 14px;
                    height: 18px;
                    line-height: 18px;
                
                &:not(:last-child) 
                    margin-bottom: 10px;
                
            
        
    

    // 适配一体机样式
    @media screen and  (min-width: 1000px) 
        $right-side-size: 150px;
        .header-logo 
            height: 100px;
        
        .banner 
            .swiper-container 
                top: 100px;
                bottom: 100px;
            
             .right-side 
                right: 4%;
                bottom: 50%;
                width: $right-side-size;
                transform: translate3d(0, 50%, 0);
                a 
                    width: $right-side-size;
                    height: $right-side-size;
                    padding: 12px;
                    border: 5px solid #fff;
                    span 
                        font-size: 45px;
                        height: 55px;
                        line-height: 55px;
                    
                
            
        

    
</style>

技术图片

在景区页面中引入了listTpl.vue

//listTpl.vue
<template>
    <div>
        <div class="list-tpl" >
            <ul v-if="!isType" class="list-item">
                <li v-for="item in items">
                    <router-link :to="name: 'listDetail', params: id: item.id, type: type, identifier: identifier">
                        <div class="list-image">
                            <img :src="item.imageUrl">
                        </div>
                        <aside >
                            <h3> item.title </h3>
                            <article> item.description </article>
                        </aside>
                    </router-link>
                </li>
            </ul>

            <ul v-if="isType" class="list-item">
                <li v-for="item in items">
                    <div class="list-image">
                        <img :src="item.imageUrl">
                    </div>
                    <aside >
                        <h3> item.title </h3>
                        <article v-html= "item.description"></article>
                        <a class="jump-url" v-if="!isApp" :href="item.jumpUrl" target="_blank">去预订</a>
                        <a class="jump-url" v-if="isApp" @click="showQRCode(item.qrCode)">去预订</a>
                    </aside>
                </li>
            </ul>
        </div>
        <div v-if="isShowQrBox" id="qrcode" @click="closeQrcodeBox">
            <div class="mask"></div>
            <div id="qrcode-content"></div>
        </div>
    </div>
</template>

<script>
    import Vue from 'vue';
    import '../static/lib/js/jquery.qrcode.min.js';
    export default 
        data() 
            return 
                isApp: false,               // 判断是否使用不同的遍历模块, true => 调出二维码模板
                isType: false,              // 当type = 13 || 14时,显示去预定的模板
                isShowQrBox: false
            
        ,
        props: ['identifier', 'items', 'type'],
        created() 
            // 在listTpl页面中 只在预订门票和特色购物里面调出而二维码
            if(this.type == 13 || this.type == 14)
                this.isType = true;
                // 判断是否是园区一体机
                let isApp = window.localStorage ? localStorage.getItem('isApp') : Cookie.read('isApp');
                if(isApp == 'true') 
                    this.isApp = true;
                
            
        ,
        methods: 
            showQRCode(url) 
                this.isShowQrBox = true;
                jQuery('#qrcode #qrcode-content').empty();
                Vue.nextTick(function() 
                    jQuery('#qrcode #qrcode-content').qrcode(url);
                );
            ,
            // 关闭二维码框
            closeQrcodeBox() 
                this.isShowQrBox = false;
            
        
    
</script>
<style scoped lang="scss">
    .list-tpl 
        margin-top: 45px;
        .list-item 
            margin: 10px auto;
            list-style: none;
            height: 100vh;
            overflow: auto;
            background: #EDEDED;    
            li 
                display: inline-block;
                width: 100%;
                padding: 2%;
                margin-bottom: 10px;
                overflow: hidden;
                box-sizing: border-box;
                background: #fff;
                a 
                    display:inline-block;
                
                .list-image 
                    float: left;
                    width: 30vw;
                    height: 30vw;
                    margin-right: 3vw;
                    box-sizing: border-box;
                    img 
                        width: 100%;
                        height: 100%;   
                    
                
                aside 
                    position: relative;
                    min-height: 30vw;
                    font-size: 14px;
                    text-align: left;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    box-sizing: border-box;
                    h3 
                        color: #CD1940;
                        padding: 2px 0 5px 0;
                        font-size: 16px;
                        font-weight: bold;
                    
                    article 
                        font-size: 14px;
                        color: #000;
                        line-height: 1.4;
                        text-align: justify;
                    
                    .jump-url 
                        position: absolute;
                        right: 0;
                        bottom: 0;
                        width: 60px;
                        height: 30px;
                        line-height: 30px;
                        text-align: center;
                        color: #FFF;
                        background: #e60012;
                        box-sizing: border-box;
                    
                
            
        
    
    #qrcode 
        position: relative;
        .mask 
            position: fixed;
            display: block;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.4);
            z-index: 10;
        
        #qrcode-content 
            position: fixed;
            left: 50%;
            top: 50%;
            padding: 15px;
            text-align: center;
            background: #fff;
            z-index: 100;
            transform: translate3d(-50%, -50%, 0);
            &:after 
                content: '扫一扫上面的二维码图案';
                display: block;
                padding-top: 10px;
            
        
    

    @media screen and (min-width: 1000px) 
        .list-tpl 
            margin-top: 100px;
            .list-item 
                li 
                    aside 
                        h3 
                            font-size: 38px;
                        
                        article 
                            font-size: 32px;
                        
                        .jump-url 
                            position: absolute;
                            right: 0;
                            bottom: 0;
                            width: 120px;
                            height: 60px;
                            line-height: 60px;
                            text-align: center;
                            color: #FFF;
                            font-size: 24px;
                            background: #e60012;
                            box-sizing: border-box;
                        
                    
                
            
        
    
</style>

/#/?app=true

我们其实可以猜测这个组件里面主要是展示景区相关图片,并且还有二维码扫码的功能,用jQuery做的
完整的景区介绍的代码

<template>
    <div>
        <v-header goBack="true" headTitle="景区介绍"></v-header>
        <list-tpl :items="scenicInfo" :type="type"  identifier="1"></list-tpl>
        <loading :show="done"></loading>
    </div>
</template>

<script>
    import vHeader from '../components/header.vue';
    import listTpl from '../components/listTpl.vue';
    import loading from '../components/loading.vue'; 
      
    export default 
      data() 
        return 
            done: false,
            type: '0',      // 这个类型应该是字符串,需要跟路由匹配到
            scenicInfo: []
        
      ,
      components: 
        vHeader, listTpl, loading
      ,
      mounted() 
        // 页面初始化时加载数据
        this.initPage();
      ,
      methods: 
        initPage() 
            this.done = true;
            let url = '/JSY_H5/h5/querySSSList';
            this.$http.get(url).then((response) => 
                // 遍历数据,改变数据结构,套用同一套模板listTpl
                response.data.rows.forEach((item, index) => 
                    let tmp = ;
                    tmp['description'] = item['SS_DESCRIPTION'];
                    tmp['id'] = item['SS_NO'];
                    tmp['imageUrl'] = item['SS_IMAGE_URL'];
                    tmp['title'] = item['SS_TITLE'];
                    this.$data.scenicInfo.push(tmp);
                );
                this.$data.done = false;
            , (response) => 
                this.$data.done = false;
            );
        
      
    
  </script>
//loading.vue
<template>
  <transition>
    <svg class="spinner" :class=" show: show " v-show="show" width="68px" height="68px" viewBox="0 0 44 44">
      <circle class="path" fill="none" stroke-width="4" stroke-linecap="round" cx="22" cy="22" r="20"></circle>
    </svg>
  </transition>
</template>

<script>
  export default 
    props: ['show']
  
</script>

<style lang="scss">
  $offset: 126;
  $duration: 1.4s;
  .spinner 
    position: fixed;
    z-index: 999;
    transition: opacity .15s ease;
    animation: rotator $duration linear infinite;
    animation-play-state: paused;
    right: 50%;
    top: 20%;
    margin-right: -34px;
    &.show 
      animation-play-state: running
    

    &.v-enter, &.v-leave-active 
      opacity: 0;
    

    &.v-enter-active, &.v-leave 
      opacity: 1;
    
  

  @keyframes rotator 
    0% 
      transform: scale(0.5) rotate(0deg);
    
    100% 
      transform: scale(0.5) rotate(270deg);
    
  

  .spinner .path 
    stroke: #42b983;
    stroke-dasharray: $offset;
    stroke-dashoffset: 0;
    transform-origin: center;
    animation: dash $duration ease-in-out infinite;
  

  @keyframes dash 
    0% 
      stroke-dashoffset: $offset;
    
    50% 
      stroke-dashoffset: ($offset/2) transform rotate(135deg);
    
    100% 
      stroke-dashoffset: $offset transform rotate(450deg);
    
  

</style>

技术图片

//src\\page\\service.vue
<template>
    <div>
        <v-header goBack="true" :headTitle="headTitle"></v-header>
        <list-tpl :items="serviceInfo" :type="type"  identifier="2"></list-tpl>
        <v-footer :pathName="index" v-show=" type == 6 || type == 15"></v-footer>
        <loading :show="done"></loading>
    </div>
</template>

<script>
    import vHeader from '../components/header.vue';
    import listTpl from '../components/listTpl.vue';
    import loading from '../components/loading.vue';
    import vFooter from '../components/footer.vue';
    export default 
        data() 
            return 
                type: null,
                done: false,
                index: 0,          // 动态显示footer导航栏显示位置
                serviceInfo: []
            
        ,
        computed: 
            headTitle: function() 
                let type = `$this.type`;
                switch(type) 
                    case '3':
                        type = '景区公告';
                        break;
                    case '6':
                        type = '餐饮住宿';
                        break;
                    case '7':
                        type = '周边景点';
                        break;
                    case '13':
                        type = '预订门票';
                        break;
                    case '14':
                        type = '特色购物';
                        break;
                    case '15':
                        type = '景区服务';
                        break;
                
                return type;
            ,
        ,
        components: 
            vHeader, listTpl, loading, vFooter
        ,
        created() 
            this.type = this.$route.params.type;
       ,
        mounted() 
            // 页面初始化时加载数据
            this.initPage();
        ,
        // 只在当前路由改变,但是该组件被复用时调用
        // to 表示 route即将要进去的路由
        // from 表示 route正要离开的路由
        beforeRouteUpdate(to, from, next) 
            this.type = to.params.type;
            next(this.initPage());          
        ,
        methods: 
            initPage() 
                // 判断footer底部导航栏的显示位置
                if(this.type == 6) 
                    this.index = 2;  
                 else if (this.type == 15) 
                    this.index = 0;
                
                this.$data.serviceInfo = [];   // 初始化数据,防止footer底部导航栏切换数据没有清空
                this.done = true;
                let url = `/JSY_H5/h5/queryServiceList?type=$this.type`;
                this.$http.get(url).then((response) => 
                    // 遍历数据,改变数据结构,套用同一天模板listTpl
                    response.data.rows.forEach((item, index) => 
                        let tmp = ;
                        tmp['description'] = item['INFO_DESCRIPTION'];
                        tmp['id'] = item['INFO_NO'];
                        tmp['imageUrl'] = item['INFO_IMAGE_URL'];
                        tmp['title'] = item['INFO_TITLE'];
                        tmp['qrCode'] = item['QR_CORE_URL'];
                        tmp['jumpUrl'] = item['JUMP_URL'];
                        this.$data.serviceInfo.push(tmp);
                    );
                    this.$data.done = false;
                , (response) => 
                    this.$data.done = false;
                );
            
        
    
  </script>

技术图片

//src\\page\\travelBox.vue
<template>
    <div>
        <v-header goBack="true" headTitle="旅行百宝箱"></v-header>
        <div class="travel-box">
            <section class="item-box">
                <router-link :to="name: 'listDetail', params: id: 5, type: 20, identifier: 0">
                    <i class="iconfont">&#xe656;</i>
                    <span>旅游线路</span>
                </router-link>
                <router-link :to="name: 'externalMap'">
                    <i class="iconfont">&#xe621;</i>
                    <span>外部交通</span>
                </router-link>
                <router-link :to="name: 'listDetail', params: id: 4, type: 21, identifier: 0">
                    <i class="iconfont">&#xe638;</i>
                    <span>景区地图</span>
                </router-link>
                <router-link :to="name: 'service', params: type: 7">
                    <i class="iconfont">&#xe600;</i>
                    <span>周边景点</span>
                </router-link>
                <router-link :to="name: 'service', params: type: 6">
                    <i class="iconfont">&#xe7f1;</i>
                    <span>餐饮,住宿</span>
                </router-link>
            </section>          
        </div>
     </div>
</template>

    <script>
        import vHeader from '../components/header'
        export default 
            data() 
                return 
                
            ,
            components: 
                vHeader
            ,
            mounted() 

            
        
    </script>

    <style scoped lang="scss">
        .travel-box 
            margin-top: 42px;
                height: 100vh;
                background: #F5F5F5;
                a 
                    display: inline-block;
                    width: 32%;
                    height: 100px;
                    margin: 0 2% 2% 0;
                    color: #5D656B;
                    background: #fff;
                    text-align: center;
                    box-sizing: border-box;
                    &:nth-child(3n + 0) 
                        margin-right: 0;
                    
                    i 
                        display: block;
                        font-size: 48px;
                        line-height: 60px;
                        margin-top: 10px;
                    
                    span 
                        font-size: 16px;
                    
                
        
        @media screen and (min-width: 1000px) 
            .travel-box 
                margin-top: 100px;
                a 
                    height: 200px;
                    i 
                        font-size: 64px;
                        line-height: 100px;
                    
                    span 
                        font-size: 24px;
                    
                
            
        
</style>
//listDetail
<template>
    <div class="detail">
        <v-header goBack="true" :headTitle="listDetail.title"></v-header>
        <div class="audio-play" v-if="this.identifier == 1 && listDetail.audio">
            <i v-on:click="playAudio" class="iconfont">&#xe66b;&nbsp;音频播放</i>
            <audio  id="audio" :src="listDetail.audio" loop="true">
                你的浏览器不支持 <code>audio</code> 音频播放功能.
            </audio>
        </div>
        <div class="detail-body" v-show="isShow">
            <section v-html="listDetail.content"></section>
            <review :id="detailId" :qrCodeUrl="qrCodeUrl" v-if="needReview"></review>
        </div>
        <loading :show="done"></loading>
    </div>
</template>

<script>
    import loading from '../components/loading.vue';
    import vHeader from '../components/header.vue';
    import review from '../components/review.vue';
    export default 
        data() 
          return 
            done: false,
            isShow: false,     // 只有当数据加载完成之后才能够实现出来
            needReview: false,  //是否需要显示评论(只有景点才需要,其他的地方都是不需要的,默认关闭)
            detailId: '',
            type: '',          // 判断当前的模块信息
            identifier: '',    // 标识符 景点介绍模块为1 旅行百宝箱模块为0
            shopUrl: '',
            qrCodeUrl: '',      // 二维码的生成地址
            listDetail:       // 详情列表信息
                title: '',
                content: '',
                audio: null
            
          
        ,
        components: 
            loading, vHeader, review
        ,
        created() 
            this.detailId = this.$route.params.id;
            this.identifier =  this.$route.params.identifier || 0;
            this.type = this.$route.params.type;
        ,
        mounted() 
           this.initPage();
        ,
        methods: 
            initPage() 
                let listDetailUrl = '';
                this.$data.done = true;
                if(this.identifier == 1)   
                    listDetailUrl = `/JSY_H5/h5/querySSSOne?id=$this.detailId`;   // 景点介绍调用的接口
                 else if(this.identifier == 2) 
                    listDetailUrl = `/JSY_H5/h5/queryServiceOne?id=$this.detailId`;   // service.vue下面过来调用接口
                 else 
                    listDetailUrl = `/JSY_H5/h5/queryServiceList?type=$this.detailId`;     // 旅游线路,景区地图调用的接口
                

                this.$http.get(listDetailUrl).then((response) => 
                    this.done = false;
                    this.isShow = true;
                    let data = response.data.rows;
                    // 景点介绍
                    if(this.identifier == 1) 
                        this.listDetail.title = data[0].SS_TITLE;
                        this.listDetail.content = data[0].SS_CONTENT;
                        this.listDetail.audio = data[0].SS_VIDEO_URL;
                        this.qrCodeUrl = data[0].QR_CORE_URL;
                        this.needReview = true;
                     else 
                        // 资讯
                        this.listDetail.title = data[0].INFO_TITLE;
                        this.listDetail.content = data[0].INFO_CONTENT;
                        this.needReview = false;
                    
                    // 由于后台传过来是一段字符串 需要使用正则来适配一体机文字大小
                    let isApp = window.localStorage ? localStorage.getItem('isApp') : Cookie.read('isApp');
                    if(isApp == 'true') 
                        this.listDetail.content = this.listDetail.content.replace(/font-size:\\s*\\d+px;/g, 'font-size: 32px;');
                    
                , (response) => 
                    console.log('opps Is Error: ' + response);
                    this.done = false;
                )
            ,
            playAudio() 
                let audio = document.getElementById('audio');
                var isPlaying = audio.currentTime > 0 && !audio.paused && !audio.ended 
                    && audio.readyState > 2;

                if (!isPlaying) 
                  audio.load(); 
                  audio.play();
                
            
        
    
</script>

<style lang="scss">
  .detail 
    padding-top: 40px;  // 移除头部header的高度
    .audio-play 
        width: 100%;
        color: #fff;
        padding: 1% 4%;
        text-align: right;
        background: #000;
        opacity: .4;
        box-sizing: border-box;
        i 
            display: inline-block;
            width: 100px;
            height: 30px;
            line-height: 30px;
        
    
    .detail-body 
        padding: 10px 10px 40px 10px ;
        section 
            width:100%;
            img 
                width:100%;
            
        
    
    footer 
        position: absolute;
        right: 0;
        bottom: 0;
        width: 100%;
        height: 40px;
        background: #f6f6f6;
        text-align: right;
        a 
            display: inline-block;
            width: 80px;
            height: 40px;
            line-height: 40px;
            padding: 2px;
            color: #FFF;
            text-align: center;
            background: #e60012;
            box-sizing: border-box;
        
    
  
  @media screen and (min-width: 1000px) 
    .detail 
        padding-top: 100px;
        .audio-play 
            i 
                font-size: 32px;
                width: 200px;
                height: 50px;
                line-height: 50px;
            
        
        footer 
            height: 100px;
        
    

</style>

这个组件中引用了review组件

<template>
    <div>
        <div class="reviews" v-show="isShow">
            <ul>
                <li><i class="iconfont">&#xe73d;</i> visitCount </li>
                <li @click.once="upVote" :class="active: isActive "><i class="iconfont">&#xe644;</i> goodCount </li>
                <li @click="showReviewBox"><i class="iconfont">&#xe761;</i>写评论</li>
                <li @click="showCommentBox"><i class="iconfont">&#xe649;</i> reviewCount </li>
            </ul>
        </div>
        <transition name="slide-fade-down">
            <div v-show="isShowReviewBox" class="reviews-box">
                <div class="header">
                    <span @click="closeReviewBox">取消</span>
                    <span>评论</span>
                    <span @click="addReview" :class="isSend">发送</span>
                </div>
                <div class="body">
                    <textarea v-model="reviewContent" autofocus maxlength="120" required></textarea>
                </div>
            </div>
        </transition>
        <transition name="slide-fade-right">
        <div v-show="isShowCommentBox" class="comment-box">
            <div @click.stop.prevent="closeCommentBox" class="mask"></div>
            <div class="comment-main">
                <section v-for="(item, index) in reviewData">
                    <div class="reviews-author"><span>游客</span></div>
                    <div class="reviews-body">
                        <p class="reviews-content"> item.SSR_CONTENT </p>
                        <p> item.ENTRY_DATE_TIME  | time</p>
                    </div>
                </section>
            </div>
        </div>
        </transition>
        <div v-if="isShowQrBox" id="qrcode" @click="closeQrcodeBox">
            <div class="mask"></div>
            <div id="qrcode-content"></div>
        </div>
        <div v-if="isShowTipBox" class="tip-box">
            您的评论已经提交,请等待审核通过...
        </div>
    </div>
</template>

<script>
    import Vue from 'vue';
    import '../static/lib/js/jquery.qrcode.min.js'
    export default 
        data() 
            return 
                isShow: true,               
                SS_NO: this.id,             // 当前的景点id
                isActive: false,
                reviewData: [],             // 评论数
                visitCount: 0,              // 访问数
                goodCount: 0,               // 点赞数
                reviewCount: 0,             // 评论数
                reviewContent: '',          // 评论内容
                isShowReviewBox: false,     // 是否显示评论框
                isShowCommentBox: false,     // 是否显示评论列表
                isShowQrBox: false,          // 是否显示二维码
                isShowTipBox: false           // 是否显示评论成功提示框
            
        ,
        props: ['id', 'qrCodeUrl'],
        mounted() 
            this.initPage();
        ,
        computed: 
            isSend: function () 
                return 
                    active: !!this.$data.reviewContent.length
                   
            
        ,
        filters: 
            // 格式化时间
            time: function(date) 
                if(!date) return '';
                var date = new Date(date);
                var Y = date.getFullYear() + '-';
                var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
                var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
                    return Y + M + D;
            
        ,
        methods: 
            // 评分
            showRate(rate) 
                if(!rate) rate = 5;
                return "★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);
            ,
            // 判断是否显示评论界面
            showReviewBox() 
                let isApp = window.localStorage ? localStorage.getItem('isApp') : Cookie.read('isApp');
                if(isApp == 'true')            // 如果当前是一体机访问 则无法添加评论 调出二维码
                    let url = this.qrCodeUrl;
                    this.$data.isShowReviewBox = false;
                    this.isShowQrBox = true;
                    jQuery('#qrcode #qrcode-content').empty();
                    Vue.nextTick(function() 
                        jQuery('#qrcode #qrcode-content').qrcode(url);          // 使用ES6来进行字符串转义
                    );
                 else 
                    this.$data.isShowReviewBox = !this.$data.isShowReviewBox;
                
            ,
            showCommentBox() 
                if(this.reviewCount == 0) return false;     // 如果当前的评论数为0 则不显示评论列表
                this.$data.isShowCommentBox = !this.$data.isShowCommentBox;
            ,
            // 关闭评论界面
            closeReviewBox() 
                this.$data.isShowReviewBox = false;
            ,
            closeCommentBox() 
                this.$data.isShowCommentBox = false;
            ,
            // 添加评论
            addReview() 
                let url = `/JSY_H5/h5/saveSSR`;
                this.$http.post(url, 
                    SS_NO: this.$data.SS_NO,
                    SSR_CONTENT: this.$data.reviewContent
                ).then( (response) => 
                    this.closeReviewBox();
                    this.reviewContent = '';
                    this.isShowTipBox = true;
                    // 需要使用箭头函数来邦定this的值
                    setTimeout(() => 
                        this.isShowTipBox = false;
                    , 1000);
                , (response) => 
                    console.log('opps Is Error: ' + response);
                )
            ,
            initPage() 
                let url = `/JSY_H5/h5/querySSRList?id=$this.$data.SS_NO`;
                this.$http.get(url).then((response) => 
                    this.$data.reviewData = response.data.rows;
                , (response) => 
                    console.log('opps Is Error: ' + response);
                );
                this.getUserVisit();    // 获取评论接口中 访问量和点赞数
            ,
            // 获取当前景点的页面访问量点赞数以及评论数
            getUserVisit() 
                let url = `/JSY_H5/h5/addInteractive?id=$this.$data.SS_NO`;  // 游客访问量
                this.$http.get(url).then((response) => 
                    this.$data.goodCount = response.data.GOODED_COUNT;
                    this.$data.visitCount = response.data.LOOKED_COUNT;
                    this.$data.isActive = response.data.IS_GOODED;
                    this.$data.reviewCount = response.data.REVIEW_COUNT;
                , (response) => 
                    console.log('opps Is Error: ' + response);
                );
            ,
            // 添加点赞
            upVote() 
                let url = `/JSY_H5/h5/addInteractive?id=$this.$data.SS_NO&ACTION="good"`;  // 当前景点-点赞数
                this.$http.get(url).then((response) => 
                    this.$data.goodCount = response.data.GOODED_COUNT;
                    this.$data.isActive = true;
                , (response) => 
                    console.log('opps Is Error: ' + response);
                );
            ,
            // 关闭二维码框
            closeQrcodeBox() 
                this.isShowQrBox = false;
            
        
       
</script>


<style scoped lang="scss">
    /* 底部详情操作框 */
    .reviews 
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        height: 60px;
        z-index: 100;
        background: #F6F6F6;
        ul  
            padding: 0 5%;
            li 
                display: inline-block;
                width: 25%;
                height: 30px;
                line-height: 30px;
                margin: 15px 4% 0 0;
                text-align: center;
                border-radius: 10%;
                background: #fff;
                box-sizing: border-box;
                i 
                    margin-right: 5px;
                
                &:last-child 
                    color: #e60012;
                    width: 13%;
                    margin-right: 0;
                
            
        
        /* 选中样式 */
        .active 
            color: #e60012;
            pointer-events: none;
        
    
    /* 评论框基本样式 */
    .reviews-box 
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 200;
        width: 100%;
        height: 100px;
        padding: 2% 8% 5%; 
        background: #F6F6F6;
        box-sizing: border-box;
        .header 
            width: 100%;
            padding-bottom: 5px;
            text-align: center;
            span 
                color: #000;
                &:first-child 
                    float: left;
                
                &:last-child 
                    float: right;
                    color: #333;
                    pointer-events: none;
                    &.active 
                        pointer-events: auto;
                        color: #e60012;
                    
                
            
        
        /* 用户编辑框 */
        .body 
            textarea 
                min-height: 40px;
                width: 100%;
                padding: 2%;
                font-size: 14px;
                border-radius: 5%;
                border: 2px solid #F6F6F6;
                background: #fff;
                box-sizing: border-box;
                resize: none;
                box-shadow: none;
            
        
    
    /* 动画效果 */
    .slide-fade-down-enter-active, .slide-fade-down-leave-active  
        transition: all 1s ease-in;
    
    .slide-fade-down-enter, .slide-fade-down-leave-to
        transform: translate3d(0, 100px, 0);
    

    .slide-fade-right-enter-active, .slide-fade-right-leave-active  
        transition: all 1s ease-in;
    
    .slide-fade-right-enter, .slide-fade-right-leave-to
        transform: translate3d(100%, 0, 0);
    
    .tip-box 
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate3d(-50%, -50%, 0);
        width: 200px;
        height: 100px;
        font-size: 18px;
        text-align: justify;
        padding: 10px 20px;
        background: #fff;
        box-shadow: 1px 1px 10px rgba(0, 0, 0, .5);
        box-sizing: border-box;
    
    /* 右侧评论列表 */
    .comment-box 
        position: fixed;
        top: 40px;
        bottom: 50px;
        right: 0;
        width: 80%;
        overflow-y: auto;
        background: #F6F6F6;
        .mask 
            position: fixed;
            display: block;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            background: rgba(0, 0, 0, .5);
            z-index: 100;
        
        .comment-main 
            position: relative;
            z-index: 100;
            section 
                min-height: 80px;
                padding: 2%;
                background: #fff;
                overflow: hidden;
                text-align: left;
                font-size: 14px;
                border-bottom: 2px solid #F6F6F6;
                box-sizing: border-box;
                .reviews-author 
                    float: left;
                    width: 25%;
                    height: 80px;
                    padding-left: 4%;
                    margin-right: 2%;
                    color: #333;
                    box-sizing: border-box;
                
                .reviews-body 
                    min-height: 80px;
                    overflow: hidden;
                    .reviews-content 
                        min-height: 40px;
                    
                    p 
                        &:first-child span 
                            color: #e60012;
                        
                        &:last-child 
                            font-size: 12px;
                        
                    
                
            
        
    
    @media screen and  (min-width: 1000px) 
        .comment-box 
            top: 100px;
            bottom: 60px;
            .comment-main 
                section 
                    font-size: 32px;
                    .reviews-body 
                        p 
                            &:last-child 
                                font-size: 24px;
                            
                        
                    
                
            
        
    
    #qrcode 
        position: relative;
        .mask 
            position: fixed;
            display: block;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.4);
            z-index: 10;
        
        #qrcode-content 
            position: fixed;
            left: 50%;
            top: 50%;
            padding: 15px;
            text-align: center;
            background: #fff;
            z-index: 100;
            transform: translate3d(-50%, -50%, 0);
            &:after 
                content: '扫一扫上面的二维码图案';
                display: block;
                padding-top: 10px;

            
        
    
</style>
//src\\page\\externalMap.vue
<template>
    <div>
        <v-header goBack="true" headTitle="外部地图"></v-header>
        <div class="travel-box">
            <div id="allmap"></div>
        </div>

    </div>
</template>

<script>
    import vHeader from '../components/header'
    export default 
        data() 
            return 
            
        ,
        components: 
            vHeader
        ,
        mounted() 

            //百度地图API功能
            var map = new BMap.Map("allmap");    // 创建Map实例
            map.centerAndZoom(new BMap.Point(119.199201,34.019519), 18);  // 初始化地图,设置中心点坐标和地图级别
            map.addControl(new BMap.MapTypeControl());   //添加地图类型控件
            map.setCurrentCity("淮安国缘宾馆");          // 设置地图显示的城市 此项是必须设置的
            map.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放
            // 编写自定义函数,创建标注
            function addMarker(point)
              var marker = new BMap.Marker(point);
              map.addOverlay(marker);
            
            // 向指定地图里面添加标注
            addMarker(new BMap.Point(119.199201,34.019519));
        
    
</script>

<style scoped lang="scss">
    .travel-box 
        margin-top: 60px;
    
    #allmap 
        width: 100%;
        height: 400px;
    
    @media screen and (min-width: 1000px) 
        .travel-box 
            margin-top: 100px;
        
        #allmap 
            height: 600px;
        
    
</style>

后记:我挺喜欢这个项目的代码的,哈哈哈,因为都看得懂,还能够猜到作者的意图。

以上是关于vuevue-znly的主要内容,如果未能解决你的问题,请参考以下文章