微信小程序demo

3,574 阅读11分钟

前言

  闲来无事,笔者利用业余时间学习了下微信小程序,并且做了一个demo,类似豆瓣的相关功能,本篇主要根据网页的跳转讲解该demo的运行过程,重点在数据的传输而非css的讲解,对于想要在尽量短的时间掌握微信小程序常用用法的童鞋会比较有用

准备工作

  申请账号,获取appId, 开发工具需要appId才能使用   

 开发工具下载地址developers.weixin.qq.com/miniprogram…

正文

  源码地址:github.com/qw870602/co…

  使用工具打开项目,我们会看到如下页面

项目结构截图


data中的文件用来模拟数据库,images顾名思义主要放图片,pages包含所有的页面,utils主要是工具类,app.js是给全局变量赋值的文件,app.wxss是设置全局的css样式,我们看到该文件对全局的<text>标签和<input>标签的样式预设置了一些属性,app.json是入口文件,换言之,我们从app.json这个文件开始看起

{
    "pages": [
    "pages/welcome/welcome",
    "pages/posts/post",
    "pages/movies/movies",
    "pages/movies/movie-detail/movie-detail",
    "pages/posts/post-detail/post-detail",
    "pages/movies/more-movie/more-movie"
],
    "window": {
    "navigationBarBackgroundColor": "#405f80"
},
    "tabBar": {
    "borderStyle": "white",
        "position": "bottom",
        "list": [
        {
            "pagePath": "pages/posts/post",
            "text": "阅读",
            "iconPath": "images/tab/yuedu.png",
            "selectedIconPath": "images/tab/yuedu_hl.png"
        },
        {
            "pagePath": "pages/movies/movies",
            "text": "电影",
            "iconPath": "images/tab/dianying.png",
            "selectedIconPath": "images/tab/dianying_hl.png"
        }
    ]
}
}

"pages":[] 包含了本demo所有的页面路径,其中最上面的路径就是我们打开项目看到的欢迎页

window中的navigationBarBackgroundColor是导航栏背景颜色,这个型号代表深蓝色,这里并不在首页显示,下面的tarBar是设置底部tab栏的属性

小程序首先找到app.json,然后去找首页welcome, welcome页面的主体由以下4个文件组成


wxml可以类比为html, js主要用来捕捉事件做页面跳转,wxss类似css ,json主要用来做一些配置

我们看一下wxml的内容

<view class="container">
    <image class="avatar" src="/images/avatar/1.png"></image>
    <text class="motto">Hello, 七月</text>
    <view class="journey-container" bindtap="onTap">
      <text class="journey">开启小程序之旅</text>
    </view>
</view>

所有内容包裹在container中,在wxss中设置了背景色为浅蓝色,在json中设置了导航栏的颜色也为浅蓝色,故我们看到整个背景是浅蓝色,至于图片和文字的位置,样式的css实现,读者可自行研究,点击 “开启小程序之旅” ,通过bindTap组件触发onTab函数,再调用wx.switchTab跳转到post页面

页面效果


post.wxml

<import src="post-item/post-item-template.wxml" />
<view>
    <swiper catchtap="onSwiperTap" vertical="{{false}}" indicator-dots="true" autoplay="true" interval="5000">
        <swiper-item>
            <image id="7" src="/images/wx.png" data-postId="3"></image>
        </swiper-item>
        <swiper-item>
            <image src="/images/vr.png" data-postId="4"></image>
        </swiper-item>
        <swiper-item>
            <image src="/images/iqiyi.png" data-postId="5"></image>
        </swiper-item>
    </swiper>
    <block wx:for="{{postList}}" wx:for-item="item" wx:for-index="idx">
        <!--//template-->
        <view catchtap="onPostTap" data-postId="{{item.postId}}">
            <template is="postItem" data="{{...item}}"/>
        </view>
    </block>
</view>

最外层被view包裹,第一部分的swiper是轮播图组件,下面的block其实就是获取最上面import的集合数据,底层的tab栏其实就是之前app.json中设置的,因为我们跳转的是post, 然后阅读的pagePath设置的就是post,所以底层 “阅读” tab栏就是被选中的状态,轮播图的组件属性不再介绍,可参照api文档

点击轮播图中的任意一张


其流程是通过catchTap组件捕捉到点击事件,然后调用post.js的onSwiperTap方法跳转到post-detail页面并顺带将被点击页面的id传过去

post-detail.wxml

<!--先静后动,先样式再数据-->
<view class="container">
    <image class="head-image" src="{{isPlayingMusic?postData.music.coverImg:postData.headImgSrc}}"></image>
    <image catchtap="onMusicTap" class="audio" src="{{isPlayingMusic? '/images/music/music-stop.png': '/images/music/music-start.png'}}"></image>
    <view class="author-date">
        <image class="avatar" src="{{postData.avatar}}"></image>
        <text class="author">{{postData.author}}</text>
        <text class="const-text">发表于</text>
        <text class="date">{{postData.dateTime}}</text>
    </view>
    <text class="title">{{postData.title}}</text>
    <view class="tool">
        <view class="circle-img">
            <image wx:if="{{collected}}" catchtap="onColletionTap"  src="/images/icon/collection.png"></image>
            <image wx:else catchtap="onColletionTap" src="/images/icon/collection-anti.png"></image>
            <image catchtap="onShareTap"  class="share-img" src="/images/icon/share.png"></image>
        </view>
        <view class="horizon"></view>
    </view>
    <text class="detail">{{postData.detail}}</text>
</view>

我们看到wxml中的数据很多都是通过 {{  }} 动态获取的,我们知道传了1个id值过来,那么是怎么根据id值获取详情信息的呢?答案在post-detail.js中

post-detail.js ,有点长,可跳过,需要看时再回头查

var postsData = require('../../../data/posts-data.js')
var app = getApp();
Page({
    data: {
        isPlayingMusic: false
    },
    onLoad: function (option) {
        var postId = option.id;
        this.data.currentPostId = postId;
        var postData = postsData.postList[postId];
        this.setData({
            postData: postData
        })

        var postsCollected = wx.getStorageSync('posts_collected')
        if (postsCollected) {
            var postCollected = postsCollected[postId]
            if (postCollected){
                this.setData({
                    collected: postCollected
                })
            }
        }
        else {
            var postsCollected = {};
            postsCollected[postId] = false;
            wx.setStorageSync('posts_collected', postsCollected);
        }

        if (app.globalData.g_isPlayingMusic && app.globalData.g_currentMusicPostId
            === postId) {
            this.setData({
                isPlayingMusic: true
            })
        }
        this.setMusicMonitor();
    },

    setMusicMonitor: function () {
        //点击播放图标和总控开关都会触发这个函数
        var that = this;
        wx.onBackgroundAudioPlay(function (event) {
            var pages = getCurrentPages();
            var currentPage = pages[pages.length - 1];
            if (currentPage.data.currentPostId === that.data.currentPostId) {
                // 打开多个post-detail页面后,每个页面不会关闭,只会隐藏。通过页面栈拿到到
                // 当前页面的postid,只处理当前页面的音乐播放。
                if (app.globalData.g_currentMusicPostId == that.data.currentPostId) {
                    // 播放当前页面音乐才改变图标
                    that.setData({
                        isPlayingMusic: true
                    })
                }
                // if(app.globalData.g_currentMusicPostId == that.data.currentPostId )
                // app.globalData.g_currentMusicPostId = that.data.currentPostId;
            }
            app.globalData.g_isPlayingMusic = true;

        });
        wx.onBackgroundAudioPause(function () {
            var pages = getCurrentPages();
            var currentPage = pages[pages.length - 1];
            if (currentPage.data.currentPostId === that.data.currentPostId) {
                if (app.globalData.g_currentMusicPostId == that.data.currentPostId) {
                    that.setData({
                        isPlayingMusic: false
                    })
                }
            }
            app.globalData.g_isPlayingMusic = false;
            // app.globalData.g_currentMusicPostId = null;
        });
        wx.onBackgroundAudioStop(function () {
            that.setData({
                isPlayingMusic: false
            })
            app.globalData.g_isPlayingMusic = false;
            // app.globalData.g_currentMusicPostId = null;
        });
    },

    onColletionTap: function (event) {
        // this.getPostsCollectedSyc();
        this.getPostsCollectedAsy();
    },

    getPostsCollectedAsy: function () {
        var that = this;
        wx.getStorage({
            key: "posts_collected",
            success: function (res) {
                var postsCollected = res.data;
                var postCollected = postsCollected[that.data.currentPostId];
                // 收藏变成未收藏,未收藏变成收藏
                postCollected = !postCollected;
                postsCollected[that.data.currentPostId] = postCollected;
                that.showToast(postsCollected, postCollected);
            }
        })
    },

    getPostsCollectedSyc: function () {
        var postsCollected = wx.getStorageSync('posts_collected');
        var postCollected = postsCollected[this.data.currentPostId];
        // 收藏变成未收藏,未收藏变成收藏
        postCollected = !postCollected;
        postsCollected[this.data.currentPostId] = postCollected;
        this.showToast(postsCollected, postCollected);
    },

    showModal: function (postsCollected, postCollected) {
        var that = this;
        wx.showModal({
            title: "收藏",
            content: postCollected ? "收藏该文章?" : "取消收藏该文章?",
            showCancel: "true",
            cancelText: "取消",
            cancelColor: "#333",
            confirmText: "确认",
            confirmColor: "#405f80",
            success: function (res) {
                if (res.confirm) {
                    wx.setStorageSync('posts_collected', postsCollected);
                    // 更新数据绑定变量,从而实现切换图片
                    that.setData({
                        collected: postCollected
                    })
                }
            }
        })
    },

    showToast: function (postsCollected, postCollected) {
        // 更新文章是否的缓存值
        wx.setStorageSync('posts_collected', postsCollected);
        // 更新数据绑定变量,从而实现切换图片
        this.setData({
            collected: postCollected
        })
        wx.showToast({
            title: postCollected ? "收藏成功" : "取消成功",
            duration: 1000,
            icon: "success"
        })
    },

    onShareTap: function (event) {
        var itemList = [
            "分享给微信好友",
            "分享到朋友圈",
            "分享到QQ",
            "分享到微博"
        ];
        wx.showActionSheet({
            itemList: itemList,
            itemColor: "#405f80",
            success: function (res) {
                // res.cancel 用户是不是点击了取消按钮
                // res.tapIndex 数组元素的序号,从0开始
                wx.showModal({
                    title: "用户 " + itemList[res.tapIndex],
                    content: "用户是否取消?" + res.cancel + "现在无法实现分享功能,什么时候能支持呢"
                })
            }
        })
    },

    onMusicTap: function (event) {
        var currentPostId = this.data.currentPostId;
        var postData = postsData.postList[currentPostId];
        var isPlayingMusic = this.data.isPlayingMusic;
        if (isPlayingMusic) {
            wx.pauseBackgroundAudio();
            this.setData({
                isPlayingMusic: false
            })
            // app.globalData.g_currentMusicPostId = null;
            app.globalData.g_isPlayingMusic = false;
        }
        else {
            wx.playBackgroundAudio({
                dataUrl: postData.music.url,
                title: postData.music.title,
                coverImgUrl: postData.music.coverImg,
            })
            this.setData({
                isPlayingMusic: true
            })
            app.globalData.g_currentMusicPostId = this.data.currentPostId;
            app.globalData.g_isPlayingMusic = true;
        }
    },

    /*
     * 定义页面分享函数
     */
    onShareAppMessage: function (event) {
        return {
            title: '离思五首·其四',
            desc: '曾经沧海难为水,除却巫山不是云',
            path: '/pages/posts/post-detail/post-detail?id=0'
        }
    }

})

实际上这里执行的onload函数,通过传进去的id获取到对应的对象放在postData中

下面的postCollected部分主要用来控制收藏按钮的显示,如果初始状态为未收藏,那么就不对collected赋值,wxml中collected如果没有值的话会被认为是false,那么按钮就显示为未收藏的状态

再下面的app.globalData.g_isPlayingMusic && app.globalData.g_currentMusicPostId === postId  显然是不成立的,因为app.js中设置了globalData.g_isPlayingMusic出事状态为false。虽然会执行setMusicMonitor函数,但是执行进去之后并不会进入到设置isPlayingMusic这个变量值的模块里面去,所以页面是没有播放音乐的

head-image 是背景图片,随着是否播放音乐变化,audio是背景图片上的播放图标

我们点击一下播放按钮,看看是什么效果


我们发现头部的背景图片变成了谭咏麟的图片,中间的播放按钮变成了正在播放的状态,屏幕正中间弹出了1个音乐播放器,内部执行了哪些程序呢?

其实是先被catchTap组件捕获,然后调用onMusicTap方法,执行了这部分代码

else {
   wx.playBackgroundAudio({
   dataUrl: postData.music.url,
   title: postData.music.title,
   coverImgUrl: postData.music.coverImg,
  })
   this.setData({
     isPlayingMusic: true
  })
   app.globalData.g_currentMusicPostId = this.data.currentPostId;
   app.globalData.g_isPlayingMusic = true;
}

然后再次跳转到这个页面

再次点击播放按钮,暂停播放


再次触发onMusicTap方法,实际上主要执行了这部分代码

if (isPlayingMusic) {
   wx.pauseBackgroundAudio();
   this.setData({
   isPlayingMusic: false
  })
   // app.globalData.g_currentMusicPostId = null;
   app.globalData.g_isPlayingMusic = false;
}

我们看一下收藏功能的实现,点击收藏

首先弹出一个提示框,提示收藏成功,然后红框部分由浅色变为深蓝色标识已经收藏,每次点击会触发onColletionTap方法,该方法每次会将属性collected的值取反,并且根据postCollected的值提示一段文字

再看一下分享功能的实现

点击 “分享”,效果如图


触发onShareTap函数

onShareTap: function (event) {
    var itemList = [
        "分享给微信好友",
        "分享到朋友圈",
        "分享到QQ",
        "分享到微博"
    ];
    wx.showActionSheet({
        itemList: itemList,
        itemColor: "#405f80",
        success: function (res) {
            // res.cancel 用户是不是点击了取消按钮
            // res.tapIndex 数组元素的序号,从0开始
            wx.showModal({
                title: "用户 " + itemList[res.tapIndex],
                content: "用户是否取消?" + res.cancel + "现在无法实现分享功能,什么时候能支持呢"
            })
        }
    })
}

点击 “分享到朋友圈”


提示框展示的内容实际上是wx.showModal的内容,因为我们并没有真的实现分享功能,所以这里点击 “取消” 或 “确定” 都会回到原界面

点击左上角的回退箭头回到主界面


点击 “正是虾肥蟹壮时”,效果如下


我们发现和点击头部轮播图的效果是一样的,这里又是怎么实现的呢?

在post.wxml中有这一部分

<block wx:for="{{postList}}" wx:for-item="item" wx:for-index="idx">
    <!--//template-->
    <view catchtap="onPostTap" data-postId="{{item.postId}}">
    <template is="postItem" data="{{...item}}"/>
    </view>
</block>

实际上这里的点击触发的的是onPostTab函数,和之前的onSwiperTap是一个效果

onPostTap: function (event) {
    var postId = event.currentTarget.dataset.postid;
    // console.log("on post id is" + postId);
    wx.navigateTo({
        url: "post-detail/post-detail?id=" + postId
    })
}

<template>其实是微信定义的一个模板组件,模板的样式由顶部的导入语句

<import src="post-item/post-item-template.wxml" />  就可以推断定义在post-item/post-item-template.wxml 这个文件中

我们再回到主界面,然后点击底部tab栏的 “电影“

我们看一下movies.wxml

<import src="movie-list/movie-list-template.wxml" />
<import src="movie-grid/movie-grid-template.wxml" />
<view class="search">
    <icon type="search" class="search-img" size="13" color="#405f80"></icon>
    <input type="text" placeholder="血战钢锯岭、你的名字"
           placeholder-class="placeholder" bindfocus="onBindFocus"
           bindconfirm="onBindConfirm"/>
    <image wx:if="{{searchPanelShow}}" src="/images/icon/xx.png" class="xx-img" catchtap="onCancelImgTap"></image>
</view>
<view class="container" wx:if="{{containerShow}}">
    <view class="movies-template">
        <template is="movieListTemplate" data="{{...inTheaters}}" />
    </view>

    <view class="movies-template">
        <template is="movieListTemplate" data="{{...comingSoon}}" />
    </view>
    <view class="movies-template">
        <template is="movieListTemplate" data="{{...top250}}"/>
    </view>
</view>

<view class="search-panel" wx:if="{{searchPanelShow}}">
    <template is="movieGridTemplate" data="{{...searchResult}}"/>
</view>

一共3大部分,搜索栏search,主体内容container,其中包括正在上映,即将上映,top250三大内容,搜索面板search-panel。观察一下movies.js,看看传了哪些值进来

data: {
    inTheaters: {},
    comingSoon: {},
    top250: {},
    searchResult: {},
    containerShow: true,
    searchPanelShow: false,
},

onLoad: function (event) {
    var inTheatersUrl = app.globalData.doubanBase +
        "/v2/movie/in_theaters" + "?start=0&count=3";
    var comingSoonUrl = app.globalData.doubanBase +
        "/v2/movie/coming_soon" + "?start=0&count=3";
    var top250Url = app.globalData.doubanBase +
        "/v2/movie/top250" + "?start=0&count=3";

    this.getMovieListData(inTheatersUrl, "inTheaters", "正在热映");
    this.getMovieListData(comingSoonUrl, "comingSoon", "即将上映");
    this.getMovieListData(top250Url, "top250", "豆瓣Top250");
}

从这里看出searchPanelShow为false,所以第三部分是不显示的,于是就有了我们看到的效果

点击搜索栏,输入 ”肖申克“ ,然后点击搜索


我们看一下点击搜索时发生了什么

onBindFocus: function (event) {
    this.setData({
        containerShow: false,
        searchPanelShow: true
    })

},

onBindConfirm: function (event) {
    var text = event.detail.value;
    var searchUrl = app.globalData.doubanBase + "/v2/movie/search?q=" + text;
    this.getMovieListData(searchUrl, "searchResult", "");
}

主题内容container不再显示,搜索结果数据放入searchResult,并且以serach-panel的模板显示出来

回到电影主界面,点击正在热映的 ”更多“


执行代码

onMoreTap: function (event) {
    var category = event.currentTarget.dataset.category;
    wx.navigateTo({
        url: "more-movie/more-movie?category=" + category
    })
}

跳转到了 more-movie页面,再次执行more-movie.js

data: {
    movies: {},
    navigateTitle: "",
        requestUrl: "",
        totalCount: 0,
        isEmpty: true,
},
onLoad: function (options) {
    var category = options.category;
    this.data.navigateTitle = category;
    var dataUrl = "";
    switch (category) {
        case "正在热映":
            dataUrl = app.globalData.doubanBase +
                "/v2/movie/in_theaters";
            break;
        case "即将上映":
            dataUrl = app.globalData.doubanBase +
                "/v2/movie/coming_soon";
            break;
        case "豆瓣Top250":
            dataUrl = app.globalData.doubanBase + "/v2/movie/top250";
            break;
    }
    wx.setNavigationBarTitle({
        title: this.data.navigateTitle
    })
    this.data.requestUrl = dataUrl;
    util.http(dataUrl, this.processDoubanData)
}

这里相当于又去豆瓣重新查了一下

回到电影主界面,我们随意点击一部影片,比如 ”后来的我们“


从movie-template.wxml中我们发现点击影片后会执行onMovieTap方法,这个方法在movies.js中

onMovieTap:function(event){
    var movieId = event.currentTarget.dataset.movieid;
    wx.navigateTo({
        url: "movie-detail/movie-detail?id="+movieId
    })
}

跳转到movie-detail.wxml

点击右上角的电影图片,有1个放大的效果


执行代码

/*查看图片*/
viewMoviePostImg: function (e) {
    var src = e.currentTarget.dataset.src;
    wx.previewImage({
        current: src, // 当前显示图片的http链接
        urls: [src] // 需要预览的图片http链接列表
    })
}

我们看到其实这个大图是从豆瓣拿的