项目预览 小程序商城主要分为首页、分类、购物车和我的四个模块。
项目需求 接口文档 1 https://www.showdoc.com.cn/128719739414963?page_id=2513282464078443
项目搭建 新建小程序项目 进入官网,自行获取appId并下载微信开发者工具,在程序中点“+”创建新的小程序
搭建目录结构
目录名
作用
components
存放组件
images
tabbar所用图片
request
请求的接口封装
utils
自己的帮助库
styles
存放公共样式
搭建项目页面
页面名称
文件名称
首页
index
分类页面
category
商品列表页面
goods_list
商品详情页面
goods_detail
购物车页面
cart
收藏页面
collect
个人中心页面
user
意见反馈页面
feedback
登录页面
login
授权页面
auth
结算页面
pay
订单页面
order
搜索页面
search
引入字体图标
打开阿里巴巴字体图标网站:https://www.iconfont.cn/
搜索需要的图标,添加购物车,并添加至项目
生成新的链接,并通过链接在网页中打开,复制全部
在本地的styles文件夹下创建iconfont.wxss文件,并将复制文件粘贴即可
在app.wxss中引入
1 @import "./styles/iconfont.wxss" ;
搭建项目tabbar结构
在images文件夹下存放上面几个类似的图片,并在app.json中如下配置即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 "tabBar" : { "list" : [ { "pagePath" : "pages/index/index" , "text" : "首页" , "iconPath" : "images/主页.png" , "selectedIconPath" : "images/主页1.png" } , { "pagePath" : "pages/category/index" , "text" : "分类" , "iconPath" : "images/分类.png" , "selectedIconPath" : "images/分类1.png" } , { "pagePath" : "pages/cart/index" , "text" : "购物车" , "iconPath" : "images/购物车.png" , "selectedIconPath" : "images/购物车1.png" } , { "pagePath" : "pages/user/index" , "text" : "我的" , "iconPath" : "images/用户.png" , "selectedIconPath" : "images/用户1.png" } ] }
项目开发 首页 首页共分为:搜索框、轮播图、导航和楼层四个部分。又因为搜索框属于常见的可复用的,所以可以单独将搜索框抽离成一个组件来使用。
搜索框组件 1.组件json文件中的”component”:true
2.wxml文件代码:
1 2 3 4 5 <view class ="serach_input" > <navigator class ="" target ="" url ="/pages/search/index" hover-class ="navigator-hover" open-type ="navigate" > 搜索 </navigator > </view >
搜索框组件只是一个模仿input框的伪搜索框,实际上的作用是导航到search页面下
3.wxss代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .serach_input { height : 90 rpx; padding : 10 rpx; background-color : var (--themeColor); } .serach_input navigator { height : 100% ; display : flex; justify-content : center; align-items : center; background-color : #fff ; border-radius : 15 rpx; color : #666666 ; }
4.在index文件夹下的json中引入组件。
1 2 3 "usingComponents" : { "SearchInput" : "../../components/SearchInput/SearchInput" } ,
轮播图、导航和楼层布局 1.wxml文件代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <swiper autoplay circular indicator-dots > <swiper-item class ="" wx:for ="{{swiperList}}" wx:key ="goods_id" > <navigator class ="" target ="" url ="/pages/goods_detail/index?goods_id={{item.goods_id}}" > <image class ="" mode ="widthFix" src ="{{item.image_src}}" > </image > </navigator > </swiper-item > </swiper > <view class ="index_cate" > <navigator class ="" wx:for ="{{CateList}}" wx:key ="name" url ="/pages/category/index" open-type ="switchTab" > <image class ="" mode ="widthFix" src ="{{item.image_src}}" > </image > </navigator > </view > <view class ="index_floor" > <view class ="floor_group" wx:for ="{{floorList}}" wx:for-item ="item1" wx:for-index ="index1" wx:key ="floor_title" > <view class ="floor_title" > <image mode ="widthFix" src ="{{item1.floor_title.image_src}}" > </image > </view > <view class ="floor_list" > <navigator wx:for ="{{item1.product_list}}" wx:for-item ="item2" wx:for-index ="index2" wx:key ="name" url ="/pages/goods_list/index?query=精选" > <image src ="{{item2.image_src}}" mode ="{{index2===0?'widthFix':'scaleToFill'}}" > </image > </navigator > </view > </view > </view >
2.wxss样式布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 .index_swiper swiper { width : 750 rpx; height : 340 rpx; } .index_swiper swiper image { width : 100% ; } .index_cate { display : flex; } .index_cate navigator { flex : 1 ; padding : 20 rpx; } .index_cate navigator image { width : 100% ; } .index_floor .floor_group .floor_title { padding : 10 rpx 0 ; } .index_floor .floor_group .floor_title image { width : 100% ; } .index_floor .floor_group .floor_list navigator { float : left; width : 33.33% ; } .index_floor .floor_group .floor_list navigator:nth-last-child (-n+4 ) { height : 27.72711207vw ; border-left : 10 rpx solid #fff ; } .index_floor .floor_group .floor_list navigator:nth-child (2 ),.index_floor .floor_group .floor_list navigator:nth-child (3 ) { border-bottom : 10 rpx solid #fff ; } .index_floor .floor_group .floor_list navigator image { width : 100% ; height : 100% ; }
3.request函数封装
整个页面的数据都是通过wx.request函数来实现调用,为了代码的复用性,可将这块函数单独作为一个模块封装起来,其他页面使用时也只需要调用即可!
具体操作为:在request的文件夹下的js文件中写上如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 let ajaxTimes=0 ; export const request = (params ) => { let header={...params.header } if (params.url .includes ("/my/" )){ header["Authorization" ]=wx.getStorageSync ("token" ); } ajaxTimes++ wx.showLoading ({ title : '加载中' , mask : true }) const baseUrl = "https://api-hmugo-web.itheima.net/api/public/v1" return new Promise ((resolve, reject ) => { wx.request ({ ...params, header, url : baseUrl + params.url , success : (result ) => { resolve (result.data .message ) }, fail : (err ) => { reject (err) }, complete : () => { ajaxTimes--; if (ajaxTimes===0 ){ wx.hideLoading () } } }); }) }
4.js代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import { request } from "../../request/index" ;Page ({ data : { swiperList : [], CateList :[], floorList :[] }, onLoad : function ( ) { this .getSwiperList (); this .getCateList (); this .getFloorList (); }, getSwiperList ( ) { request ({ url : '/home/swiperdata' }) .then (result => { this .setData ({ swiperList : result }) }) }, getCateList ( ) { request ({ url : '/home/catitems' }) .then (result => { this .setData ({ CateList : result }) }) }, getFloorList ( ) { request ({ url : '/home/floordata' }) .then (result => { this .setData ({ floorList : result }) }) } })
搜索框 搜索框的页面十分简陋,这里特别注意的是一点,针对移动端的用户体验问题。
一般来说,移动端很少会搜索信息的时候还需要点击”搜索”按钮才开始搜索,而对监听input框的事件设置延迟搜索效果更好,一定程度上减少了http发送次数,又优化了用户体验。
wxml和wxss的页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <view class ="search_row" > <input placeholder ="请输入您要搜索的商品" value ="{{inpValue}}" bindinput ="handleInput" > </input > <button hidden ="{{!isFocus}}" bindtap ="handleCancel" > 取消</button > </view > <view class ="search_content" > <navigator url ="/pages/goods_detail/index?goods_id={{item.goods_id}}" wx:for ="{{goods}}" wx:key ="goods_id" class ="search_item" > {{item.goods_name}} </navigator > </view >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 page { background-color : #dedede ; padding : 20 rpx; } view.search_row { height : 60 rpx; display : flex; } view.search_row input { background-color : #fff ; flex : 1 ; height : 100% ; padding-left : 30 rpx; } view.search_row button { width : 110 rpx; height : 100% ; font-size : 28 rpx; padding : 0 ; margin : 0 10 rpx; display : flex; justify-content : center; align-items : center; font-size : 26 rpx; font-weight : normal; } .search_content { margin-top : 40 rpx; } .search_content .search_item { background-color : #fff ; font-size : 26 rpx; padding : 15 rpx 10 rpx; border-bottom : 1 rpx solid #969595 ; overflow : hidden; white-space : nowrap; text-overflow : ellipsis; }
实机效果图
页面布局中,在搜索之前或者input框为空时,”取消”按钮将会消失这一小小的特点。而每一个搜索出来的列表项,都是导向商品详情页面下。
js功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import {request} from "../../request/index" Page ({ data : { goods :[], isFocus :false , inpValue :"" }, TimeId :-1 , handleInput (e ){ const {value}=e.detail if (!value.trim ()){ clearTimeout (this .TimeId ) this .TimeId =setTimeout (()=> { this .setData ({ goods :[], isFocus :false }) },1000 ) return } this .setData ({ isFocus :true }) clearTimeout (this .TimeId ); this .TimeId =setTimeout (()=> { this .qsearch (value) },1000 ) }, qsearch (query ){ request ({url :"/goods/qsearch" ,data :{query}}).then (res => { this .setData ({ goods :res }) }) }, handleCancel ( ){ this .setData ({ inpValue :"" , goods :[] }) } })
分类 页面布局
页面其实可以看做两个部分,其一是搜索框组件,其二是scroll-view内容。具体页面内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <view class ="cates" > <SearchInput > </SearchInput > //搜索框组件 <view class ="cates_container" > <scroll-view class ="left_menu" scroll-y scroll-top ="{{scrollTop}}" > <view class ="menu_item {{index===currentIndex?'active':''}}" wx:for ="{{leftMenuList}}" wx:key ="*this" bindtap ="handleItemTap" data-index ="{{index}}" > {{item}} </view > </scroll-view > <scroll-view class ="right_content" scroll-y > <view class ="goods_group" wx:for ="{{rightContent}}" wx:for-index ="index1" wx:for-item ="item1" > <view class ="goods_title" > <text class ="delimiter" > /</text > <text class ="title" > {{item1.cat_name}}</text > <text class ="delimiter" > /</text > </view > <view class ="goods_list" > <navigator wx:for ="{{item1.children}}" wx:for-index ="index2" wx:for-item ="item2" wx:key ="cat_id" url ="/pages/goods_list/index?cid={{item2.cat_id}}" > <image mode ="widthFix" src ="{{item2.cat_icon}}" > </image > <view class ="goods_name" > {{item.cat_name}}</view > </navigator > </view > </view > </scroll-view > </view > </view >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 page { height : 100% ; } .cates { height : 100% ; } .cates .cates_container { height : calc ( 100vh - 90 rpx ); display : flex; } .cates .cates_container .left_menu { flex : 2 ; } .cates .cates_container .left_menu .menu_item { height : 80 rpx; display : flex; justify-content : center; align-items : center; font-style : 30 rpx; } .cates .cates_container .left_menu .active { color : var (--themeColor); } .cates .cates_container .right_content { flex : 5 ; } .cates .cates_container .right_content .goods_group .goods_title { height : 80 rpx; display : flex; justify-content : center; align-items : center; } .cates .cates_container .right_content .goods_group .goods_title .delimiter { color : #ccc ; padding : 0 10 rpx; } .cates .cates_container .right_content .goods_group .goods_list { display : flex; flex-wrap : wrap; } .cates .cates_container .right_content .goods_group .goods_list navigator { width : 33.33% ; text-align : center; } .cates .cates_container .right_content .goods_group .goods_list navigator image { width : 50% ; }
js功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 import { request } from "../../request/index" ;Page ({ data : { leftMenuList :[], rightContent :[], currentIndex :0 , scrollTop :0 }, Cates :[], onLoad : function (options ) { const Cates =wx.getStorageSync ("cates" ); if (!Cates ){ this .getCates (); }else { if (Date .now ()-Cates .time >1000 *300 ){ this .getCates () }else { this .Cates =Cates .data ; this .dealListCont (); } } }, getCates ( ){ request ({ url :"/categories" }).then (res => { this .Cates =res; wx.setStorageSync ("cates" , {time :Date .now (),data :this .Cates }); this .dealListCont (); }) }, dealListCont ( ){ let leftMenuList=this .Cates .map (v => v.cat_name ) let rightContent=this .Cates [0 ].children ; this .setData ({ leftMenuList, rightContent }) }, handleItemTap (e ){ const {index}=e.currentTarget .dataset ; let rightContent=this .Cates [index].children ; this .setData ({ currentIndex :index, rightContent, scrollTop :0 }) } })
商品列表 在分类右侧商品栏中,点击每一个商品都会来到相应的商品1列表下。商品列表算是分类和商品详情页面的中转站,也是商城中必不可少的一栏。
列表页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <SearchInput > </SearchInput > <Tabs tabs ="{{tabs}}" bindtabsItemChange ="handleTabsItemChange" > //bindtabsItemChange绑定的是tabs组件传回的tabsItemChange这个事件名 <block wx:if ="{{tabs[0].isActive}}" > <view class ="first_tab" > <navigator class ="goods_item" wx:for ="{{goodsList}}" wx:key ="goods_id" url ="/pages/goods_detail/index?goods_id={{item.goods_id}}" > <view class ="goods_img_wrap" > <image class ="" src ="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode ="widthFix" > </image > </view > <view class ="goods_info_wrap" > <view class ="goods_name" > {{item.goods_name}}</view > <view class ="goods_price" > ¥ {{item.goods_price}}</view > </view > </navigator > </view > </block > <block wx:elif ="{{tabs[1].isActive}}" > //没有数据,否则可以显示其他有关销量数据的商品 </block > <block wx:else ="{{tabs[2].isActive}}" > //没有数据,否则可以显示其他有关价格数据的商品 </block > </Tabs >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 .first_tab .goods_item { display : flex; border-bottom : 2 rpx solid #ccc ; } .first_tab .goods_item .goods_img_wrap { flex : 2 ; display : flex; justify-content : center; align-items : center; } .first_tab .goods_item .goods_img_wrap image { width : 70% ; } .first_tab .goods_item .goods_info_wrap { flex : 3 ; display : flex; flex-direction : column; justify-content : space-around; } .first_tab .goods_item .goods_info_wrap .goods_name { display : -webkit-box; overflow : hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2 ; } .first_tab .goods_item .goods_info_wrap .goods_price { color : var (--themeColor); }
实机效果:
tab栏组件 tab切换的组件,原理同搜索框组件一样,不过它多了个父传子和子传父的数据传输过程。
tab组件布局 1 2 3 4 5 6 7 8 9 10 11 12 <view class ="tabs" > <view class ="tabs_title" > <view wx:for ="{{tabs}}" wx:key ="id" class ="title_item {{item.isActive?'active':''}}" bindtap ="handleItemTap" data-index ="{{index}}" > {{item.value}} </view > </view > <view class ="tabs_content" > <slot > </slot > </view > </view >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .tabs .tabs_title { display : flex; } .tabs .tabs_title .title_item { display : flex; justify-content : center; align-items : center; flex : 1 ; padding : 15 rpx 0 ; } .active { color : var (--themeColor); border-bottom : 5 rpx solid currentColor; }
js功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Component ({ properties : { tabs :{ type :Array , value :[] } }, data : { }, methods : { handleItemTap (e ){ const {index}=e.currentTarget .dataset ; this .triggerEvent ("tabsItemChange" ,{index}) } } })
js功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import { request } from "../../request/index" ;Page ({ data : { tabs : [ { id : 0 , value : "综合" , isActive : true }, { id : 1 , value : "销量" , isActive : false }, { id : 2 , value : "价格" , isActive : false } ], goodsList : [] }, QueryParams : { query : "" , cid : "" , pagenum : 1 , pagesize : 10 }, totalPages : 1 , onLoad : function (options ) { this .QueryParams .cid = options.cid ||"" ; this .QueryParams .query = options.query ||"" ; this .getGoodsList (); }, onReachBottom ( ) { if (this .QueryParams .pagenum >= this .totalPages ) { wx.showToast ({ title : '没有下一页数据了' , "icon" : "none" }); } else { this .QueryParams .pagenum ++; this .getGoodsList () } }, onPullDownRefresh ( ) { this .setData ({ goodsList : [] }) this .QueryParams .pagenum = 1 ; this .getGoodsList () }, getGoodsList ( ) { const res = request ({ url : '/goods/search' , data : this .QueryParams }) .then (res => { const total = res.total ; this .totalPages = Math .ceil (total / this .QueryParams .pagesize ) this .setData ({ goodsList : [...this .data .goodsList , ...res.goods ] }) wx.stopPullDownRefresh () }) }, handleTabsItemChange (e ) { const { index } = e.detail ; let { tabs } = this .data ; tabs.forEach ((v, i ) => i === index ? v.isActive = true : v.isActive = false ) this .setData ({ tabs }) } })
商品详情 详情页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <view class ="detail_swiper" > <swiper indicator-dots autoplay circular > <swiper-item wx:for ="{{goodsObj.pics}}" wx:key ="pics_id" data-url ="{{item.pics_mid}}" bindtap ="handlePreviewImage" > <image src ="{{item.pics_mid}}" mode ="widthFix" > </image > </swiper-item > </swiper > </view > <view class ="goods_price" > ¥{{goodsObj.goods_price}}</view > <view class ="goods_name_row" > <view class ="goods_name" > {{goodsObj.goods_name}}</view > <view class ="goods_collect" bindtap ="handleCollect" > <text class ="iconfont {{isCollect?'icon-Collection':'icon-shoucang1'}} " > </text > <view class ="collect_text" > 收藏</view > </view > </view > <view class ="goods_info" > <view class ="goods_info_title" > 图文详情</view > <view class ="goods_info_content" > <rich-text class ="" nodes ="{{goodsObj.goods_introduce}}" > </rich-text > </view > </view > <view class ="btm_tool" > <view class ="tool_item" > <view class ="iconfont icon-kefu" > </view > <view > 联系客服</view > <button open-type ="contact" > </button > </view > <view class ="tool_item" > <view class ="iconfont icon-share-line" > </view > <view > 分享</view > <button open-type ="share" > </button > </view > <navigator url ="/pages/cart/index" open-type ="switchTab" class ="tool_item" > <view class ="iconfont icon-gouwuchezhengpin" > </view > <view > 购物车</view > </navigator > <view class ="tool_item btn_cart" bindtap ="handleCartAdd" > 加入购物车 </view > <view class ="tool_item btn_buy" > 立即购买 </view > </view >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 page { padding-bottom : 90 rpx; } .detail_swiper swiper { height : 65vw ; text-align : center; } .detail_swiper swiper image { width : 60% ; } .goods_price { padding : 15 rpx; font-size : 32 rpx; font-weight : 600 ; color : var (--themeColor); } .goods_name_row { border-top : 5 rpx solid #dedede ; border-bottom : 5 rpx solid #ddeedd ; padding : 10 rpx 0 ; display : flex; } .goods_name_row .goods_name { flex : 5 ; color : #999 ; font-size : 26 rpx; padding : 0 10 rpx; display : -webkit-box; overflow : hidden; -webkit-line-clamp: 2 ; -webkit-box-orient: vertical; } .goods_name_row .goods_collect { flex : 1 ; display : flex; flex-direction : column; justify-content : center; align-items : center; border-left : 1 rpx solid #000 ; } .goods_name_row .goods_collect .icon-Collection { color : orangered; } .goods_name_row .goods_collect .collect_text { font-size : 26 rpx; color : #999 ; } .goods_info .goods_info_title { font-size : 32 rpx; color : var (--themeColor); font-weight : 600 ; padding : 20 rpx; } .btm_tool { border-top : 1 rpx solid #ccc ; position : fixed; left : 0 ; bottom : 0 ; width : 100% ; height : 90 rpx; background-color : #fff ; display : flex; } .btm_tool .tool_item { flex : 1 ; display : flex; flex-direction : column; justify-content : center; align-items : center; font-size : 24 rpx; position : relative; } .btm_tool .tool_item button { position : absolute; top : 0 ; left : 0 ; width : 100% ; height : 100% ; opacity : 0 ; } .btm_tool .btn_cart { flex : 2 ; background-color : #ffa500 ; color : #fff ; font-weight : 600 ; font-size : 30 rpx; } .btm_tool .btn_buy { flex : 2 ; background-color : #eb4450 ; color : #fff ; font-weight : 600 ; font-size : 30 rpx; }
js功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 import { request } from "../../request/index" ;Page ({ data : { goodsObj :{}, isCollect :false }, GoodsInfo :{}, onShow : function ( ) { let pages= getCurrentPages (); let currentPage=pages[pages.length -1 ] let options=currentPage.options const {goods_id}=options; this .getGoodsDetail (goods_id) }, getGoodsDetail (goods_id ){ request ({url :'/goods/detail' ,data :{goods_id}}) .then (res => { let goodsObj=res; this .GoodsInfo =res; let collect=wx.getStorageSync ("collect" )||[]; let isCollect=collect.some (v => v.goods_id ===this .GoodsInfo .goods_id ) this .setData ({ goodsObj :{ goods_name :goodsObj.goods_name , goods_price :goodsObj.goods_price , goods_introduce :goodsObj.goods_introduce .replace (/\.webp/g ,'.jpg' ), pics :goodsObj.pics }, isCollect }) }) }, handlePreviewImage (e ){ const urls=this .GoodsInfo .pics .map (v => v.pics_mid ) const current=e.currentTarget .dataset .url wx.previewImage ({ current, urls }); }, handleCartAdd ( ){ let cart=wx.getStorageSync ("cart" )||[]; let index=cart.findIndex (v => v.goods_id ===this .GoodsInfo .goods_id ) if (index===-1 ){ this .GoodsInfo .num =1 this .GoodsInfo .checked =true cart.push (this .GoodsInfo ) }else { cart[index].num ++ } wx.setStorageSync ("cart" , cart); wx.showToast ({ title : '加入成功' , icon : 'success' , duration : 1500 , mask : true }); }, handleCollect ( ){ let isCollect=false ; let collect=wx.getStorageSync ("collect" )||[]; let index=collect.findIndex (v => v.goods_id ===this .GoodsInfo .goods_id ) if (index!=-1 ){ collect.splice (index,1 ) isCollect=false wx.showToast ({ title : '取消成功' , icon : 'success' , image : '' , duration : 1500 , mask : true }); }else { collect.push (this .GoodsInfo ) isCollect=true wx.showToast ({ title : '收藏成功' , icon : 'success' , image : '' , duration : 1500 , mask : true }); } wx.setStorageSync ("collect" , collect); this .setData ({ isCollect }) } })
页面完成图
其中收藏、联系客服、分享、购物车、加入购物车功能均已实现,而立即购买仍没有去实现!
购物车 页面布局 购物车页面布局很简单,只有添加收货地址和商品列表这两大项,重难点在于js的代码实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <view class ="recieve_address_row" > <view class ="address_btn" wx:if ="{{!address.userName}}" > <button type ="primary" bindtap ="handleChooseAddress" plain > 获取收货地址</button > </view > <view wx:elese class ="user_info_row" > <view class ="user_info" > <view > {{address.userName}}</view > <view > {{address.all}}</view > </view > <view class ="user_phone" > {{address.telNumber}}</view > </view > </view > <view class ="cart_content" > <view class ="cart_title" > 购物车</view > <view class ="cart_main" > <block wx:if ="{{cart.length!=0}}" > <view class ="cart_item" wx:for ="{{cart}}" wx:key ="goods_id" > <view class ="cart_chk_wrap" > <checkbox-group data-id ="{{item.goods_id}}" bindchange ="handleItemChange" > <checkbox checked ="{{item.checked}}" > </checkbox > </checkbox-group > </view > <navigator class ="cart_img_wrap" url ="" > <image class ="" src ="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode ="widthFix" > </image > </navigator > <view class ="cart_info_wrap" > <view class ="goods_name" > {{item.goods_name}}</view > <view class ="goods_prie_wrap" > <view class ="goods_price" > ¥{{item.goods_price}}</view > <view class ="cart_num_tool" > <view bindtap ="handleItemNumEdit" data-id ="{{item.goods_id}}" data-operation ="{{-1}}" class ="num_edit" > -</view > <view class ="goods_num" > {{item.num}}</view > <view bindtap ="handleItemNumEdit" data-id ="{{item.goods_id}}" data-operation ="{{1}}" class ="num_edit" > +</view > </view > </view > </view > </view > </block > <block wx:else > <image class ="" src ="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1597867170872&di=16a56791de9a4a330bb64ba090fad32c&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2Fe1b1467beea0a9c7d6a56b32bac6d7e5dcd914f7c3e6-YTwUd6_fw658" mode ="widthFix" > </image > </block > </view > </view > <view class ="footer_tool" > <view class ="all_chk_wrap" > <checkbox-group bindchange ="handleItemAllCheck" > <checkbox checked ="{{allChecked}}" > 全选</checkbox > </checkbox-group > </view > <view class ="total_price_wrap" > <view class ="total_price" > 合计:<text class ="total_price_text" > ¥{{totalPrice}}</text > </view > <view > 包含运费</view > </view > <view class ="order_pay_wrap" bindtap ="handlePay" > 结算({{totalNum}}) </view > </view >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 page { padding-bottom : 90 rpx; } .recieve_address_row .address_btn { padding : 20 rpx; } .recieve_address_row .address_btn button { width : 60% ; } .recieve_address_row .user_info_row { display : flex; padding : 20 rpx; } .recieve_address_row .user_info_row .user_info { flex : 5 ; } .recieve_address_row .user_info_row .user_phone { flex : 3 ; text-align : right; } .cart_content .cart_title { padding : 20 rpx; font-size : 36 rpx; color : var (--themeColor); border-top : 1 rpx solid currentColor; border-bottom : 1 rpx solid currentColor; } .cart_content .cart_main .cart_item { display : flex; padding : 10 rpx; border-bottom : 1 rpx solid #ccc ; } .cart_content .cart_main .cart_item .cart_chk_wrap { flex : 1 ; display : flex; justify-content : center; align-items : center; } .cart_content .cart_main .cart_item .cart_img_wrap { flex : 2 ; display : flex; justify-content : center; align-items : center; } .cart_content .cart_main .cart_item .cart_img_wrap image { width : 80% ; } .cart_content .cart_main .cart_item .cart_info_wrap { flex : 4 ; display : flex; flex-direction : column; justify-content : space-around; padding : 0 20 rpx; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_name { display : -webkit-box; overflow : hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2 ; color : #666 ; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap { display : flex; justify-content : space-between; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .goods_price { color : var (--themeColor); font-size : 34 rpx; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool { display : flex; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool .num_edit { width : 55 rpx; height : 55 rpx; display : flex; justify-content : center; align-items : center; border : 1 rpx solid #ccc ; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool .goods_num { width : 55 rpx; height : 55 rpx; display : flex; justify-content : center; align-items : center; } .footer_tool { position : fixed; bottom : 0 ; left : 0 ; width : 100% ; height : 90 rpx; background-color : #fff ; display : flex; border-top : 1 rpx solid #ccc ; } .footer_tool .all_chk_wrap { flex : 2 ; display : flex; justify-content : center; align-items : center; } .footer_tool .total_price_wrap { flex : 5 ; padding-right : 15 rpx; text-align : right; } .footer_tool .total_price_wrap .total_price .total_price_text { color : var (--themeColor); font-size : 34 rpx; font-weight : 600 ; } .footer_tool .order_pay_wrap { flex : 3 ; background-color : var (--themeColor); color : #fff ; font-size : 32 rpx; font-weight : 600 ; display : flex; justify-content : center; align-items : center; }
js功能实现 utils封装函数 由于涉及wx的封装函数过多,故而在utils中封装了一些函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 export const getSetting =( )=>{ return new Promise ((resolve,reject )=> { wx.getSetting ({ success : (result )=> { resolve (result) }, fail : (err )=> { reject (err) } }); }) } export const chooseAddress =( )=>{ return new Promise ((resolve,reject )=> { wx.chooseAddress ({ success : (result )=> { resolve (result) }, fail : (err )=> { reject (err) } }); }) } export const openSetting =( )=>{ return new Promise ((resolve,reject )=> { wx.openSetting ({ success : (result )=> { resolve (result) }, fail : (err )=> { reject (err) } }); }) } export const showModal =({content} )=>{ return new Promise ((resolve,reject )=> { wx.showModal ({ title : '提示' , content, showCancel : true , cancelText : '取消' , cancelColor : '#000000' , confirmText : '确定' , confirmColor : '#3CC51F' , success : (res ) => { resolve (res) }, fail :(err )=> { reject (err) } }); }) } export const showToast =({title} )=>{ return new Promise ((resolve,reject )=> { wx.showToast ({ title, icon : 'none' , success : (res )=> { resolve (res) }, fail : (err )=> { reject (err) } }); }) } export const login =( )=>{ return new Promise ((resolve,reject )=> { wx.login ({ timeout :10000 , success : (res )=> { resolve (res) }, fail : (err )=> { reject (res) } }); }) } export const requestPayment =(pay )=>{ return new Promise ((resolve,reject )=> { wx.requestPayment ({ ...pay, success : (result )=> { resolve (result) }, fail : (err )=> { reject (err) } }); }) }
js代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 import { request } from "../../request/index" ;import { getSetting, chooseAddress, openSetting,showModal,showToast } from "../../utils/asyncWx" ;Page ({ data : { address :{}, cart :[], allChecked :false , totalPrice :0 , totalNum :0 }, onShow ( ){ const address=wx.getStorageSync ("address" ); const cart=wx.getStorageSync ("cart" )||[]; this .setData ({address}) this .setCart (cart) }, onLoad : function (options ) { }, handleChooseAddress ( ) { try { getSetting ().then (res => { const scopAddress = res.authSetting ["scope.address" ] if (scopAddress === false ) { openSetting () }else { chooseAddress ().then (res => { res.all =res.provinceName +res.cityName +res.countyName +res.detailInfo wx.setStorageSync ("address" , res); }) } }) } catch (error) { console .log (error); } }, handleItemChange (e ){ const goods_id=e.currentTarget .dataset .id let {cart}=this .data ; let index=cart.findIndex (v => v.goods_id ===goods_id) cart[index].checked =!cart[index].checked this .setCart (cart) }, setCart (cart ){ let allChecked=true ; let totalPrice=0 ; let totalNum=0 ; cart.forEach (v => { if (v.checked ){ totalPrice+=v.num *v.goods_price totalNum+=v.num ; }else { allChecked=false } }) allChecked=cart.length !=0 ?allChecked :false this .setData ({ cart, allChecked, totalPrice, totalNum }) wx.setStorageSync ("cart" , cart); }, handleItemAllCheck ( ){ let {cart,allChecked}=this .data allChecked=!allChecked cart.forEach (v => v.checked =allChecked) this .setCart (cart) }, handleItemNumEdit (e ){ let {operation,id}=e.currentTarget .dataset let {cart} = this .data const index=cart.findIndex (v => v.goods_id ===id) if (cart[index].num ===1 &&operation===-1 ){ showModal ({content :"您是否要删除?" }).then (res => { if (res.confirm ){ cart.splice (index,1 ) this .setCart (cart) } }) }else { cart[index].num +=operation this .setCart (cart) } }, handlePay ( ){ const {address,totalNum}=this .data ; if (!address.userName ){ showToast ({title :"您还没有选择收货地址" }) return } if (totalNum===0 ){ showToast ({title :"您还没有选购商品" }) return } wx.navigateTo ({ url : '/pages/pay/index' , }); } })
页面图
支付 此项功能是调取wx自带的函数,需要appId为商用id才可以,具体流程是,获取登录的用户token,根据token才能够实现支付效果。我不能通过此项步骤,实机操作会出现报错,所以只能伪装获取到token以及跳过支付界面!
页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <view class ="recieve_address_row" > <view class ="user_info_row" > <view class ="user_info" > <view > {{address.userName}}</view > <view > {{address.all}}</view > </view > <view class ="user_phone" > {{address.telNumber}}</view > </view > </view > <view class ="cart_content" > <view class ="cart_title" > 购物车</view > <view class ="cart_main" > <view class ="cart_item" wx:for ="{{cart}}" wx:key ="goods_id" > <navigator class ="cart_img_wrap" url ="" > <image class ="" src ="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode ="widthFix" > </image > </navigator > <view class ="cart_info_wrap" > <view class ="goods_name" > {{item.goods_name}}</view > <view class ="goods_prie_wrap" > <view class ="goods_price" > ¥{{item.goods_price}}</view > <view class ="cart_num_tool" > <view class ="goods_num" > X {{item.num}}</view > </view > </view > </view > </view > </view > </view > <view class ="footer_tool" > <view class ="total_price_wrap" > <view class ="total_price" > 合计:<text class ="total_price_text" > ¥{{totalPrice}}</text > </view > <view > 包含运费</view > </view > <view class ="order_pay_wrap" bindtap ="handleOderPay" > 支付({{totalNum}}) </view > </view >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 page { padding-bottom : 90 rpx; } .recieve_address_row .user_info_row { display : flex; padding : 20 rpx; } .recieve_address_row .user_info_row .user_info { flex : 5 ; } .recieve_address_row .user_info_row .user_phone { flex : 3 ; text-align : right; } .cart_content .cart_title { padding : 20 rpx; font-size : 36 rpx; color : var (--themeColor); border-top : 1 rpx solid currentColor; border-bottom : 1 rpx solid currentColor; } .cart_content .cart_main .cart_item { display : flex; padding : 10 rpx; border-bottom : 1 rpx solid #ccc ; } .cart_content .cart_main .cart_item .cart_img_wrap { flex : 2 ; display : flex; justify-content : center; align-items : center; } .cart_content .cart_main .cart_item .cart_img_wrap image { width : 80% ; } .cart_content .cart_main .cart_item .cart_info_wrap { flex : 4 ; display : flex; flex-direction : column; justify-content : space-around; padding : 0 20 rpx; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_name { display : -webkit-box; overflow : hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2 ; color : #666 ; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap { display : flex; justify-content : space-between; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .goods_price { color : var (--themeColor); font-size : 34 rpx; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool { display : flex; } .cart_content .cart_main .cart_item .cart_info_wrap .goods_prie_wrap .cart_num_tool .goods_num { width : 55 rpx; height : 55 rpx; display : flex; justify-content : center; align-items : center; } .footer_tool { position : fixed; bottom : 0 ; left : 0 ; width : 100% ; height : 90 rpx; background-color : #fff ; display : flex; border-top : 1 rpx solid #ccc ; } .footer_tool .total_price_wrap { flex : 5 ; padding-right : 15 rpx; text-align : right; } .footer_tool .total_price_wrap .total_price .total_price_text { color : var (--themeColor); font-size : 34 rpx; font-weight : 600 ; } .footer_tool .order_pay_wrap { flex : 3 ; background-color : var (--themeColor); color : #fff ; font-size : 32 rpx; font-weight : 600 ; display : flex; justify-content : center; align-items : center; }
auth页面 登录获取token 1 <button type ="primary" plain open-type ="getUserInfo" bindgetuserinfo ="handleGetUserInfo" > 获取授权</button >
1 2 3 4 button { width : 70% ; margin-top : 40 rpx; }
js实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import { request,request2 } from "../../request/index" ;import { login } from "../../utils/asyncWx" ;Page ({ data : { }, onLoad : function (options ) { }, handleGetUserInfo (e ) { try { const { encryptedData, rawData, iv, signature } = e.detail login ().then (res => { const {code}=res; const loginParams={ encryptedData, rawData, iv, signature ,code} request ({url :'/users/wxlogin' ,data :loginParams,method :"POST" }) .then (res => { wx.setStorageSync ("token" , "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIzLCJpYXQiOjE1NjQ3MzAwNzksImV4cCI6MTAwMTU2NDczMDA3OH0.YPt-XeLnjV-_1ITaXGY2FhxmCe4NvXuRnRB8OMCfnPo" ); wx.navigateBack ({ delta : 1 }); }) }) } catch (error) { console .log (error); } } })
支付功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 import { request } from "../../request/index" ;import { getSetting, chooseAddress, openSetting, showModal, showToast, requestPayment } from "../../utils/asyncWx" ;Page ({ data : { address : {}, cart : [], totalPrice : 0 , totalNum : 0 }, onShow ( ) { const address = wx.getStorageSync ("address" ); let cart = wx.getStorageSync ("cart" ) || []; cart = cart.filter (v => v.checked ) let totalPrice = 0 ; let totalNum = 0 ; cart.forEach (v => { totalPrice += v.num * v.goods_price totalNum += v.num ; }) this .setData ({ cart, totalPrice, totalNum, address }) }, onLoad : function (options ) { }, handleOderPay ( ) { try { const token = wx.getStorageSync ("token" ); if (!token) { wx.navigateTo ({ url : '/pages/auth/index' }); return } const order_price = this .data .totalPrice ; const consignee_addr = this .data .address .all ; const cart = this .data .cart ; let goods = [] cart.forEach (v => goods.push ({ goods_id : v.goods_id , goods_number : v.num , goods_price : v.goods_price })) const orderParams = { order_price, consignee_addr, goods } request ({ url : "/my/orders/create" , method : "POST" , data : orderParams}) .then (res => { const order_number = res.order_number request ({ url : "/my/orders/req_unifiedorder" , method : "POST" , data : { order_number } }) .then (res => { showToast ({ title : "支付成功!" }).then (res => { console .log ({ errMsg : "requestPayment:ok" }); }) let newCart=wx.getStorageSync ("cart" ); newCart=newCart.filter (v => !v.checked ) wx.setStorageSync ("cart" , newCart); wx.redirectTo ({ url : '/pages/order/index' , }); }) }) } catch (error) { showToast ({ title : "支付失败" }).then (res => { console .log ({ errMsg : "requestPayment:cancel" }); }) } } })
页面图
我的 页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 <view class ="user_info_wrap" > <view wx:if ="{{userinfo.avatarUrl}}" class ="user_img_wrap" > <image class ="user_bg" src ="{{userinfo.avatarUrl}}" > </image > <view class ="user_info" > <image class ="user_icon" src ="{{userinfo.avatarUrl}}" > </image > <view class ="user_name" > {{userinfo.nickName}}</view > </view > </view > <view wx:else class ="user_btn" > <navigator class ="" target ="" url ="/pages/login/index" hover-class ="navigator-hover" open-type ="navigate" > 登录 </navigator > </view > </view > <view class ="user_content" > <view class ="user_main" > <view class ="history_wrap" > <navigator url ="" > <view class ="his_num" > 0</view > <view class ="his_name" > 收藏的店铺</view > </navigator > <navigator url ="/pages/collect/index" > <view class ="his_num" > {{collectNums}}</view > <view class ="his_name" > 收藏的商品</view > </navigator > <navigator url ="" > <view class ="his_num" > 0</view > <view class ="his_name" > 关注的商品</view > </navigator > <navigator url ="" > <view class ="his_num" > 0</view > <view class ="his_name" > 我的足迹</view > </navigator > </view > <view class ="orders_wrap" > <view class ="orders_title" > 我的订单</view > <view class ="order_content" > <navigator class ="" target ="" url ="/pages/order/index?type=1" hover-class ="navigator-hover" open-type ="navigate" > <view class ="iconfont icon-dingdan-" > </view > <view class ="order_name" > 全部订单</view > </navigator > <navigator class ="" target ="" url ="/pages/order/index?type=2" hover-class ="navigator-hover" open-type ="navigate" > <view class ="iconfont icon-fukuantongzhi" > </view > <view class ="order_name" > 待付款</view > </navigator > <navigator class ="" target ="" url ="/pages/order/index?type=3" hover-class ="navigator-hover" open-type ="navigate" > <view class ="iconfont icon-huoche" > </view > <view class ="order_name" > 待收货</view > </navigator > <navigator > <view class ="iconfont icon-tuihuotuikuan-" > </view > <view class ="order_name" > 退款/退货</view > </navigator > </view > </view > <view class ="address_wrap" > 收货地址管理 </view > <view class ="app_info_wrap" > <view class ="app_info_item app_info_contact" > <text > 联系客服</text > <text > 400-618-4000</text > </view > <navigator url ="/pages/feedback/index" class ="app_info_item" > 意见反馈</navigator > <view class ="app_info_item" > 关于我们</view > </view > <view class ="recommend_wrap" > 把应用推荐给他人 </view > </view > </view >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 page { background-color : #ccc ; } .user_info_wrap { height : 40vh ; background-color : var (--themeColor); position : relative; } .user_info_wrap .user_img_wrap { position : relative; } .user_info_wrap .user_img_wrap .user_bg { width : 100% ; height : 45vh ; filter : blur (6 rpx); } .user_info_wrap .user_img_wrap .user_info { position : absolute; left : 50% ; transform : translateX (-50% ); top : 20% ; text-align : center; } .user_info_wrap .user_img_wrap .user_info .user_icon { width : 150 rpx; height : 150 rpx; border-radius : 50% ; z-index : 100 ; margin : 0 auto; } .user_info_wrap .user_img_wrap .user_info .user_name { color : #fff ; margin-top : 40 rpx; font-size : 40 rpx; } .user_info_wrap .user_btn { position : absolute; left : 50% ; transform : translateX (-50% ); top : 40% ; border : 1 rpx solid greenyellow; color : greenyellow; font-size : 38 rpx; padding : 30 rpx; border-radius : 10 rpx; } .user_content { position : relative; } .user_content .user_main { position : absolute; width : 90% ; left : 50% ; transform : translateX (-50% ); top : 40 rpx; color : #666 ; padding-bottom : 100 rpx; } .user_content .user_main .history_wrap { background-color : #fff ; display : flex; flex-direction : row; } .user_content .user_main .history_wrap navigator { flex : 1 ; text-align : center; padding : 10 rpx 0 ; } .user_content .user_main .history_wrap navigator .his_num { color : var (--themeColor); } .user_content .user_main .orders_wrap { background-color : #fff ; margin-top : 30 rpx; } .user_content .user_main .orders_wrap .orders_title { padding : 20 rpx; border-bottom : 1 rpx solid #ccc ; } .user_content .user_main .orders_wrap .order_content { display : flex; } .user_content .user_main .orders_wrap .order_content navigator { flex : 1 ; text-align : center; padding : 15 rpx 0 ; } .user_content .user_main .orders_wrap .order_content navigator .iconfont { color : var (--themeColor); font-size : 40 rpx; } .user_content .user_main .address_wrap { margin-top : 30 rpx; background-color : #fff ; padding : 20 rpx; } .user_content .user_main .app_info_wrap { margin-top : 30 rpx; background-color : #fff ; } .user_content .user_main .app_info_wrap .app_info_item { padding : 20 rpx; border-bottom : 1 rpx solid #ccc ; } .user_content .user_main .app_info_contact { display : flex; justify-content : space-between; } .user_content .user_main .recommend_wrap { margin-top : 30 rpx; background-color : #fff ; padding : 20 rpx; }
收藏浏览 页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <Tabs tabs ="{{tabs}}" bindtabsItemChange ="handleTabsItemChange" > <view class ="collect_main" > <view class ="collect_title" > <view class ="collect_tips active" > 全部</view > <view class ="collect_tips" > 正在热卖</view > <view class ="collect_tips" > 即将上线</view > </view > <view class ="collect_content" > <navigator class ="goods_item" wx:for ="{{collect}}" wx:key ="goods_id" url ="/pages/goods_detail/index?goods_id={{item.goods_id}}" > <view class ="goods_img_wrap" > <image class ="" src ="{{item.goods_small_logo?item.goods_small_logo:'https://ww1.sinaimg.cn/large/007rAy9hgy1g24by9t530j30i20i2glm.jpg'}}" mode ="widthFix" > </image > </view > <view class ="goods_info_wrap" > <view class ="goods_name" > {{item.goods_name}}</view > <view class ="goods_price" > ¥ {{item.goods_price}}</view > </view > </navigator > </view > </view > </Tabs >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 .collect_main { background-color : #f3f4f6 ; } .collect_main .collect_title { padding : 30 rpx 0 ; display : flex; } .collect_main .collect_title .collect_tips { padding : 15 rpx; border : 1 rpx solid #ccc ; margin-left : 25 rpx; background-color : #fff ; } .collect_main .collect_title .active { color : var (--themeColor); border-color : currentColor; } .collect_main .collect_content .goods_item { display : flex; border-bottom : 2 rpx solid #ccc ; background-color : #fff ; } .collect_main .collect_content .goods_item .goods_img_wrap { flex : 2 ; display : flex; justify-content : center; align-items : center; } .collect_main .collect_content .goods_item .goods_img_wrap image { width : 70% ; } .collect_main .collect_content .goods_item .goods_info_wrap { flex : 3 ; display : flex; flex-direction : column; justify-content : space-around; } .collect_main .collect_content .goods_item .goods_info_wrap .goods_name { display : -webkit-box; overflow : hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2 ; } .collect_main .collect_content .goods_item .goods_info_wrap .goods_price { color : var (--themeColor); }
收藏界面js功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 Page ({ data : { collect :[], tabs : [ { id : 0 , value : "商品收藏" , isActive : true }, { id : 1 , value : "品牌收藏" , isActive : false }, { id : 2 , value : "店铺收藏" , isActive : false }, { id : 3 , value : "浏览足迹" , isActive : false } ], }, onShow : function ( ) { const collect=wx.getStorageSync ("collect" )||[]; this .setData ({ collect }) }, handleTabsItemChange (e ) { const { index } = e.detail ; let { tabs } = this .data ; tabs.forEach ((v, i ) => i === index ? v.isActive = true : v.isActive = false ) this .setData ({ tabs }) } })
页面图
登录 页面布局 1 <button type ="primary" plain open-type ="getUserInfo" bindgetuserinfo ="handleGetUserInfo" > 登录</button >
1 2 3 4 button { width : 70% ; margin-top : 40 rpx; }
js功能实现 1 2 3 4 5 6 7 8 9 10 11 Page ({ handleGetUserInfo (e ){ const {userInfo}=e.detail wx.setStorageSync ("userinfo" , userInfo); wx.navigateBack ({ delta : 1 }); } })
订单 页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <Tabs tabs ="{{tabs}}" bindtabsItemChange ="handleTabsItemChange" > <view class ="order_main" > <view wx:for ="{{orders}}" wx:key ="order_id" class ="order_item" > <view class ="order_no_row" > <view class ="order_no_text" > 订单编号</view > <view class ="order_no_value" > {{item.order_number}}</view > </view > <view class ="order_price_row" > <view class ="order_price_text" > 订单价格</view > <view class ="order_price_value" > ¥{{item.order_price}}</view > </view > <view class ="order_time_row" > <view class ="order_time_text" > 订单日期</view > <view class ="order_time_value" > {{item.create_time_cn}}</view > </view > </view > </view > </Tabs >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 .order_main .order_item { padding : 20 rpx; border-bottom : 1 rpx solid #ccc ; color : #666 ; } .order_main .order_item .order_no_row { display : flex; justify-content : space-between; padding : 10 rpx 0 ; } .order_main .order_item .order_price_row { display : flex; justify-content : space-between; padding : 10 rpx 0 ; } .order_main .order_item .order_price_row .order_price_value { color : var (--themeColor); font-size : 32 rpx; } .order_main .order_item .order_time_row { display : flex; justify-content : space-between; padding : 10 rpx 0 ; }
js功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 const { request } = require ("../../request/index" );Page ({ data : { orders :[], tabs : [ { id : 0 , value : "全部" , isActive : true }, { id : 1 , value : "待付款" , isActive : false }, { id : 2 , value : "待收货" , isActive : false }, { id : 3 , value : "退款/退货" , isActive : false } ], }, onShow : function (options ) { const token=wx.getStorageSync ("token" ); if (!token){ wx.navigateTo ({ url : '/pages/auth/index' , }); return } let pages= getCurrentPages (); let currentPage=pages[pages.length -1 ] const {type}=currentPage.options this .changeTitleByIndex (type-1 ) this .getOrders (type) }, getOrders (type ){ request ({url :"/my/orders/all" ,data :{type}}) .then (res => { this .setData ({ orders :res.orders .map (v => ({...v,create_time_cn :(new Date (v.create_time *1000 ).toLocaleString ())})), }) }) }, changeTitleByIndex (index ){ let { tabs } = this .data ; tabs.forEach ((v, i ) => i === index ? v.isActive = true : v.isActive = false ) this .setData ({ tabs }) }, handleTabsItemChange (e ) { const { index } = e.detail ; this .changeTitleByIndex (index); this .getOrders (index+1 ) } })
订单页面图
反馈 页面布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <Tabs tabs ="{{tabs}}" bindtabsItemChange ="handleTabsItemChange" > <view class ="fb_main" > <view class ="fb_title" > 问题的种类</view > <view class ="fb_tips" > <text > 功能建议</text > <text > 购买遇到问题</text > <text > 性能问题</text > <text > 其他</text > </view > <view class ="fb_content" > <textarea placeholder ="请描述您的问题" value ="{{textVal}}" bindinput ="handleTextInput" > </textarea > <view class ="fb_tool" > <button bindtap ="handleChooseImg" > +</button > <view class ="up_img_item" wx:for ="{{chooseImgs}}" wx:key ="*this" bindtap ="handleRemoveImg" data-index ="{{index}}" > <UpImg src ="{{item}}" > </UpImg > </view > </view > </view > <view class ="form_btn_wrap" > <button type ="warn" bindtap ="handleFormSubmit" > <icon type ="success_no_circle" size ="23" color ="white" > </icon > 提交 </button > </view > </view > </Tabs >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 page { background-color : #eeeeee ; } .fb_main { padding : 20 rpx; color : #666 ; } .fb_main .fb_tips { display : flex; flex-wrap : wrap; } .fb_main .fb_tips text { width : 30% ; padding : 10 rpx; text-align : center; background-color : #fff ; margin : 20 rpx 10 rpx; } .fb_main .fb_content { background-color : #fff ; margin-top : 20 rpx; } .fb_main .fb_content textarea { padding : 10 rpx; } .fb_main .fb_content .fb_tool { display : flex; flex-wrap : wrap; padding-bottom : 30 rpx; } .fb_main .fb_content .fb_tool button { width : 90 rpx; height : 90 rpx; font-size : 60 rpx; padding : 0 ; display : flex; justify-content : center; align-items : center; color : #ccc ; margin : 0 ; margin-left : 20 rpx; margin-top : 20 rpx; } .fb_main .fb_content .fb_tool .up_img_item { margin-left : 20 rpx; margin-top : 20 rpx; } .fb_main .form_btn_wrap { margin-top : 20 rpx; display : flex; justify-content : flex-end; } .fb_main .form_btn_wrap button { margin : 0 ; width : 30% ; display : flex; align-items : center; justify-content : center; }
js功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 Page ({ data : { tabs : [ { id : 0 , value : "体验问题" , isActive : true }, { id : 1 , value : "商品、商家投诉" , isActive : false } ], chooseImgs :[], textVal :"" }, UploadImgs :[], handleTabsItemChange (e ) { const { index } = e.detail ; let { tabs } = this .data ; tabs.forEach ((v, i ) => i === index ? v.isActive = true : v.isActive = false ) this .setData ({ tabs }) }, handleChooseImg ( ){ wx.chooseImage ({ count : 9 , sizeType : ['original' ,'compressed' ], sourceType : ['album' ,'camera' ], success : (result )=> { this .setData ({ chooseImgs :[...this .data .chooseImgs ,...result.tempFilePaths ] }) } }); }, handleRemoveImg (e ){ const {index}=e.currentTarget .dataset let {chooseImgs}=this .data chooseImgs.splice (index,1 ) this .setData ({ chooseImgs }) }, handleTextInput (e ){ this .setData ({ textVal :e.detail .value }) }, handleFormSubmit (e ){ const {textVal,chooseImgs}=this .data if (!textVal.trim ()){ wx.showToast ({ title : '输入不合法' , icon : 'none' , mask : true }); return } wx.showLoading ({ title : "正在上传中" , mask : true }); if (chooseImgs.length !=0 ){ chooseImgs.forEach ((v,i )=> { wx.uploadFile ({ url : 'https://media.mogu.com/image/scale?appKey=15m&w=500&h=500&quality=100' , filePath : v, name : "image" , formData : {}, success : (result )=> { let url=JSON .parse (result.data ).result .url this .UploadImgs .push (url) if (i===chooseImgs.length -1 ){ wx.hideLoading (); console .log ("把文本的内容和外网的图片数组 提交到后台中" ); this .setData ({ textVal :"" , chooseImgs :[] }) wx.showToast ({ title : '全部上传成功' , icon : 'none' , duration : 1500 , mask : true , }); wx.navigateBack ({ delta : 1 }); } } }); }) }else { wx.hideLoading (); console .log ("只是提交了文本" ); wx.navigateBack ({ delta : 1 }); } } })
反馈页面图
js功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Page ({ data : { userinfo :{}, collectNums :0 }, onShow : function ( ) { const userinfo=wx.getStorageSync ("userinfo" ); const collect=wx.getStorageSync ("collect" )||[]; this .setData ({ userinfo, collectNums :collect.length }) }, })
总结 此次微信小程序,大多是和线上视频的老师跟打的。大多数的原理实现以及页面布局还算清楚,可从6.购物车 开始,特别是支付那一段,的确有些晦涩别扭,是后续复习的注重点。
笔记于2020/8/24 20:50完成
纪念!