Chapter1
1.问题
-
什么是微信小程序?
-
为什么要做小程序?
-
如何开发小程序?
2.环境搭建
-
申请微信公众平台
-
登录后选择开发管理的开发控制并保存appid
-
在开发小程序中有开发工具,下载稳定版
-
创建项目
-
扫码登录
-
点击小程序添加
-
指定文件位置和文件名
-
将appid粘贴,并选择javascript
-
-
文件介绍
-
pages文件夹存放页面
-
utils文件夹存放工具
-
js写js代码,json写配置,wxss写css代码,wxml写html代码
-
最外层的时全局页面文件,都可以被应用
-
如果没有需要创建app.json
{ #放置页面路径 "pages":[ "pages/index/index", "pages/logs/logs" ], }
-
如果要在手机上看点击最上方的预览,如果没有创建app.js需要创建。
-
wxml
#代表span <text></text> #代表div <view></view>
-
3.小程序
3.1 全局配置
-
app.json决定页面路劲、窗口表现、设置网络超时时间、设置多tab等。
-
配置项
-
小程序根目录下的
app.json
文件用来对微信小程序进行全局配置。文件内容为一个 JSON 对象,有以下属性:属性 类型 必填 描述 entryPagePath string 否 小程序默认启动首页 pages string 是 页面路径列表 window Object 否 全局的默认窗口表现 tabBar Object 否 底部 tab
栏的表现
-
-
entryPagePath
-
指定小程序的默认启动路径(首页),常见情景是从微信聊天列表页下拉启动、小程序列表启动等。如果不填,将默认为
pages
列表的第一项。不支持带页面路径参数。{ "entryPagePath": "pages/index/index" }
-
-
pages
-
用于指定小程序由哪些页面组成,每一项都对应一个页面的 路径(含文件名) 信息。文件名不需要写文件后缀,框架会自动去寻找对应位置的
.json
,.js
,.wxml
,.wxss
四个文件进行处理。 -
未指定
entryPagePath
时,数组的第一项代表小程序的初始页面(首页)。 -
则需要在 app.json 中写
{ "pages": ["pages/index/index", "pages/logs/logs"] }
-
-
window
-
用于设置小程序的状态栏、导航条、标题、窗口背景色。
属性 类型 默认值 描述 navigationBarBackgroundColor HexColor #000000 导航栏背景颜色,如 #000000
navigationBarTextStyle string white 导航栏标题颜色,仅支持 black
/white
navigationBarTitleText string 导航栏标题文字内容 navigationStyle string default 导航栏样式,仅支持以下值: default
默认样式custom
自定义导航栏,只保留右上角胶囊按钮。参见注 2。backgroundColor HexColor #ffffff 窗口的背景色 backgroundTextStyle string dark 下拉 loading 的样式,仅支持 dark
/light
backgroundColorTop string #ffffff 顶部窗口的背景色,仅 iOS 支持 backgroundColorBottom string #ffffff 底部窗口的背景色,仅 iOS 支持 enablePullDownRefresh boolean false 是否开启全局的下拉刷新。 详见 Page.onPullDownRefresh onReachBottomDistance number 50 页面上拉触底事件触发时距页面底部距离,单位为 px。 详见 Page.onReachBottom pageOrientation string portrait 屏幕旋转设置,支持 auto
/portrait
/landscape
详见 响应显示区域变化restartStrategy string homePage 重新启动策略配置 initialRenderingCache string 页面初始渲染缓存配置,支持 static
/dynamic
visualEffectInBackground string none 切入系统后台时,隐藏页面内容,保护用户隐私。支持 hidden
/none
handleWebviewPreload string static 控制预加载下个页面的时机。支持 static
/manual
/auto
-
如:
{ "window": { "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "navigationBarTitleText": "微信接口功能演示", "backgroundColor": "#eeeeee", "backgroundTextStyle": "light" } }
-
-
tabBar
-
如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。
属性 类型 必填 默认值 描述 color HexColor 是 tab 上的文字默认颜色,仅支持十六进制颜色 selectedColor HexColor 是 tab 上的文字选中时的颜色,仅支持十六进制颜色 backgroundColor HexColor 是 tab 的背景色,仅支持十六进制颜色 borderStyle string 否 black tabbar 上边框的颜色, 仅支持 black
/white
list Array 是 tab 的列表,详见 list
属性说明,最少 2 个、最多 5 个 tabposition string 否 bottom tabBar 的位置,仅支持 bottom
/top
custom boolean 否 false 自定义 tabBar,见详情 -
其中 list 接受一个数组,只能配置最少 2 个、最多 5 个 tab。tab 按数组的顺序排序,每个项都是一个对象,其属性值如下:
属性 类型 必填 说明 pagePath string 是 页面路径,必须在 pages 中先定义 text string 是 tab 上按钮文字 iconPath string 否 图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position
为top
时,不显示 icon。selectedIconPath string 否 选中时的图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position
为top
时,不显示 icon。
-
-
配置示例
{ "pages": [ "pages/index/index", "pages/logs/index" ], "window": { "navigationBarBackgroundColor": "#FFDAB9", "navigationBarTextStyle": "black", "navigationBarTitleText": "Demo" }, "tabBar": { "list": [{ "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/index", "text": "我的" }] } }
3.2 组件
3.2.1 视图容器
名称 | 功能说明 |
---|---|
view | 视图容器 |
-
view
-
功能描述
-
视图容器
-
-
属性说明
属性 类型 默认值 必填 说明 hover-class string none 否 指定按下去的样式类。当 hover-class="none"
时,没有点击态效果hover-stop-propagation boolean false 否 指定是否阻止本节点的祖先节点出现点击态 hover-start-time number 50 否 按住后多久出现点击态,单位毫秒 hover-stay-time number 400 否 手指松开后点击态保留时间,单位毫秒
-
3.2.2 基本内容
名称 | 功能说明 |
---|---|
text | 文本 |
-
text
-
功能描述
-
文本。
-
-
属性说明
属性 类型 默认值 必填 说明 selectable boolean false 否 文本是否可选 (已废弃) user-select boolean false 否 文本是否可选,该属性会使文本节点显示为 inline-block space string 否 显示连续空格 decode boolean false 否 是否解码 -
space
合法值 说明 ensp 中文字符空格一半大小 emsp 中文字符空格大小 nbsp 根据字体设置的空格大小
-
3.2.3 媒体组件
名称 | 功能说明 |
---|---|
image | 图片 |
-
image
-
功能描述
-
图片。支持 JPG、PNG、SVG、WEBP、GIF 等格式,2.3.0 起支持云文件ID。
-
-
属性说明
属性 类型 默认值 必填 说明 src string 否 图片资源地址 mode string scaleToFill 否 图片裁剪、缩放的模式 webp boolean false 否 默认不解析 webP 格式,只支持网络资源 lazy-load boolean false 否 图片懒加载,在即将进入一定范围(上下三屏)时才开始加载 show-menu-by-longpress boolean false 否 长按图片显示发送给朋友、收藏、保存图片、搜一搜、打开名片/前往群聊/打开小程序(若图片中包含对应二维码或小程序码)的菜单。 binderror eventhandle 否 当错误发生时触发,event.detail = {errMsg} bindload eventhandle 否 当图片载入完毕时触发,event.detail = {height, width} -
mode
合法值 说明 scaleToFill 缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素 aspectFit 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。 aspectFill 缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。 widthFix 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变 heightFix 缩放模式,高度不变,宽度自动变化,保持原图宽高比不变 top 裁剪模式,不缩放图片,只显示图片的顶部区域 bottom 裁剪模式,不缩放图片,只显示图片的底部区域 center 裁剪模式,不缩放图片,只显示图片的中间区域 left 裁剪模式,不缩放图片,只显示图片的左边区域 right 裁剪模式,不缩放图片,只显示图片的右边区域 top left 裁剪模式,不缩放图片,只显示图片的左上边区域 top right 裁剪模式,不缩放图片,只显示图片的右上边区域 bottom left 裁剪模式,不缩放图片,只显示图片的左下边区域 bottom right 裁剪模式,不缩放图片,只显示图片的右下边区域
-
-
fg
4 flex布局
-
一种非常方便的布局方式。
-
在容器中记住4个样式即可。
样式 值 说明 display flex flex布局显示 flex-direction row/column 指定是x或y轴 justify-content value-1 指定轴的排列方式 align-items value-1 指定斥轴的排列方式 -
value-1
value 说明 flex-start 左对齐 flex-end 右对齐 space-around 平均分布 space-between 无边框平均分布
5 样式
5.1 像素
-
px 写html都见过,是用来指定块级标签大小的。
-
rpx 应该没有人见过,是为移动端所使用的动态像素,可以通过不同的屏幕大小调整像素大小。
6 跳转
6.1 通过事件跳转
-
index.wxml
<!-- bindtap绑定事件,data-是规定后面写变量名 --> <view bindtap="clickMe" data-nid="123" >点击跳转</view>
-
index.js
Component({ /** * 组件的方法列表 */ methods: { /** * 绑定的事件 */ clickMe (e) { var nid = e.currentTarget.dataset.nid; console.log(nid); // 跳转 wx.navigateTo({ // 通过拼接来在url上携带数据 url: '/pages/redirect/redirect?id='+nid, }) } } })
-
redirect.js
Page({ /** * 生命周期函数--监听页面加载 */ onLoad(options) { console.log(123456789); console.log(options); } })
6.2 通过标签跳转
-
index.wxml
<navigator url="/pages/redirect/redirect?nid=666">跳转页面</navigator>
7.数据绑定
-
bind.wxml
<!-- 数据绑定就这样写就对了 --> <text>李永好:{{message}}</text> <!-- 按钮标签 --> <button bindtap="changeData">点击知道你</button>
-
bind.js
// pages/redirect/redirect.js Page({ /** * 页面的初始数据 */ data: { // 需要在data中写才能做到数据绑定。 message:"傻到窒息" }, changeData() { //this就代表page这个字典 // 获取数据 console.log(this.data.message); // 修改数据 this.setData({message:"沙雕"}) } })
8.提取用户信息
-
login.wxml
<view>图片: <image src="{{page}}" style="height:200rpx;width:200rpx;"></image> </view> <view>名称:{{name}}</view> <!-- 绑定事件 --> <button bindtap="getUserName">获取信息</button>
-
login.js
Page({ /** * 页面的初始数据 */ data: { // 需要在data中写才能做到数据绑定。 message:"傻到窒息", name:"傻逼", page:"/statlc/images/a.jpg" }, getUserName(){ //wx.openSetting({}) // 应为调用了函数后this就不等于初始this了 var that = this; // 调用微信的接口,获取当前用户信息 wx.getUserProfile({ desc: '获取用户信息', success:(res) => { // 调用成功后触发 that.setData({ // 拿到名称和图片并赋值 name:res.userInfo.nickName, page:res.userInfo.avatarUrl }) }, fail(res){ //调用失败后触发 console.log("fail",res) } }) },
-
可以通过wx.openSetting({})永久授权(无用)
9.选择定位
-
wxml
<view bindtap="getLocalpath">{{localpath}}</view>
-
js
// pages/redirect/redirect.js Page({ /** * 页面的初始数据 */ data: { // 需要在data中写才能做到数据绑定。 message:"傻到窒息", name:"傻逼", page:"/statlc/images/a.jpg", localpath:"请选择位置" }, getLocalpath(){ var that = this; // 选择位置 wx.chooseLocation({ success (res) { // 位置名称 that.setData({localpath:res.address}); } }) },
10.for
-
wxml
<text>for</text> <view> <!-- 循环数据 --> <view wx:for="{{datalist}}">{{index}}-{{item}}</view> <!-- 给指定的调用名称起别名 --> <view wx:for="{{datalist}}" wx:for-index="v" wx:for-item="value">{{v}}-{{value}}</view> </view> <view> <!-- 调用字典 --> {{userinfo.name}} {{userinfo.age}} </view> <view> <view wx:for="{{userinfo}}">{{index}}-{{item}}</view> </view>
-
js
data:{ datalist:["猴","狗","李永昊"], userinfo:{ name:"alex", age:18, } },
11.上传图片到内存
-
wxml
<!--pages/publish/publish.wxml--> <text>pages/publish/publish.wxml</text> <view bindtap="uploadImage">请上传图片</view> <view> <image style="width: 200rpx;height: 200rpx; margin-right: 5rpx;" wx:for="{{imageList}}" src="{{item}}"></image> </view>
-
js
// pages/publish/publish.js Page({ /** * 页面的初始数据 */ data: { imageList:["/statlc/images/a.jpg",] }, uploadImage(){ var that = this; wx.chooseImage({ //最大选择数 count: 9, //图片个数original原图,compressed压缩图 sizeType:["compressed","original"], //camera相机取图片,album本地相册 sourceType:["album","camera"], success(res){ //设置imagelist,页面上自动修改 // that.setData({ // imageList:res.tempFilePaths // }); //先列表中添加单条数据 // that.data.imageList.push("/statlc/images/a.jpg"); //怎加图片并保留之前图片 that.setData({ imageList:that.data.imageList.concat(res.tempFilePaths) }) } }) } })
12.双向绑定
-
wxml
<view>电话号码:{{phone}}</view> <!-- 输入数据时触发bindinput的指定方法 --> <input value="{{phone}}"bindinput="bindPhone"placeholder="请输入手机号"/>
-
js
// pages/telphone/telphone.js Page({ /** * 页面的初始数据 */ data: { phone:"" }, bindPhone(e){ //e中会有从input输入的数据 this.setData({phone:e.detail.value}); }, }
13.连接服务器
-
右上角点击详情里的本地设置。
-
选择不验证合法域名。。。
-
wxml
<view>验证码:</view> <input value="{{code}}"bindinput="bindCode"placeholder="请输入验证码"/> <button bindtap="login">登陆</button>
-
js
// pages/telphone/telphone.js Page({ /** * 页面的初始数据 */ data: { code:"" }, bindCode(e){ this.setData({code:e.detail.value}); }, login(){ // xw.request向后台发送 wx.request({ url: 'http://127.0.0.1:8000/api/login/', data: {phone:this.data.phone,code:this.data.code}, method: "POST", dataType:"JSON", success: (result) => { console.log(result) }, fail: (res) => {}, }) }, }
14.弹窗
-
wx.showToast(Object object)
属性 类型 默认值 必填 说明 title string 是 提示的内容 icon string success 否 图标 image string 否 自定义图标的本地路径,image 的优先级高于 icon duration number 1500 否 提示的延迟时间 mask boolean false 否 是否显示透明蒙层,防止触摸穿透 success function 否 接口调用成功的回调函数 fail function 否 接口调用失败的回调函数 complete function 否 接口调用结束的回调函数(调用成功、失败都会执行) -
icon
合法值 说明 success 显示成功图标,此时 title 文本最多显示 7 个汉字长度 error 显示失败图标,此时 title 文本最多显示 7 个汉字长度 loading 显示加载图标,此时 title 文本最多显示 7 个汉字长度 none 不显示图标,此时 title 文本最多可显示两行,1.9.0及以上版本支持 -
js
wx.showToast({ // 要显示的错误信息 title: '错误', // 要显示的图片 icon:"none" })
15.示例
15.1 示例
-
app.json
{ "pages": [ "pages/index/index", "pages/logs/logs" ], "window": { "navigationBarBackgroundColor": "#FFDAB9", "navigationBarTextStyle": "black", "navigationBarTitleText": "Demo" }, "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/logs", "text": "我的" } ] }, "sitemapLocation": "sitemap.json" }
-
index.wxml
<!--pages/index/index.wxml--> <!--1--> <view class="v1"> <text>asdf</text> <text>asdf</text> <text>asdf</text> <text>asdf</text> <text>asdf</text> </view> <!-- 示例二 --> <view>示例二</view> <view class="auction"> <view class="item"> <view class="title">第一场 拍卖牛马兄弟</view> <view class="tips"> <view class="status">2020-10-11</view> <view class="count">9999次围观</view> </view> <view class="big"> <image src="/statlc/images/a.jpg"></image> </view> <view class="small"> <image src="/statlc/images/a.jpg"></image> <image src="/statlc/images/a.jpg"></image> <image src="/statlc/images/a.jpg"></image> <image src="/statlc/images/a.jpg"></image> <image src="/statlc/images/a.jpg"></image> </view> </view> </view>
-
index.wxss
/* pages/index/index.wxss */ .v1{ display:flex; justify-content: space-between; /* flex-start flex-end space-around space-between */ } .auction .item .title{ /* 字体大小 */ font-size: 50rpx; } .auction .item .tips{ /* 以flex显示 */ display: flex; /* 以x轴正向增大显示 */ flex-direction: row; /* 设置平均分布不带边框 */ justify-content: space-between; /* 设置字体大小 */ font-size: 30rpx; /* 设置字体颜色 */ color: #8c8c8c; } .auction .item .big{ height: 400rpx; /* 超出范围将隐藏 */ overflow:hidden; } .auction .item .big .image{ width: 100%; height: 100%; } .auction .item .small{ display: flex; flex-direction: row; justify-content: flex-start; } .auction .item .small image{ height: 100rpx; width: 100rpx; /* 设置右边距为20 */ padding-right: 20rpx; }
Chapter2
1.登录和注册(手机验证)
1.1 我的页面
-
app.js
App({ /** * 当小程序初始化完成时,会触发 onLaunch(全局只触发一次) */ onLaunch: function () { }, globalData:{}, })
-
wxml
<view class="view_contain"> <!-- 第一部分 --> <view class="view_1"> <view class="view_image_text"> <image class="image_radius" src="/statlc/images/a.jpg" /> <navigator url="/pages/atuh/atuh">{{phone}}</navigator> </view> </view> <!-- 第二部分 --> <view class="view_2"> <view class="view_tupianwenzi"> <image class="image_tupian" src="/statlc/images/a.jpg"></image> <text class="text_saoyisao">待付款</text> </view> <view class="view_tupianwenzi"> <image class="image_tupian" src="/statlc/images/a.jpg"></image> <text class="text_saoyisao">待发货</text> </view> <view class="view_tupianwenzi"> <image class="image_tupian" src="/statlc/images/a.jpg"></image> <text class="text_saoyisao">待收货</text> </view> <view class="view_tupianwenzi"> <image class="image_tupian" src="/statlc/images/a.jpg"></image> <text class="text_saoyisao">待评价</text> </view> </view> <!-- 第三部分 --> <view class="view_3"> <view class="list-item"> <image class="item-image" src="/statlc/images/a.jpg"></image> <text class="item-text">我的收藏</text> <image class="image-jiantou" src="/statlc/images/a.jpg"></image> </view> <view class="line"></view> <view class="list-item"> <image class="item-image" src="/statlc/images/a.jpg"></image> <text class="item-text">我的评价</text> <image class="image-jiantou" src="/statlc/images/a.jpg"></image> </view> <view class="line"></view> <view class="list-item"> <image class="item-image" src="/statlc/images/a.jpg"></image> <text class="item-text">版本更新</text> <image class="image-jiantou" src="/statlc/images/a.jpg"></image> </view> <view class="line"></view> <view class="list-item"> <image class="item-image" src="/statlc/images/a.jpg"></image> <text class="item-text">售后记录</text> <image class="image-jiantou" src="/statlc/images/a.jpg"></image> </view> <view class="line"></view> <view class="list-item"> <image class="item-image" src="/statlc/images/a.jpg"></image> <text class="item-text">分享邀请</text> <image class="image-jiantou" src="/statlc/images/a.jpg"></image> </view> <view class="line"></view> </view> </view>
-
wxss
/* 使用page就是为了保证 满屏 */ page { width: 100%; height: 100%; } .view_contain { width: 100%; height: 100%; background: #f0eeed } /* 第一部分 */ .view_1 { display: flex; justify-content: center; width: 100%; height: 25%; background: #a0deee; } .view_image_text { width: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; } .image_radius { height: 50px; width: 50px; border-radius: 30px; } /* 第二部分 */ .view_2 { width: 100%; height: 15%; display: flex; flex-direction: row; align-items: center; justify-content: center; background: white; } .view_tupianwenzi { display: flex; flex-direction: column; width: 120rpx; align-items: center; margin-left: 25rpx; margin-right: 25rpx; } .image_tupian { display: flex; width: 100rpx; height: 100rpx; } /* 第三部分 */ .view_3 { width: 100%; height: 50%; /* background: #f0eeed; */ } .list-item { display: flex; flex-direction: row; align-items: center; width: 100%; height: 80rpx; margin-top: 20rpx; position: relative; /*父元素位置要设置为相对*/ background: white; } .item-image { width: 50rpx; height: 50rpx; margin: 20rpx; } .item-text { color: gray; font-size: 35rpx; margin-left: 20rpx; } .image-jiantou { width: 20rpx; height: 35rpx; position: absolute; /* 要约束所在位置的子元素的位置要设置成绝对 */ right: 0; /* 靠右调节 */ margin-right: 35rpx; } /* 黑线 使得产生阴影效果 */ .line { width: 100%; height: 3rpx; background: lightgray; margin-left: 90rpx; }
-
js
//获取全局app文件的数据 var app = getApp(); Page({ /** * 页面的初始数据 */ data: { phone:"登录" }, /** * 生命周期函数--监听页面加载(第一次访问时执行) */ onl oad(options) {}, /** * 生命周期函数--监听页面初次渲染完成(第一次访问时执行) */ onReady() {}, /** * 生命周期函数--监听页面显示(每次访问时执行) */ onShow() { //本地storage中获取值 var phone = wx.getStorageSync('phone'); if(phone){ this.setData({ //查全局文件的globalData // phone:app.globalData.phone phone:phone }) }else{ this.setData({ phone:"登录" }) } }, })
1.2 登录页面
-
wxml
<view class="main_candidate"> <view class="inputbox flex"> <text class="input-label">手机号</text> <input name="name" placeholder="请输入手机号" bindinput="bindPhone" maxlength="11" class="primary" value="{{phone}}"/> </view> <view class="inputbox flex"> <text class="input-label" >验证码</text> <input name="code" placeholder="请输入验证码" bindinput="bindCode" maxlength="4" class="primary" value="{{code}}"/> <button class="getCode" bindtap="messageCode">发送验证码</button> </view> <button class="login" form-type="submit"bindtap="login">立即登录</button> <view class="register"> <text bindtap="register">没有账号?去注册</text> </view> </view>
-
wxss
.input-label { color: #888; font-size: 12pt; height: 25rpx; line-height: 25rpx; padding: 0 25rpx; border-right: 1px solid #d8d8d8; } .main_candidate{ width: 100%; height: 100%; background-color: #ffffff; margin-top: 30px; } .inputbox{ padding-left: 6px; box-sizing: border-box; border-bottom: 1px solid #dadada; width: 100%; height: 50px; line-height: 50px; font-size: 14px; background-color: #fff; } .flex{ border-radius: 5px; border: 2px solid #f4f4f4; display: flex; align-items: center; margin: 40rpx 0; } .primary{ flex:1; } .inputbox button{ width: 110px; height: 38px; color:#fff; background-color: #5dd5c8; font-size: 16px; } .login{ margin-top: 20px; background-color: #5dd5c8; color: #fff; font-size: 20px; } .register{ color: blue; font-size: 16px; margin: 0 auto; width: 40%; margin-top: 10px; }
-
js
Page({ /** * 页面的初始数据 */ data: { phone:"18993538183", code:"" }, bindPhone(e){ this.setData({phone:e.detail.value}); }, bindCode(e){ this.setData({code:e.detail.value}); } })
1.3 向后台发送验证码请求
-
js
/** * 发送短信验证码 */ messageCode(e){ //*****************1.判断手机号长度*************** // 输入的数据不等于11执行 if (this.data.phone.length !=11){ //弹窗 wx.showToast({title: '手机号长度错误',icon:"none"}) return;} //*****************2.判断手机号是否正规*************** //正则匹配手机格式 //"/xxx/"中写正则 var reg = /^(1[3|4|5|6|7|8|9]\d{9})$/; if(!reg.test(this.data.phone)){ //弹窗 wx.showToast({title: '手机号长度错误',icon:"none"}) return;} //*****************3.通过request发送到后台*************** //发送短信验证码,登陆成功之后获取jwt和微信用户信息,保存到globalData和本地存储中。 wx.request({ url: 'http://127.0.0.1:8000/api/message/', data: {phone:this.data.phone}, method: "GET", success: (result)=>{ //返回的数据模型{"status":True/False,"message":"错误信息"} if(result.data.status){ //倒计时计数器 wx.showToast({title: result.data.message,icon:"none"}) }else{ //短信发送失败 wx.showToast({title: result.data.message,icon:"none"}) } }, }) }
1.4 用腾讯云向客户发送认证码
-
auction/urls
from django.contrib import admin from django.urls import path,re_path,include from api1 import urls urlpatterns = [ path('admin/', admin.site.urls), re_path("^api/", include("api1.urls")), ]
-
api/urls
from django.urls import path,re_path,include from api1 import views urlpatterns = [ re_path('^login/', views.ListView.as_view()), re_path('^message/', views.MessageView.as_view()), ]
-
auction/settings
from pathlib import Path INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'api1.apps.Api1Config' ] DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # 配置django-redis CACHES = { "default":{ "BACKEND":"django_redis.cache.RedisCache", # 主机ip和端口 "LOCATION":"redis://127.0.0.1:6000", "OPTIONS":{ "CLIENT_CLASS":"django_redis.client.DefaultClient", # 最大访问数 "CONNECTION_POOL_KWARGS":{"max_connections":100} # 密码 # "POASSWORD":"密码", } } }
-
api/serializer/account
from rest_framework import serializers from .validators import phone_validator # 我们没有号码对应的表所以需要继承serializers.Serializer class MessageSerialiaer(serializers.Serializer): # 数据进来后先去判断是否为空,再去判断列表中的函数 phone = serializers.CharField(label="手机号",validators=[phone_validator,])
-
api/serizlizer/validators
import re from rest_framework.exceptions import ValidationError def phone_validator(value): if not re.match("^(1[3|4|5|6|7|8|9]\d{9})$",value): # 主动推送特定的错误 raise ValidationError("手机号格式错误")
-
api/utils/tencent/msg
from tencentcloud.common import credential from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.sms.v20190711 import sms_client,models def send_message(phone,code): # TODO tencent.send_message(phone,random_code) """ 1.注册腾讯云,开通腾讯云短信。 2.创建应用 sdk appid = 1400705782 3.申请签名(个人:小程序) 476491 python小程序 4.申请模板 1473776 普通短信 5.申请腾讯云api https://console.cloud.tencent.com/cam/capi appid 1312898916 SecretId AKIDzAB4nInCPOAkdDrMhoheMogIgqjNn35s SecretKey c09IAm9HCxZ4VduAwTSFsPq6A2f7MK5b 6.调用相关接口去发送短信 pip install --upgrade tencentcloud-sdk-python sdk,写好的工具 """ try: phone = "{}{}".format("+86",phone) cred = credential.Credential("AKIDW3Rgszw84ylQxMzNn7KOJ6kFPSLSL5c5MU","GQSMXmtsjR0QhuIalzTp250nU6digZSD") client = sms_client.SmsClient(cred,"ap-guangzhou") req = models.SendSmsRequest() # 短信应用id req.SmsSdkAppid = "1400302209" # 短信签名内容 req.Sign = "python之路" # 下发手机号+86标准 req.PhoneNumberSet = [phone] # 模板id req.TemplateID = "516680" # 模板参数 req.TemplateParamSet = [code] # 发送短信 resp = client.SendSms(req) # 输出json格式的字符串回包 if resp.SendStatusSet[0].Code == "Ok": return True #print(resp.to_json_string(indent=2)) except TencentCloudSDKException as err: #print(err) pass
-
api/view
from rest_framework.response import Response from rest_framework.views import APIView import random from .serializer import account from .utils.tencent import msg class MessageView(APIView): def get (self, request, *args, **kwargs): # ********************1.获取手机号和手机格式校验************************* ser = account.MessageSerialiaer(data=request.query_params) if not ser.is_valid(): return Response({"status":False,"message":"手机格式错误"}) phone = ser.validated_data.get("phone") # *****3.生成随机验证码和验证码发送到手机上,购买服务器进行发送短信:腾讯云****** random_code = random.randint(1000,9999) result = msg.send_message(phone,random_code) if not result: return Response({"status":False,"message":"短信发送失败"}) # ******5.吧验证码+手机号保留(过期时间30s)和搭建redis服务器(云redis)****** from django_redis import get_redis_connection conn = get_redis_connection() conn.set(phone,random_code,ex=60) return Response({"status":True,"message":"发送成功"})
1.5 向后台发送登录请求
-
js
// pages/atuh/atuh.js var app = getApp(); Page({ /** * 页面的初始数据 */ data: { phone:"18993538183", code:"" }, /** * 用户登陆 */ login(){ // xw.request向后台发送 wx.request({ url: 'http://127.0.0.1:8000/api/login/', data: {phone:this.data.phone,code:this.data.code}, method: "POST", success: (result) => { if(result.data.status){ //1.去公共的app.js中调用globalData,里面赋值 //app.globalData.phone = result.data.data.phone; //console.log(app.globalData); // 在本地“cookie”中赋值(并不是cookie只是功能一样,就这样叫) wx.setStorageSync('phone',result.data.data.phone); //登陆成功跳到上一级 wx.navigateBack({}); //查看跳转层级记录 //var pages = getCurrentPages(); //prevPage = pages[pages.length-2] }else{ wx.showToast({title: result.data.message,}) } } }) }, })
1.6 处理登录请求
-
api/models
from django.db import models class UserInfo(models.Model): #创建唯一索引 phone = models.CharField(verbose_name="手机号",max_length=11,unique=True) token = models.CharField(verbose_name="用户TOKEN",max_length=64,null=True,blank=True)
-
api/serializer/account
# !/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework import serializers from .validators import phone_validator from django_redis import get_redis_connection from rest_framework.exceptions import ValidationError class LoginSerializer(serializers.Serializer): #这些都是自定义的验证字段 # 数据进来后先去判断是否为空,再去判断列表中的函数 phone = serializers.CharField(label="手机号",validators=[phone_validator,]) code = serializers.CharField(label="短信验证码",) #钩子验证code字段validate_+字段名 def validate_code(self,value): #应该写开应为主动推送的不一样 if len(value) !=4 or not value.isdecimal(): #主动推送错误信息 raise ValidationError("短信格式错误") #在函数内部取值时,使用initial_data应为用了request.data后会将其赋值给它 phone = self.initial_data.get("phone") #去取验证码 conn = get_redis_connection() code = conn.get() if not code: raise ValidationError("验证码过期") if value != code.decode("utf-8"): raise ValidationError("验证码错误") return value
-
api/view
from rest_framework.response import Response from rest_framework.views import APIView from . import models import uuid from .serializer import account # Create your views here. # value就是指定字段的指定数据 class ListView(APIView): def post (self, request, *args, **kwargs): print(request.data) """ 1.校验手机号是否合法 2.校验验证码,redis 验证码获取状态:无数据/有错误/有无错误 3.去数据库中获取用户信息 4.将一些信息返回给小程序 """ ser = account.LoginSerializer(data=request.data) if not ser.is_valid(): return Response({"status":False,"message":"验证码错误"}) # 验证后使用它获取数据 phone = ser.validated_data.get("phone") # 会帮你做如果有则返回一个查询对象的参数和一个逻辑值有是真没有是假,如果没有则创建并返回数据和False user_object,flag = models.UserInfo.objects. get_or_create(phone=phone) # 修改或添加token的值 user_object.token = str(uuid.uuid4()) user_object.save() return Response({"status":True,"data":{"token":user_object.token,"phone":phone}})
1.7 完善
-
我的.wxml
<!-- 第一部分 --> <view class="view_1"> <view class="view_image_text"> <image class="image_radius" src="{{userinfo.avatarUrl}}" /> <navigator url="{{userinfo.url}}">{{userinfo.nickName}}</navigator> </view> </view>
-
我的.js
// pages/logs/logs.js var app = getApp(); Page({ /** * 页面的初始数据 */ data: { userinfo:"登录" }, /** * 生命周期函数--监听页面显示(每次访问时执行) */ onShow() { //本地storage中获取值 this.setData({ userinfo:app.globalData.userinfo }) }, })
-
登录.wxml
<view class="main_candidate"> ... <button class="login" form-type="submit"bindtap="onClicksubmit">立即登录</button> ... </view>
-
登录.js
// pages/atuh/atuh.js var app = getApp(); Page({ /** * 用户登陆 */ onClicksubmit(){ // xw.request向后台发送 wx.request({ url: 'http://127.0.0.1:8000/api/login/', data: {phone:this.data.phone,code:this.data.code}, method: "POST", success: (result) => { if(result.data.status){ //获取用户信息 wx.getUserProfile({ desc: '获取用户信息', success:(res) => { // 初始化用户信息 app.initUserInfo(result.data.data,res.userInfo); //登陆成功跳到上一级 wx.navigateBack({}); } }) }else{ wx.showToast({title: result.data.message,}) } } }) }, })
-
app.js
App({ /** * 当小程序初始化完成时,会触发 onLaunch(全局只触发一次) */ onLaunch: function () { //清理在session的数据 //wx.removeStorageSync('userinfo') var userinfo = wx.getStorageSync('userinfo'); if(userinfo){ //在内存中存储一份,加快读取速度。 this.globalData.userinfo=userinfo } }, globalData:{ userinfo:{nickName:"登录", url:"/pages/atuh/atuh" } }, initUserInfo(res,localInfo){ var info = { token:res.token, phone:res.phone, nickName:localInfo.nickName, avatarUrl:localInfo.avatarUrl, url:"null" } //1.去公共的app.js中调用globalData,里面赋值 this.globalData.userinfo = info; // 在本地“cookie”中赋值(并不是cookie只是功能一样,就这样叫) wx.setStorageSync('userinfo',info); }, })
1.8知识梳理
-
全局数据和局部数据
-
app.js中的app{globalData}中存放全局应用数据。
-
用它获取全局数据getApp();
-
-
每个页面中有.js的文件中的page{data}中存放局部数据。
-
用它获取局部数据this.data(this要看是谁的对象)
-
-
-
使用到的api(详细请到官网浏览)
-
登录时用到的wx.wx.request({})向后台发送请求。
-
提醒用户时用到wx.showToast({})弹出提示框(无交互)。
-
在类似cookie中存储数据用到wx.setStorageSync('key',value)(在本地存储,并不是cookie只是功能一样,就这样叫)。
-
清理在session的数据用到wx.removeStorageSync('key')。
-
获取在session的数据用到wx.getStorageSync('key')。
-
用到wx.getUserProfile({})来获取用户信息。
-
执行后页面跳回上一个页面用到wx.navigateBack({})在要执行的函数中加入。
-
-
全局事件和局部事件
-
app.js中有onLaunch: function () {}当小程序初始化完成时,会触发(全局只触发一次)。
-
每个页面都会的
-
显示前触发
-
页面的初始数据data: {}
-
生命周期函数--监听页面加载onLoad(options) {}
-
生命周期函数--监听页面初次渲染完成onReady() {}
-
-
用户加载/跳转/关闭时触发
-
生命周期函数--监听页面显示onShow() {}
-
生命周期函数--监听页面隐藏onHide() {}
-
生命周期函数--监听页面卸载onUnload() {}
-
-
用户触发
-
页面相关事件处理函数--监听用户下拉动作onPullDownRefresh() {}
-
页面上拉触底事件的处理函数onReachBottom() {}
-
用户点击右上角分享onShareAppMessage() {}
-
-
-
2.页面之间的数据传递
-
父页面
-
wxml
<text bindtap="getTopic">{{topicText}}</text>
-
js
Page({ data: { topicText:"请选择话题", topiId:null }, getTopic (){ //跳转 wx.navigateTo({ url: '/pages/topic/topic', }) }, setTopicData(res){ //设置data中的数据 this.setData({ topicText:res.title, topiId:res.id }); }, })
-
-
子页面
-
wxml
<view class="container"> <view class="item" wx:for="{{topicList}}" bindtap="chostTopic" data-xx="{{item}}"> <text>{{item.title}}</text> <text>{{item.count}}</text> </view> </view>
-
wxss
.item{ padding: 40rpx; display: flex; flex-direction: row; justify-content: space-between; }
-
js
// pages/topic/topic.js Page({ /** * 页面的初始数据 */ data: { topicList:[ {id:1,title:"你吧把",count:10000}, {id:1,title:"吧把",count:10}, {id:1,title:"wo吧把",count:1010}, {id:1,title:"我吧把",count:1200}, {id:1,title:"你把",count:100}, ] }, chostTopic(e){ //获取触发事件时传入的指定值 var topicInfo = e.currentTarget.dataset.xx; // 把这个值传递给它的副页面 // 他会按顺序拿到每个页面的对象。 var pages = getCurrentPages(); var prevPage = pages[pages.length-2]; //调用对象的指定事件 prevPage.setTopicData(topicInfo); //放回上级页面 wx.navigateBack({}); } })
-
3.云存储
3.1 阶段
-
第一阶段:文件服务器,将文件存储在某个指定的服务器上(支持目录结构的划分)。
-
第二阶段:分为文件服务器和对象服务器,对象存储优化了存储和操作但是无目录结构。
-
第三阶段:使用云服务存储,实现为每个人提供存储服务器。
3.2 使用腾讯云存储
-
点击存储桶列表并创建桶。
-
右上角选择sdk文档中的小程序中详细介绍。
-
wxml
<!--pages/publish/publish.wxml--> <text>pages/publish/publish.wxml</text> <view bindtap="uploadImage">请上传图片</view> <view> <image style="width: 200rpx;height: 200rpx; margin-right: 5rpx;" wx:for="{{imageList}}" src="{{item}}"></image> </view> <view bindtap="uploadFile">点击上传</view>
-
js
// pages/publish/publish.js var COS = require("../../utils/cos-wx-sdk-v5.js") Page({ /** * 页面的初始数据 */ data: { imageList:[], onlineImageList:[] }, uploadImage(){ var that = this; wx.chooseImage({ count: 9, sizeType:["compressed","original"], sourceType:["album","camera"], success(res){ //设置imagelist,页面上自动修改 // that.setData({ // imageList:res.tempFilePaths // }); //先列表中添加单条数据 // that.data.imageList.push("/statlc/images/a.jpg"); //怎加图片并保留之前图片 that.setData({ imageList:that.data.imageList.concat(res.tempFilePaths) }) } }) }, uploadFile(){ // 存储图片在服务器上的url var onlineImageList = []; var that = this; //创建连接对象 var cos = new COS({ // 必选参数 getAuthorization: function (options, callback) { // 服务端其他语言参考 COS STS SDK :https://github.com/tencentyun/qcloud-cos-sts-sdk // STS 详细文档指引看:https://cloud.tencent.com/document/product/436/14048 wx.request({ url: 'http://127.0.0.1:8000/api/credential/', data: { // 可从 options 取需要的参数 }, success: function (result) { var data = result.data; var credentials = data && data.credentials; if (!data || !credentials) return console.error('credentials invalid'); callback({ TmpSecretId: credentials.tmpSecretId, TmpSecretKey: credentials.tmpSecretKey, // v1.2.0之前版本的sdk使用XCosSecurityToken而不是SecurityToken SecurityToken: credentials.sessionToken, // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误 StartTime: data.startTime, // 时间戳,单位秒,如:1580000000 ExpiredTime: data.expiredTime, // 时间戳,单位秒,如:1580000900 }); } }); } }); // var cos = new COS({ // SecretId: 'AKIDzAB4nInCPOAk5DrMhoheMogIgqjNn35s', // SecretKey: 'c09IAm9HCxZ4Vu9AwTSFsPq6A2f7MK5b', // }); for(var index in this.data.imageList){ var filePath = this.data.imageList[index]; // 先选择文件,得到临时路径 cos.postObject({ Bucket:"static-1312898916", Region:"ap-nanjing", Key:index+"xzx.png", FilePath:filePath, onProgress(info){ console.log(JSON.stringify(info)); } }, function (err,data){ console.log(data); onlineImageList.push(data.Location); }) } }, })
-
api/urls
from django.urls import re_path from api1 import views urlpatterns = [ re_path('^credential/', views.CredentialView.as_view()), ]
-
api/view
from rest_framework.response import Response from rest_framework.views import APIView class CredentialView(APIView): def get (self,*args,**kwargs): import json import os from sts.sts import Sts config = { 'url': 'https://sts.tencentcloudapi.com/', # 域名,非必须,默认为 sts.tencentcloudapi.com 'domain': 'sts.tencentcloudapi.com', # 临时密钥有效时长,单位是秒 'duration_seconds': 1800, 'secret_id': 'AKIDzAB4nInCPOAk5DrMhoheMogIgqjNn35s', # 固定密钥 'secret_key': 'c09IAm9HCxZ4Vu9AwTSFsPq6A2f7MK5b', # 换成你的 bucket 'bucket': 'static-1312898916', # 换成 bucket 所在地区 'region': 'ap-nanjing', # 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径 # 例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用) 'allow_prefix': '*', # 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923 'allow_actions': [ # 简单上传 'name/cos:PostObject', ], } try: sts = Sts(config) response = sts.get_credential() print('get data : ' + json.dumps(dict(response), indent=4)) return Response(response) except Exception as e: print(e)
第3章
1.知识
1.1 进度条(详细请到官网查询)
-
wxml
<!--默认是绿色,也可以修改--> <progress percent="{{percent2}}" activeColer="#DC143C" ></progress> <view wx:for="{{imageList}}"> <view>{{item.title}}</view> <progress percent="{{item.percent}}"></progress> </view> <button bindtap="changePercent">点击</button>
-
js
data: { imageList:[ {id:1,title:"image1",percent:30}, {id:2,title:"image2",percent:60}, {id:3,title:"image3",percent:90}, ] }, changePercent(){ var num =2; this.setData({ //要实现嵌套数据的修改,必须使用这种 //中括号包含字符型路径:数据 //也可以使用字符拼接路径 ["imageList[0].percent"]:80, ["imageList["+num+"].percent"]:100, }) },
1.2 闭包
-
闭包是将数据封装后,异步后数据不会出现错误选定。
var dataList = ["a","b","c"] //每个发出的repuest是一个包(将传入的值装入函数) //如果不写一个函数将值传入请求来后答应的会是“c” for (var i in dataList){ (function(data){ wx.repuest({ url:"xxx", success(res){ console.log(data); } }) })(dataList[i]) }
-
自执行函数
(function(data){ console.log(data); })("我是你baba") //用两个括号第一个括号写函数,第二个写要传入的参数。
1.3 下拉页面刷新
-
json文件
-
全局
window{ //是否允许下拉刷新 enablePullDownRefresh:True }
-
局部
{ //是否允许下拉刷新 enablePullDownRefresh:True }
-
-
wx.stopPullDownRefresh(Object object)
-
功能描述
-
停止当前页面下拉刷新。
-
-
参数
属性 类型 默认值 必填 说明 success function 否 接口调用成功的回调函数 fail function 否 接口调用失败的回调函数 complete function 否 接口调用结束的回调函数(调用成功、失败都会执行) -
示例代码
Page({ onPullDownRefresh () { wx.stopPullDownRefresh() } })
-
-
wx.startPullDownRefresh(Object object)
-
功能描述
-
开始下拉刷新。调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。
-
-
参数
属性 类型 默认值 必填 说明 success function 否 接口调用成功的回调函数 fail function 否 接口调用失败的回调函数 complete function 否 接口调用结束的回调函数(调用成功、失败都会执行) -
示例代码
wx.startPullDownRefresh()
-
2.发布页面
2.1 发布页面
2.2 创建表
2.3 获取sos认证
-
wxml
-
js
-
api/url
re_path("^oss/credential/$",auth.OssCredentialView.as_view()),
-
utils/tencent/oss.get_credential
-
api/view
class OssCredentialView(APIView): def get(self,*args,**kwargs): from utils.tencent.oss import get_credential return Response(get_credential())
2.4 获取话题
-
wxml
-
js
-
api/url
re_path("^topic/$",topic.TopicView.as_view()),
-
api/view
from rest_framework.generics import ListAPIView from rest_framework.serialiaers import ModelSerializer from . import models class TopicSerializer(ModelSerializer): class Meta: model = models.Topic fields = "__all__" class TopicView(ListAPIView): serializer_class = TopicSerializer queryset = models.Topic.objects.all().order_by("-count")
2.5 图片提交至云服务桶
2.6 提交和获取新闻
-
wxml
-
js
-
api/url
path("^news/$",news.NewsView.as_view())
-
api/view
class CreateNewsTopicModelSerializer(serializers.Serializer): key = serializers.CharField() cos_path = serializers.CharField() class CreateNewsModelSerializer(serializers.ModelSerializer): imageList = CreateNewsTopicModelSerializer(many=True) class Meta: model = models.News exclude = ["user","viewer_count","comment_count"] def create(self,validated_data): image_list = validated_data.pop("imageList") news_object = models.News.objects.create(**validated_data) data_list = models.NewsDetail.objects.bulk_create( [models.NewsDetail(**info,news=news_object)for info in image_list] ) news_object.imageList = data_list if news_object.topic: news_object.topic.count +=1 news_object.save() return news_object class NewsView(CreateAPIView,ListAPIView): queryset=models.News.objects.prefetch_related("user","topic").order_by("-id") filter_backends = [ReachBottomFilter,PullDownRefreshFilter] def perform_create(self,serializer): new_object = serializer.save(user_id=1) return new_object def get_serializer_class(self): if self.request.method == "POST": return CreateNewsModelsSerializer if self.request.method == "GET": return ListNewsModelsSerializer
3.详细页
第4章
1.首页
day1 第58个视频停止
标签:微信,程序开发,js,phone,wx,data,pages,页面 From: https://www.cnblogs.com/fxy1024/p/17002833.html