微信小程序開發: 《超級顏值計算器》小程序開發實戰

本文將對微信小程序從開始開發到發佈上線的詳細過程進行講解,歡迎大家加入微信羣交流討論,點擊獲取入羣二維碼

《超級顏值計算器》小程序源碼託管在Github上,可自行clone修改: https://github.com/dreamans/BeautyCalculator

開發前的準備工作

開發前準備工作請參考以下文章:

微信小程序開發: 開發前準備工作
微信小程序開發: 小程序源文件結構及含義

小程序預覽

小程序名稱:《超級顏值計算器》
小程序碼:
這裏寫圖片描述

UI預覽:
這裏寫圖片描述
這裏寫圖片描述

賬號申請

註冊小程序

註冊小程序, 註冊成功後進入郵箱查找激活郵件,如實填寫相關信息,如下:

這裏寫圖片描述
這裏寫圖片描述

設置小程序信息

註冊成功後會進入小程序發佈流程,按要求設置小程序相關信息即可,如下:

這裏寫圖片描述

AppID&&AppSecret

設置 - 開發設置 中獲取小程序的AppID和AppSecret

其他接口申請

小程序依賴face++的圖像面部識別接口,小夥伴可到其官網上自行申請賬號,申請地址接口文檔地址

註冊成功後登錄face++的Console平臺,進入應用管理 - API Key 獲取接口所需的key和secret。

這裏寫圖片描述

有一點問題需要注意,免費版用戶被限制了調用併發量,會經常出現 CONCURRENCY_LIMIT_EXCEEDED 併發數超過限制 錯誤。

開始開發

開發者工具

運行 開發者工具, 選擇 小程序項目,填寫項目目錄、AppID(mp平臺中獲取)、項目名稱等。

這裏寫圖片描述

小程序配置文件

在根目錄下創建小程序配置文 app.json,並配置相關信息

小程序頁面列表

每一項代表一個頁面,第一項代表程序的初始頁面,此處我們設置了兩個頁面,分別是:

pages/index/index 程序主頁面
pages/reward/reward 程序打賞頁面

"pages": [
    "pages/index/index",
    "pages/reward/reward"
],

window 設置

接下來我們來設置小程序的導航欄樣式和標題名稱等window屬性:

"window": {
    "navigationBarTitleText": "超級顏值計算器",
    "navigationBarBackgroundColor": "#ff8c00",
    "navigationBarTextStyle": "white",
    "navigationStyle": "default",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "light",
    "enablePullDownRefresh": false
},

tabBar 設置

此次我們開發的小程序是一個多tab的應用,以下是tabBar的設置信息:

"tabBar": {
    "color": "#9ca0a3",
    "selectedColor": "#00ae66",
    "list": [
        {
            "pagePath": "pages/index/index",
            "text": "首頁",
            "iconPath": "asset/icon/home.png",
            "selectedIconPath": "asset/icon/home_selected.png"
        },
        {
            "pagePath": "pages/reward/reward",
            "text": "打賞",
            "iconPath": "asset/icon/reward.png",
            "selectedIconPath": "asset/icon/reward_selected.png"
        }
    ]
},

其他配置信息請見源文件

全局樣式設置

在根目錄下創建全局樣式文件 app.wxss,會作用於當前小程序的所有頁面。

當前小程序所使用的全局樣式:

.icon-arrow:after{
    content: "\2715";
}
.icon-love:after {
    content: "\2740";
}
.icon-male:after {
    content: "\2642";
}
.icon-female:after {
    content: "\2640";
}

全局註冊小程序函數

小程序啓動之後,在 app.js 中執行App函數用來註冊一個小程序,onLaunch回調會在小程序初始化完成時觸發,並且全局只觸發一次。

在根目錄下創建 app.js

App({
    onLaunch() {
        let self = this;
        wx.getSystemInfo({
            success: function (res) {
                self.globalData.screenWidth = res.windowWidth;
                self.globalData.screenHeight = res.windowHeight;
            }
        });
    },
    globalData: {
        screenWidth: null,
        screenHeight: null
    }
});

代碼說明:

小程序在初始化完成後會調用微信獲取系統信息API(wx.getSystemInfo) ,獲取窗口的寬高,並存放到globalData屬性中,供其他page使用。

主頁面開發

文件列表:
pages/index/index.js 核心js業務邏輯
pages/index/index.wxml 模板文件
pages/index/index.wxss 樣式文件
pages/index/index.json (暫未用到)

創建視圖

創建 pages/index/index.wxml 文件,代碼如下:

<view class="container">
    <view class="pic-info" style="width: {{ picSelfAdaptWidth }}vw; height: {{ picSelfAdaptHeight }}vh">
        <image wx:if="{{ !testPicFile }}" class="pic-none" src="../../asset/icon/pic_bg.png"></image>
        <image wx:else class="pic-inner" src="{{ testPicFile }}"></image>
    </view>
    <view wx:if="{{ testPicFile && !testPicResult }}" class="close" bindtap="handleCancelPic">
        <i class="arrow">×</i>
    </view>
    <view class="pic-result" wx:if="{{ testPicResult }}">
        <view class="score-box"><i class="icon-love"></i> 顏值 <span class="score">{{ testPicResult.beauty }}</span>, 擊敗 {{ testPicResult.defeat}}% 用戶</view>
        <view class="score-ext">
            <span wx:if="{{ userInfo }}">用戶: {{userInfo.nickName}}</span> 
            <span>性別: <i class="icon-{{ testPicResult.gender }}"></i></span>
            <span>年齡: {{ testPicResult.age }}</span>
        </view>
    </view>
    <view>
        <button wx:if="{{ !testPicFile }}" class="btn btn-select" bindtap="handleUploadPic">選擇照片/拍照</button>
        <button wx:if="{{ testPicFile && !testPicResult }}" class="btn btn-compute" open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo">計算顏值</button>
        <button open-type="share" wx:if="{{ testPicResult }}" class="btn btn-share">邀請好友來玩</button>
        <button wx:if="{{ testPicResult }}" class="btn btn-bottom" bindtap="handlePlayAgain">再試一次</button>
    </view>
</view>
變量說明

picSelfAdaptWidth 爲圖片容器顯示寬度,默認爲 70vw

picSelfAdaptHeight 爲圖片容器顯示高度,根據圖片真實寬高和默認寬度計算得來,具體計算方法見 源代碼

testPicFile 爲用戶選取的照片臨時地址;

testPicResult 爲照片識別結果集對象,所包含的屬性有 beauty:顏值(0-100), defeat: 擊敗用戶百分比, gender: 性別, age: 年齡

userInfo 爲用戶基本信息,包括 nickName: 用戶暱稱;

事件說明

handleUploadPic 點擊選取照片時觸發的事件;

handleGetUserInfo 點擊計算顏值時觸發的事件;

handlePlayAgain 點擊再試一次時觸發的事件;

handleCancelPic 選取完照片後點擊右上角紅叉時觸發的事件;

創建樣式

創建 pages/index/index.wxss 文件,代碼如下:

page {
    background-color: #ff8c00;
}
.container {
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    box-sizing: border-box;
}
.pic-info {
    margin-top: 6vh;
    border: solid #fff;
    border-width: 30rpx 30rpx 30rpx 30rpx;
    box-shadow: 10rpx 10rpx 15rpx #333;
    background: #fff;
    display: flex;
    align-items: center;
    justify-content:center;
    border-radius: 20rpx;
}
.btn {
    background: #dd5900;
    color: #fff;
    border: 1px solid #fff;
}
.btn-select {
    margin-top: 80rpx;
}
.btn-compute {
    margin-top: 50rpx;
    margin-bottom: 50rpx
}
.btn-share {
    margin-top: 50rpx;
}
.btn-bottom {
    margin-top: 30rpx;
    margin-bottom: 50rpx;
}
.pic-none {
    width: 90%;
    height: 90%;
}
.pic-inner {
    width: 100%;
    height: 100%;
}
.pic-result {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-top: 50rpx;
    color: #fff;
}
.pic-result .score-box{
    font-size: 34rpx;
}
.pic-result .score {
    font-size: 50rpx;
}
.pic-result .score-ext {
    margin-top: 30rpx;
    font-size: 28rpx;
}
.pic-result .score-ext span {
    margin-right: 30rpx;
}
.pic-result .score-box {
    background: #fff;
    padding: 10rpx 20rpx;
    border-radius: 20rpx;
    color: #dd5900;
}
.close {
    position: absolute;
    top: 7vh;
    right: 12vw;
}
.close .arrow {
    display: flex;
    align-items: center;
    justify-content:center;
    font-size: 38rpx;
    width: 50rpx;
    height: 50rpx;
    border-radius: 25rpx;
    background: #dd5900;
    color: #fff;
}

創建視圖JS邏輯文件

創建 pages/index/index.js 文件,代碼如下:


const app = getApp()
const picDefaultWidth = 50;
const picDefaultHeight = 30;
const picTestFileDefaultWidth = 70;

Page({
    onShareAppMessage(res) {
        return {
            title: '我的顏值擊敗了全國 94% 的用戶,不服來戰!',
            path: '/pages/index/index',
            imageUrl: '../../asset/img/prview.png'
        }
    },
    data: {
        picSelfAdaptWidth: picDefaultWidth,
        picSelfAdaptHeight: picDefaultHeight,
        testPicFile: '',
        testPicResult: null,
        userInfo: null
    },
    handleGetUserInfo(e) {
        this.setData({
            userInfo: e.detail.userInfo
        });
        this.handleComputePic();
    },
    handleCancelPic() {
        this.setData({
            testPicFile: '',
            testPicResult: null,
            picSelfAdaptHeight: picDefaultHeight,
            picSelfAdaptWidth: picDefaultWidth
        });
    },
    handleUploadPic() {
        let self = this;
        let ret = wx.chooseImage({
            count: 1,
            sizeType: "compressed",
            success: function(res) {
                self.setData({
                    testPicFile: res.tempFiles[0].path
                });
                self.getImageInfo(res.tempFiles[0].path, function(res) {
                    self.setPicAdaptHeight(res.width, res.height);
                });
            }
        });
    },
    handlePlayAgain() {
        this.setData({
            testPicFile: '',
            testPicResult: null,
            picSelfAdaptHeight: picDefaultHeight,
            picSelfAdaptWidth: picDefaultWidth
        });
    },
    handleComputePic() {
        let self = this;
        wx.showLoading({
            title: "顏值計算中",
            mask: true
        });
        wx.uploadFile({
            url: 'https://api-cn.faceplusplus.com/facepp/v3/detect', //僅爲示例,非真實的接口地址
            filePath: self.data.testPicFile,
            name: 'image_file',
            formData: {
                'api_key': 'DVc8JblEbcBjgq55TtDW0sheUhBeCaGe',
                'api_secret': 'lMUVhSAg_ruN4PmwgNCk0IiWPNAF2_Sr',
                'return_attributes': 'gender,age,beauty'
            },
            success: function (res) {
                if (res.statusCode != 200) {
                    wx.showToast({
                        title: '服務器被擠爆了,請稍後再試',
                        icon: 'none',
                        duration: 2000
                    });
                    return false;
                }
                let data = JSON.parse(res.data);
                console.log(data)
                console.log(res)
                if (!data.faces || data.faces.length == 0) {
                    wx.showToast({
                        title: '沒有識別到人臉,請更換照片重試',
                        icon: 'none',
                        duration: 2000
                    });
                    return false;
                }
                let human = [];
                data.faces.forEach(item => {
                    let beauty = 50;
                    if (item.attributes.gender.value == 'Male') {
                        beauty = item.attributes.beauty.female_score;
                    } else {
                        beauty = item.attributes.beauty.male_score;
                    }
                    human.push(beauty);
                });
                let beautyIndex = human.indexOf(Math.max.apply(null, human));
                let maxBeautyHuman = data.faces[beautyIndex];

                let humanAttr = {
                    age: maxBeautyHuman.attributes.age.value,
                    gender: maxBeautyHuman.attributes.gender.value,
                    beauty: 50
                };
                if (humanAttr.gender == 'Male') {
                    humanAttr.beauty = maxBeautyHuman.attributes.beauty.female_score;
                } else {
                    humanAttr.beauty = maxBeautyHuman.attributes.beauty.male_score;
                }

                humanAttr.gender = humanAttr.gender == 'Male' ? "male" : "female";
                humanAttr.beauty = Math.ceil(humanAttr.beauty) + 15;
                humanAttr.beauty = humanAttr.beauty > 97 ? 97 : humanAttr.beauty;
                humanAttr.defeat = self.computeBeautyDefeatRatio(humanAttr.beauty);

                self.setData({
                    testPicResult: humanAttr
                });

                wx.hideLoading();
            },
            fail: function() {
                wx.showToast({
                    title: '網絡異常,請稍後再試',
                    icon: 'none',
                    duration: 2000
                });
            }
        })
    },
    getImageInfo(imgSrc, scb, ecb) {
        wx.getImageInfo({
            src: imgSrc,
            success: scb,
            fail: ecb
        });
    },
    setPicAdaptHeight(picWidth, picHeight) {
        let h = (app.globalData.screenWidth * 0.7 / picWidth) * picHeight / app.globalData.screenHeight * 100;
        this.setData({
            picSelfAdaptHeight: h,
            picSelfAdaptWidth: picTestFileDefaultWidth
        });
    },
    computeBeautyDefeatRatio(beauty) {
        return Math.ceil(Math.sqrt(beauty) * 10);
    }
})

執行流程

啓動小程序
  • App()函數執行並且 onLaunch 回調被觸發,獲取window的寬高,存入 globalData 屬性中;
首頁渲染
  • picSelfAdaptWidthpicSelfAdaptHeight 分別賦默認值,顯示照片顯示區顯示默認圖片;

  • 此時 testPicFile, testPicResult 均爲空,視圖將只顯示 “選擇照片/拍照” 按鈕;

點擊”選擇照片/拍照”按鈕
  • 觸發 handleUploadPic 事件, 通過調用 wx.chooseImage 來選擇本地圖片或者拍照,然後將選取的照片臨時路徑賦值給 testPicFile;

  • 然後調用自定義方法 getImageInfo 來獲取圖片的寬高等信息,再調用 setPicAdaptHeight 傳入寬高來計算圖片自適應高的值 picSelfAdaptHeight

  • 此時由於 testPicFile 非空,”選擇照片/拍照”按鈕將隱藏,”計算顏值”按鈕顯示;

  • 同時圖片預覽區右上角出現”紅叉”按鈕;

點擊”紅叉”按鈕
  • 將會觸發 handleCancelPic 事件,會重置data對象中所有屬性值,回到最初始狀態;
點擊”計算顏值”按鈕
  • 首先使用open-type(微信開放能力)開放的功能獲取用戶信息(從bindgetuserinfo綁定的回調函數中獲取用戶信息),此時會觸發 handleGetUserInfo , 獲取用戶信息後會執行 this.handleComputePic 顏值計算方法,
顏值計算方法核心邏輯
  • 調起Loading,顯示顏值計算中;

  • 通過微信API wx.uploadFile 向face++接口發送請求,查看源代碼

  • 對face++API的返回值進行進一步處理:

    • 顏值數據中會有同性顏值打分和異性顏值打分,我們取異性顏值打分數據;
    • 返回數據中若有多組面部打分數據的話,我們取顏值最高一組;
    • 爲原始顏值分 +15 分的 buff, 最高不超過97分;
    • 計算擊敗率,具體算法請見源碼;
邀請好友
  • 使用 button 組件的 open-type 功能來觸發用戶轉發;
  • 在 Page 中定義 onShareAppMessage 函數,設置該頁面的轉發信息 參見文檔
Page({
    onShareAppMessage(res) {
        return {
            title: '我的顏值擊敗了全國 94% 的用戶,不服來戰!',
            path: '/pages/index/index',
            imageUrl: '../../asset/img/prview.png'
        }
    },
    ...
點擊”再試一次”按鈕
  • 觸發 handlePlayAgain 事件,重置data中全部屬性值;
    handlePlayAgain() {
        this.setData({
            testPicFile: '',
            testPicResult: null,
            picSelfAdaptHeight: picDefaultHeight,
            picSelfAdaptWidth: picDefaultWidth
        });
    }

打賞頁面開發

打賞頁面比較簡單,不進行講解,可自行參考源碼

測試

在開發者工具中有 測試 按鈕,可點擊申請測試報告,如下:

這裏寫圖片描述

報告每24小時可申請一次,內容如下:

這裏寫圖片描述

發佈

上傳代碼

首先需要上傳小程序代碼,點擊 上傳 按鈕進行上傳

這裏寫圖片描述

這裏寫圖片描述

填寫版本號,項目備註後就開始上傳代碼

這裏寫圖片描述

提交審覈

登錄微信公衆平臺,進入 開發管理 找到開發版本卡片,點擊 提交審覈

這裏寫圖片描述

如實填寫審覈信息

這裏寫圖片描述

等待審覈通過後會有微信通知,大概1個工作日我就收到審覈通過的通知了(贊一下微信的審覈速度),然後登陸公衆平臺,進入 開發管理 ,你會看小程序的狀態是審覈通過待發布,點擊 發佈即可。

可以打開微信在搜索框中輸入小程序名稱,如果能正常搜索到,說明此次發佈成功。

這裏寫圖片描述

結尾

至此,小程序就順利誕生了,希望此文章對剛入坑小程序開發的夥伴們有所幫助,也歡迎大家加微信羣討論 獲取入羣二維碼


請作者喝杯咖啡

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章