- 一文教你如何更优雅的处理微信支付商户订单投诉?
在国内做产品涉及使用微信支付商户收款时,难免会遇到一些消费者对支付订单发起投诉,商户如果处理不当会导致商户号出现被延长收款结算周期、限制收款能力、调整交易额度或限制提现等处罚,严重的会关闭商户主体下商户全部支付权限。 那么商户该如何正确处理消费者投诉,降低各项投诉指标呢?一些简单实用的小技巧解除你的烦恼。 微信支付对于消费者投诉处理时效要求: 1)商户最晚需在投诉单生成1天内回复用户投诉受理情况(如9月20日的投诉,商户需在9月21日24点前回复用户) 2)商户最晚需在投诉单生成3天内处理完用户问题,并在商户平台标记“投诉处理完成”(如9月20日的投诉,商户需要在9月23日24点前处理完结) 如何判断一笔投诉单属于已解决?对与投诉单微信支付的认可的处理完成标准如下: ①订单原路全额退款 ②用户主动在投诉入口回复“撤诉” ③商户通过投诉交互功能协商,确认投诉已协商达成一致,点击处理完成,用户点击“已解决”且用户不再重复投诉。 一些处理小技巧: 1)在产品中增加明显的“联系客服”入口,一般用户可以联系到客服的情况下,一般不会优先去投诉支付订单,可以有效降低商户号的客诉率 2)接入微信支付商户的“消费者投诉”接口,实时获取投诉通知,通过类似企业微信机器人能力,让指定人员可以实时接到客户投诉信息,及时处理客户投诉;针对一些投诉即将到达72h的投诉单对指定人员进行多次推送,以免出现投诉单出现超时未处理的情况,微信支付考核商户投诉处理三大指标之一的“及时处理率”就是近30天(T-33天到T-4天)内发起的投诉单,在首次投诉发起后72h内处理完成的比例。 3)在投诉单对用户进行回复后,不要直接去点击“处理完成”,当商家点击“处理完成”后,用户是无法再进行直接回复的,会发起二次投诉,标记”投诉处理完成"前是需要已完成处理用户问题的,此类操作会导致“重复投诉率”大幅度提升,不少商户都在此处踩坑。建议申请结单前确认是否已经与消费者妥善协商处理,针对重复投诉的商户需要关注并及时处理,如重复投诉较多,需要及时排查原因并做优化。 4)当遇到一些恶意投诉/不合理的投诉,请按照商户公司自己的流程妥善处理,并将具体的处理情况和结果回复用户。如果确认无法满足用户的诉求且已经是最终处理方案,在连续发起结单3次后用户仍不满意可以先暂停处理,但这单会被记录服务不满意。如果是恶意投诉,微信侧针对重复投诉会提供10%的“容错率”,如果因为此类恶意投诉导致风控,提供相关凭证在商户后台进行申诉即可。 以下是在处理客诉过程中不可取的行为,会触及“平台消费者投诉管理规范”高压线: 🙅不要主观上先去给用户扣上一顶“恶意投诉”的帽子,搞清楚状况之前,不要妄下结论,主观是大忌,会影响你对客诉处理过程中的判断 🙅不要在沟通过程中(无论是在交易投诉系统还是电话或者商家自建客服系统等)去恶意诱导或辱骂用户 🙅不要去恶意骚扰用户,例如在交易投诉系统拿到用户手机号给他来个“轰炸机”套餐 🙅不要脱离平台去使用三方聊天工具进行沟通 有问题欢迎跟帖讨论,文明沟通,理性发言!!!
2023-09-19 - 关于__wxConfig.envVersion 这个api的使用?
_wxConfig.envVersion 这个api是官方提供的吗?怎么在文档中找不到? 这个api在审核的时候返回值是'develop'吗? 望回复,谢谢
2019-09-27 - ios小程序,wx.canvastotempfilepath生成图片的模糊的问题
手机版本 iPhone 6 plus ios10.3.1 iPhoneSE ios10.3.1 ios端 通过wx.canvastotempfilepath生成的图片非常模糊,即便截取200x200的图片,显示为100X100依然模糊 安卓端完全正常
2017-05-17 - canvasToTempFilePath生成图片模糊
为什么我的 canvasToTempFilePath生成的图片是模糊的啊? 有大神知道怎么生成跟手机小程序显示的页面一样的吗?
2017-06-14 - wx.getUserProfile不能和wx.login一起使用?
mac 模拟器1.05.2102010 基础库2.16.0 调用wx.login获取code后,再调用wx.getUserProfile,可能会失败,触发fail函数,error msg: ''getUserProfile:fail can only be invoked by user TAP gesture"。 如果不能同时使用,那如何校验用户信息的准确性或者解密encryptedData呢?
2021-04-08 - #小程序云开发挑战赛#-抽屉表情-为了获得T恤
应用场景 我在跟朋友聊天,灵感来了 “我有个很应景的表情包,这时候我要是发出去的话,肯定很有意思” 于是我打开微信表情寻找,划呀划划呀划……过了大概30秒,我突然感觉有点头疼,这种感觉就像我今天下午跟女朋友玩1000张的拼图,里面的天空是一望无际的蓝色一样,让人奔溃。图还没找到,可是我们的话题已经来到了另一个世界。之前想找的那张表情就像我冰箱里龙眼,放了太久,还没拿出来吃就烂了。 有时候我知道表情包上写着什么,可是却没有办法搜索。 我手机相册里有从各种app上收集来的几百张表情图,每次想要发送一张,我就得在几百张图片中来回寻找,可谓众里寻他千某度。 生活已经很累了,就不要再徒增烦恼了。 所以就有了今天这个小程序:抽屉表情;就像名字一样,你想要的表情就在这个抽屉里,打开就能看到。 主要功能 上传表情图,管理你手机里的表情包。 给表情添加标签,通过标签快速搜索。 使用表情模板功能,超简单三步,表情配图并发送。 目标用户 我,以及跟我一样有上面烦恼的同学。 实现思路 为了达到低成本,高效率开发,服务端使用了小程序云函数三件套:云函数,数据库,云存储。 小程序端使用了小程序兼容层框架 Taro,使用前端流行的 react 框架开发。其特点是高效便捷。 使用了typescript增加代码质量与可维护性,使用了mobx作为状态管理工具,实现了更好的页面组件的数据流通信。 架构图 [图片] 效果截图 [图片] [图片] [图片] [图片][图片] [图片] [图片] 功能代码展示 import { observable, action } from 'mobx' import { TemplateIndexData } from 'src/types' /** * 表情制作模块 store */ export class MakeStore { /** * 首页数据 */ @observable indexData: TemplateIndexData[] = [] /** * 设置首页的数据 */ @action.bound setIndexData(data) { this.indexData = data } /** * 存储表情模板集合的map * 用户缓存表情模板集合的数据,避免接口重复加载 */ @observable collectionMap: Map = new Map() /** * 存储表情模板的map */ @observable templateMap: Map = new Map() /** * 设置模板集合 */ @action.bound setCollection(data) { this.collectionMap.set(data._id, data) const { templates = [] } = data templates.forEach(template => { this.templateMap.set(template._id, template) }); } /** * 获取单个模板集合 */ @action.bound getCollection(id) { return this.collectionMap.get(id) } /** * 获取单个模板数据 */ @action.bound getTemplate(id) { return this.templateMap.get(id) } } const makeStore = new MakeStore() export default makeStore import React, { Component } from 'react' import { View, Canvas, Textarea, Button, Input } from '@tarojs/components' import Taro, { getImageInfo } from '@tarojs/taro' import './edit.scss'; import { inject, observer } from 'mobx-react'; import { MemeTemplate, Store } from 'src/types'; type PageProps = { store: Store } type PageState = { loading: boolean, canvasStyle: { width: number, height: number }, /** 表情模板 */ template: MemeTemplate, text?: string, } @inject('store') @observer class MakeEdit extends Component { /** 开发模式,显示文字框与调试框 */ dev = false ctx: Taro.CanvasContext imgRatio = 1 image: getImageInfo.SuccessCallbackResult /** 每1px(非物理像素) 等于多少 rpx */ pxOfRpx = 1 state: PageState = { loading: false, canvasStyle: { width: 690, height: 460 }, template: null as any } /** * 由于图片显示时有可能进行缩放 * 所以需要计算出缩放后的文本框的位置与大小 */ get computedTextArea() { const { x, y, w, h } = this.state.template.textArea const result = { x: x * this.imgRatio, y: y * this.imgRatio, w: w * this.imgRatio, h: h * this.imgRatio, } return result } async loadImg() { this.setState({ loading: true }) Taro.showLoading({ title: '加载中', mask: true }) const res = await Taro.getImageInfo({ src: this.state.template.url }) this.setState({ loading: false }) Taro.hideLoading() return res } async onLoad(option) { const template = this.props.store.make.getTemplate(option.id) this.setState({ template }) //由于setState 不会马上执行,需要等待 setState 执行完成 await Promise.resolve() const res = await this.loadImg() const { screenWidth } = Taro.getSystemInfoSync() /** 每1px 有多少 rpx */ this.pxOfRpx = 750 / screenWidth this.image = res this.ctx = Taro.createCanvasContext('canvas') this.initCanvas() this.drawContent() } initCanvas() { const maxWidth = 690; const maxHeight = 690; const width = this.image.width * this.pxOfRpx const height = this.image.height * this.pxOfRpx /** 最大宽高与图片宽高的最小比值 */ const ratio = Math.min(maxWidth / width, maxHeight / height) /** 图片需要缩放的比例,图片宽高没有超过最大宽高的话,不需要缩放 */ this.imgRatio = Math.min(ratio, 1) this.setState({ canvasStyle: { width: width * this.imgRatio, height: height * this.imgRatio, } }) } /** * dev:绘制文本框 */ drawRect() { const ctx = this.ctx ctx.setStrokeStyle('red') const { x, y, w, h } = this.computedTextArea ctx.strokeRect(x, y, w, h) ctx.draw(true) } /** * 绘制文本 * 根据文本的长度,自动计算出字体大小。简化用户操作 */ drawText(lines) { if (!lines.length) return const textStyle = this.state.template.textStyle const textColor = textStyle?.color || 'black' let maxFontSize = textStyle?.maxFontSize || 50 const ctx = this.ctx ctx.setTextBaseline("top") ctx.setTextAlign("center") ctx.setFillStyle(textColor) const { x, y, w, h } = this.computedTextArea let fontSize = maxFontSize ctx.setFontSize(fontSize) //计算出长度最长那行的值 const longestValue = lines.map(text => ctx.measureText(text).width).sort((a, b) => b - a)[0] //文字行数 * 行高不能超过文本框高度,计算出最大字体大小 maxFontSize = Math.min(maxFontSize, h / lines.length / 1.2) //根据比例,计算出最合适的字体大小 fontSize = Math.min(maxFontSize, w / longestValue * fontSize) ctx.setFontSize(fontSize) //逐行绘制 const lineHeight = fontSize * 1.2 for (const [index, text] of lines.entries()) { ctx.fillText(text, x + w / 2, y + lineHeight * index) } ctx.draw(true) } async drawImg() { const ctx = this.ctx const { width, height } = this.state.canvasStyle ctx.drawImage(this.image.path, 0, 0, width / this.pxOfRpx, height / this.pxOfRpx) ctx.draw(true) } /** * 绘制所有内容 */ drawContent() { this.ctx.draw(false) const text = this.state.text || '' const lines = text.split('\n').filter(item => item).map(item => item.trim()) this.drawImg() this.dev && this.drawRect() this.drawText(lines) } /** * 文本变化时绘制内容 */ handleTextChange = e => { this.setState({ text: e.target.value }) this.drawContent() } /** * 将canvas上的内容生成临时文件,并且预览 */ previewImg() { const ctx = this.ctx //真机上如果没有操作,会无法draw成功 ctx.setStrokeStyle('red') ctx.draw(true, () => { Taro.canvasToTempFilePath({ canvasId: 'canvas', success: function (res) { Taro.previewImage({ urls: [res.tempFilePath] }) }, }) }); } handleComplete = async () => { this.previewImg() } handleCancel = () => { Taro.navigateBack() } /** * 开发时,调试文本框与文字样式 */ handleDevInput = (e) => { const value: string = e.target.value const [x, y, w, h, fz, color] = value.split(',') if (value.split(',').some(item => !item)) { return } this.setState({ template: { ...this.state.template, textArea: { x: +x, y: +y, w: +w, h: +h }, textStyle: { maxFontSize: +fz, color } } }) console.log(`设置文本框`); console.log(JSON.stringify({ id: this.state.template._id, ...this.state.template })); this.drawContent() } render() { return ( {!this.state.loading && } {/* *点击完成后,长按图片分享给朋友哦! */} { this.dev && this.state.template?.textArea && ( x,y,w,h,fz,color ) } 完成 取消 ) } } export default MakeEdit 团队简介 为了获得T恤队,口号是:不忘初心
2020-09-20 - #小程序云开发挑战赛#-迷你小摊-我的小摊
应用场景 迷你小摊是一款在地摊经济下提供地摊摊主、游客用户连接的一个连接桥梁,地摊摊主发布摊位信息,游客可浏览该地区的摊位信息,导航到想去的摊位。 目标用户 想要发布自己摊位信息的摊主和想逛地摊的用户。 实现思路 结合 Vant Weapp 搭建前端的显示页面,使用云函数、云开发数据库、云存储提供数据的存储服务,加上腾讯地图开放平台的web服务,坐标解析,距离计算, 地图导航等功能实现。 架构图 [图片] 效果截图 游客端截图 游客首页 [图片] 查看小摊 [图片] 发布评分评论 [图片] 导航去到小摊 [图片] 游客我的页 [图片] 游客历史记录页 [图片] 游客收藏页 [图片] 游客所有评论页 [图片] 商家端截图 商家小摊位页 [图片] 商家小摊发布页 [图片] 商家小摊修改页 [图片] 商家摊位管理页 [图片] 切换商家摊位 [图片] 迷你小摊介绍视频 https://v.qq.com/x/page/c3154noe6gz.html 迷你小摊体验二维码 [图片] 团队简介 两个大四的学生
2020-09-20 - #小程序云开发挑战赛#-急速查病-YnnnP
应用场景: 简介:急速查病是一个以提供健康资讯、疾病预防、疾病数据查询、医患交流为主的健康小程序。致力于为每一位网友提供优质的健康信息服务,并为广大网友及专业人士提供一个高质量的健康交流平台而不懈努力。目前小程序设有人体器官的相关疾病查询,医生咨询,未来急速查病还将提供更多、更专业、更完善的健康信息,成为网络生活中最值得信赖的健康顾问。 功能展示 视频 [视频] 当前版本较low,后续有时间再进行更新迭代。 [图片] [图片] [图片] [图片][图片][图片][图片][图片][图片] 视频链接: 实现思路 首页: 1.点击相应器官获取相关症状,此处是在云数据库中bodyparts中操作,具体为每个器官设置一个position字段,前端获取bodyparts表中数据后在view标签遍历如图:[图片][图片] 在view标签style中是每一个设置的position属性,即item.position, [图片] 这样一来,点击人体器官图上的相应的坐标就会有反应,同时人体图上data—partname传item.partname,即器官名称,再拿器官名称去查询数据库,此处查询的表依旧为bodyparts,如图 里面有个xiangguanzhengzhuang列表字段,获取到之后,遍历循环在相关症状栏,到这里实现了点击器官获取相关症状的逻辑 2.再来,点击相关症状获取到该症状的具体信息实现思路,该操作在symptom_detail表中进行, [图片] 首先,该表中包含所有症状信息,症状即为相应器官的症状,在遍历的相关症状栏中给症状名view一个bindtap,同时这个view标签的data—zhengzhuanname需要传的是症状名,点击症状名view调用getdetailzhengzhuang函数,如图: [图片] [图片] 此函数是条件搜索云数据库symptom_detail,这样就可以搜索到该症状的具体对象信息,之后遍历即可 流程框架图 [图片] 团队简介: 人数:2人 两人均来自广西医科大学。 P同学负责主程序,云开发,前端,数据库设计。 C同学负责资料收集及前端设计,图片制作。 二维码:[图片]
2020-09-19 - #云开发挑战赛#-快刷题库answer question-老师说啥就是啥
#云开发挑战赛#-快刷题库answer question-老师说啥就是啥 应用场景: 随开随刷的校园题库类小程序 目标用户:高校学生 实现思路:通过云函数实现答题板块 结构图:[图片] 效果图:[图片]代码链接:https://github.com/zengrun-001/Quick-answer 作者简介:增润
2020-09-15 - #小程序云开发挑战赛#-全国核酸检测资质医院查询-酷亿队
应用场景:需要出具核酸检测的人群方便查询有资质医院,快速获取医院位置信息以及电话进行咨询 用户人群:各大高校学生、出行人员等 实现思路:实在是不需要啥思路、真是是有手就会。 团队简介:7天B站学习小白 代码分享:https://github.com/zwz888mm/zhang 演示视频:https://v.qq.com/x/page/c3150kzzkka.html [图片] [图片]
2020-09-11 - #小程序云开发挑战赛#-WiFi生成码-决明子
首先感谢官方提供这次的比赛。刚好最近在上手小程序云开发,项目呢也是边练习边做出来的,正好试试水参加一下这次的比赛。 随着微信小程序的不断更新,能力也越来越丰富。我这次的项目初衷就是想结合一下小程序调用手机WiFi和小程序兼用iOS和安卓的能力,做一个只需要拿出微信扫一下码,就可以快速连接WiFi,解决需要繁琐操作或者使区分app才能连接WiFi的困境。 这个项目虽然比较小,但是充分调用了小程序的能力,并且也结合了云开发的优势。微信小程序可以在iOS和安卓上运行,解决了传统项目必须去兼容这个两个端的接口,减少了工作量。开发出的小程序运行也比较稳定和成熟,不会出现重大bug的情况。并且像这么小的项目如果同时写两端成本比较高。再结合云开发,都不需要配置服务器,项目就可以完美的运行。 本项目展示了,云开发的基本操作,如:增删改查。还有生成小程序太阳码,上传图片到云存储,下载图片等。充分展示小程序和云开发能力。 [图片]
2020-09-12 - #小程序云开发挑战赛# - 高级打卡鸡
高级打卡鸡 作品简介 高级打卡鸡,顾名思义,专用于记录用户[代码]打卡[代码],在各个时间点记录地理位置,打点位置会进行[代码]连线[代码],可以看到自己在世界地图上遍布的足迹。 体验直通车 [图片] 应用场景 喜欢记录,对自己足迹关注,想了解自己在世界上遍布的足迹,非常方便各类[代码]旅游人士[代码],看到自己慢慢的打满[代码]地图[代码],非常有[代码]成就感[代码],也非常有意义。 效果介绍 首页 首页,可以在地图看到自己所在的位置,并进行[代码]打卡[代码],打卡后会生成分享图,便于进行朋友圈传播,也可以直接进行分享。 [图片] [图片] 记录页 [代码]历史记录[代码]页,可以看到自己以往所有的打点详情,并可以左滑删除对应记录 [图片] 世界圈 可以在[代码]世界圈[代码]看到各个用户的打卡情况,自身的打卡与其他用户做区分,同时为了保证用户的隐私,仅展示打卡时间和打卡地点。 右上角还新增了榜单功能,可以进入到榜单页看到各个用户的打卡排行榜,争取多打卡展示到top10吧。 [图片] [图片] 个人页 展示一些基本用户信息,放置了[代码]天气信息[代码],可以方便的查看天气情况 点击头像有惊喜,现在流行的[代码]头像制作[代码]功能,已集成到打卡鸡中,头像使用了挺久,换个头像试试? [图片] [图片] 功能原理 架构图 [图片] 源码分析 https://github.com/hzxulin/punch-chicken ps: 喜欢的关注我一波吧 团队简介 Charles Hsu,一位会点ps,会点后端,喜欢倒腾的前端开发工程师
2020-09-27 - #小程序云开发挑战赛#-答案sou-芝麻西瓜
1.应用场景与解决问题 调查研究表明,当前高校学生在课后习题上所花的时间主要集中在思考以及查阅相关资料上。尽管这两不是必不可少的,但是查询相关资料所用的时间往往占有重大比重,所以如何有效的缩减资料查询时间,是本程序解决的问题。因此,答案sou小程序的使用场景也显而易见:该小程序针对的是高校学生,在大学生解决课后习题时,想要查看习题的相关题解可以通过该小程序实现。最便捷的方法是用户通过程序扫描图书背后的条形码,获取图书题解信息,用户之后可以通过对应的章节获取用户所需的题解信息。当然上述方法只是方法之一,用户同样可以通过书籍分类、书籍搜索查找。课后习题的查找只是应用场景之一,该小程序同样适用于考研学生或者等级考试。考研学生或者等级考试学生可以在该系统上获取历年的考研真题、英语四六级等级考试真题及其解析。 2.目标用户 答案sou是一款用于解决大学生搜索答案困难而诞生的小程序。 3.实现思路 该系统的实现通过前端微信小程序以及配合云开发技术实现整个系统架构。前端小程序界面的构建部分使用了目前比较流行的小程序前端ui框架—Vant Weapp,vant ui封装了许多美观,可靠的组件,除了借用vant ui之外,系统自身也封装了许多可以复用的自定义组件。微信小程序云开发使得在小程序端可以直接操作云端的数据库,当然这有查询条目的限制,但这种限制可以通过云函数突破。该系统创建了许多云函数与用户的操作相对应。用户相应的操作会调用相关的云函数,通过云函数实现对云数据库,云存储进行操作。通过小程序以及云开发技术的实现大大降低了开发整个系统的周期。整个项目的难点在于数据的收集。该系统的所有数据通过python scrapy框架爬取而得,并通过相关的处理函数对爬取而得的数据进行一定的格式处理,使得数据成为符合系统要求的数据。之后将所有有效的数据上传至小程序云中。至此便可操控数据库,对云文件可以根据云id进行相关操作。资源的爬取是耗时耗力的,其中还要处理各种异常。 4.运行效果图 [图片][图片][图片] 5.功能代码展示 //获取热门书籍 async getHotBook () { const { data: data } = await db.collection('hotBook').field({ id:true, isbn:true, name:true, author:true, cover:true, view_num:true, publisher:true, }).orderBy('view_num','desc').get() return data }, 6.作品体验 [图片]
2020-09-06 - 关于云函数时区的问题
这个问题可能对有些场景不敏感,但是我下面说的场景那是太重要了,那就是签到 由于时区的问题,比如现在是28号,晚上8点,我在29号凌晨签到的时候,由于云函数端采用的是UTC+0 ,所以始终签到的是28号,问题非常重要, 这样就导致始终签到的是28号 [图片] [图片] 官方文档如下 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/notice.html 时区 云函数中的时区为 UTC+0,不是 UTC+8,在云函数中使用时间时需特别注意。如果需要默认 UTC+8,可以配置函数的环境变量,设置 [代码]TZ[代码] 为 [代码]asia/shanghai[代码]。 这种情况就造成了下面这个问题 云开发服务器的nodejs时区是utc+0 小程序本地开发的时区是utc+8 同一段云函数在本地调试和云端调试时表现不一致 关于云函数时区,我看了几个帖子,这里整理下 1、云函数中时区问题 https://developers.weixin.qq.com/community/develop/doc/0002eea7518aa0ea5f39ce7fd56c09 2、云开发,获得的日期怎么能成为北京时间的日期? https://developers.weixin.qq.com/community/develop/doc/000246fdf244305f44397a2e556000 这个帖子里面给出了两个方案,我验证后都没有生效, 3、云开发nodejs环境时区问题 https://developers.weixin.qq.com/community/develop/doc/0008c28e6687d8ddb2b8cf65056400 现在解决了,就是通过上面第三个问题里面的经验,增加环境变量之后,要重新部署云函数,或许要等个半小时。 写在2020-05-25 今天又写这块需求,增加环境变量之后一定要重新部署云函数,然后等个几分钟就好 [图片] 关于UTC不知道是什么的可以先了解下 https://time.is/UTC
2020-05-25 - 微信小程序支持双向绑定
贴个官方大大的链接 https://developers.weixin.qq.com/miniprogram/dev/framework/view/two-way-bindings.html 刚试了一下 真舒爽 基本库版本需要2.9.3以上 [图片]
2020-11-10 - 如何只使用一个云函数搞定一个庞大而复杂的系统
吐槽 翻遍社区的文章,关于云开发的干货,少之又少,大部分都还是官方文档的搬来搬去,没啥营养,是时候放出一点技术"干货"了(有经验的开发者都能想到的方案)! 正题 小程序云开发的云函数的最大限制是 [代码]50[代码] 个,假设每个接口都使用 [代码]1[代码] 个云函数的话,有 [代码]10[代码] 张表,每张表都有 [代码]增删改查[代码] 四个接口,那么就会有 [代码]40[代码] 个接口,再加上一些其他接口,差不多刚刚好够用,那如果有 [代码]20[代码] 张表,甚至更多的表、更多的接口呢?对于中小型的小程序来说足够使用,那如果一个非常庞大而复杂的系统该怎么办呢? 而且每一个云函数的运行环境是独立的,想要共享一些数据也不是特别方便,那么有没有什么办法突破这样的限制呢? 其实解决方案很简单,只需要一点点的 [代码]OOP[代码] 思想和利用 [代码]JavaScript[代码] 的特性,一个云函数就可以搞定所有的接口。 具体的实现请往下看。 思路 云函数的运行环境是 [代码]Nodejs[代码] , 那么使用的语言就是 [代码]JavaScript[代码] ,可以充分的利用 [代码]JavaScript[代码] 的特性。 [代码]JavaScript[代码] 中的 [代码]属性访问表达式[代码] 有两种语法 [代码]expression . identifier expression [ expression ] [代码] 第一种写法是一个表达式后跟随一个句点 [代码].[代码] 和一个标识符。表达式指定对象,标识符则指定需要访问的属性的名称。 第二种写法是使用方括号 [代码][][代码],方括号内是另一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的名称或者代表要访问数组元素的索引。 不管使用哪种形式的属性访问表达式,在 [代码].[代码] 和 [代码][][代码] 之前的表达式总是会首先计算。 虽然 [代码].[代码] 的写法更加简单,但这种方式只适用于要访问的属性名称的合法标识符,并需要准确知道访问的属性的名字,如果属性的名称是一个保留字或者包含空格和标点符号,或者是一个数字(对于数组来说),则必须使用方括号 [代码][][代码] 的写法。当属性名是通过运算得出的值而不是固定值的时候,这时也必须使用方括号 [代码][][代码] 写法。 感谢社区大神 @卢霄霄 提供参考资料,详见 [代码]JavaScript权威指南[代码] (犀牛书)4.4章节。 可以使用 [代码][][代码] 的形式来完成动态的属性访问。具体实现请往下看。 实现 上面说了太多废话了,下面直接开干吧。 新建云函数 在云开发目录中新建一个云函数,我这里命名为 [代码]cloud[代码]。 打开 [代码]index.js[代码] 文件你会看到下面这段代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } } [代码] 这个云函数仅作为入口使用,上面提到了云函数的运行环境是 [代码]Nodejs[代码] 那么 [代码]Nodejs[代码] 的特性也是可以使用的,这里主要用到的是全局对象 [代码]global[代码],详见文档 在文件中,写入一些必要的全局变量,主要还是云数据库方面的,方便后面使用。 在初始化后面插入代码 [代码]global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate [代码] 这样就可以在同一个云函数环境中直接访问这些全局变量。 创建公共类 然后新建一个文件夹,我这里命名为 [代码]controllers[代码] ,这个文件夹用于存放所有的接口。 在 [代码]controllers[代码] 中新建一个 [代码]base-controller.js[代码] 文件,创建一个叫做 [代码]BaseController[代码] 的类,用于提供一些公用的方法。 内容如下: [代码]class BaseController { /** * 调用成功 */ success (data) { return { code: 0, data } } /** * 调用失败 */ fail (erroCode = 0, msg = '') { return { erroCode, msg, code: -1 } } } module.exports = BaseController [代码] 看到这里大家可能有点没看懂在做什么,那么请继续往下看。 创建接口 假设创建一些要操作用户相关的的接口,可以在 [代码]controllers[代码] 文件夹中新建一个 [代码]user-controller.js[代码] 的文件,创建一个名为 [代码]UserController[代码] 的类,并继承上面的 [代码]BaseController[代码] 类,内容如下: [代码]const BaseController = require('./base-controller.js') class UserController extends BaseController { // ... } module.exports = UserController [代码] 可以在这个类中编写所有关于 [代码]user[代码] 的接口方法。 编写接口 假设要分页查询用户信息,可以在 [代码]UserController[代码] 类中创建一个 [代码]list[代码] 方法。 代码如下: [代码]async list (data) { const { pageIndex, pageSize } = data let result = await db.collection('users') .skip((pageIndex - 1) * pageSize) .limit(pageSize) .get() .then(result => this.success(result.data)) .catch(() => this.fail([])) return result } [代码] 由于上面已经定义了全局变量 [代码]db[代码] 所以在 [代码]UserController[代码] 中无需引入 [代码]wx-server-sdk[代码] 引入接口类 写到这里接口已经完成了,还需要再引入这些接口类才可以进行访问。在 [代码]index.js[代码] 中引入 [代码]user-controller.js[代码] [代码]const User = require('./controllers/user-controller.js') [代码] 然后创建一个 [代码]api[代码] 变量,[代码]new[代码] 一个 [代码]User[代码] 实例 [代码]const api = { user: new User() } [代码] 在 [代码]main[代码] 方法中调用 [代码]UserController[代码] 中的方法。 [代码]exports.main = async (event, context) => { const { data } = event let result = await api['user']['list'](data) return result } [代码] 写到这里基本已经完成了接口的调用,但想要一个云函数动态调用所有接口还需要做一些改动。 动态调用接口 刚开始的时候介绍了 [代码]属性访问表达式[代码],限制稍微改动一下 [代码]main[代码] 方法 [代码]exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } [代码] 在小程序调用云函数时,需要传入 [代码]controller[代码]、[代码]action[代码] 和 [代码]data[代码] 参数即可 [代码]const result = await wx.cloud.callFunction({ name: 'cloud', data: { controller, action, data } }) [代码] 完整 [代码]index.js[代码] 文件的代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const User = require('./controllers/user-controller.js') const api = { user: new User() } // 初始化 cloud cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate // 云函数入口 exports.main = async (event, context) => { exports.main = async (event, context) => { const { controller, action, data } = event const result = await api[controller][action](data) return result } } [代码] 其他实现 云开发官方团队打造的轮子 tcb-router
2020-05-26 - 【技巧】swiper仿tab切换
大家好,上次给大家分享了swiper多图片的解决方案:https://developers.weixin.qq.com/community/develop/doc/000068ff25ccf0bae4e76eab156c04 今天再给大家分享一个关于swiper的小技巧,利用swiper仿tab切换。 相信大家在app或浏览器上阅读新闻时,比如今日头条,会有这样一个场景,左右滑动的时候可以切换不同栏目,体验非常好,但是小程序好像没有提供相关组件,如果想实现这种效果该怎么做呢今天就给大家介绍一下在小程序里是怎么实现的。 首先先看下效果 [图片] 实现原理很简单,利用小程序swiper再配合scroll-view就能实现,不过这里面有几点需要注意一下: 1.scroll-view一定要给一个高度,不然会有问题; 2.切换的时候只显示当前的swiper-item里的内容,其它swiper-item里的内容可以先隐藏掉,这是因为如果你的swiper-item里的图片太多的话可能会造成页面回收,因为新闻列表大多是图文列表,而tab经常是不止两个的,可能是7、8个或更多,如果每个tab都显示的话到时上拉加载页面会非常庞大,所以这里我建议不用显示的内容先隐藏,记住是swiper-item里的内容不是swiper-item,到时切换回来时再重新渲染,如果你要保存滚动的位置还要做其它的一些处理,这里就不仔细讲解了; 3.这里适用的是整个页面都是tab切换的,如果只是在页面的某处实现tab切换,还要考虑高度的问题,加载数据的时候根据数据个数长度来计算高度,每次加载数据都要计算高度,切换到不同的tab也是,这部分比较麻烦,因为要计算,不过并不难,只要 计算正确的话是没有问题的; 大概就是这样,基本实现思路,大家可以根据这个思路去拓展,在上面加上自己的功能,over! 代码片段:https://developers.weixin.qq.com/s/89OO1smX736d 系甘先,得闲饮茶
2019-02-26