百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

甜蜜暴击!程序员为媳妇儿做了个记录心情的小程序

lipiwang 2024-10-19 08:20 25 浏览 0 评论



作者 | 黄彬

责编 | 仲培艺

闲暇之余,听媳妇嘀咕说要给她做一个能表达她每日心情的小程序,只能她在上面发东西。既然媳妇发话了,就花点心思做一个吧,因为没有 UI 图,所有布局全靠自己瞎编,内容略长,感兴趣的可以一览。

下面将以图片、代码的形式和大家讲解这个小 demo 的实现过程:



首页


首页效果图



首页讲解

  • 音乐(下面仅展示音乐相关代码)


<div class="bg_music" @tap="audioPlay">
 <image src="../../static/images/music_icon.png" class="musicImg" :class="isPlay?'music_icon':''"/>
 <image src="../../static/images/music_play.png" class="music_play" :class="isPlay?'pauseImg':'playImg'"/>
</div>
<audio id="myAudio" :src="audioUrl" autoplay loop></audio>


data () {
 return {
 isPlay: true,
 audioCtx: ''
 }
},
onLoad () {
 const that = this
 that.audioCtx = wx.createAudioContext('myAudio')
 that.getMusicUrl()
},
methods: {
 getMusicUrl () {
 const that = this
 const db = wx.cloud.database()
 const music = db.collection('music')
 music.get().then(res => {
 that.audioUrl = res.data[0].musicUrl
 that.audioCtx.loop = true
 that.audioCtx.play()
 })
 },
 audioPlay () {
 const that = this
 if (that.isPlay) {
 that.audioCtx.pause()
 that.isPlay = !that.isPlay
 tools.showToast('您已暂停音乐播放~')
 } else {
 that.audioCtx.play()
 that.isPlay = !that.isPlay
 tools.showToast('背景音乐已开启~')
 }
 }
}


.bg_music
 position fixed
 right 0
 top 20rpx
 width 100rpx
 z-index 99
 display flex
 justify-content flex-start
 align-items flex-start
 .musicImg
 width 60rpx
 height 60rpx
 .music_icon
 animation musicRotate 3s linear infinite
 .music_play
 width 28rpx
 height 60rpx
 margin-left -10rpx
 transform-origin top
 -webkit-transform rotate(20deg)
 .playImg
 animation musicStop 1s linear forwards
 .pauseImg
 animation musicStart 1s linear forwards
#myAudio
 display none

1. 通过 wx.createInnerAudioContext() 获取实例 ,安卓机上音乐能正常播放,iOS 上不行,具体原因感兴趣的可以去深究一下;

2. 由于前面邀请函小程序相关文章发出后,问得最多的问题依然是音乐无法播放这块,所以这个 demo 中就再给大家讲解了下实现的原理。

  • 日历


这里日历使用了小程序插件,之所以在首页放一个日历是为了页面不显得太单调。下面讲解下插件是如何使用的:

1. 登录微信公众平台>设置>第三方设置>添加插件>搜索相关插件的名字(使用 APPID 搜索更好)>点击某个插件右侧的查看详情,进入插件详情页添加插件,一般都能立马添加通过;

2. 插件详情里面一般都有使用文档,或 Git 地址,插件的具体属性事件都会在文档里有介绍;

3. 下面讲解下如何在项目中使用插件:

① 找到 src 根目录下的 app.json 文件,添加如下内容:

// "cloud": true,
"plugins": {
 "calendar": {
 "version": "1.1.3",
 "provider": "wx92c68dae5a8bb046"
 }
}

② 在需要引用该插件的页面的 .json 文件中加入如下内容:

{
 // "navigationBarTitleText": "媳妇的心情日记",
 // "enablePullDownRefresh": true,
 "usingComponents": {
 "calendar": "plugin://calendar/calendar"
 }
}

③ 在页面中直接使用如下(具体属性方法的意思根据对应插件有所不同):

<calendar
 :class="showCalendar?'':'hide_right'"
 class="right"
 weeks-type="en"
 cell-size="20"
 :header="showHeader"
 show-more-days=true
 calendar-style="demo4-calendar"
 board-style="demo4-board"
 :days-color="demo4_days_style"
 @dayClick="dayClick"
/>

天气和地址

① 这里我借助的是高德微信小程序 SDK;

② 首先获取使用相关 API 需要的 Key 值,如下:



③ 下载对应 SDK(.js 文件)并引入到项目中;

④ 通过相关 API 获取天气和地址:

getWeather () {
 const that = this
 let myAmapFun = new amapFile.AMapWX({key: '你申请的key'})
 myAmapFun.getWeather({
 success (res) {
 // 成功回调
 that.address = res.liveData.city
 that.weather = res.liveData.weather + ' '
 that.temperature = res.liveData.temperature + '℃'
 that.winddirection = res.liveData.winddirection + '风' + res.liveData.windpower + '级'
 },
 fail (info) {
 // 失败回调
 console.log(info)
 }
 })
},

发表日记

这里涉及到发表文字图片内容,在个人小程序提交审核后很大可能不会被通过,虽然第一次提交我的个人小程序通过审核了,后面几次审核均未通过,虽然我这里只限制了我和媳妇两个人能发日记,其他人压根看不到右下角的发布加号,但是审核人员会查代码,代码中一旦被他们发现有类似发表相关的内容或字样就会导致审核不通过,好在已经通过了一次,媳妇能正常写点东西,也算基本符合要求,遗憾的是后面实现点赞相关的功能都没有更新到线上。

① 通过唯一的 OpenID 来判断是否显示首页右下角的发布加号;

② 后面会具体讲解页面里上传图片到云开发及存储到数据库相关功能。

点赞功能

① 这里点赞功能借助小程序云开发的云函数来实现,结合代码:

<ul class="list">
 <li class="item" v-for="(item, index) in diaryList" :key="item._id" @tap="toDetail(item)">
 <image class="like" src="../../static/images/like_active.png" v-if="likeList[index] === '2'" @tap.stop="toLike(item._id, '1', item.like)"/>
 <image class="like" src="../../static/images/like.png" v-if="likeList[index] === '1'" @tap.stop="toLike(item._id, '2', item.like)"/>
 <image class="img" :src="item.url" mode="aspectFill"/>
 <p class="desc">{{item.desc}}</p>
 <div class="name-weather">
 <span class="name">{{item.name}}</span>
 <span class="weather">{{item.weather}}</span>
 </div>
 <p class="time-address">
 <span class="time">{{item.time}}</span>
 <!-- <span class="address">{{item.address}}</span> -->
 </p>
 </li>
</ul>
<div class="dialog" v-if="showDialog">
 <div class="box">
 <h3>提示</h3>
 <p>是否授权使用点赞功能?</p>
 <div class="bottom">
 <button class="cancel" @tap="hideDialog">取消</button>
 <button class="confirm" lang="zh_CN" open-type="getUserInfo" @getuserinfo="login">确认</button>
 </div>
 </div>
</div>
// 获取日记列表
getDiaryList () {
 const that = this
 wx.cloud.callFunction({
 name: 'diaryList',
 data: {}
 }).then(res => {
 that.getSrcFlag = false
 that.diaryList = res.result.data.reverse()
 that.likeList = []
 that.diaryList.forEach((item, index) => {
 item.like.forEach(itemSecond => {
 if (itemSecond.openId === that.openId) {
 that.likeList.push(itemSecond.type)
 }
 })
 if (that.likeList.length < index + 1) {
 that.likeList.push('1')
 }
 })
 wx.hideNavigationBarLoading()
 wx.stopPullDownRefresh()
 })
},
// 点赞或取消点赞
toLike (id, type, arr) {
 const that = this
 that.tempObj = {
 id: id,
 type: type,
 like: arr
 }
 wx.getSetting({
 success (res) {
 if (res.authSetting['scope.userInfo']) {
 // 已经授权,可以直接调用 getUserInfo 获取头像昵称
 wx.getUserInfo({
 success: function (res) {
 that.userInfo = res.userInfo
 wx.cloud.callFunction({
 name: 'like',
 data: {
 id: id,
 type: type,
 like: arr,
 name: that.userInfo.nickName
 }
 }).then(res => {
 if (type === '1') {
 tools.showToast('取消点赞成功')
 } else {
 tools.showToast('点赞成功~')
 }
 // getOpenId()方法里会执行一遍获取日记列表
 that.getOpenId()
 })
 }
 })
 } else {
 that.showDialog = true
 }
 }
 })
},
// 授权获取用户信息
login (e) {
 const that = this
 console.log(that.tempObj, e)
 if (e.target.errMsg === 'getUserInfo:ok') {
 wx.getUserInfo({
 success: function (res) {
 that.userInfo = res.userInfo
 wx.cloud.callFunction({
 name: 'like',
 data: {
 id: that.tempObj.id,
 type: that.tempObj.type,
 like: that.tempObj.like,
 name: that.userInfo.nickName
 }
 }).then(res => {
 if (that.tempObj.type === '1') {
 tools.showToast('取消点赞成功')
 } else {
 tools.showToast('点赞成功~')
 }
 // getOpenId()方法里会执行一遍获取日记列表
 that.getOpenId()
 })
 }
 })
 }
 that.showDialog = false
}

② 首页获取日记列表,在存储日记到数据库集合的时候我会在每条日记对象中添加一个 like 属性,like 默认是一个空数组;

③ 当用户点赞或取消点赞时,组件 data 中 tempObj 属性会临时存储三个参数:1) 对应日记的 _id;2) 用户操作的类型是点赞(点赞是‘2’)或是取消点赞(取消点赞是‘1’);3) 对应日记的 like 数组;

④ 通过小程序 API 的 wx.getSetting({}) 来判断用户是否已经授权。如果授权了获取用户信息,未授权则弹框引导用户点击确认按钮去手动授权;

⑤ 授权成功后,拿到用户信息,我们开始调用点赞或取消点赞相关的云函数,如下:

const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()
exports.main = async (event, context) => {
 try {
 // wxContext内包含用户的openId
 const wxContext = cloud.getWXContext()
 // 定义空数组
 let arr = []
 if (event.like && event.like.length > 0) {
 // 让定义的数组等于用户操作的当前日记下的like数组
 arr = event.like
 // 定义一个计数变量
 let count = 0
 // 循环遍历,当openId相同时替换like数组中的相同项,并存储对应的type
 arr.forEach((item, index) => {
 if (item.openId === wxContext.OPENID) {
 count++
 arr.splice(index, 1, {
 openId: wxContext.OPENID,
 type: event.type,
 name: event.name
 })
 }
 })
 // 当计数变量为0时,说明在这条日记中,like数组中未存储过此用户,直接push此用户并存储type
 if (count === 0) {
 arr.push({
 openId: wxContext.OPENID,
 type: event.type,
 name: event.name
 })
 }
 } else {
 // 如果此条日记like数组本身就为空,直接push当前用户并存储type
 arr.push({
 openId: wxContext.OPENID,
 type: event.type,
 name: event.name
 })
 }
 // 通过云开发操作数据库的相关api,即update通过_id来更新集合中某条数据
 return await db.collection('diary').doc(event.id).update({
 data: {
 like: arr
 }
 })
 } catch (e) {
 console.error(e)
 }
}

⑥ 相关云函数操作说明都写在上面的注释里,点赞功能未更新到线上(原因是因为审核不通过),感兴趣的可根据上面思路实现此功能。



发表心情

效果图



讲解

① 通过首页右下角的发布加号,进入发布心情页;

② 地址等相关信息是从首页通过路由带过来的;

③ 下面重点讲解下关于上传图片到云存储并写入数据库的操作过程,内容如下:

upload () {
 const that = this
 wx.chooseImage({
 count: 1,
 sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
 sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
 success: function (res) {
 wx.showLoading({
 title: '上传中'
 })
 // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
 let filePath = res.tempFilePaths[0]
 const name = Math.random() * 1000000
 const cloudPath = 'picture/' + name + filePath.match(/\.[^.]+?$/)[0]
 wx.cloud.uploadFile({
 cloudPath, // 云存储图片名字
 filePath // 临时路径
 }).then(res => {
 console.log(res)
 wx.hideLoading()
 that.imgUrl = res.fileID
 }).catch(e => {
 console.log('[上传图片] 失败:', e)
 })
 }
 })
},
save () {
 const that = this
 if (that.desc) {
 that.getSrcFlag = false
 const db = wx.cloud.database()
 const diary = db.collection('diary')
 if (that.imgUrl === '../../static/images/default.png') {
 that.imgUrl = '../../static/images/default.jpg'
 }
 diary.add({
 data: {
 desc: that.desc,
 time: tools.getNowFormatDate(),
 url: that.imgUrl,
 name: that.name,
 weather: that.weather,
 address: that.address,
 like: []
 }
 }).then(res => {
 wx.reLaunch({
 url: '/pages/index/main'
 })
 }).catch(e => {
 console.log(e)
 })
 } else {
 tools.showToast('写点什么吧~')
 }
}

④ 这里的 Cloudpath 可以自己定义,存储到云中是这样的:



⑤ 我们通过组件 data 中的 ImgURL 临时存储手动上传的图片路径,最终通过保存按钮一起存储到云数据库,存入到数据库是这样的:






日记详情页

详情页效果图



讲解

详情就不过多讲解,这里利用了一些小程序 API,比如动态改变头部标题,每次进入动态随机改变顶部标题背景,点赞数也是从首页带过来的。



访客页

效果图

① 授权前



② 授权后






Demo 中碰到的疑问

有些用户头像获取为空

① 如下:



② 数据库查看后头像字段确实为空:






总结

云开发虽然能用,但对于大型项目个人还是不推荐,从图片和数据加载这块的效果来看,传统服务端拿到的数据明显要快很多,既然有这么一个免费的工具,我想感兴趣的同学可以利用起来,玩点小 Demo、新花样,希望这篇文章能够帮助到有需要的同学。

最后,由于媳妇不希望别人看到她写的内容,已隐藏通过小程序名搜索该小程序,大家如果想实现类似功能请参照源码。源码地址:https://gitee.com/roberthuang123/old-days作者简介:黄彬,2015年6月毕业于湖北工业大学,专业化学工程与工艺,一直从事前端工作,主要做小程序相关的开发,精通Vue框架,熟悉WebPack工程化开发。声明:本文经作者授权转载,如需转载请联系原作者。

相关推荐

前端入门——css 网格轨道详细介绍

上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...

Islands Architecture(孤岛架构)在携程新版首页的实践

一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...

HTML中script标签中的那些属性

HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...

CSS 中各种居中你真的玩明白了么

页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...

CSS样式更改——列表、表格和轮廓

上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...

一文吃透 CSS Flex 布局

原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...

css实现多行文本的展开收起

背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...

css 垂直居中的几种实现方式

前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...

WordPress固定链接设置

WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...

面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿

前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...

3种CSS清除浮动的方法

今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...

2025 年 CSS 终于要支持强大的自定义函数了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...

css3属性(transform)的一个css3动画小应用

闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...

CSS基础知识(七)CSS背景

一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...

CSS 水平居中方式二

<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...

取消回复欢迎 发表评论: