个人案例
- 从0到100:找搭子小程序开发笔记(一)
# 背景调查 “找搭子”小程序:能够解决人们在社交、休闲和约会方面的需求,提供方便快捷的方式来找到合适的伴侣或活动伙伴。许多人在社交场合中感到焦虑或不安,因此他们更倾向于使用在线平台来认识新的朋友或搭子。有些人可能生活在一个较小或有限的社交圈子中,很难扩展自己的社交网络。在这种情况下,“找搭子”小程序可以为他们提供一个更广泛的选择范围,认识来自不同背景和兴趣的人。 # 功能规划 - 1.搭子聚会发布与管理:用户可以创建和加入各种活动或事件,例如饭搭、旅搭、运动、户外、电影、音乐会等,并要求搭子一起参与。 - 2.报名:用户可以通过小程序报名参加搭子活动。 - 3.活动日程:提供活动的详细日程,方便安排时间。 - 4.会员管理:平台组织者可以使用小程序来管理会员资料。 - 5.活动反馈:活动的参与者可以通过小程序提供活动反馈和评价,发起人和平台可以借此改进未来的活动。 - 6.报名管理:活动发起人可以查看活动报名情况,包括已报名和审核的人数。 对于需要报名审核的活动进行报名名单审核。 - 7.签到管理:每次活动报名成功后,报名者获得一个签到二维码,出示给活动发起人,可以进行扫描签到。 # 概要设计 [图片][图片] # 数据库设计 ``` ActivityModel.DB_STRUCTURE = { _pid: 'string|true', ACTIVITY_ID: 'string|true', ACTIVITY_TYPE: 'int|true|default=0|comment=类型 0=后台发起,1=用户发起', ACTIVITY_USER_ID: 'string|false', ACTIVITY_TITLE: 'string|true|comment=标题', ACTIVITY_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中', ACTIVITY_CHECK_REASON: 'string|false|comment=审核理由', ACTIVITY_CATE_ID: 'string|true|default=0|comment=分类', ACTIVITY_CATE_NAME: 'string|false|comment=分类冗余', ACTIVITY_CANCEL_SET: 'int|true|default=1|comment=取消设置 0=不允,1=允许,2=仅截止前可取消', ACTIVITY_CHECK_SET: 'int|true|default=0|comment=审核 0=不需要审核,1=需要审核', ACTIVITY_IS_MENU: 'int|true|default=1|comment=是否公开展示名单', ACTIVITY_MAX_CNT: 'int|true|default=20|comment=人数上限 0=不限', ACTIVITY_START: 'int|false|comment=活动开始时间', ACTIVITY_END: 'int|false|comment=活动截止时间', ACTIVITY_STOP: 'int|true|default=0|comment=报名截止时间 0=永不过期', ACTIVITY_START_DAY: 'string|false', ACTIVITY_END_DAY: 'string|false', ACTIVITY_ORDER: 'int|true|default=9999', ACTIVITY_VOUCH: 'int|true|default=0', ACTIVITY_FORMS: 'array|true|default=[]', ACTIVITY_OBJ: 'object|true|default={}', ACTIVITY_JOIN_FORMS: 'array|true|default=[]', ACTIVITY_ADDRESS: 'string|false|comment=详细地址', ACTIVITY_ADDRESS_GEO: 'object|false|comment=详细地址坐标参数', ACTIVITY_QR: 'string|false', ACTIVITY_VIEW_CNT: 'int|true|default=0', ACTIVITY_JOIN_CNT: 'int|true|default=0', ACTIVITY_COMMENT_CNT: 'int|true|default=0', ACTIVITY_USER_LIST: 'array|true|default=[]|comment={name,id,pic}', ACTIVITY_ADD_TIME: 'int|true', ACTIVITY_EDIT_TIME: 'int|true', ACTIVITY_ADD_IP: 'string|false', ACTIVITY_EDIT_IP: 'string|false', }; ``` # 核心实现 ``` // 注册 async register(userId, { mobile, name, pic, forms, status }) { // 判断是否存在 let where = { USER_MINI_OPENID: userId } let cnt = await UserModel.count(where); if (cnt > 0) return await this.login(userId); where = { USER_MOBILE: mobile } cnt = await UserModel.count(where); if (cnt > 0) this.AppError('该手机已注册'); // 入库 let data = { USER_MINI_OPENID: userId, USER_MOBILE: mobile, USER_NAME: name, USER_PIC: pic, USER_OBJ: dataUtil.dbForms2Obj(forms), USER_FORMS: forms, USER_STATUS: Number(status), } await UserModel.insert(data); return await this.login(userId); } /** 获取手机号码 */ async getPhone(cloudID) { let cloud = cloudBase.getCloud(); let res = await cloud.getOpenData({ list: [cloudID], // 假设 event.openData.list 是一个 CloudID 字符串列表 }); if (res && res.list && res.list[0] && res.list[0].data) { let phone = res.list[0].data.phoneNumber; return phone; } else return ''; } /** 取得我的用户信息 */ async getMyDetail(userId) { let where = { USER_MINI_OPENID: userId } let fields = 'USER_PIC,USER_MOBILE,USER_NAME,USER_FORMS,USER_OBJ,USER_STATUS,USER_CHECK_REASON' return await UserModel.getOne(where, fields); } /** 修改用户资料 */ async editBase(userId, { mobile, name, pic, forms }) { let whereMobile = { USER_MOBILE: mobile, USER_MINI_OPENID: ['<>', userId] } let cnt = await UserModel.count(whereMobile); if (cnt > 0) this.AppError('该手机已注册'); let where = { USER_MINI_OPENID: userId } let user = await UserModel.getOne(where); if (!user) return; if (user.USER_PIC && user.USER_PIC != pic) { // 删除老头像文件 cloudUtil.deleteFiles(user.USER_PIC); // 更新活动和打卡里的头像 let ActivityModel = require('../model/activity_model.js'); let wherePic = { 'ACTIVITY_USER_LIST.USER_MINI_OPENID': userId, } let dataPic = { 'ACTIVITY_USER_LIST.$.USER_PIC': pic } ActivityModel.edit(wherePic, dataPic); } let data = { USER_MOBILE: mobile, USER_NAME: name, USER_PIC: pic, USER_OBJ: dataUtil.dbForms2Obj(forms), USER_FORMS: forms, }; if (user.USER_STATUS == UserModel.STATUS.UNCHECK) data.USER_STATUS = UserModel.STATUS.UNUSE; await UserModel.edit(where, data); } /** 登录 */ async login(userId) { let where = { 'USER_MINI_OPENID': userId }; let fields = 'USER_ID,USER_MINI_OPENID,USER_NAME,USER_PIC,USER_STATUS'; let user = await UserModel.getOne(where, fields); let token = {}; if (user) { // 正常用户 token.id = user.USER_MINI_OPENID; token.key = user.USER_ID; token.name = user.USER_NAME; token.pic = user.USER_PIC; token.status = user.USER_STATUS; // 异步更新最近更新时间 let dataUpdate = { USER_LOGIN_TIME: this._timestamp }; UserModel.edit(where, dataUpdate); UserModel.inc(where, 'USER_LOGIN_CNT', 1); } else token = null; return { token }; } ``` # UI设计 [图片] [图片] [图片] [图片] # 后台管理系统 [图片] [图片] [图片]
06-11 - 从0到1:基于微信小程序的瑜伽馆预约平台的开发笔记
背景分析 随着国民健身意识越来越强,各式各样的健身模式不断出现。瑜伽也受到了大众的喜爱,瑜伽行业发展越来越快,作为馆主,你还在微信群里让你的会员使用接龙的方式进行约课吗?你还在用传统的Excel进行排课吗?如果有一款小程序会员点一下就能约课,会不会让你惊喜、意外、激动——没错,瑜伽预约小程序就是为了解决馆主会员约课的痛点应运而生。功能包括瑜伽馆动态,瑜伽常识,瑜伽老师预约,瑜伽课程预约等模块。 该系统基于MVC架构,采用基于微信小程序平台开发,校园用户操作轻松快捷:无需下载安装APP。 功能抽象 [图片] 数据库设计 [图片] 遇到的技术难点 小程序启动是用户体验中极为重要的一环,启动耗时过长会造成小程序用户流失 开发者代码注入(逻辑层)预约小程序启动时需要从代码包内读取小程序的配置和代码,并注入到 JS 引擎中。 在预约主包代码注入过程中,会触发小程序的 App.onLaunch 和首次 App.onShow 生命周期。 在预约开发者代码注入完成后,框架侧会根据预约用户访问的页面进行一些页面数据初始化工作,触发首页的 Page.onLoad, Page.onShow 事件。 对启动耗时的影响预约开发者代码的注入耗时直接影响小程序的启动耗时。 在主流的 JS 引擎中,代码注入耗时包括编译和执行等环节,代码量、同步接口调用和一些复杂的计算,都会影响注入耗时。 由于预约首页渲染需要使用逻辑层发送的数据,如果开发者代码注入耗时过长,也会延迟首页渲染开始的时间。 在部分平台预约上,微信客户端会使用 V8 引擎的 Code Caching 技术对代码编译结果进行缓存,降低二次注入时的编译耗时 开发者代码注入(渲染层)预约开发者的 wxss 和 wxml 会经过编译注入到渲染层,包含页面渲染需要的页面结构和样式信息。 渲染层的注入耗时主要和预约页面结构复杂度和使用的自定义组件数量有关。 渲染层和逻辑层的预约开发者代码注入是并行进行的。 对启动耗时的影响 由于预约首页渲染需要使用渲染层的页面结构和样式信息,如果开发者代码注入耗时过长,会延迟预约首页渲染开始的时间。 首页(初次)渲染在预约开发者代码注入完成后,结合逻辑层得到的数据和渲染层得到的页面结构和样式信息,预约小程序框架会进行小程序首页的渲染, 展示小程序首屏,并触发首页的 Page.onReady 事件。Page.onReady 事件触发标志小程序启动过程完成。 对启动耗时的影响预约首页渲染耗时主要受页面结构和参与渲染的数据量影响 小程序首屏渲染完成从开发者角度看,预约小程序首屏渲染完成的标志是首页 Page.onReady 事件触发。 从框架的角度来说,预约小程序的首屏的内容是基于小程序的初始数据,以及在渲染开始前到达的 setData 数据渲染的。 首屏渲染完成不表示小程序页面一定有完整内容,开发者触发的 setData(例如通过 wx.request 异步请求数据)不一定能参与到首屏渲染中。 由于框架和启动流程的差异,小程序定义的首屏渲染完成不等同于浏览器的 load 事件。 小程序启动阶段从预约用户点击访问小程序到小程序首屏渲染完成(首页 Page.onReady 事件触发)。 打开率/到达率预约小程序首屏渲染完成 PV 与 用户点击访问小程序 PV 的比值。流失率 = 1 - 打开率 小程序代码包优化代码包优化的核心手段是降低代码包大小,预约小程序代码包大小直接影响了下载耗时,影响用户启动预约小程序时的体验。 开发者可以采取以下手段优化预约小程序代码包大小 1 预约小程序分包加载 2 预约小程序代码重构和优化 3 控制代码包内图片等资源 4 及时清理没有使用到的预约小程序代码和资源 界面设计 [图片][图片][图片][图片][图片][图片] 后台界面设计 [图片][图片][图片][图片][图片][图片]
2022-03-16 - 基于微信小程序的大学校园社团平台的概要设计
UI设计背景和思路 随着社会的在新时代下的飞跃发展,尤其是高等教育事业的发展愈来愈可观,每年节节攀升的学生数量不断翻新, 作为大学生另一处学习发展中心的社团组织,也呈现出一种百花争艳的景象,而如何有效的对社团进行规范化管理, 就显得十分有必要。 好的校园社团管理系统不仅可以大大提高社团的办事效率,增强各社团之间的交流与沟通,还可以为社团节约大量的人力物力。 进行系统开发设计时,遵循界面友好、安全性较高、操作灵活、实用性较强的指标进行。该系统基于MVC架构,采用基于微信小程序平台开发, 校园用户操作轻松快捷:无需下载安装APP。 功能模块设计[图片] 数据库设计 [图片] 关键技术点微信小程序前端开发框架没有自带的验证功能,虽然有表单的数据控制属性,但是略显简陋,且不成体系,尤其是结合后端很别扭,因此小程序的表单验证时候一般有两种方法,一是自己裸写验证规则,但是需要比较扎实的正则表达式基础,一种是自己封装Validate插件进行表单验证,基于以上原因, 封装了前端数据校验规则一套: 小程序集中校验路由 function check(data, rules, that) { let returnData = {}; for (let k in rules) { let arr = rules[k].split('|'); let desc = ''; // 校友录小程序数据项说明 for (let i = 0; i < arr.length; i++) { if (arr[i].indexOf('name=') > -1) { desc = arr[i].replace('name=', ''); break; } } // 校验 let formName = arr[0]; let val = data[formName]; if (val === undefined) val = ''; if (!Array.isArray(val)) val = String(val).trim(); // 前后去校友录小程序空格 returnData[k] = val; for (let i = 1; i < arr.length; i++) { let result = ''; let rules = arr[i].split(':'); // 校友录小程序空不校验 if (rules[0] != 'required' && val == '') continue; switch (rules[0]) { case 'required': result = checkRequired(val, desc); break; case 'array': result = checkArray(val, desc); break; case 'date': result = checkDate(val, desc); break; case 'time': result = checkTime(val, desc); break; case 'datetime': result = checkDatimeTime(val, desc); break; case 'min': result = checkMin(val, Number(rules[1]), desc); break; case 'max': result = checkMax(val, Number(rules[1]), desc); break; case 'len': result = checkLen(val, Number(rules[1]), desc); break; case 'in': result = checkIn(val, rules[1], desc); break; case 'email': result = checkEmail(val, desc); break; case 'mobile': result = checkMobile(val, desc); break; case 'int': result = checkInt(val, desc); break; case 'id': result = checkId(val, desc); break; case 'letter': result = checkLetter(val, desc); break; case 'letter_num': result = checkLetterNum(val, desc); break; } if (result) { wx.showModal({ title: '温馨提示', content: result, showCancel: false, success(res) { // 校友录小程序自动聚焦 if (that) that.setData({ [formName + 'Focus']: true }); } }); return false; } else { if (that) { // 删除校友录小程序原有的自动聚焦 if (helper.isDefined(that.data[formName + 'Focus'])) { that.setData({ //TODO delete? [formName + 'Focus']: false }); } } } } } return returnData; } 小程序分项数据校验function checkRequired(value, desc) { if (value == '') return desc + '不能为空'; } /** * 校验校友录小程序字符长度 * @param {*} value * @param {*} min * @param {*} max */ function isCheckLen(value, min, max) { //TODO 数字怎么处理 if (!helper.isDefined(value)) return false; if (typeof (value) != 'string') return false; if (value.length < min || value.length > max) return false; return true; } /** * 校验校友录小程序数字大小 * @param {*} value * @param {*} min * @param {*} max */ function isCheckM(value, min, max) { if (!helper.isDefined(value)) return false; if (typeof (value) == 'string' && /^[0-9]+$/.test(value)) value = Number(value); if (typeof (value) != 'number') return false; if (value < min || value > max) return false; return true; } function checkMin(value, len, desc) { if (value.length < len) return desc + '不能小于' + len + '位'; }; function checkMax(value, len, desc) { if (value.length > len) return desc + '不能大于' + len + '位'; }; function checkLen(value, len, desc) { if (value.length != len) return desc + '必须为' + len + '位'; }; function checkMobile(value, desc) { if (value == '') return ''; if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) return desc + '格式不正确'; } function checkInt(value, desc) { if (value == '') return ''; if (!/^[0-9]+$/.test(value)) return desc + '必须为数字'; } function checkLetter(value, desc) { if (value == '') return; if (!/^[A-Za-z]+$/.test(value)) return desc + '必须为字母'; } function checkLetterNum(value, desc) { if (value == '') return; if (!/^\w+$/.test(value)) return desc + '必须为字母,数字和下划线'; } function checkId(value, desc, min = 1, max = 32) { if (value == '') return; if (value.length < min || value.length > max) return false; if (!/^\w+$/.test(value)) return desc + '必须为ID格式'; } function isCheckId(value, min = 1, max = 32) { if (!helper.isDefined(value)) return false; if (typeof (value) != 'string') return false; if (value.length < min || value.length > max) return false; if (!/^\w+$/.test(value)) return false; return true; } // 校友录小程序邮箱 function checkEmail(value, desc) { if (value == '') return; let hint = desc + '必须为邮箱格式'; let reg = /^[A-Za-z0-9+]+[A-Za-z0-9\.\_\-+]*@([A-Za-z0-9\-]+\.)+[A-Za-z0-9]+$/; if (!reg.test(value)) return hint; } // 校友录小程序短日期,形如 (2008-07-22) function checkDate(value, desc) { if (value == '') return; let hint = '请选择' + desc; if (value.length != 10) return hint; let r = value.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/); if (r == null) return hint; let d = new Date(r[1], r[3] - 1, r[4]); let chk = d.getFullYear() == r[1] && (d.getMonth() + 1) == r[3] && d.getDate() == r[4]; if (!chk) return hint; } // 校友录小程序短时间,形如 (13:04:06) function checkTime(value, desc) { if (value == '') return; let hint = desc + '必须为时间格式'; if (value.length != 8) return hint; let a = value.match(/^(\d{1,2})(:)?(\d{1,2})\2(\d{1,2})$/); if (a == null) return hint; if (a[1] > 24 || a[3] > 60 || a[4] > 60) return hint; } // 长时间,形如 (2008-07-22 13:04:06) function checkDatimeTime(value, desc) { if (value == '') return; let hint = desc + '必须为完整时间格式'; if (value.length != 19) return hint; var reg = /^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/; var r = value.match(reg); if (r == null) return hint; var d = new Date(r[1], r[3] - 1, r[4], r[5], r[6], r[7]); let chk = d.getFullYear() == r[1] && (d.getMonth() + 1) == r[3] && d.getDate() == r[4] && d.getHours() == r[5] && d.getMinutes() == r[6] && d.getSeconds() == r[7]; if (!chk) return hint; } function checkArray(value, desc) { if (value == '') return; if (!Array.isArray(value)) return desc + '填写错误'; } function checkIn(value, ref, desc) { if (value == '') return; let arr = ref.split(','); if (!arr.includes(value) && !arr.includes(value + '')) return desc + '填写错误'; } /** * 检查校友录小程序枚举类型 * @param {*} value * @param {*} ref 格式 1,2,3 */ function isCheckIn(value, ref) { if (!helper.isDefined(value)) return false; let arr = ref.split(','); if (!arr.includes(value) && !arr.includes(value + '')) return false; //字符,数字都支持 return true; } UI设计[图片][图片][图片][图片][图片][图片] 后台设计[图片][图片][图片][图片] [图片][图片]
2022-03-11 - 小程序自定义组件如何实现下拉加载和上拉刷新
当父组件引用了子组件的时候,会遇到父组件执行子组件的方法,比如下拉刷新上拉加载等事件只有在页面中才能检测到,但是获取数据的方法在子组件,这时就可以执行子组件方法。 思路很简单,类似于vue中给子组件加ref执行子组件方法道理一样,这里是给子组件加一个 属性: id="子组件名称",比如: <list id="list"></list> 然后在父组件对应的方法中直接 this.selectComponent("#list").getList(); 如果涉及到多次调用该子组件的方法,可以在onReady生命周期中定义一下,比如: onReady:function(){ this.list = this.selectComponent("#list"); }, 之后在方法中再调用的时候直接用this.list.方法名就可以了。比如: onPullDownRefresh() { let that=this; this.list.getList(); setTimeout(function(){ wx.stopPullDownRefresh() },1000) },
2021-07-25 - 小程序中实现页面截图
最近接到一个需求,需要在小程序中实现页面截图,我一开始的考虑是使用官方提供的扩展组件wxml-to-canvas,但是实际体验下来效果很糟糕,首先它并不能截取实际的页面,而是必须传入[代码]wxml[代码]和[代码]wxss[代码];然后他能支持的效果也很少,并不能满足需求中稍微复杂的效果。最终我决定用web-view加载的网页中使用html2canvas来实现功能。 实际代码 网页部分我用了[代码]vue[代码],首先需要安装[代码]html2canvas[代码] [代码]npm install html2canvas [代码] 页面中引入 [代码]import html2canvas from 'html2canvas'; [代码] 需要截图的dom节点上添加ref属性 [代码]<div ref="page"> [代码] 截图代码 [代码]... document.body.scrollTop = 0; // 将页面滚动至顶部后再开始截图,才能保证截图的完整 html2canvas(this.$refs.page, { allowTaint: false, useCORS: true, width: document.body.scrollWidth, height: document.body.scrollHeight // 实际体验中发现最好设置宽高为页面的宽高才能获得完整的截图 }).then(canvas => { this.savedPic = canvas.toDataURL('images/png') // 用于在页面中展示的截图完成的网址 ... // 以下代码为模拟a标签的点击直接下载截图 // 但是这部分代码在移动端网页和小程序中并不会生效 let a = document.createElement('a'), blob = this.dataURLToBlob(canvas.toDataURL('images/png')); a.setAttribute('href', URL.createObjectURL(blob)); a.setAttribute('download', 'pic.png'); document.body.appendChild(a); a.click(); URL.revokeObjectURL(blob); document.body.removeChild(a); }); ... [代码] 兼容性 网页毕竟不是原生小程序,还是会存在一些兼容性问题,比如网页中不能使用小程序的wx.saveImageToPhotosAlbum直接保存生成好的截图。移动端和微信中也不支持模拟a标签的点击来下载图片,最终只能通过展示生成的截图并提示用户长按图片来实现保存图片的功能,用户体验会差点,但是考虑到截图效果比[代码]wxml-to-canvas[代码]好太多了,还是可以接受的。 最后说一下[代码]html2canvas[代码]的支持度,目前实际用下来发现不支持的样式为阴影和伪元素,其他基本上都支持。网页中的图片必须为本地图片或者支持跨域的网络图片。用到图片的地方建议直接使用[代码]img[代码]标签,而不是背景图片,[代码]img[代码]标签展示的图片清晰度远远高于背景图片。
2021-07-13 - 纯WXSS实现-小程序模块高度等于屏幕高度减固定值
直接上代码,相信有需要的朋友一看就懂 page{ height: 100%; } view { height: -webkit-calc(100% - 100rpx) !important; height: -moz-calc(100% - 100rpx) !important; height: calc(100% - 100rpx) !important; }
2021-07-13 - 📸 FUTAKE:一个灵感相册,去开启(现已开放注册)
隆重介绍 FUTAKE 📸 所有最佳瞬间的全新归属,你的灵感相册。 发现、探索、捕捉生活中的每一刻灵光一闪, 随手一拍,定格此刻美好。 点击进入 FUTAKE 小程序 → [图片] 📸FUTAKE 是一个图片分享小程序,可以发布照片、关注好友。 采用以 Instagram 为灵感的界面设计,并以抖音式的滑动交互来切换作品。 FUTAKE,关于美,关于美好生活。 🌁FUTAKE 致敬 Instagram 设计美学 —— 清澈纯粹,大幅留白,作品至上。 FUTAKE 让当前屏幕上展示的每个作品,在截屏中所呈现的美感都是完美的。 FUTAKE 致力于吸引随手拍照、爱好创意、探索生活灵感的用户的喜爱。 ⚡️好的产品,将日常生活变得趣味,指尖获得愉悦,进入灵感跳跃的另一层场景。 FUTAKE,让美好加速流通,让生活灵动闪烁,让世界增添新意。 [图片] FUTAKE,你的灵感相册, 分享此刻世界,开始一个新故事吧。 查看 FUTAKE 官网 → OR 点击进入 FUTAKE 小程序 →
2021-07-15 - 分享一下测试词汇量小程序的开发经验
开发的主要思路是: 1、首先要找一批不同等级的典型词汇来给用户做测试,我找了6个level的共100个单词。 2、然后对每个level的词设置分数。 3、接着对最后得分设置几个level,根据测试结果给到用户合理的学习建议。 4、开发一个页面供用户答题。 5、将测试结果生成海报,有利于小程序的推广。 小程序界面如下,就花了2-3天业余时间做的,比较简陋,欢迎提出宝贵意见和建议,谢谢! [图片] [图片] [图片]
2021-07-04 - 用开源博客系统wordpress打造一个连载漫画小程序
*爱书者小程序2.0/3.0开源设计稿和源代码(前后端)去公众号【APP比比】自取 市面上一个看小说小程序随随便便上千块。于是乎自学代码还有设计终于磕磕碰碰完成了我的爱书者小程序4.0。 [图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片] 这次,我把所有小程序外观、字体、内容布局、流量主广告的操作都放到了后台,免得改一个小细节还要重新提交小程序。基本上小程序就是一个框架,主页提供三个容器,我可以任意把专题、语录、资讯、书籍、自定义页面等放到第一个容器或者其他容器。做到随心情更改。 [图片][图片][图片][图片][图片] 第二个改动就是完全组件化,连头部、底部导航都用自定义组件。免得弹层、滑动交互被遮挡。让交互就是这么丝滑 [图片] [图片] 这个小程序制作的初衷就是闲置图书在社区或者公司里进行免费共享。后来觉得可以放点电子书大家免费下载,后来觉得还可以增加一些联盟书城的好书推荐购买,后来觉得还可以做成在线连载阅读,于是渐渐的有了今天的模样。 然后组建了个群,让喜爱这款小程序的用户一起提意见。 这不,首先有人说还可以做成手游分享,于是增加了后台可更改图书封面比例和圆角比例的开关,嗯,内页总不能用书籍那些固定字段吧,于是支持自定义字段。 [图片] [图片] 有人说想通过这个爱书者小程序做成银行卡活动发布小程序,可直接进行跳转到官方小程序公众号什么的,好的,支持,全站小程序均支持后台配置跳转链接、跳转小程序、联盟口令跳转,基本上可实现联盟返利。 [图片] [图片] 回归正题。让我们看看现在爱书者小程序4.0都支持哪些功能吧! 书籍共享借阅:可后台设置书馆位置、预借信息、联系馆长;书籍连载:自定义章节模式、设置背景色和文字大小、左右滑动翻页、当前阅读进度书籍网盘下载:一键跳转百度盘小程序进行资源下载,支持开启流量主广告点击后查看书籍交易:支持跳转二手交易小程序如转转、或者京东、当当小程序。也支持口令复制打开APP,可做成返利小程序书籍阅读:支持pdf文档直接打开阅读。支持开启流量主广告点击后查看书籍推送到kindle:正在完善书籍字段自动抓取:后台编辑书籍时支持输入isbn一键抓取豆瓣读书数据书籍字段自定义:如果想做成其他内容,可以自己无限添加自定义字段,支持跳转小程序、弹出图片、内部跳转和文本复制专题:支持关联选择书籍,做成一个专辑。支持专题封面自定义。封面高度、封面图文位置均可调节。全面支持富文本编辑器语录:支持关联选择书籍,支持语录文字朗读、支持自定义背景音乐。做成心情语录、电影台词语录也很不错。全面支持富文本编辑器资讯:全面支持富文本编辑器,包括视频以及的比例调整、音频、高亮代码、画廊、全屏海报等等。同时内置活动倒计时组件、全能按钮组件等会员中心:支持天气背景定制、支持暗黑模式转化、支持订阅文章更新、支持自定义字段用户交互:底部菜单自定义、侧滑栏模块自定义、多级分类筛选、点赞、喜欢、评论、评分、上下篇、分享给朋友、分享到朋友、生成文章海报、然后就是这样一款小程序,从4.0开始收费69元(之前的2.0和3.0还继续开源),根据功能迭代慢慢涨价 嗯,做了个4.0售卖的公众号文章,购买或者体验小程序都可以去了解一下:https://mp.weixin.qq.com/s/unK2Bs7vIdgFYsJhmarZ2w
2021-07-18 - 答题活动小程序活动方案图片制作技巧
答题活动小程序活动方案图片制作技巧 ~ 场景: 由于答题活动有一个模块是活动方案展示的界面,这个界面目前是按照图片来展示的,但是一般用户会提供一个word文档 也就是目前有一个word文档 目标: 通过将该word文档转成图片, 目前的操作路径 1、将word转成pdf; 2、将pdf转成图片; 3、将图片上的文字,复制到背景模板图片,由于这个图片是由word转来 的,所以图片背景都是纯白色,复制上面的文字,就可以保留原先word中文字的字体,和粗细效果, 然后覆盖到活动方案模板图片上,就可以制作出一个完美的活动方案图片 ~ 这个方案也是近期摸索出来的,给我省了很多的时间,也给制作出来的活动方案图片保持了统一的风格,为后续答题活动小程序搭建带来了很大的便利性。
2021-07-20 - 云开发之获取随机数据
想给前端返回随机的数据,就得用到 aggregate() 聚合进行操作,普通的操作云数据库没有 随机 这个字段。 [图片] match == where == 条件筛选sample == 随机size == 随机数量对云数据库进行操作,一般都不会出问题,catch用不上,但经不住腾讯云服务空间的 读取溢出 问题。
2021-07-20 - 推荐几款收集、征集、征稿专用小程序|办公常备,Fotoo征集一下
推荐几款收集征集征稿专用小程序(常备办公小程序)|收集照片、收集视频、收集文档、收集音频、收集素材,Fotoo征集一下、腾讯文档(收集表)、问卷星、石墨文档(表单)、金山表单、麦客表单 如果你要经常需要各种收集,收集照片、收集视频、收集文档、收集音频、素材收集, 这几款小程序绝对能大家高效完成各种收集 一 、收集征集征稿类 【Fotoo征集一下】一站式征集、投稿工具。 收集照片、收集视频、收集音频、收集文档、收集文字; 照片征稿、视频征稿、音频征稿、文档征稿、文字征稿; 照片征集、视频征集、音频征集、文档征集、文字征集都支持呢 [图片] [图片] 二、信息收集类 1、腾讯文档(收集表) [图片] 2、石墨文档(表单) [图片] 3、金山表单 4、麦客表单 5、问卷星
2021-07-20 - 由考研刷题小程序谈谈不要忽略内容而设计
考研刷题小程序设计与开发之谈谈为什么要形成自己的设计风格,怎么样达到最好的展现效果系列。 对于小程序开发者来说,特别是对于初级工程师或者小程序初学者来说,排版的好坏是这个阶段核心要考虑的问题,也就是细节。 比如,往往会存在忽略内容而设计的问题。它将实际显示哪些图像,标题有多长?因为,一旦您将真实的内容填满你的设计稿,您的精美设计就会变的异常难看。 如果您想提高自己的技能,请设计开发一些可能成为产品最终目标的产品。举个栗子,以自己的作品作为参考的话,譬如在线考试小程序、考研刷题小程序等题库小程序,从需求原型构思到实现产出,不断打磨,不断总结,自成体系。 具体来说,在开始进行UI设计与开发之前,您需要知道页面的每个部分都将显示哪种内容。您还需要知道内容的最小和最大显示长度,正确处理执行。 a.选择正确的配图 如果作为概念设计稿,那么选择你能拿到的最好的配图当然是可行的。但是一旦你要为真实的应用创建设计,那么配图的质量就必须要考虑。所以,尽量选择跟主题相关的配图,而不是在图库中的跟主题毫无关联的图片。只有在这种情况下,你才能真实了解最终的成品是什么样子的。而考研题库,也就是跟学习有关的配图,会极大地丰富界面内容。 微信授权登录 [图片] b.注意重复的列表和块 通常情况下,列表有以下几种形式:图像+文本,图标+文本,数字+文本等。您应该考虑用哪种形式可以让信息更有效的表达。 对于描述功能的小文本块,您可以使用三列布局。但是,如果您有多于五行的文本,并且需要全部显示而没有省略号,则必须用另一种视觉解决方案。为什么呢?因为手机阅读者没有阅读较长的文本的习惯。此时,可以使用水平滚动或者两列布局的图片列表。 考研政治题库小程序,按照科目维度划分四大模块,马克思原理、毛中特、思修、近代史,而我选择两行两列的布局方案。横平竖直,整个界面显得整洁而耳目一新。 基于科目分类 [图片] 另外,要考虑一下内容区块的极端情况,比如列表的文字最长和最短大概有多少字数。优秀的开发者应该始终主动思考。以考虑客户将来可能需要扩展UI的可能性。 [图片]
2021-07-21 - 考研刷题小程序从哪个方向切入比较合适
其实考研现在人群基数很大,如果真能抓住这波人群,那收益是无多考虑的, 近期我在调研哪个科目更合适我切入,大家看下面的截图 [图片] ~ 在考研的各个科目里面,肯定是公共课基数最大,在公共课里面就属政治了,政治里面考虫独一份的,考虫政治是政治刷题里面的一面旗帜,其小程序产品无论从体验还是题库安排都是上上乘 请继续看图 [图片] ~ [图片] ~ 其中在排名前8的小程序里面,前五个都1000+,后面也是300+, 这个每日日活,确实比较可观了 ~ 通过这么一分析,我觉得考研政治市场在,但是压力不发也十分激烈,也就是所谓的内卷很严重了 ~ 那么是否还有机会 ~ 我比较了下头部的几个小程序,用户体验各有优劣,这其实让我看到了机会 如果把产品做好,题库做丰富,机会还是有的, 也由于我之前做过考研其他科目的刷题小程序,所以从目前需要做的就是,考研政治的题库准备了, 我们拭目以待,考研政治刷题小程序我来了
2021-07-23