摘要:微信小程序小米事先声明,这是一个高仿小米的微信小程序。写完之后查文档才发现,微信小程序官方提供了自定义组件的方法有需要的可以查看微信小程序文档写完这个组件后我总解了一下需要注意的问题选中了的当前页面,再次点击因该无效。
微信小程序-小米Lite
事先声明,这是一个高仿小米Lite的微信小程序。
我呢现在是一个大三快大四的学生,这个小程序花了我很长时间,把能写的功能基本上都写了。我秉着分享开源的心理,尽量把我写的这个小程序怎么写的,为什么这样写,详细的告诉大家。为什么是尽量?这是因为,我不太会说,可能说的不是很清楚,所以只能尽量.
项目预览
ok实现的效果就是这样。
easy-moke
VSCode
微信小程序开发者工具
(阿里巴巴矢量图标库)
文件目录├│ ├ ├ │ ├ │ ├ │ ├ │ ├ ├ │ ├ │ ├ │ ├ │ ├ │ ├ │ ├ │ ├ │ ├ ├ │ └util.js ├ │ └weui.wxss ├ │ ├Api.js │ ├main.js │ └mock.js
对于初学者来说,可能拿到设计图就立马写,其实这样很不好,写出来的代码会有很多重复的代码,这样不利于之后的维护。所以应该把一些公用的代码封装,之后直接调用就行了,之后维护起来也更加的方便。
API封装我们前端想要获取页面的数据,就需要发送HTTP请求后端提供给我们的API接口,从API接口中获取我们想要的数据。在微信小程序中,微信官方给我们提供了一个方法wx.request来请求.
一个程序,需要的HTTP请求会很多,如果我们每个请求都去写一个wx.request,这样写出来的代码,看起来会很冗长,他人看我们的代码时也会很累,也不利于我们之后的修改。因此为了代码的整洁,和之后的修改方便。我就把所有的API请求请求封装在wxapi文件目录下。
// Api.js
const banners = "https://www.easy-mock.com/mock/5cf9c392006feb28c7eedf28/banners"
const navdata = "https://www.easy-mock.com/mock/5cf9c392006feb28c7eedf28/navdata"
const goodList = "https://www.easy-mock.com/mock/5cf9c392006feb28c7eedf28/goodList"
const category = "https://www.easy-mock.com/mock/5cf9c392006feb28c7eedf28/category"
const findData = "https://www.easy-mock.com/mock/5cf9c392006feb28c7eedf28/findData"
const userData = "https://www.easy-mock.com/mock/5cf9c392006feb28c7eedf28/userData"
const goodDetail = "https://www.easy-mock.com/mock/5cf9c392006feb28c7eedf28/goodDetail"
const QQ_MAP_KEY = "NNFBZ-6DRCP-IRLDU-VEQ4F-TXLP2-PFBEN"
const MAPURL = "https://apis.map.qq.com/ws/geocoder/v1/"
module.exports = {
banners,
navdata,
goodList,
category,
findData,
userData,
goodDetail,
QQ_MAP_KEY,
MAPURL
}
import * as MOCK from "./mock"
import * as API from "./Api"
const request = (url,mock = true,data) => {
let _url = url
return new Promise((resolve, reject) => {
if (mock) {
let res = {
statusCode: 200,
data: MOCK[_url]
}
if (res && res.statusCode === 200 && res.data) {
resolve(res.data)
} else {
reject(res)
}
} else {
wx.request({
url: url,
data,
success(request) {
resolve(request.data)
},
fail(error) {
reject(error)
}
})
}
});
}
// showLoading
const showLoading = () => {
wx.showLoading({
title: "数据加载中",
mask: true,
});
}
// 获取地理位置
const geocoder = (lat, lon) => {
return request(API.MAPURL,false,{
location: `${lat},${lon}`,
key: API.QQ_MAP_KEY,
get_poi: 0
})
}
module.exports = {
getBanners: () => {
// return request("banners")
return request(API.banners,false) //首页 banners
},
getNavData: () => {
// return request("navdata")
return request(API.navdata,false) //首页 navdata
},
getGoodList: () => {
// return request("goodList")
return request(API.goodList,false) //首页 商品列表
},
getCategroy: () => {
// return request("category")
return request(API.category,false) //分类页面
},
getFindData: () => {
// return request("findData")
return request(API.findData,false) //发现 页面
},
getUserData: () => {
// return request("userData")
return request(API.userData,false) // 我的 页面
},
getGoodDetail: () => {
// return request("goodDetail")
return request(API.goodDetail,false) //商品详情
},
showLoading,
geocoder
}
看到这里,可能就会有一些疑问,为什么我在每个请求后面都加上了一个false?
这是因为,我在写这个微信小程序开始时,没有使用easy-mock来模拟http请求数据。我是把假数据都放在mock.js文件中。然后使用 return request("banners")这种方式就可以获取我想要的数据。
API封装完了,该怎么调用呢?我就以首页的banners数据为例
// index.js
const WXAPI = require("../../wxapi/main")
onLoad: function (options) {
WXAPI.showLoading()
this.getBanners()
},
getBanners() {
WXAPI
.getBanners()
.then(res => {
wx.hideLoading()
this.setData({
banners: res.data
})
})
},
记住,如果想要发送HTTP请求数据的页面,都必须加上这一句const WXAPI = require("../../wxapi/main")
定义的组件开始准备OK,现在开始写页面。第一步要写的是tabBar部分。
tabBar组件看起来是不是有点奇怪,为什么有点透明的感觉?因为这个tabBar组件是我自己写的。
一般来将,直接在把tabBar组件写在app.json中,就可以了。
但是我觉得不是那么好看,所以就自己撸了一个tabBar组件出来。
写完之后查文档才发现,微信小程序官方提供了自定义tabBar组件的方法,有需要的可以查看微信小程序文档
写完这个组件后我总解了一下,需要注意的问题.
选中了的当前页面,再次点击因该无效。
所以我在app.js中存入了一个page属性,来存储当前页面,然后在点击事件goToPage()方法中加入判断去解决。
首页 分类 发现 购物车 我的
// components/tabbar/tabbar.js
// 全局里面存了一个page 表示当前 路由
const app = getApp();
Component({
/**
* 组件的属性列表
*/
properties: {
// 是否选中
on:{
type: String,
value: ""
}
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
// 跳转到相应的页面
// 加了一个判断
// 因为如果现在显示的是当前页面就不需要再跳转
goToPage(e) {
let page = e.currentTarget.dataset.page || "user";
if(app.globalData.page === page) {
return ;
}
wx.redirectTo({
url: `/pages/${page}/${page}`,
});
app.globalData.page = page;
}
}
})
/* components/tabbar/tabbar.wxss */
.tabbar {
width: 100%;
height: 100rpx;
background-color: #ffffff;
display: flex;
position: fixed;
bottom: 0;
font-size: 26rpx;
z-index: 99;
}
.shouye,.fenlei,.faxian,.gouwuche,.wode {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
opacity: 0.5;
}
.icon {
height: 60rpx;
}
.on {
color:#f96702;
}
关于如何使用的问题,只需要在,需要tabBar功能的页面底部加上就可以.
比如index页面
记得在json中引入组件
{
"usingComponents": {
"goodList": "../../components/goodList/goodList",
"tabbar": "../../components/tabbar/tabbar"
}
}
icon组件
tabBar组件中需要图标,我使用的是阿里的图标库
如何使用?
先去图标库中找到你所需要的图标,加入购物车。
然后添加至项目,自己新建一个就可以了。
在然后下载之本地,把其中的CSS文件里面所有的内容都复制到wxss文件中就行了.
这个组件很简单,定义出来就可以直接使用,并且修改颜色和大小.
properties: {
type: {
type: String,
value: ""
},
color: {
type: String,
value: "#000000"
},
size: {
type: Number,
value: "45"
}
},
goodList组件
在首页中存在很多这样的商品列表,一个一个写,这样写出来的代码会导致首页的代码显得很多,并且不好维护,所以我就封装成了一个组件.
{{name}} {{brief}} ¥{{price}} ¥{{oldPrice}}
properties: {
// 图片链接
url: {
type: String,
value: ""
},
// 名称
name: {
type: String,
value: ""
},
// 信息
brief: {
type: String,
value: ""
},
// 新的价格
price: {
type: String,
value: ""
},
// 旧的价格
oldPrice: {
type: String,
value: ""
}
},
/* components/goodList/goodList.wxss */
.goodList-good {
position: relative;
width: 100%;
height: 100%;
}
.goodList-good-img {
width: 100%;
height: 312rpx;
position: relative;
}
.goodList-good-img image {
width: 100%;
height: 100%;
}
.goodList-good_detail {
padding: 26rpx 23rpx;
}
.good_detail_name {
width:100%;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.good_detail_brief {
width:100%;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
font-size: 25rpx;
color: #8c8c8c;
}
.good_detail_price {
display: flex;
justify-content: flex-start;
align-items: flex-end;
}
.good_detail_price .price {
color: #a36d4a;
font-size: 28rpx;
padding-right: 16rpx;
}
.good_detail_price .oldPrice {
font-size: 24rpx;
color: #8c8c8c;
text-decoration: line-through;
}
你们会发现,我的边框为什么那么细。
0.5px边框如何画初学者,可能会说,0.5px边框,不就是border: 0.5px吗,其实这是错的。
浏览器会把任何小于1px的边框都解析成1px,所以你写0.5px其实浏览器会解析成1px,这样就实现不了效果。
其实也很简单,使用伪类去画。
例如,goodList组件的右部边框.
在index页面的html中,我在包裹goodList的view标签的class中加上了rightBorder这个类来表示画出上边框。
.rightBorder::after {
content: "";
position: absolute;
height: 200%;
width: 1rpx;
right: -1rpx;
top: 0;
transform-origin: 0 0;
border-right: 1rpx solid#e0e0e0;
transform: scale(.5);
z-index: 1;
}
其实画0.5px边框就记住。
使用伪类,并且 position: absolute;
如果想画上下的边框,就把width设置成200%,,左右就把height设置成200%
然后使用transform: scale(.5); 宽高都缩小0.5倍就成了0.5px边框.
其它还有一些 细节问题就按照所需写就行。
userList组件只是我的页面中的一个列表信息,这个很简单
{{List.text}} ({{List.state}})
/* components/userList/userList.wxss */
.main {
width: 100%;
height: 120rpx;
display: flex;
align-items: center;
background-color: #fff;
padding: 40rpx;
box-sizing: border-box;
}
.main image {
width: 80rpx;
height: 80rpx;
}
.main .text {
font-size: 32rpx;
padding: 0 10rpx 0 5rpx;
}
.main .state {
font-size: 26rpx;
color: #8c8c8c;
}
properties: {
List: {
type: Object
}
},
页面和功能介绍
首页搜索部分
我这里引入的是weui组件的搜索样式。如何用?
下载WEUI小程序版,可以在微信开发者工具中打开。
(推荐)把styly文件夹下的weui.wxss 放到自己项目中weui文件夹下
在app.wxss中@import "./weui/weui.wxss".就可以使用了
首页轮播部分这是微信小程序提供的一个组件swiper,微信小程序开发者文档
swiper和swiper-item组合起来就可以实现,一些配置信息,请查看官方文档
具体代码
data: {
banners: [],
indicatorDots: true,
autoPlay: true,
interval: 3000,
duration: 1000,
navdata: [],
goodList: [],
goodListOne: {},
name:"",
},
一个常用的功能
在商城小程序中经常要做一个这样的功能.例如:
功能要求:
点击左边的商品列表,右边的商品信息会滑动到对应位置.
滑动右边的商品信息,左边的商品列表显示的高亮会对应发生变化.
功能要求并不难,但是对于初学者而言,可能会有些问题。我就直接说功能该怎么做
首先:分析一下,页面结构是左右布局。并且两边都可以滑动.所以可以使用微信给我们提供的
scroll-view组件.两个组件就可以采用float,分布在左右两边.
{{item[0].name}} {{item[0].name}} {{product.desc}}
/* miniprogram/pages/category/category.wxss */
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar
{
width: 0px;
height: 0px;
background-color: pink;
}
.categroy-left {
height: 100%;
width: 150rpx;
float: left;
border-right: 1px solid #ebebeb;
box-sizing: border-box;
position: fixed;
font-size: 30rpx;
padding-bottom: 100rpx;
box-sizing: border-box;
}
.categroy-left .cate-list {
height: 90rpx;
line-height: 90rpx;
text-align: center;
border: 2px solid #fff;
}
.categroy-left .cate-list.on {
color: #ff4800;
font-size: 34rpx;
}
/* 右边的列表 */
.categroy-right {
width: 600rpx;
float: right;
height: 1334rpx;
/* height: 100%; */
padding-bottom: 100rpx;
box-sizing: border-box;
overflow: hidden;
}
.right-title {
width: 100%;
text-align: center;
position: relative;
padding-top: 30rpx;
/* font-size: 30rpx; */
padding-bottom: 30rpx;
}
.right-title text::before, .right-title text::after {
content: "";
position: absolute;
width: 60rpx;
/* height: 1px; */
top: 50%;
border-top: 1px solid #e0e0e0;
/* transform: scale(.5); */
}
.right-title text::before {
left: 30%;
}
.right-title text::after {
right: 30%;
}
.right-list {
/* height: 100%; */
background-color: #fff;
}
.right-content {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
}
.right-content .list-detail {
flex-shrink: 0;
width: 33.3%;
height: 100%;
font-size: 26rpx;
text-align: center;
}
.right-content .list-detail image {
width: 120rpx;
height: 120rpx;
padding: 10rpx;
/* background-color: pink; */
}
这个功能的难点就在于js逻辑
先贴出data中需要的数据
data: {
categroy:[], //商品信息
curIndex: "A", //当前的选中的标签
toView: "A", //去到的标签
// 存入每个list的高度叠加
heightArr: [],
// 最后一个list,就是最后一个标签的id
endActive: "A"
},
点击左边右边滑动
这一功能很简单就能实现
只需要在右边scroll-view组件中添加事件scroll-into-view="{{toView}}",toView就是商品显示的id
注意,右边每个商品信息,页面渲染时必须加上id属性
然后左边的scroll-view组件只需添加一个点击事件去修改toView的值就行了
// 点击左边标签要修改的信息
switchTab(e) {
this.setData({
curIndex: e.target.dataset.index,
toView: e.target.dataset.index
})
},
滑动右边左边高亮对应改变
首先需要计算出 右边商品每一块占据的高度,并且存入数组中,可以放入onReady生命周期中
// 计算出右边每个商品占据的高度
getPageMessage() {
// console.log(4)
let self = this
let heightArr = []
let h = 0
const query = wx.createSelectorQuery()
query.selectAll(".right-list").boundingClientRect()
query.exec( res => {
res[0].forEach( item => {
h += item.height
heightArr.push(h)
})
self.setData({
heightArr: heightArr
})
})
},
在右边的scroll-view组件中加上事件。bindscroll="scrollContent,这是scroll-view提供的事件,在滑动时就会触发.
// 页面滑动时触发
scrollContent(e) {
const scrollTop = e.detail.scrollTop
const scrollArr = this.data.heightArr
const length = scrollArr.length - 1
let endChar = String.fromCharCode(65 + length)
let curChar = this.getCurrentIndex(scrollTop)
if(this.data.endActive != endChar) {
this.setData({
curIndex: curChar
})
} else {
this.setData({
endActive: "A"
})
}
},
// 判断curIndex应该是那个
getCurrentIndex(scrollTop) {
const scrollArr = this.data.heightArr
let find = scrollArr.findIndex(item => {
// 提前10rpx触发效果
return scrollTop < item - 10
})
let curChar = String.fromCharCode(65 + find)
return curChar
},
以上就可以实现所有的功能要求了。
但是这样的滑动并不是很完美,右边滑动到最下面,左边高亮却不是最后一个
相对完美效果就是这样
想要完美一点就要在,右边scroll-view添加一个事件:bindscrolltolower="scrollEnd"
// 页面滑动到底部触发
scrollEnd() {
const scrollArr = this.data.heightArr
const length = scrollArr.length - 1
let endChar = String.fromCharCode(65 + length)
this.setData({
curIndex: endChar,
endActive: endChar
})
},
购物逻辑
想要实现这样的效果并不困难。
逻辑顺序: 首页点击商品信息 -> 商品详情页面显示对应的商品详情信息 -> 购物车页面显示商品购买的商品信息. -> 修改之后,商品详情页面显示修改的信息。
想要实现这样的功能,必须有一个 id 在页面跳转时,传入给跳转的页面,跳转的页面再通过id值获取页面所需的数据
例如:首页 -> 商品详情页
这是一条商品的列表的信息,通过点击事件bindtap="goDetails",跳到对应的页面.
goDetails(e) {
const id = e.currentTarget.dataset.id
wx.navigateTo({
url: `/pages/goodDetails/goodDetails?id=${id}`,
});
},
商品详情页:
传入的id值可以再onLoad生命周期的options参数上获取
onLoad: function (options) {
WXAPI.showLoading()
// 获取用户的地址信息
const address = wx.getStorageSync("Address");
this.setData({
id: options.id,
address
})
this.getGoodDetail()
},
数据的存储逻辑
我是把数据直接存在本地缓存中(也可以直接存入到云数据库中),使用的是 wx.setStorage()
在本地存入了两个数据,一个是所有购买的商品信息,一个是总的商品购买数量
// 添加到购物车
toAddCart() {
let cartData = wx.getStorageSync("goods") || [];
let data = {
id: this.data.id,
name: this.data.goodData.name,
memory: this.data.memory,
color: this.data.color,
price: this.data.price,
num: this.data.selectNum,
img: this.data.imgSrc,
select: true
}
// wx.removeStorageSync("goods");
cartData.push(data)
const allNum =this.getAllNum(cartData)
wx.setStorage({
key: "goods",
data: cartData,
success: (res) => {
console.log(res)
let pageIndex = getCurrentPages()
let backIndex = pageIndex.length - 2
wx.navigateBack({
delta: backIndex
})
},
fail: () => {},
complete: () => {}
});
// 存储数量到storage
wx.setStorageSync("allNum", allNum);
// 写到外面就可以让showToast 显示在前一个页面
setTimeout(()=>{
wx.showToast({
title: "已加入购物车",
icon: "success",
duration: 2000
});
},500)
},
// 获取所有的数量
getAllNum(cartData) {
return cartData.reduce((sum, item) => {
return sum + (+item.num)
},0)
},
概述的参数切换
实现这个功能只需要加一个状态,点击时就修改状态的值,并且修改相关渲染的数据就行。
data: {
state: "details_img", //判断概述 参数
}
概述 参数
// 改变概述和参数
changeState() {
let state = this.data.state
if(state === "details_img") {
state = "param_img"
} else {
state = "details_img"
}
this.setData({
state
})
},
购物数据改变,商品详情数据修改
对比一下上面两张图的区别.
在购物页面中选的商品数量和具体的商品信息,之后跳转回商品详情页面中,对应的数据会修改
已选 {{default_change.name}} {{default_change.memory}} {{default_change.color}} × {{default_change.num}}
{{allNum}}
上面时两块修改的html结构
在购物页面点击确认之后,我默认就把商品添加到购物车中,并且位于数据的最后一条
返回商品详情页面时,会重新触发onShow生命周期的函数。
所以我只需要,在onShow中触发修改方法就行.
// 改变默认的版本数据 default_change
changeDefauleChange() {
const goods = wx.getStorageSync("goods") || [];
if(goods.length === 0) {
return
}
const id = this.data.id
const default_change = goods[goods.length - 1]
let memory = default_change.memory.toString()
memory = memory.substring(0,memory.length - 4)
default_change.memory = memory
this.setData({
default_change
})
},
画一个三角形
这一个三角形是使用CSS画出来的,并不是图标。
使用CSS画出一个三角形,也不是那么困难。使用的是伪类和border属性
.right:before,
.right:after {
content: "";
position: absolute;
top: 35%;
right: 0;
border-width: 8px;
/* transform: translateY(10000rpx); */
border-color: transparent transparent transparent transparent;
border-style: solid;
transform: rotate(90deg);
}
.right:before {
border-bottom: 8px #aaaaaa solid;
}
.right:after {
right: 1px;
/*覆盖并错开1px*/
border-bottom: 8px #fff solid;
}
修改商品数量
可以直接使用微信小程序提供的picker组件,具体配置请查看文档
使用腾讯地图获取地理位置先搜索腾讯地图,并且注册开发者信息,申请一个密钥key.
// 获取地理位置
const geocoder = (lat, lon) => {
return request(API.MAPURL,false,{
location: `${lat},${lon}`,
key: API.QQ_MAP_KEY,
get_poi: 0
})
}
然后把密钥复制,因为我封装了所有的API接口。所以使用了 API.QQ_MAP_KEY代替,这里就填写申请的密钥就行.
想要获取用户的经纬度信息,可以使用wx.getLocation(),就可以获取用户的经纬度信息了.
getLocation() {
wx.getLocation({
type: "gcj02",
success: this.getAddress,
fail: () => {
this.openLocation()
}
})
},
getAddress(res) {
let { latitude: lat, longitude: lon} = res
WXAPI.geocoder(lat, lon)
.then(res => {
if(res.status !== 0 || !res.result) {
return
}
let {address_component
} = res.result
const Address = {
city: address_component.city,
district: address_component.district
}
wx.setStorageSync("Address", Address);
})
},
因为我未让用户授权,所以直接把获取的地理位置,保存在本地storage中.
获取的地理位置,就可以在商品详情页面的送至显示出来
结语做这个项目的过程来说是快乐的,没有使用云函数(页面数据并不多,我觉得不需要就可以写出来了),所以总共加起来写的时间也很短,不到一个星期就写完了。写完之后的那一刻的成就感也很好。如果你觉得这篇文章有帮到你的地方,不妨给个赞吧!同时也非常希望在下方看到给出的建议!最后奉上源码.如果有需要就自取吧!
最后,说点题外话,因为我是2020届的毕业生,现在面临实习压力。因为需要话时间去看面试题,所以后面写的一段文章,我只是简要的把重要的功能逻辑写了出来,如果写的不清楚,请见谅。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/105297.html
摘要:嗯这句话就是作为第一次做仿小程序项目的我,历经磨难得出来的肺腑之言。作为想要走上代码这条不归路的程序员,心浮气躁就是为以后整个项目给自己挖坑奠定了良好的基础。 前言 关于小程序,在这里有一句话送给正准备阅读的你-世界上本没有坑,路走的多了就有了;世界上本没有路,坑填的多了就有了。嗯~~~这句话就是作为第一次做仿小程序项目的我,历经‘磨难’得出来的肺腑之言。好了,不多说,进入正...
摘要:嗯这句话就是作为第一次做仿小程序项目的我,历经磨难得出来的肺腑之言。作为想要走上代码这条不归路的程序员,心浮气躁就是为以后整个项目给自己挖坑奠定了良好的基础。 前言 关于小程序,在这里有一句话送给正准备阅读的你-世界上本没有坑,路走的多了就有了;世界上本没有路,坑填的多了就有了。嗯~~~这句话就是作为第一次做仿小程序项目的我,历经‘磨难’得出来的肺腑之言。好了,不多说,进入正...
摘要:在现如今的游戏市场寒冬中,拥有微信庞大的用户量以及更好兼容性的小程序游戏,优势就显得格外明显。扫描二维码即可报名您在现场将有这些体验来自腾讯云云开发团队与微信团队联合打造干货分享,内容包括微信小游戏首发经验分享。 有人说微信小程序游戏的百花齐放 活像十几年前的4399小游戏称霸互联网的景象 歪,斗地主吗,三缺二, 不用下app,小程序就能玩,我保证不抢地主让你抢! ...... ‘...
阅读 1834·2021-09-22 15:52
阅读 3684·2021-09-22 14:59
阅读 3070·2021-09-02 15:12
阅读 1161·2021-08-20 09:35
阅读 1709·2019-08-30 14:09
阅读 2818·2019-08-30 13:56
阅读 1891·2019-08-26 18:27
阅读 3491·2019-08-26 13:37