简易校园二手平台小程序搭建(无后台)

时间:2024-02-21 17:29:58
博客班级 https://edu.cnblogs.com/campus/zjcsxy/SE2020
作业要求 https://edu.cnblogs.com/campus/zjcsxy/SE2020/homework/11334
作业目标 完成一个小程序,上传代码,完成一篇博文
作业源代码 https://github.com/Hsaeno/2020-software-demo1
姓名 谢健
学号 31801141
院系 浙大城市学院计算机系

项目灵感来源:经常能在校园内部的小程序或者微信群上看到闲置物品的出售,类似闲鱼的大平台又不适用于区域内部的交易,所以萌生了校园二手交易平台的想法。
项目描述:本项目完成的是一个校园二手交易平台的简易搭建,因服务器原因,以静态呈现,如有后续,会考虑连接后台。主要完成了首页、校园讨论、发布、买卖双方信息交流以及个人信息展示。
需求分析:作为一个二手平台,首页推荐是需要的,发布功能是最重要的,这里面同时也就涉及到了将图片预览、上传的功能,买卖双方的聊天功能也是必不可少的,同时个人也要能够看到自己的历史交易和收藏。校园作为一个附带功能,也提供了一个供学生内部交流的平台。
本项目基于微信云开发,代码中云开发组件部分不予讲解,详情请参考开发者文档。自我完成部分重要结构将进行注释并做部分分析!
开发工具:微信开发者工具

项目页面展示


全局配置
JSON文件(app.json)

{
  "pages": [
    "pages/index/index",  //首页
    "pages/chat/chat",    //聊天进入界面
    "pages/post/post",    //发布页
    "pages/school/school", //校园页
    "pages/userInfo/userInfo"  //用户信息页
  ],
  "window": {
    "backgroundTextStyle": "dark",
    "navigationBarBackgroundColor": "#ffd747",      //导航栏背景颜色
    "navigationBarTitleText": "ZUCC闲置交易平台",   //导航栏
    "navigationBarTextStyle": "black",
    "enablePullDownRefresh": true                  //下拉自动刷新
  },
  "tabBar": {
    "backgroundColor": "#fafafa",
    "borderStyle": "white",
    "selectedColor": "#333",
    "color": "#333",
    "list": [
      {
        "pagePath": "pages/index/index",            //跳转导航页
        "iconPath": "./images/ic_menu_home_nor.png",      //图标样式
        "selectedIconPath": "./images/ic_menu_home_pressed.png", //图标选中样式
        "text": "首页"                              //页面名称
      },
      {
        "pagePath": "pages/school/school",
        "iconPath": "./images/xiaoyuan.png",
        "selectedIconPath": "./images/xiaoyuan_pressed.png",
        "text": "校园"
      },
      {
        "pagePath": "pages/post/post",
        "iconPath": "./images/ic_menu_add_nor.png",
        "selectedIconPath": "./images/ic_menu_add_nor.png",
        "text": "发布"
      },
      {
        "pagePath": "pages/chat/chat",
        "iconPath": "./images/ic_menu_chat_nor.png",
        "selectedIconPath": "./images/ic_menu_chat_pressed.png",
        "text": "消息"
      },
      {
        "pagePath": "pages/userInfo/userInfo",
        "iconPath": "./images/ic_menu_me_nor.png",
        "selectedIconPath": "./images/ic_menu_me_pressed.png",
        "text": "我的"
      }
    ]
  },
  "sitemapLocation": "sitemap85.json"
}

JS文件(app.js) 该文件由云开发时自动生成,直接调用即可

   // 展示本地存储能力
    var logs = wx.getStorageSync(\'logs\') || []
    logs.unshift(Date.now())
    wx.setStorageSync(\'logs\', logs)

    // 登录
    wx.login({
      success: res => {
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
      }
    })
    // 获取用户信息
    wx.getSetting({
      success: res => {
        if (res.authSetting[\'scope.userInfo\']) {
          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
          wx.getUserInfo({
            success: res => {
              // 可以将 res 发送给后台解码出 unionId
              this.globalData.userInfo = res.userInfo

              // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
              // 所以此处加入 callback 以防止这种情况
              if (this.userInfoReadyCallback) {
                this.userInfoReadyCallback(res)
              }
            }
          })
        }
      }
    })
  },
  globalData: {
    userInfo: null
  }

wxss样式(app.wxss)

.container {
  box-sizing: border-box;
  background-color: #f4f4f4;
  font-family: PingFangSC,helvetica,\'Heiti SC\'; //字体
} 

view,image,text,navigator{
  box-sizing: border-box;
  padding:0;
  margin:0;
}

view,text{
  font-family: PingFangSC,helvetica,\'Heiti SC\';
  font-size: 29rpx;
  color: #333;
}

各页面代码

首页(index)

wxml文件

<view class="container">
  <swiper class="banner" indicator-dots="true" autoplay="true" interval="3000" duration="1000" circular="true"> //轮播动画,调用for循环在js中读取数据完成,不必逐一配置
    <block wx:for=\'{{background}}\' wx:key="key">
          <swiper-item>
            <image src=\'{{item}}\'></image>
          </swiper-item>
        </block>
  </swiper>
  <view class="search_menu">                              
    <view class=\'page_row\' bindtap="suo">              
      <view class="search">  
        <view class="df search_arr">  
          <icon class="searchcion" size=\'20\' type=\'search\'></icon>  
          <input class="" placeholder="请输入关键字" value="{{searchValue}}"/>        //搜索框
        </view>  
      </view>  
      <view class=\'sousuo\'>
        <text class = "sousuotext">搜索</text>                                    //搜索按钮,这里采用text和css样式进行模拟一个按钮
      </view>  
    </view>  
  <!-- 首页分类 -->
  <view class="m-menu">
    <navigator open-type="navigate" wx:for="{{channel}}" wx:key="key">            //分类栏跳转
      <image src="{{item.imgurl}}"></image>
      <text>{{item.name}}</text>
    </navigator>
  </view>
  </view>
  <view class="recommdant">
    <text class="cnxh">猜你喜欢</text>
    <navigator open-type="navigate" wx:for="{{user_rec}}" wx:key="key">   // 推荐栏,调用for循环。
      <view class=".recommdant_text_img">
        <image src="{{item.img}}"></image>
       <text class="recommdant_text">{{item.title}}</text>
      </view>
    </navigator>
  </view>
</view>

js文件

    //数据绑定
    channel:                                                                                                              //分类
      [
        {
          name:"二手手机",                                                                                                     //文字
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/essj.png"                        //图片存储位置,因为小程序的存储设置,这里采用云存储完成
        },
        {
          name:"数码产品",
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/sm.png"
        },
        {
          name:"二手图书",
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/ess.png"
        },
        {
          name:"化妆品",
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/hzp.png"
        },
        {
          name:"全部分类",
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/fl.png"
        }
      ],
    background:[                                                                                                //轮播背景图
      "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/index_swiper3.jpg",
      "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/index_swiper1.jpg",
      "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/index_swiper2.jpg"
    ],
    user_rec:[                                                                                                   //猜你喜欢
      {
        img:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/iphone.jpg",
        title:"自用Ipone11 promax"
      },
      {
        img:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/sony.jpg",
        title:"索尼降噪豆三代 京东购买"
      },
      {
        img:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/kyzz.jpg",
        title:"2021考研政治全套便宜出"
      },
      {
        img:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/ysl.jpg",
        title:"YSL口红未拆封低价卖"
      }
    ]

校园页(school)

分左右2栏,样式不同。为了方便展示,2栏使用相同的数据。

wxml文件

<view class= "school_container">                  //左右两栏不同样式
  <view class="left-culumn">                        //左栏
  <navigator open-type="navigate" wx:for="{{school_info_left}}" wx:key="key">
      <view class="left-school_box">
        <image src="{{item.imgurl}}" class="userinfo_img"></image>
       <text class="recommdant_text" style="font-weight:200">{{item.title}}</text>
      </view>
    </navigator>
  </view>
  <view class="right-culumn">                        //右栏
  <navigator open-type="navigate" wx:for="{{school_info_left}}" wx:key="key">
      <view class="right-school_box">
        <image src="{{item.imgurl}}" class="userinfo_img"></image>
       <text class="recommdant_text" style="font-weight:200">{{item.title}}</text>
      </view>
    </navigator>
  </view>
</view>

js文件

chool_info_left:[
        {
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/nokia.jpg",
          title:"#数码好物分享# 极为罕见的白色5510"
        },
        {
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/clothes.jpg",
          title:"#潮牌配色指南# 早春卫衣人靠衣服马靠鞍"
        },
        {
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/mz.jpg",
          title:"#数码好物分享# 精致的千元机魅族Note"
        },
        {
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/shoes.jpg",
          title:"#这是我穿过最酷的潮牌# 百看不厌"
        },
        {
          imgurl:"cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/waitao.jpg",
          title:"#晒晒那些高颜值好物# 通勤换季ootd | 初秋穿搭 小香风粗花昵外套真的好显气质!"
        },
      ]

发布页(post)

图片能够进行预览,删除和上传到云端。

云端存储记录

wxml文件

<!--pages/post/post.wxml-->
<view class="post_container">
  <input class="title" placeholder-style="color: #aeaeae;" placeholder=" 标题, 品牌品类都是买家喜欢搜索的" value="{{searchValue}}"/>  // 商品标题栏
  <view class="content">
    <!-- <input class="reason" placeholder-class="placeholderinput" placeholder="描述一下宝贝的转手原因、入手渠道和使用感受" value="{{searchValue}}"/>   -->  // 商品描述
    <textarea class="reason" placeholder="  描述一下宝贝的转手原因、入手渠道和使用感受" decode="{{true}}" placeholder-style="color: #aeaeae;"></textarea>
  </view>
  <form bindsubmit="formSubmit" bindreset="formReset" > //
 <view class=\'tu1\'>   
   <block wx:for="{{img_arr}}" wx:key="index">    
    <view class=\'logoinfo\'>      
      <image class=\'logoinfo\' mode="aspectFill" src=\'{{item}}\' data-index="{{index}}" bindtap="previewImg" bindlongpress="deleteImg" name="headimage" ></image>    //事件绑定,图片预览  
      </view>   
       </block>
       <view class="addpic">
        <image bindtap=\'upimg\' class=\'tu\' src="../../images/add_pic.png"></image>  // 事件绑定,上传图片 
       <text>添加图片</text>
       </view>
        </view>  
    <view class="sellinfo">            
       <view class="price">
         <text style=" position: relative; top: 35rpx;">价格:</text>                  //价格
        <input class="title" placeholder-style="color: #aeaeae;" placeholder="在此输入你的预想价格" value="{{searchValue}}"/> 
       </view>
       <view class="position">                                                //位置
        <text  style=" position: relative; top: 35rpx;">位置:  </text>
        <input class="title" placeholder-style="color: #aeaeae;" placeholder="请输入你的交易位置" value="{{searchValue}}"/> 
       </view>
       <view class="catagory"></view>
     </view>  
   <view class=\'an1\'> 
   <button form-type="submit" class=\'an\'>发布</button>    //提交
   </view>  
</form>
</view>

js文件

const app = getApp()
wx. cloud. init()  //微信云端初始化
var form_data;
var psw_vaule = [];
Page({
  data: {
    tempFilePaths: [],
    img_arr: [],
 
  },
  //上传图片到服务器 
  formSubmit: function () {
    var that = this
    var adds = that.data.img_arr;
    for (var i = 0; i < this.data.img_arr.length; i++) {
      let filePath = that.data.img_arr[i];    //读取数组中用户添加的图片
      let cloudPath = \'my-image\'+new Date().getTime() + filePath.match(/\.[^.]+?$/)[0];  //这里利用时间不会重复的特性进行文件上传,防止图片覆盖
      wx.cloud.uploadFile({   //利用微信自带的函数将文件上传至云端
        cloudPath, 
        filePath,
        name: \'content\',
        formData: {
          \'user\': adds
        },
        success: function (res) {
          console.log(res)
          if (res) {
            wx.showToast({
              title: \'已提交发布!\', //上传提示
              duration: 3000
            });
          }
        }
      })
    }
    // this.setData({
    //   formdata: \'\'
    // })
  },
  //从本地获取照片 
  upimg: function () {
    var that = this;
    if (this.data.img_arr.length < 9) {
      wx.chooseImage({
        count: 9,        //一次性上传到小程序的数量     
        sizeType: [\'original\', \'compressed\'],  //原图或压缩
        sourceType: [\'album\', \'camera\'],
        success(res) {
          console.log(res)
          const tempFilePaths = res.tempFilePaths
          console.log(res.tempFilePaths)
          //concat() 方法用于连接两个或多个数组
          that.setData({
            img_arr: that.data.img_arr.concat(res.tempFilePaths),  //将用户添加的图片放到预设数组里面
          })
        }
      })
    } else {
      wx.showToast({
        title: \'最多上传九张图片\',  //设置阈值,超过提示
        icon: \'loading\',
        duration: 2000
      })
    }
  },
  //删除照片功能与预览照片功能 
  deleteImg: function (e) {
    var that = this;
    var img_arr = that.data.img_arr;
    var index = e.currentTarget.dataset.index;
    wx.showModal({
      title: \'提示\',
      content: \'确定要删除此图片吗?\',
      success: function (res) {
        if (res.confirm) {
          console.log(\'点击确定了\');
          img_arr.splice(index, 1);   //图片删除
        } else if (res.cancel) {
          console.log(\'点击取消了\');
          return false;
        }
        that.setData({
          img_arr: img_arr
        });
      }
    })
  },
  //预览图片
  previewImg: function (e) {
    var index = e.currentTarget.dataset.index;
    var img_arr = this.data.img_arr;
    wx.previewImage({
      current: img_arr[index],     //预览
      urls: img_arr
    })
  },
})

聊天页(chat)


模拟了针对不同用户的聊天界面,可以通过点击,进入聊天详情

wxml文件

<view class="chat_seller">
    <navigator url="../im/room/room" open-type="navigate" wx:for="{{chat_info}}" wx:key="key" class="chat_info"> //不同用户的聊天信息,for循环模拟
      <image src="{{item.photo}}"></image>
      <view style="display:flex;flex-direction:colum;  align-items: initial;">
        <text class="name">{{item.name}}</text>
      <text class="time">{{item.time}}</text>
      </view>
    </navigator>
  </view>

js文件

    chat_info:[
      {
        photo:  "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/tx1.jpg",
        name: "qz1003",
        time: "2小时前"
      },
      {
        photo:  "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/tx2.jpg",
        name: "zt1234",
        time: "5小时前"
      },
      {
        photo:  "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/tx3.jpg",
        name: "frt991",
        time: "8小时前"
      },
      {
        photo:  "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/tx4.jpg",
        name: "xqr233",
        time: "9小时前"
      },
      {
        photo:  "cloud://ash-1gecd5xpb564b8d4.6173-ash-1gecd5xpb564b8d4-1303835441/cloud_img/tx5.jpg",
        name: "ptf123",
        time: "9小时前"
      }
    ]

聊天室(chatroom)


聊天可以发送文字信息和图片信息,因为没有搭建后台数据库,暂时只能在本地看到,无法上传。这部分的完成通过云开发提供的聊天室组件完成,将对文字发送部分进行注释,如有错误,望指正。后续可以通过用户的id,进行1对1聊天的修改。

文字信息相关

 async onConfirmSendText(e) {
      this.try(async () => {
        if (!e.detail.value) {
          return
        }

        const { collection } = this.properties       //相关信息的初始化获取,如envid等
        const db = this.db
        const _ = db.command

        const doc = {
          _id: `${Math.random()}_${Date.now()}`,   //随机初始化一个数据id供上传甄别使用
          groupId: this.data.groupId,
          avatar: this.data.userInfo.avatarUrl,
          nickName: this.data.userInfo.nickName,         //用户名
          msgType: \'text\',
          textContent: e.detail.value,                  //用户输入的文字信息
          sendTime: new Date(),
          sendTimeTS: Date.now(), // fallback
        }

        this.setData({                              //信息设置
          textInputValue: \'\',
          chats: [
            ...this.data.chats,
            {
              ...doc,
              _openid: this.data.openId,           
              writeStatus: \'pending\',
            },
          ],
        })
        this.scrollToBottom(true)

        await db.collection(collection).add({
          data: doc,
        })

        this.setData({
          chats: this.data.chats.map(chat => {
            if (chat._id === doc._id) {
              return {
                ...chat,
                writeStatus: \'written\',
              }
            } else return chat
          }),
        })
      }, \'发送文字失败\')                  //报错提醒
    },

图片选择与之前提到的类似,这里就不再进行赘述。

个人信息页(userinfo)


首先会进行是否登录的信息判断

信息授权

登录后会显示个人的相关信息

wxml文件

<view class="user_container">
<view class="userinfo">
  <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 微信账号登录 </button> //一开始的信息判断
  <block wx:else>
    <view class="base-info">
      <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>               //用户的头像
    <text class="userinfo-nickname">{{userInfo.nickName}}</text>                                     //用户的名称
    <text class="userinfo-id">闲置号:Hsaenox</text>                                                //闲置号因为小程序专属,没有后台数据,这里进行静态设置。
    </view>
    <navigator open-type="navigate" wx:for="{{channel}}" wx:key="key">                              //相关的信息,包括我的收藏,我的订单等
      <view class="user_menu">
        <image src="{{item.imgurl}}" class="userinfo_img"></image>
       <text class="recommdant_text">{{item.name}}</text>
      </view>
    </navigator>
  </block>
</view>
<view>
</view>
</view>

json文件

  bindViewTap: function() {
    wx.navigateTo({
      url: \'../logs/logs\'      
    })
  },
  onLoad: function () {
    if (app.globalData.userInfo) {                  //如果已经登录,则从全局获取信息
      this.setData({
        userInfo: app.globalData.userInfo,  
        hasUserInfo: true                        //标记符,已登录
      })
    } else if (this.data.canIUse){
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    } else {
      // 在没有 open-type=getUserInfo 版本的兼容处理
      wx.getUserInfo({
        success: res => {
          app.globalData.userInfo = res.userInfo         //获取信息,并写入全局
          this.setData({
            userInfo: res.userInfo,
            hasUserInfo: true
          })
        }
      })
    }
  },
  getUserInfo: function(e) {                     //获取信息函数
    console.log(e)
    app.globalData.userInfo = e.detail.userInfo
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  }
})

收获总结
第一次编写小程序,从毫无头绪到逐渐入门,我从中感受到了开发的乐趣。微信开发工具的不熟悉, 开发语言的不了解,开发过程中遇到的各种bug,这些都需要自己查找资料,不断试错才能够完成。虽然过程是痛苦的,但一个星期的全身心投入,当看到最后的成品之后还是有着满足感。通过本次开发,也让我对于小程序有了基本的入门,但我知道现在完成的仅仅是个demo。如果后面想要做出真正能够让人满意的东西,在软件工程理论知识和开发技术方面,我都还有很长的路要走,我也会不断激励自己,不断进步。

后续程序设想
对于后续的程序,整体上,需要连入后台数据库,完成数据上传、下载的功能。
个人搜索模块,需要将搜索功能连入数据库,完成对闲置在卖物品的搜索。分类功能,需要参考其他app,采用更为细致的精准分类模式,帮助用户准确查找到所需要的物品。猜你喜欢板块,考虑引入机器学习和深度学习,对每个用户结合历史浏览记录和搜索记录进行推荐商品精准投放。
校园模块,考虑加入筛选和关键词功能,添加发布按钮,每个人都可以发布自己的帖子,分享自己的想法,打造成一个类似内嵌校园贴吧的产物。
发布模块,引入地图api接口,可以直接选择自身位置,而不用手动文字输入。
消息模块,添加服务器,完成用户间的交互功能,根据双方用户id进行识别,完成数据传输。用户脱机时,将数据存储在云端,用户上线后,再传输到设备上。
交易模块,用户能够购买指定商品,通过腾讯提供接口,完成双方的交易。
总体功能设想概述
设想中,程序能够完成商品分类、搜索、推荐,并且完成个人精准推荐。内嵌校园论坛,方便交流。发布更为便捷,简化发布学习成本。买卖双方能够进行实时聊天,完成交易。总体上,完成一个校园定制化的二手交易平台。