《发现》页面
调用组件外部的样式
components 内部的组件无法直接调用外部的样式。可通过以下方式调用组件外部样式:
方法一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 父组件wxml: <!-- iconfont 和 icon-sousuo 是传入组件内部的样式名称,iconfont(自定义名称)="iconfont(外部样式文件中定义的样式名)" --> <x-search iconfont="iconfont" icon-sousuo="icon-sousuo"/>
子组件js: // 组件外部样式 externalClasses: [ 'iconfont', // 对应的是上面等号前面的名称 'icon-sousuo' ],
子组件wxml: 即可实现调用组件外的样式 <i class="iconfont icon-sousuo" />
注意:如果想在组件内部再次修改样式,不能够引用外部传进来的class名称进行修改,可以另起一个class名称进行修改。
|
方法二:
消除样式隔离
1 2 3 4 5
| 组件内: Component({ options: { styleIsolation: "apply-shared", }, });
|
组件插槽 slot
单个插槽
1 2 3 4 5 6 7 8 9 10 11 12 13
| 父组件调用传入插槽内容: <组件标签> <view> <view>插槽内容</view> <view>插槽内容</view> </view> </组件标签>
组件内部定义slot标签: <view> <!-- slot插槽 --> <slot></slot> </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
| 父组件调用传入插槽内容: <组件标签> <view slot="slot2"> <view>插槽1内容</view> <view>插槽1内容</view> </view>
<view slot="slot1"> <view>插槽2内容</view> <view>插槽2内容</view> </view> </组件标签>
组件js : options: {// 设置 multipleSlots: true // 打开多个插槽功能 },
组件内部定义slot标签: <view> <!-- slot插槽 具名插槽--> <slot name="slot1"></slot> <slot name="slot2"></slot> </view>
|
判断用户授权
授权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| wx.getSetting({ success: (res) => { console.log(res); if (res.authSetting["scope.userInfo"]) { wx.getUserInfo({ success(res) { console.log(res); }, }); } else { } }, });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <button class="login" open-type="getUserInfo" bindgetuserinfo="onGetUserInfo" > 获取微信授权信息 </button>
bindgetuserinfo 事件会询问用户是否同意授权
js中: onGetUserInfo(event) { const userInfo = event.detail.userInfo if (userInfo) { this.setData({ modalShow: false }) this.triggerEvent('loginSuccess', userInfo) } else { this.triggerEvent('loginFail') } }
|
原生组件
原生组件
1 2 3 4 5 6 7 8 9 10 11
| auto-focus 自动获取焦点
<textarea class="content" placeholder="分享新鲜事..." maxlength="140" auto-focus bindinput="onInput" bindfocus="onFocus" bindblur="onBlur" ></textarea>
|
选择上传图片
上传图片
1 2 3 4 5 6 7 8 9 10
| let max = 9 - this.data.images.length; wx.chooseImage({ count: max, sizeType: ["original", "compressed"], sourceType: ["album", "camera"], success: (res) => { console.log(res); }, });
|
图片裁剪
图片裁剪
1 2
| <image class="image" src="{{item}}" mode="aspectFill"></image>
|
获取标签自定义属性 data-* (删除图片的实现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <!-- 显示图片 --> <block wx:for="{{images}}" wx:key="*this"> <view class="image-wrap"> <!-- mode 图片裁剪 aspectFill 保证短边完整显示 --> <image class="image" src="{{item}}" mode="aspectFill"></image> <icon class="iconfont icon-shanchu" bindtap="onDelImage" data-index="{{index}}"></icon> </view> </block>
// 删除图片 onDelImage(event) { // event.target.dataset.index 获取标签属性data-index的值 this.data.images.splice(event.target.dataset.index, 1) // splice会改变原有数组 this.setData({ images: this.data.images }) },
|
全屏预览图片(点击图片放大预览)
全屏预览图片
1 2 3 4 5 6 7
| onPreviewImage(event) { wx.previewImage({ urls: this.data.images, current: event.target.dataset.imgsrc }) },
|
文件上传云存储(发布博客例子)
文件上传云存储
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
| send() { if (content.trim() === '') { wx.showToast({ title: '请输入内容', icon: 'none' }) return } wx.showLoading({ title: '发布中', })
let promiseArr = [] let fileIds = [] this.data.images.forEach((item) => { let p = new Promise((resolve, reject) => { let suffix = /\.\w+$/.exec(item)[0] wx.cloud.uploadFile({
cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix, filePath: item, success: (res) => { fileIds.push(res.fileID) resolve() }, fail: (err) => { console.error(err) reject() } }) }) promiseArr.push(p) })
Promise.all(promiseArr).then((res) => { db.collection('blog').add({ data: { ...userInfo, content, img: fileIds, createTime: db.serverDate() } }).then((res) => { wx.hideLoading() wx.showToast({ title: '发布成功', }) wx.navigateBack()
}) }).catch((err) => { wx.hideLoading() wx.showToast({ title: '抱歉,发布失败', icon: 'none' }) }) },
|
js 模块化 (时间格式化)
在目录 utils 中新建 formatTime.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
| module.exports = (date) => { let fmt = "yyyy-MM-dd hh:mm:ss"; const o = { "M+": date.getMonth() + 1, "d+": date.getDate(), "h+": date.getHours(), "m+": date.getMinutes(), "s+": date.getSeconds(), };
if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, date.getFullYear()); }
for (let k in o) { if (new RegExp("(" + k + ")").test(fmt)) { fmt = fmt.replace( RegExp.$1, o[k].toString().length === 1 ? "0" + o[k] : o[k] ); } }
return fmt; };
|
在组件引入 js 模块
1 2 3 4
| import formatTime from '../../utils/formatTime.js'
使用: formatTime(new Date('Wed Aug 28 2019 16:23:06 GMT+0800 (中国标准时间)'))
|
阻止事件冒泡
bind
和 catch
都可以绑定事件,它们的区别是 bind
有事件冒泡,而 catch
没有
返回上一个页面并执行方法
API
1 2 3 4 5
| wx.navigateBack(); const pages = getCurrentPages(); const prevPage = pages[pages.length - 2]; prevPage.onPullDownRefresh();
|
图片懒加载
API
1 2 3 4
| 给image标签设置 lazy-load 为 true <image class="img" src="{{item}}" lazy-load="true"></image>
.img { background: #eee; }
|
懒加载占位图可以给 image 设置背景图或背景色
模糊查询
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
| app.router("blogList", async (ctx, next) => { const keyword = event.keyword; let w = {}; if (keyword.trim() != "") { w = { content: db.RegExp({ regexp: keyword, options: "i", }), }; }
ctx.body = await blogCollection .where(w) .skip(event.start) .limit(event.count) .orderBy("createTime", "desc") .get() .then((res) => { return res.data; }); });
|
提升模糊查询的效率 (添加索引,对数据量大的查询效果明显)
云开发控制台 > 数据库相应的集合 > 索引管理 > 添加索引 > 输入自定义索引名称、该字段的值是否唯一、被查询的字段名、升序/降序 > ok
小程序端调用云数据库
一般调用云数据库的操作都写在云函数内,其实小程序端也可以对数据库进行操作。
小程序端一次最多只能查询 20 条数据,云函数端最多可查询 100 条数据,可使用多次查询拼接的方式突破限制。
1 2 3 4 5 6 7 8
| const db = wx.cloud.database(); db.collection("blog") .orderBy("createTime", "deac") .get() .then((res) => { console.log(res); });
|
云数据库权限管理
注意:云控制台和服务端(云函数)始终有所有数据读写权限,
但权限的管理仅对小程序端发起的请求有效。
仅创建者可写,所有人可读 (适合于文章)
仅创建者可读写 (适用于私密内容)
仅管理端可写,所有人可读(适用于商品信息)
仅管理端可读写(适用于后台敏感数据)
数据库中 1 对 N 关系的三种设计方式
第一种:N 的数量较少 几十个以内
1 条记录存储 N 个子数据
如一条博客中,最多有 9 张图片,这 9 张图片可和其他数据放在一个记录中。
1 2 3 4 5 6 7 8
| [ { id:... img:[ '...', '...', '...', '...', '...', '...', '...', '...', '...' ] } ]
|
第二种:N 的数量较多 几十到几百个
1 存储 每个 N 的 id
可分两个数据库集合,
一个为 ‘目录’ 集合,存放 ‘详情’ 集合下的每条数据的 id 目录
一个为 ‘详情’ 集合,每条数据对应一个单独的 id 和 详细数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 目录集合: [ { 'id':"11", 'name': '产品1', 'xqs': ['111','222','333', ... ] } ]
详情集合: [ {'id':"111",name:'零件1',title:'...' ...}, {'id':"222",name:'零件2',title:'...' ...}, {'id':"333",name:'零件3',title:'...' ...}, ... ]
|
如歌单列表,与歌曲详情的数据组合设计。
第三种:N 的数量巨大 几百成千上万个
每个 N 都存储 1 的 id
如新浪博客中的一条博客下面有几千条评论
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
| 一条新浪博客: [{ 'id':'11', 'content':'博客内容' ... }]
上千条评价: [ { 'id':'111111' 'blogId':'11', 'content': '评价内容1' }, { 'id':'222222' 'blogId':'11', 'content': '评价内容2' }, { 'id':'33333' 'blogId':'11', 'content': '评价内容3' }, ... ]
|
云调用
通过云函数调用服务端的开发接口
这些接口如:模板消息推送、生成小程序码…
模板消息推送
1、使用 from 表单才能触发消息推送,并设置 report-submit=”true”
1 2 3 4 5 6 7 8 9 10
| <form slot="modal-content" report-submit="true" bind:submit="onSend"> <textarea name="content" class="comment-content" placeholder="写评论" value="{{content}}" fixed="true" ></textarea> <button class="send" form-type="submit">发送</button> </form>
|
2、需要到微信公众平台做相应的设置:
微信公众平台 > 功能 > 模板消息 > 添加模板 > 选择相应的模板> 添加成功后会有一个模板 ID
3、新建一个云函数,用于云调用。在该云函数下新建配置文件:config.json ,用于配置权限
config.json :
1 2 3 4 5
| { "permissions": { "openapi": ["templateMessage.send"] } }
|
云函数设置消息推送:
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
| exports.main = async (event, context) => { const { OPENID } = cloud.getWXContext();
const result = await cloud.openapi.templateMessage.send({ touser: OPENID, page: `/pages/blog-comment/blog-comment?blogId=${event.blogId}`, data: { keyword1: { value: event.context, }, keyword2: { value: event.time, }, }, templateId: "LNwKMcYwlz-0HabgBhmZi6CWZrlNSBiNJ2h0SMorcxQ", formId: event.formId, });
return result; };
|
4、在提交表单事件完成后调用消息推送云函数
1 2 3 4 5 6 7 8 9 10 11 12
| wx.cloud .callFunction({ name: "sendMessage", data: { content, formId, blogId: this.properties.blogId, }, }) .then((res) => { console.log(res); });
|
云函数多集合查询数据库
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
| app.router("blogDetail", async (ctx, next) => { let blogId = event.blogId;
let detail = await blogCollection .where({ _id: blogId, }) .get() .then((res) => { return res.data; });
const countResult = await blogCollection.count(); const total = countResult.total; let commentList = { data: [], }; if (total > 0) { const batchTimes = Math.ceil(total / MAX_LIMIT); const tasks = []; for (let i = 0; i < batchTimes; i++) { let promise = db .collection("blog-comment") .skip(i * MAX_LIMIT) .limit(MAX_LIMIT) .where({ blogId, }) .orderBy("createTime", "desc") .get(); tasks.push(promise); } if (tasks.length > 0) { commentList = (await Promise.all(tasks)).reduce((acc, cur) => { return { data: acc.data.concat(cur.data), }; }); } } ctx.body = { detail, commentList, }; });
|
分享功能
分享功能需要 button 标签,设置 open-type=”share”
1 2 3 4 5 6 7 8 9 10
| <button open-type="share" data-blogid="{{blogId}}" data-blog="{{blog}}" class="share-btn" hover-class="share-hover" > <i class="iconfont icon-fenxiang icon"></i> <text>分享</text> </button>
|
在 js 中有 onShareAppMessage 方法,点击 button 会自动执行此方法
1 2 3 4 5 6 7 8 9 10 11
| onShareAppMessage: function (event) { console.log(event)
let blogObj = event.target.dataset.blog return { title: blogObj.content, path: `/pages/blog-comment/blog-comment?blogId=${blogObj._id}`, } }
|
不同场景获取用户信息的方式
场景一:只想在界面上显示自己的昵称和头像
以组件的方式:根据 type 类型获取不同用户数据
该方式不需要授权,只能用于在 wxml 显示自己的信息
open-data
1 2 3
| <open-data type="userAvatarUrl"></open-data> <open-data type="userNickName"></open-data> ...
|
场景二:在 JS 中获取用户信息
该方式要在用户授权以后才能获取用户信息
wx.getUserInfo
1 2 3 4 5
| wx.getUserInfo({ success: (res) => { console.log(res); }, });
|
在未授权的情况下需要用户先授权:
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
| wx.getSetting({ success: (res) => { if (res.authSetting['scope.userInfo']) { wx.getUserInfo({ success: (res) => {
app.setGlobalData('userInfo', res.userInfo)
this.onLoginSuccess({ detail: res.userInfo }) } }) } else { this.setData({ modalShow: true }) } } })
授权按钮 <button class="login" open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">获取微信授权信息</button>
onGetUserInfo(event) { const userInfo = event.detail.userInfo if (userInfo) { this.setData({ modalShow: false }) this.triggerEvent('loginSuccess', userInfo) } else { this.triggerEvent('loginFail') } }
|
注意:上面这种方式没有获取到 openId
场景三:获取 openId
获取 openId 不需要用户授权
1、传统开发方式获取 openId,后台服务器由自己开发,没使用云开发
小程序端 微信服务器 后端服务器
步骤:
小程序端 调用 wx.login 向微信服务器 获取 code
小程序端 调用 wx.request 将 code 传递给 后端服务器
后端服务器 使用 code 向微信服务器 换取 openid 和 session_key
后端服务器 将 openid 发送给 小程序端
2、云开发方式获取 openId
云函数 login 中
1 2 3 4 5 6 7 8 9
| const wxContext = cloud.getWXContext();
return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, };
|
1 2 3 4 5 6 7 8 9 10
| 普通按钮 <button bindtap="getOpenid">获取openid</button>
getOpenid() { wx.cloud.callFunction({ name: 'login' }).then((res) => { console.log(res) }) }
|
openid 在小程序和公众号下是不一样的
unionid 在小程序和公众号下都是一样的