博客班级 | 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进行识别,完成数据传输。用户脱机时,将数据存储在云端,用户上线后,再传输到设备上。
交易模块,用户能够购买指定商品,通过腾讯提供接口,完成双方的交易。
总体功能设想概述
设想中,程序能够完成商品分类、搜索、推荐,并且完成个人精准推荐。内嵌校园论坛,方便交流。发布更为便捷,简化发布学习成本。买卖双方能够进行实时聊天,完成交易。总体上,完成一个校园定制化的二手交易平台。