該小程序采用的云開發(fā),沒有自己搭建后端,我心目中,只要沒有后端的內(nèi)容我就覺得很簡單。但其實(shí)我還是想有朝一日能自己獨(dú)立完成前后端所有工作,寫一個(gè)更棒的作品。
之前有寫過幾篇文章,可以回顧一下。你可能需要的文章:
天啦嚕,看了半天代碼發(fā)現(xiàn)還挺多,一時(shí)間不曉得該從哪里開始。那還是按照tab頁使用邏輯順序來吧。
啟動頁
之前文章也有過如何寫一個(gè)啟動頁面。至于為什么需要個(gè)啟動頁呢?
我覺得主要也就兩點(diǎn),一個(gè)是好看,還有個(gè)就是添加獲取用戶信息類型按鈕,間接引導(dǎo)用戶授權(quán)獲取用戶信息。
至于為啥要獲取用戶信息?
曾真有段時(shí)間,我糾結(jié)了好久,因?yàn)槲易约哼@個(gè)程序中,除了展示,好像也沒啥需要用到這個(gè)數(shù)據(jù)的地方。
但我還是寫了。
當(dāng)前登陸用戶的用戶名和頭像圖片,通過 open-data 標(biāo)簽可以無需授權(quán)直接獲取,只要指定相應(yīng)類型userNickName和userAvatarUrl即可,樣式只需要用view容器包裹起來進(jìn)行設(shè)置。button按鈕指定open-type為getUserInfo,在點(diǎn)擊事件中就可以拿到授權(quán)之后的用戶公開信息數(shù)據(jù)。
拿到用戶信息需要保存到數(shù)據(jù)庫中,也只需要首次登錄的用戶在授權(quán)之后才需要入庫,所以加個(gè)判斷當(dāng)前登陸用戶是否是首次登錄,判斷條件是每個(gè)用戶的唯一值openId。
通過這個(gè)邏輯,那需要處理的可以分為如下幾個(gè):
1、獲取當(dāng)前登錄用戶的openId。
getOpenID() {
let that = this;
wx.cloud.callFunction({
name: 'login',
complete: res => {
const openId = res.result.event.userInfo.openId;
that.setData({
openId
})
}
})
},
這個(gè)login云函數(shù)是項(xiàng)目構(gòu)建時(shí)自動生成的,云函數(shù)寫法:
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
return {
event,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
env: wxContext.ENV,
}
}
2、獲取數(shù)據(jù)庫中所有用戶數(shù)據(jù)。
getUsersList() {
const that = this;
db.collection('users').get({
success: res => {
that.setData({
usersList: res.data,
})
},
fail: console.error
})
},
3、在頁面剛加載的時(shí)候調(diào)用上面兩個(gè)方法,拿到openId和userList。在點(diǎn)擊按鈕時(shí),檢查當(dāng)前登錄用戶是否已經(jīng)存在數(shù)據(jù)庫。
goToIndex(e) {
const that = this;
const auth = e.detail.errMsg;
wx.switchTab({
url: '/pages/index/index',
});
if(auth === "getUserInfo:ok") {
const avatarUrl = e.detail.userInfo.avatarUrl;
const nickName = e.detail.userInfo.nickName;
that.checkUser(nickName, avatarUrl);
}
},
這里獲取到用戶信息數(shù)據(jù)是這個(gè)樣子滴:

4、檢查當(dāng)前登錄用戶是否已經(jīng)存在數(shù)據(jù)庫。
checkUser(name, url) {
const that = this;
const list = this.data.usersList;
const openId = this.data.openId
const ids = [];
list.forEach((item) => {
ids.push(item._openid);
})
if(ids.indexOf(openId) === -1) {
that.setUserInfo(name, url)
} else {
return;
}
},
5、如果不存在的話,將該用戶信息存入數(shù)據(jù)庫users中。管它有用沒用,先存著唄。
setUserInfo(name, url) {
db.collection('users').add({
data: {
nickName: name,
avatarUrl: url,
}
})
},
數(shù)據(jù)庫中會自動給這個(gè)字段生成一個(gè)id和openId。

首頁
首頁從上到下分為幾塊,輪播圖,輪播告示,icon列表,推薦商品展示。
輪播圖。
直接用自帶的組件,swiper和swiper-item配合使用。
<swiper class="swiper-top" indicator-dots="true" indicator-active-color="#fff" autoplay circular>
<swiper-item wx:for="{{bannersList}}" wx:key="item">
<image mode="aspectFill" data-url="{{item}}" src="{{item}}" />
</swiper-item>
</swiper>
圖片數(shù)據(jù)來自數(shù)據(jù)中定義好的。
getBannerList() {
db.collection('banners').get({
success: res=> {
this.setData({
bannersList: res.data[0].imgs,
})
},
fail: console.error
})
}
輪播告示。
和輪播圖一樣的,只是輪播方向不同,swiper中添加個(gè)參數(shù) vertical。點(diǎn)擊顯示彈窗,引用的是WeUI庫,咋用參考以往文章。
icon列表。
到這里就要用到本程序中最最最復(fù)雜的一個(gè)數(shù)據(jù)庫集合了,幾乎所有的商品數(shù)據(jù)都是存放在這個(gè)集合中的。

那icon列表就是獲取goods集合中每個(gè)對象icon字段值,推薦商品列表就是每個(gè)對象中l(wèi)ist數(shù)組中所有isHot為true的數(shù)據(jù)。
getIconList() {
const that = this;
const arr = [];
db.collection('goods').get({
success: res=> {
const list = res.data;
list.forEach((item) => {
item.list.forEach((d) => {
if(d.isHot) {
const param = {
...d,
categoryId: item._id
};
arr.push(param);
}
})
})
that.setData({
categories: list,
goodsRecommend: arr
})
},
fail: console.error
})
},
給每個(gè)icon圖片上加一個(gè)跳轉(zhuǎn)到分類頁的點(diǎn)擊事件,一般的跳轉(zhuǎn)可以使用wx.navigateTo,而tab頁的跳轉(zhuǎn)只允許使用wx.switchTab,官方文檔中指明這個(gè)方法是不可以后綴參數(shù)的。
而我這里肯定是需要點(diǎn)擊不同的icon跳轉(zhuǎn)到不同的分類欄目中的,那就需要在跳轉(zhuǎn)時(shí)候攜帶該分類id,還是當(dāng)前這個(gè)數(shù)組的下標(biāo)。
通過定義全局參數(shù),可以解決wx.switchTab無法攜帶參數(shù)的問題。
app.js中,在onLaunch里定義個(gè)全局對象。
this.globalData = {
categoryParamId: 0,
categoryParamIndex: 0,
}
商品分類頁
在menu.js中,在最開始需要引入全局變量。
const app = getApp()
那上面定義的globalData可以直接通過app拿到。
分類頁這兒主要的處理邏輯有三塊內(nèi)容。
1、區(qū)分管理員權(quán)限和普通用戶權(quán)限。
管理員權(quán)限可以有新增商品和刪除的功能,普通用戶只可以查看。
權(quán)限這塊的處理應(yīng)該會有更好的方案。
我比較挫,想到的最簡單的方法就是利用openId來做過濾。在頁面初次加載的時(shí)候獲取當(dāng)前用戶的openId,和啟動頁一樣的方法,只是回調(diào)函數(shù)中不一樣。在數(shù)據(jù)庫中定義個(gè)管理員集合,你需要給那些用戶設(shè)置成管理員,將他們的openId放在這個(gè)集合中。
我是在app.js中獲取這個(gè)管理員集合的,可能是剛剛嘗過全局變量的甜頭吧。
wx.cloud.database().collection('adminList').get({
success: res => {
this.adminList = res.data[0].admin_openId;
},
})
那在menu.js中可以直接拿到這個(gè)adminList中數(shù)據(jù),判斷一下當(dāng)前登錄用戶的openId在不在adminList中。
getOpenID() {
let that = this;
wx.cloud.callFunction({
name: 'login',
complete: res => {
const openId = res.result.event.userInfo.openId;
if(app.adminList.indexOf(openId) === -1) {
that.setData({
isAdmin: false
})
} else {
that.setData({
isAdmin: true
})
}
}
})
},
2、將設(shè)置成喜歡狀態(tài)的商品數(shù)據(jù)存入本地緩存。
當(dāng)時(shí)對于這個(gè)邏輯處理的考慮也是想了蠻久,這個(gè)小程序的制作出發(fā)點(diǎn)只是作為一個(gè)助手作用,方便用戶查看店鋪所有商品,是做一個(gè)商品分類展示的功能,不支付線上下單,主要也是因?yàn)轱@示下單這個(gè)功能太復(fù)雜,個(gè)人小程序沒權(quán)限做。
那我就想著僅僅分類展示并不滿足使用,加入個(gè)喜歡列表實(shí)用性更大。
商品的固定數(shù)據(jù)是可以存入云開發(fā)的數(shù)據(jù)庫中,但是針對于每個(gè)用戶不同的喜歡數(shù)據(jù),最好的方案就是使用緩存。
localStorange的數(shù)據(jù)形式是key / value,一開始計(jì)劃的是固定一個(gè)key,value中是個(gè)數(shù)組對象。
這一定是可行的,但我不會做......麻煩能實(shí)現(xiàn)的朋友私信我。
好的方案來不了可以來挫的嘛。我用商品的分類Id和當(dāng)前商品Id拼接起來作為key,這就保證了key唯一性,那存入本地的數(shù)據(jù)是需要在喜歡列表展示的,我需要展示的數(shù)據(jù)有分類Id,id,商品名,是不是喜歡,封面縮略圖,價(jià)格。明白了這幾點(diǎn)要求,實(shí)現(xiàn)就很簡單了。
在每個(gè)商品的愛心圖標(biāo)上加一個(gè)點(diǎn)擊事件。
joinFavorites(e) {
const that = this;
const id = e.currentTarget.dataset.id;
const index = e.currentTarget.dataset.index;
const list = this.data.goodsList[this.data.curIndex].list;
const loveList = [];
list.forEach((item) => {
if (item.id === id) {
item.isLove = !item.isLove;
}
const param = {
categoryId: this.data.curNav,
id: item.id,
name: item.goodsName,
isLove: item.isLove,
thumbnail: item.imgs[0],
price: item.newPrice
};
loveList.push(param);
})
that.setData({
goodsList: this.data.goodsList,
})
// 緩存的key以分類id和服裝id用-連接
const key = loveList[index].categoryId + "-" + loveList[index].id;
this.saveLocally(key, loveList[index]);
},
// 存入本地緩存
saveLocally(key, data) {
wx.setStorage({
key,
data,
})
},
現(xiàn)在看這個(gè)代碼,我覺得還是可以再重構(gòu)優(yōu)化的更好的。
3、從本地緩存中獲取喜歡列表詳情
有些商品是已經(jīng)加入喜歡列表的,商品上的喜歡圖標(biāo)已經(jīng)是高亮狀態(tài),等到下次進(jìn)入該分類頁,就應(yīng)該將之前設(shè)置喜歡狀態(tài)的商品顯示出來,不然每次進(jìn)來都是初始的模樣就毫無意義了。
首先是需要獲取商品列表數(shù)據(jù),再根據(jù)本地緩存數(shù)據(jù),將喜歡的商品數(shù)據(jù)修改一下狀態(tài)。
這樣就是分三步走。
獲取商品列表數(shù)據(jù)。
getGoodsList() {
const that = this;
db.collection('goods').get({
success: res => {
const list = res.data;
that.getDetails(that.data.storageData, list);
that.setData({
goodsList: list,
})
}
})
},
讀取緩存數(shù)據(jù)。
getLocally() {
const that = this;
wx.getStorageInfo({
success(res) {
if (res.currentSize > res.limitSize) {
that.setData({
isDialog: true
})
} else {
that.setData({
storageData: res.keys
})
}
},
})
},
從本地緩存中獲取喜歡列表詳情。
getDetails(localArr, goodsList) {
const that = this;
localArr.forEach((localItem) => {
const itemPId = localItem.split("-")[0].toString();
const itemId = localItem.split("-")[1].toString();
goodsList.forEach((goodItem) => {
if (itemPId === goodItem._id) {
goodItem.list.forEach((item) => {
if (itemId === item.id.toString()) {
wx.getStorage({
key: localItem,
success(res) {
item.isLove = res.data.isLove
that.setData({
goodsList,
})
}
})
}
})
}
})
})
},
主要的處理邏輯就是以上這三塊。還有些其他的交互方法,商品分類的切換,詳情頁跳轉(zhuǎn),商品刪除......這些就不寫了,可以去看代碼,都很容易理解的。
商品詳情頁
點(diǎn)擊跳轉(zhuǎn)過來的時(shí)候,攜帶的參數(shù)只有分類id和商品id。根據(jù)這兩個(gè)字段就可以在商品列表數(shù)據(jù)查詢到具體所有數(shù)據(jù)。
在當(dāng)前頁面獲取傳參過來的數(shù)據(jù)。
onLoad: function (options) {
this.setData({
categoryId: options.categoryId,
id: options.id
});
}
新增商品頁
按照之前數(shù)據(jù)庫集合中定義的數(shù)據(jù)格式,這里就分兩塊。一個(gè)是相關(guān)數(shù)據(jù)的填寫表單,一個(gè)就是上傳的圖片列表。
圖片列表上傳的實(shí)現(xiàn),官方都給了相應(yīng)的api方法。
選擇圖片:
wx.chooseImage({
sizeType: ["original", "compressed"], // 可以指定是原圖還是壓縮圖,默認(rèn)二者都有
sourceType: ["album", "camera"], // 可以指定來源是相冊還是相機(jī),默認(rèn)二者都有
success: function (res) {
// 返回選定照片的本地文件路徑列表,tempFilePath可以作為img標(biāo)簽的src屬性顯示圖片
var tempFilePaths = res.tempFilePaths;
var imgs = that.data.imgs;
for (var i = 0; i < tempFilePaths.length; i++) {
if (imgs.length >= 9) {
that.setData({
imgs: imgs,
});
return false;
} else {
const filePath = res.tempFilePaths[i];
var timestamp = Date.parse(new Date());
const cloudPath = `${timestamp}-${i}`;
const param = {
cloudPath,
filePath,
};
imgs.push(param);
}
}
that.setData({
imgs: imgs,
});
},
});
上傳圖片:
uploadImgs(list) {
const that = this;
const imgList = [];
list.forEach((item) => {
wx.cloud.uploadFile({
cloudPath: `uploadImgs/${item.cloudPath}`, // 存入uploadImgs文件夾中
filePath: item.filePath, // 文件路徑
}).then((res) => {
if(res.errMsg === "cloud.uploadFile:ok") {
imgList.push(res.fileID)
}
that.setData({
imgList,
})
if(that.data.imgList.length === that.data.imgs.length) {
that.add()
}
})
.catch((error) => {
console.log(error);
});
});
},
最終把表單數(shù)據(jù)和圖片列表數(shù)據(jù)到存入數(shù)據(jù)庫集合中。
add() {
const that = this;
wx.cloud.callFunction({
name: 'addGoods',
data: {
categoryId: that.data.categoryId,
id: that.data.id,
goodsName: that.data.goodsName,
newPrice: that.data.newPrice,
oldPrice: that.data.oldPrice,
isHot: that.data.isHot,
imgs: that.data.imgList
}
}).then()
},
商品新增的云函數(shù):
const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
const goodsName = event.goodsName;
const categoryId = event.categoryId;
const id = event.id;
const newPrice = event.newPrice;
const oldPrice = event.oldPrice;
const isHot = event.isHot;
const imgs = event.imgs;
db.collection("goods").doc(categoryId).update({
data: {
list: _.push({
id,
goodsName,
newPrice,
oldPrice,
isHot,
imgs
})
}
})
return {
categoryId,
id,
goodsName,
newPrice,
oldPrice,
isHot,
imgs
}
}
喜歡列表頁
最輕松的一個(gè)頁面,讀取本地緩存展示數(shù)據(jù)。這里還用到了WeUI的mp-slideview組件,修改這個(gè)組件的樣式還是挺麻煩,高度樣式?jīng)]改成功,多少存在點(diǎn)瑕疵。
個(gè)人信息頁
這個(gè)頁面已經(jīng)純屬和小程序主旨功能無關(guān)了,我就是無聊寫著玩湊湊頁面的。想寫些什么都可以自由發(fā)揮,隨便添加什么功能都可以,這里我就不介紹我隨便寫的東西了。
至此,該篇?dú)v經(jīng)四天的文章終于結(jié)束(主要是周末玩了兩天),目前正文字?jǐn)?shù)4500+......
我廢話可真多呀。
這個(gè)小程序會繼續(xù)維護(hù),有任何不明白的地方聯(lián)系我哦~