记《高校考勤系统》小程序(3)

836 阅读11分钟

这是其他几篇的地址:
记《高校考勤系统》小程序(1)
记《高校考勤系统》小程序(2)
记《高校考勤系统》小程序(3)

到这里我们已经完成了 tabbar 上两个(首页、课程表)主要的页面,这篇会讲到校园通知和用户管理两个页面.

五.校园通知及发布、管理

  • 校园通知这里主要分为两块:
    1.管理员信息发布,顶部公告的设置.(主要涉及图片的上传,数据的增加)
    2.用户查看.(主要涉及上拉加载,下拉刷新,点击查看详细信息)

1.校园通知发布,效果图奉上,因为前期是想实现全部功能,所以在ui上没有花费太多心思:

  • 这里的话需要用到上传图片这个功能,上传功能案例我们也可以在新建一个项目中查看到,这里也有详细的代码.

(1)顶部标题,我们可以在.json文件中加入 navigationBarTitleText 这个属性.

{
  "navigationBarTitleText": "校园消息编辑",
  "usingComponents": {}
}

(2)页面布局搭建,校园通知需要有标题,内容,发布人,以及封面图片,当然你也可以根据你的需求添加或删除.

点击查看wxss代码
page{
    width: 100%;
    height: 100%;
}
.title, .cont {
    display: flex;
    justify-content: start;
    background-color: white;
    border-bottom: 1px solid #f6f6f6;
}
.tit_tit, .cont_tit, .upphoto_tit {
    margin-left: 20rpx;
    margin-right: 30rpx;
    line-height: 100rpx;
}
.tit_input {
    width: 500rpx;
    height: 60rpx;
    line-height: 60rpx;
    padding: 20rpx;
}
.cont_cont {
    margin-top: 14rpx;
    padding: 20rpx;
}
.upphoto {
    background-color: white;
    padding-bottom: 30rpx;
}
.photo {
    width: 200rpx;
    height: 200rpx;
}
.photo_box {
    width: 200rpx;
    height: 200rpx;
    border: 3rpx dashed;
    margin-left: 20rpx;
    overflow: hidden;
}
.up {
    width: 200rpx;
    height: 200rpx;
}
.btn {
    width: 300rpx;
    margin-top: 100rpx;
    background-color: #07c160;
    color: white;
}
<view class="title">
    <text class="tit_tit">标题</text>
    <input class="tit_input" placeholder="请输入标题" bindinput="bindKeyInput"></input>
</view>
<view class="cont">
    <text class="cont_tit">内容</text>
    <textarea class="cont_cont" placeholder="请输入内容" bindinput="bindKeyInput2" maxlength="-1"></textarea>
</view>
<view class="title">
    <text class="tit_tit">发布人</text>
    <input class="tit_input" placeholder="请输入发布人" bindinput="bindKeyInput3"></input>
</view>
<view class="upphoto">
    <text class="upphoto_tit">封面图片</text>
    <!-- 上传图片 -->
    <view class="photo">
        <view class="photo_box" bindtap="doUpload">
            <image class="up" hidden="{{ upview }}" src="../../images/up.png"></image>
            <image class="up" hidden="{{ upview2 }}" src="{{ filePath }}"></image>
        </view>
    </view>
</view>
<button class="btn" bindtap="btn">确定</button>

(3)功能实现,js编写.

  • 首先我们需要将 input 框中输入的值存储到 data 中
data: {
    filePath: '', //图片路劲
    title: '', //标题
    article: '', // 内容
    user: '', //发布人
},
bindKeyInput(e) { //标题输入
    this.title = e.detail.value
    console.log(this.title)
},
bindKeyInput2(e) { //内容输入
    this.article = e.detail.value
},
bindKeyInput3(e) { //发布人输入
    this.user = e.detail.value
},
  • 然后是上传图片功能的实现.详细的步骤写在注释中
// 上传图片
doUpload: function() { 
    let timestamp = (new Date()).valueOf(); //1.创建时间戳,方便后面文件命名
    wx.chooseImage({
        count: 1,    //2.一次选择的图片数量,这里限制一张,最多九张
        sizeType: ['compressed'],
        sourceType: ['album', 'camera'],  //3.图片来源选择,分为两个1相册选择(album),2相机拍摄(camera)
        success: (res) => {
            wx.showLoading({  //4.添加提示框,提升视觉效果
                title: '上传中',
            })
            const filePath = res.tempFilePaths[0] //当前存储图片地址
            
            const cloudPath = timestamp + '.png' //5.用时间戳命名图片名称
            wx.cloud.uploadFile({  //将图片上传至云存储空间
                cloudPath,// 指定上传到的云路径
                filePath,// 指定要上传的文件的小程序临时文件路径
                success: res => {
                    this.filePath = res.fileID  //5.成功后回调,res.fileID就是我们在云存储中的路径,可以直接用这个路径来获取图片并显示
                    this.setData({ //图片上传显示,这里因为原来有图片,所以当上传图片成功后隐藏原来图片,显示用户上传的图片
                        upview: true,
                        filePath: this.filePath,
                        upview2: false
                    })
                    app.globalData.fileID = res.fileID
                    app.globalData.cloudPath = cloudPath
                    app.globalData.imagePath = filePath
                },
                fail: e => {
                    wx.showToast({
                        icon: 'none',
                        title: '上传失败',
                    })
                },
                complete: () => {
                    wx.hideLoading()//完成后隐藏提示框
                }
            })
        },
        fail: e => {
            console.error(e)
        }
    })
},

到这里图片上传功能就做好啦,是不是并没有想象中的复杂😄

(4)提交数据到数据库中.

  • 首先我们确定存入数据库中单条数据的json格式
{
    "_id": "00a6cea35dca27980575500975c25248",
    "_openid": "oQnNa5NJfKqSZntKFLGZWnZuXNbo",
    "title": "标题",
    "article": "文章内容",
    "user": "发布人",
    "filePath": "图片上传成功后的云路径",
    "timestamp": "发布时间"
}

js
btn(e) {
    this.timestamp = (new Date()).valueOf(); //获取时间戳
    var date = new Date(this.timestamp);  //这里是想做一个发布时间存储 
    var Y = date.getFullYear() + '-';
    var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
    var D = date.getDate() + ' ';
    var h = date.getHours() + ':';
    var m = date.getMinutes() + ':';
    var s = date.getSeconds();
    this.timestamp = Y + M + D + h + m + s;  //时间拼接   0000-00-00 00:00:00 格式
    const db = wx.cloud.database({ //数据库新增用户注册信息
        env: '***'
    })
    db.collection('***').add({ //向指定集合添加数据
        data: {
            title: this.title,
            article: this.article,
            user: this.user,
            filePath: this.filePath,
            timestamp: this.timestamp
        },
        success: res => {
            wx.showToast({
                icon: 'success',
                title: '发布成功',
            })
        }
    })
},

2.校园通知页

  • 这里主要注意的地方还是之前提到过的云开发中的限制,也就是每次获取数据上限是 20条 ,当然你可以搭配云函数来提高每次获取到的数据条数,不过也不建议每次获取过多数据,大量的数据会使加载变慢,降低性能.
  • 那么这里我是没有使用云函数的,最多展示20条数据,如果想要展示更多,后面有功能会讲到如何使用云函数开发.

首先还是页面的布局,这里样式用了 colorUI 中的边框阴影.初始我们只取前6条数据.

点击查看wxss代码
page {
  width: 100%;
  position: relative;
}
.cont_box {
  width: 90%;
  height: 160rpx;
  background-color: white;
  padding: 20rpx;
  margin: 0 auto;
  margin-top: 20rpx;
  margin-bottom: 10rpx;
  border-radius: 16rpx;
  display: flex;
  justify-content: space-between;
  box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
  position: relative;
}
.cont_box::before, .cont_box::after {
  position: absolute;
  content: "";
  top: 20rpx;
  bottom: 30rpx;
  left: 20rpx;
  width: 50%;
  box-shadow: 0 30rpx 20rpx rgba(0, 0, 0, 0.2);
  transform: rotate(-3deg);
  z-index: -1;
}
.cont_box::after {
  right: 20rpx;
  left: auto;
  transform: rotate(3deg);
}
.cont_left {
  width: 75%;
  height: 100%;
  position: relative;
}
.cont_tit {
  font-size: 28rpx;
}
.cont_time {
  position: absolute;
  right: 30rpx;
  bottom: 20rpx;
  font-size: 24rpx;
}
.cont_img {
  width: 160rpx;
  height: 160rpx;
}
.bottom {
  width: 100%;
  text-align: center;
  font-size: 24rpx;
  color: #ddd;
  display: inline-block;
  padding: 30rpx 0;
}
.bottom text {
  margin-left: 20rpx;
  margin-right: 20rpx;
  color: #9b9b9b;
}
<view class="cont_box" hidden="{{cont_box}}" wx:for="{{ campusNews }}" wx:key="{{index}}" data-index="{{ index }}" bindtap="contBtn">
  <view class="cont_left">
    <view class="cont_tit">{{ item.title }}</view>
    <text class="cont_time">{{ item.timestamp }}</text>
  </view>
  <image class="cont_img" src="{{ item.filePath }}"></image>
</view>
<view class="bottom" hidden="{{bottom}}">
  ————
  <text>我是有底线的</text>&emsp;————
</view>

const db = wx.cloud.database({
  env: '***'
})
db.collection('***').get().then(res => {
    this.campusNews = res.data.reverse()//逆序排序,让新添加的数据排在最前面,将数据存储在data中,方便处理
    for(let i = 0; i < 6 ; i++){
      this.data.arr.push(this.campusNews[i])  //每次取前6条数据,添加到新数组中进行渲染
    }
})

通知详情查看:

  • 在上面,我们已经获取校园通知的数据,怎么查看相对应的数据呢 ?也是通过获取点击数据的下标来判断
  • 我们在 for 循环中添加 data-index="{{ index }}" 就可以赋值下标,如上代码已经写出👆👆
  • 这里我是通过下标来获取数据,在通过url传参来传递数据(因为数据相对较少)
contBtn(e) {
    var id = e.currentTarget.dataset.index  //获取当前点击下标
    wx.navigateTo({
      url: "/pages/newsPage/newsPage?title=" + this.campusNews[id].title + "&&article=" + this.campusNews[id].article
    })
  },

跳转页面后,我们在跳转页面 .js 的 onLoad() 中就可以获取到数据,最后渲染就ok了. 因为样式都是统一的,所以只用一张页面就可以了,只是每次跳转url携带的参数不同罢了

onLoad: function (options) {
    console.log(options)
  },

例如我点击效果图中第一条信息,跳转页面获得的数据如下:

下拉刷新:首先需要在 .json 中开启这个功能,还有其他可以修改的属性可以查看 官方文档

  • 下拉刷新的原理其实和页面加载时获取数据的原理差不多,当然你也也可以在下拉函数中直接 this.onLoad() ,即下拉时再次执行 onLoad() 函数,成功获取数据后隐藏加载框就行.

onPullDownRefresh: function() {
    var arr = []   //声明一个空数组 
    wx.showNavigationBarLoading();  //显示导航栏加载框
    const db = wx.cloud.database({
      env: '****'
    })
    //获取校园消息数据
    db.collection('***').get().then(res => {
        this.campusNews = res.data.reverse()
        for (let i = 0; i < 6; i++) {
            arr.push(this.campusNews[i])  //每次渲染6条数据
        }
        this.setData({
            campusNews: arr   //前端渲染
        })
    }).then(res => {  //等上一步数据获取成功,执行下面
        wx.hideNavigationBarLoading();// 隐藏导航栏加载框
        wx.stopPullDownRefresh();// 停止下拉动作
    })
  },

上拉加载:

data: {
    campusNews: [],
    num: 1,//一次获取的数据
    arr:[]
},
onReachBottom: function() {
    wx.showLoading({
      title: '加载中',
    });
    this.data.arr = []  //重置清空数组,因为加载页面中数组中已有数据
    this.data.num ++    //上拉一次num加1
    setTimeout(res=>{              //延时触发判断
      if (this.campusNews.length - 6 * this.data.num > 0) {  //判断 数组长度 - 6*下拉次数是否大于0,如上大于0则输出6*下拉次数条数据
        for (let i = 0; i < 6 * this.data.num; i++) {
          this.data.arr.push(this.campusNews[i])
        }
        this.setData({
          campusNews: this.data.arr
        })
        wx.hideLoading();
      } else if (this.data.campusNews.length - 6 * this.data.num <= 0) { // 小于等于0,则全部输出
        this.setData({
          bottom: false,
          campusNews: this.campusNews
        })
        wx.hideLoading();
      }
    },1000)
},

3.校园通知管理

  • 这张页面主要功能就是对已经发布的通知进行管理,可以进行查看、修改及删除. 效果图如下:

  • 在此之前该页面的增删改查功能以及全部讲过了,这里就讲讲大致流程,具体实现方法可以查看之前的文章😊
    1.首先是从页面结构的搭建,在从数据库中获取数据,渲染到页面.
    2.编辑功能其实和课程表中的编辑一致,获取当前点击的数据,赋值到 input 中的 value 中.
    3.修改功能需要用到云函数,也是因为云开发权限不足,无法修改别人上传的数据.

    (这里不足的是还没有做分页功能,所以只能查看前20条数据,后续会考虑增加分页)

如果这里有疑问的可以留言或私信,一起交流😀

六.用户管理页

  • 因为要做普通用户和管理员的区分,所以我在这里加了管理员按钮方便操作
    这里就用到了之前讲的在页面加载时,判断数据库中用户的操作权限,userstatus 为 '0' 是普通用户,按钮隐藏;为 '3' 是管理员,按钮显示.

点击查看wxss代码
page {
    width: 100%;
    height: 100%;
    position: relative;
}
.lodingBox {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
}
.loding {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
}
.cont {
    width: 100%;
    height: 100%;
}
.top {
    width: 100%;
    height: 520rpx;
    position: relative;
}
.top_bg {
    width: 100%;
    height: 380rpx;
}
.avatarUrl {
    width: 160rpx;
    height: 160rpx;
    border-radius: 80rpx;
    background-size: 100% 100%;
    position: absolute;
    left: calc(50% - 80rpx);
    bottom: 80rpx;
    box-shadow: 4rpx 4rpx 8rpx rgba(26, 26, 26, 0.4);
    z-index: 99;
}
.nickName {
    margin-top: 74rpx;
    text-align: center;
}
.adminBtn{
    margin-top: 20rpx;  
}
.kfBtn {
    width: 100%;
    height: 44px;
    position: relative;
}
.kfBtn .kf{
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
}
.water{
    width: 100%;
    position: absolute;
    margin: 0;
    bottom: 134rpx;
}
.bg-gradual-green {
    background-image: none;
    color: var(--white);
}
.gif-black{  
    mix-blend-mode: screen;  
}
<view class="cont" hidden="{{cont}}">
    <view class="top">
        <image class="top_bg" src="../../images/users.png"></image>
        <image class="avatarUrl" src="{{ avatarUrl }}"></image>
        <view class="nickName">{{ nickName }}</view>
        <view class="water">
            <view class="margin radius bg-gradual-green shadow-blur">
                <image src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb67bab5042a77~tplv-t2oaga2asx-image.image" mode="scaleToFill" class="gif-black response" style="height:100rpx"></image>
            </view>
        </view>
    </view>
    <van-cell title="授权管理" icon="https://pic.yupoo.com/1145338926/445b711b/7cde2620.png" bindtap="openSetting" is-link />
    <view class="kfBtn">
        <van-cell title="在线联系" icon="https://pic.yupoo.com/1145338926/d5dd143f/4ef73555.png" bindtap="" is-link />
        <button class="kf" open-type="contact"></button>
    </view>
    <van-cell title="项目日志" icon="https://pic.yupoo.com/1145338926/3e003634/e4313e02.png" bindtap="timeline" is-link />
    <view hidden="{{ adminBtn }}" class="adminBtn">
        <van-cell title="管理员" icon="https://pic.yupoo.com/1145338926/cae3dee7/0573c27b.png" is-link bindtap="openAdmin" />
    </view>
</view>
点击查看js代码
const app = getApp()
Page({
    data: {
        avatarUrl: '',
        nickName: '',
        userInfo: {},
        open_id: '', //用户id
        adminStatus: '', // 用户状态
        adminBtn: '' //是否显示管理员按钮
  },
    openSetting() {
        wx.openSetting()
    },
    openAdmin() {
        wx.navigateTo({ //跳转管理员界面
            url: "/pages/adminChoose/adminChoose"
        })
    },
    timeline(){
        wx.navigateTo({ //跳转管理员界面
            url: "/pages/timeline/timeline"
        })
    },
    onLoad: function(options) {
        this.setData({
            adminBtn: true,
            cont:true,
            loding:false
        })
        setTimeout(res => {
            wx.stopPullDownRefresh();  //结束下拉刷新动画
            if (this.adminStatus == '3') { //判断用户权限状态
                this.setData({
                    adminBtn: false,
                    loding: true ,
                    cont: false,
                })
            } else {
                this.setData({
                    adminBtn: true,
                    loding: true,
                    cont: false,
                })
            }
        },1300)
        wx.cloud.callFunction({
            name: 'login',
            data: {},
            success: res => {
                this.open_id = res.result.openid
                const db = wx.cloud.database({ //数据库查询用户信息
                    env: 'env-urae8'
                })
                db.collection('users').where({  //数据库查询用户权限信息
                    _openid: this.open_id
                }).get({
                    success: res => {
                        this.adminStatus = res.data[0].userStatus
                    }
                })
            },
            fail: err => {
                wx.showToast({
                    icon: 'none',
                    title: '用户信息获取失败,请检查网络',
                })
        })
        // 获取用户信息
        wx.getSetting({
            success: res => {
                if (res.authSetting['scope.userInfo']) {
                    // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
                    wx.getUserInfo({
                        success: res => {
                            this.setData({
                                avatarUrl: res.userInfo.avatarUrl,
                                nickName: res.userInfo.nickName,
                                userInfo: res.userInfo
                            })
                       }
                    })
                }
            }
        })
    }
)}
  • 在线联系功能:
    1.在微信公众平台,客服中添加需要绑定的客服微信号.
    2.在 button 组件中设置 open-type='contact' 即可打开客服消息.
    3.然后在手机上搜索 ‘客服小助手’ ,绑定的的小程序就可以实现在线客服功能 .

这样就绑定好了客服😊

到这里主要的四张页面及功能就做完了,如果有什么不懂得地方欢迎留言,或者写的不好的地方,请大家指出一起探讨,之后会继续分享各个小模块的功能。大家也可以提前扫码查看小程序,欢迎指出不足,谢谢 🌞😃😃😃

ps:签到效果图,和数据字段

  • 点击地点弹出地图,首先根据自身定位,并获取周边10个主要地点。
    点击搜索弹出搜索界面,可进行模糊查询选择。
    时间选择是vant的时间选择器