微信小程序——个人相册(前端)
项目效果图
1.项目功能
1)用户管理,信息包括:头像、昵称,功能包括:获取微信用户信息、验证用户是否存在、修改头像、修改昵称
2)上传相片:上传图片
3)照册列表:封面图(轮播图)、照片列表、照片选择、删除照片
4)照片信息:照片信息包括 显示照片、大小(字节)、上传时间
2.项目结构
3.数据库
此项目使用MySQL8.0,创建数据库myphoto,SQL文件我已放在文章顶部。
4.创建项目
- 小程序项目名:myphoto
- 使用2.30.0版本的基础库以及不启用远程校验
- 准备好appid和appsecret,后端需要用到
5.代码
5.1 项目准备
-
app.js
// app.js
App({
async onLaunch(){
let res=await wx.login();
let code=res.code;
wx.request({
url: this.globalData.rootPath+'/account/getUserInfo',
data:{code},
success:(result)=>{
let userInfo=result.data;
if (userInfo) {
this.globalData.userInfo = userInfo;
wx.redirectTo({
url: '/pages/index/index',
})
} else {
wx.redirectTo({
url: '/pages/mine/mine'
});
}
}
})
},
globalData: {
rootPath:"http://localhost:8089/myhome_war_exploded",//请修改为自己的服务根路径
userInfo: null,
},
})
-
app.json
{
"entryPagePath": "pages/index/index",
"pages": [
"pages/index/index",
"pages/File/File",
"pages/mine/mine",
"pages/detail/datail"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle": "black"
},
"tabBar": {
"selectedColor": "#33a3dc",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "./pages/images/1.png",
"selectedIconPath": "./pages/images/1-1.png"
},
{
"pagePath": "pages/File/File",
"text": "文件",
"iconPath": "./pages/images/2.png",
"selectedIconPath": "./pages/images/2-2.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "./pages/images/3.png",
"selectedIconPath": "./pages/images/3-3.png"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
5.2 mine(用户登录界面)
页面需求:
① 打开小程序时,先从服务端根据用户的code获取用户信息,如果用户存在,打开index,不存在,跳转到mine
② 把从服务端获取的用户信息保存到全局对象中
获取用户信息
① 进入mine.js时判断全局对象中是否有用户信息的缓存,如果有,显示用户信息,如果没有,显示“获取用户信息”按钮。
② 点击按钮,获取微信用户信息,获取成功后隐藏“获取用户信息按钮”,并显示用户头像和昵称
③ 保存用户信息到全局对象中
保存用户信息
① 如果是获取的微信用户信息,把用户信息保存到服务端
② 在服务端保存用户信息前需通过小程序传入的code来获取openId(用于唯一标识 每一个微信用户)。
③ 数据库的主键使用UUID生成,长度为32位(去掉“-”字符)
服务端返回生成的用户ID,在小程序端,把返回的用户id添加到缓存的用户信息中
修改昵称
① 点击my视图中“修改昵称”按钮,昵称从显示状态切换为可编辑状态,同时按钮的文字修改为“确定修改”
② 修改昵称后,点击“确定修改”按钮,把修改后的昵称提交到服务端保存,并修改小程序中缓存用户信息的昵称
修改头像
① 点击头像,从本地相册中选择一张照片,确定后先替换本地头像
② 把新头像上传到服务端,并保存存放路径和访问路径保存
服务端返回访问路径到小程序,更新缓存中用户头像的路径和my中头像的路径
-
mine.wxml
<!--pages/mine/mine.wxml-->
<view class="container">
<block wx:if="{{!hasUserInfo}}">
<button bindtap="getUserInfo">获取用户信息</button>
</block>
<block wx:else>
<image src="{{userInfo.avatarUrl}}" style="border-radius: 50%; height:400rpx;width:400rpx;" bindtap="modifyAvatar"/>
<text style="padding-top: 100rpx; font-size: 50rpx;" hidden="{{isModify}}">{{userInfo.nickName}}</text>
<form bindsubmit="modifynickname">
<input type="text" name="nickname" style="margin-top: 100rpx; padding: 10rpx;font-size: 50rpx; border:2rpx solid #ddd;" hidden="{{!isModify}}" value="{{userInfo.nickName}}"/>
<button form-type="submit" type="primary" style="margin-top: 100rpx;">
{{btnTitle}}
</button>
</form>
</block>
</view>
-
mine.js
const app=getApp();
const {req} =require("../../utils/repuest")
Page({
data:{
hasUserInfo:false,
isModify:false,
btnTitle:'修改昵称',
userInfo:{
avatarUrl:'http://localhost:8089/myhome/photo/1.png',
nickName:'当前用户昵称'
}
},
onLoad(){
let userInfo=app.globalData.userInfo;
if(userInfo){
if(!userInfo.avatarUrl.startsWith("http")){
userInfo.avatarUrl=app.globalData.rootPath+userInfo.avatarUrl;
}
this.setData({
userInfo:userInfo,
hasUserInfo:true
})
}
},
getUserInfo(){
wx.getUserProfile({
desc: 'desc',
success:(res)=>{
let userinfo=res.userInfo;
// userinfo.accId='';
this.setData({
userinfo:userinfo,
hasUserInfo:true,
nickName:this.data.nickName
})
this.saveUserInfo();
}
})
},
async saveUserInfo(){
let userInfo=this.data.userinfo;
let code=(await(await wx.login())).code;
req({
url:"/account/save",
method:"POST",
header:{'content-type':'application/x-www-form-urlencoded'},
data:{...userInfo,code},
}).then((res)=>{
let accId=res.data;
if(accId){
userInfo.accId=accId;
app.globalData.getUserInfo=userInfo;
this.setData({userinfo:userInfo})
}
});
},
modifynickname(e){
let isModify=this.data.isModify;
if(!isModify){
this.setData({
isModify: true,
inputPlaceholder: "请输入新昵称",
btnTitle: "确定修改"
})
}else{
this.setData({
isModify: false,
inputPlaceholder: "当前昵称",
btnTitle: "修改昵称"
})
this.updateNickName;
}
},
updateNickName(e){
let userInfo=this.data.userInfo;
let nickName = e.detail.value.nickName; // 假设昵称输入框的值为nickName
let accId = userInfo.accId; // 假设用户的accid存在于userInfo中
// 调用req方法访问服务器实现昵称修改
req({
url: 'http://localhost:8089/myhome/account/modifyNickName',//请修改为自己的实际端口号
method: 'POST',
data: {
nickName: nickName,
accId:accId
},
success: function(response) {
// 根据服务器返回的结果进行相应的处理
console.log('昵称修改成功', response);
// 可以根据需要更新页面的数据或进行其他操作
},
fail: function(error) {
console.log('昵称修改失败', error);
// 可以根据需要进行错误处理或提示用户
}
});
},
uploadAvatar(filePath){
wx.uploadFile({
filePath: filePath,
name: 'avatar',
url:"http://localhost:8089/myhome/account/uploadAvatar",
formData:{accId:this.data.userinfo.accId},
success(res){
let avatarUrl=res.data;
if(avatarUrl){
this.setData({
"userinfo.avatarUrl":app.globalData.rootPath+avatarUrl
})
}
}
})
},
modifyAvatar(){
wx.chooseMedia({
count:1,
mediaType:'image',
sourceType:'album'
}).then(res=>{
let avatarUrl=res.tempFiles[0].tempFilePath;
this.setData({
'userinfo.avatarUrl':avatarUrl
})
this.uploadAvatar(avatarUrl);
}).catch(res=>{})
}
})
5.3 File(文件上传界面)
页面需求
① 在upload视图中“上传”按钮默认为禁用,点击按钮上方的大图标,可在本地相册中选择要上传的照片(一次只能选择一张)
② 确定后,用选中的照片替换大图标,“上传”按钮设为可用
③ 点击“上传”,把照片上传到服务端
④ 上传成功后,照片区恢复显示大图标,“上传”按钮为禁用
-
File.wxml
<!--pages/File/File.wxml-->
<view style="padding:40rpx;box-sizing: border-box;display: block;">
<view style="text-align: center;color: #f00;">点击以下图片选择相片</view>
<image src="{{filePath}}" style="width: 100%;" mode="{{mode}}" bindtap="selectPhoto"></image>
<button bindtap="uploadPhoto" disabled="{{!isSelectImg}}" type="primary">上传</button>
</view>
-
File.js
//获取应用实例
var app = getApp();
Page({
data: {
filePath:"/pages/images/2-2.png",
mode:"center",
isSelectImg:false
},
selectPhoto(){
wx.chooseMedia({
count:1,
mediaType:'image',
sourceType:'album'
}).then(res=>{
let filePath = res.tempFiles[0].tempFilePath;
this.setData({
filePath:filePath,
isSelectImg:true
})
}).catch(res=>{})
},
uploadPhoto(){
let _this = this;
wx.uploadFile({
filePath: this.data.filePath,
name: 'file',
url: getApp().globalData.rootPath+'/photo/upload',
formData:{accId: getApp().globalData.userInfo.accId},
success:res=>{
console.log(res);
if(res.data.replaceAll("\"","") =="1"){
wx.showToast({
title: '上传成功',
})
}else{
wx.showToast({
title: '上传失败',
icon:"error"
})
}
this.setData({
filePath:"/pages/images/2-2.png",
mode:"center",
isSelectImg:false
})
}
})
},
});
5.4 index(首页)
页面需求:
① 在进入index页面时首先判断缓存中是否有用户信息
② 如果不没有,页面跳转到my,并每隔1秒轮询一次,直到用户信息存在后停止轮询
③ 如果用户存在,从服务端此用户的照片(一次最多10张,最后上传的照片显示在最前面)
在页面中显示(一排显示两张)
触底分页
① 当照片滚动到视图最下方时,从服务端加载新的照片(和上一轮的照片数量相同)
② 新加载的照片追加到已显示照片的后方
开始加载前显示加载动画,加载完成后结束加载动画
下拉刷新
① 开启index页面的下拉刷新功能
在页面的下拉刷新事件回调中清空已显示的所有照片,重新从服务端加载照片
长按显示复选框和操作栏
① 在照片上长按,在每个照片的左上角显示复选框,并在视图的底部显示操作栏
② 复选框默认时都为未选中
③ 操作栏中的操作包括“取消”、“全选”、“删除”三项
取消操作
① 点击操作栏中的“取消”完成以下操作
② 清空所有选中
③ 隐藏复选框
④ 隐藏操作栏
勾选和取消勾选、全选操作
① 点击照片上的复选框,如果勾选,记录选中的照片ID
② 如果是取消勾选,从记录中删除对应的照片ID
③ 点击“全选”,勾选所有复选框,并记录所有被选中的照片ID
删除
① 点击操作栏中“删除”,删除所有被勾选的照片
② 如果未选择要删除的照片,提示“请勾选要删除的照片”
③ 删除前需询问“确定要删除选中的照片吗?”,确定后方能删除
删除成功后刷新照片
-
index.wxml
<swiper indicator-dots="{{true}}" current="{{currentIndex}}" autoplay>
<swiper-item wx:for="{{covers}}" wx:key="*this">
<image src="{{item.accessUrl}}" style="width: 100%;" mode="widthFix"></image>
</swiper-item>
</swiper>
<scroll-view style="padding: 60rpx 40rpx;box-sizing: border-box;">
<view style="display: flex;flex-wrap: wrap;">
<view wx:for="{{photoList}}" wx:key="*this" wx:for-item="photo" style="flex:none;width:50%;height:300rpx;box-sizing:border-box;padding:6rpx;position:relative;">
<checkbox mark:photoId="{{photo.photoId}}" mark:index="{{index}}" style="position: absolute;" hidden="{{!delSelect}}" bindtap="checkedPhoto" checked="{{selectList[index]}}"/>
<image src="{{photo.photoAccessUrl}}" mode="" style="width: 100%;height: 100%;" bindlongpress="enabelDelSelect" bindtap="showImage" mark:index="{{index}}"/>
</view>
</view>
</scroll-view>
<button loading="{{loadding}}" style="background: #fff;"></button>
<view style="position: fixed;bottom: 0;width: 100%;display: flex;justify-content: space-between;box-sizing: border-box;padding: 10rpx 20rpx;" wx:if="{{delSelect}}">
<text bindtap="cancelSelect" style="color: #00f;text-decoration: underline;">取消</text>
<text bindtap="selectAll" style="color: #00f;text-decoration: underline;">全选</text>
<text bindtap="deletePhoto" style="color: #00f;text-decoration: underline;">删除</text>
</view>
-
index.js
// index.js
// 获取应用实例
const app = getApp()
const {req}=require("../../utils/repuest")
Page({
data: {
covers:[],
photoList:[],
delSelect:false,
selectList:[],
pageNum:1,
loadding:true,
currentIndex:0
},
selectPhotoList(){
this.setData({loadding:true});
req({
url:"/photo/list",
data:{
accId:app.globalData.userInfo.accId,
pageNum:this.data.pageNum
}
}).then(res=>{
let photoList = res.data;
if(photoList.length > 0){
for(let photo of photoList){
photo.photoAccessUrl = app.globalData.rootPath+photo.photoAccessUrl;
}
this.setData({
photoList:this.data.photoList.concat(photoList),
pageNum:this.data.pageNum+1
})
}else{
// wx.showToast({
// title: '图片以全部加载',
// })
}
this.setData({
loadding:false
})
})
},
onLoad() {
let time = setInterval(()=>{
let userInfo = app.globalData.userInfo;
if(userInfo){
clearInterval(time);
this.selectCover();
this.selectPhotoList();
}
},1000);
},
onPullDownRefresh(){
this.setData({
covers:[],
photoList:[],
pageNum:1,
currentIndex:0
});
this.selectCover();
this.selectPhotoList();
},
//在照片上长按,在每个照片的左上角显示复选框,并在视图的底部显示操作栏
enabelDelSelect(){
this.setData({
delSelect:true
});
},
/*
① 点击操作栏中的“取消”完成以下操作
② 清空所有选中
③ 隐藏复选框
隐藏操作栏
*/
cancelSelect(){
this.setData({
selectList:[],
delSelect:false
});
},
onReachBottom(){
console.log(this.data.pageNum);
this.setData({loadding:true});
this.selectPhotoList();
},
checkedPhoto(e){
let index = e.mark.index;
let photoId = e.mark.photoId;
let selectList = this.data.selectList;
if(selectList[index]){
selectList[index]=undefined;
}else{
selectList[index]=photoId;
}
this.setData({
selectList:selectList
})
},
selectAll(){
let selectList = [];
let photoList = this.data.photoList;
for(let photo of photoList){
selectList.push(photo.photoId);
}
this.setData({
selectList:selectList
});
},
deletePhoto(){
let selectList = this.data.selectList;
let deleteList = '';
for(let id of selectList){
if(id){
deleteList+='&photoIds='+id;
}
}
if(deleteList == ''){
wx.showToast({
title: '请勾选要删除的照片',
})
}else{
wx.showModal({
title: '确定要删除选中的照片吗?',
content: '',
complete: (res) => {
if (res.cancel) {
}
if (res.confirm) {
req({
url:"/photo/delete?photoIds=''"+deleteList,
method:"GET"
}).then(res=>{
this.setData({
photoList:[],
pageNum:1
});
this.cancelSelect();
this.selectPhotoList();
})
}
}
})
}
},
showImage(e){
let index = e.mark.index;
let photoList = this.data.photoList;
wx.navigateTo({
url: '/pages/detail/datail?photoList='+JSON.stringify(photoList)+"&index="+index+"&coverLength="+this.data.covers.length,
})
},
selectCover(){
req({
url:'/cover/list?accId='+app.globalData.userInfo.accId,
method:'GET',
}).then(res=>{
let cover = res.data;
console.log(cover);
this.setData({
covers:cover,
currentIndex:0
})
})
},
onShow(){
// this.selectCover();
// this.selectPhotoList();
}
})
5.5 detail(照片详情界面)
页面需求:
① 在index中点击照片,跳转到照片详情页
② 显示点击的照片和照片的大小和上传时间
③ 可通过在照片上左、右滑动切换照片(以及照片的信息),可切换的照片范围为index中所显示的所有照片
设置封面
① 在detail视图中,如果上方显示的照片是封面,默认勾选“设为封面”复选框;
② 取消复选框的勾选,把状态传递到服务端,删除对应的封面设置
③ 点击复选框勾选,把状态传递到服务端,添加此照片为封面
④ 最多只能添加四个封面,如果勾选后此照片为第五个封面,提示“最多只能设置四个封面”,自动取消选中
如果设置和取消成功提示“封面设置成功”
查询封面
① 进入index页面时查询此用户的封面(轮播图),并显示,封面最多四张,自动播放。
② 在detail页面中设置封面时,更新此页面的封面列表
-
detail.wxml
<view class="container">
<image src="{{photo.photoAccessUrl}}" style="padding: 60rpx;width: 100%;height: 400rpx;" bindtouchstart="start" bindtouchmove="end"/>
</view>
<view style="text-align: left;padding-top: 10rpx;">
<checkbox bindtap="fm" checked="{{isFm}}">设置封面</checkbox>
<text style="text-align: left;">\n图片大小:{{photo.photoSize}}</text>
<text>\n上传时间:{{uploadTime}}</text>
</view>
-
detail.js
const app = getApp()
const {req}=require("../../utils/repuest")
Page({
/**
* 页面的初始数据
*/
data: {
photoList:[],
index:0,
photo:{},
isFm:false,
uploadTime:'',
startPoint:0,
startFlag:false,
cover:{},
coverLength:0
},
/**
* 生命周期函数--监听页面加载
*/
onLoad:function(res) {
let photoList = JSON.parse(res.photoList);
let index = res.index;
let coverLength = res.coverLength;
let photo = photoList[index];
let uploadTime = new Date(Number.parseInt(photo.uploadTimestamp));
let s = uploadTime.toLocaleString();
this.setData({
photoList:photoList,
photo:photo,
uploadTime:s,
index:index,
coverLength:coverLength
});
this.selectCover();
},
selectCover(){
req({
url:'/cover/getOne?accId='+app.globalData.userInfo.accId+"&photoId="+this.data.photo.photoId,
method:'GET'
}).then(res=>{
if(res.data){
this.setData({
cover:res.data,
isFm:true
});
}else{
this.setData({
isFm:false
});
}
})
},
fm(){
console.log(this.data.isFm);
if(!this.data.isFm){
console.log(this.data.coverLength);
if(this.data.coverLength<4){
req({
url:'/cover/save',
method:'POST',
header:{
"Content-Type":"application/x-www-form-urlencoded"
},
data:{accId:app.globalData.userInfo.accId,
photoId:this.data.photo.photoId,
accessUrl:this.data.photo.photoAccessUrl
}
}).then(res=>{
this.setData({
coverLength:Number.parseInt(this.data.coverLength)+1
});
this.selectCover();
})
}else{
wx.showM({
title: '封面只能设置四个',
})
}
}else{
req({
url:'/cover/delete?coverId='+this.data.cover.coverId,
method:'GET'
}).then(res=>{
this.setData({
coverLength:Number.parseInt(this.data.coverLength)-1
});
this.selectCover();
})
}
},
start(e){
this.setData({
startPoint:e.touches[0].clientX,
startFlag:true
})
},
end(e){
let point = this.data.startPoint;
if(((point-e.touches[e.touches.length-1].clientX)>50) && this.data.startFlag){
let index = this.data.index;
console.log(index);
if(index-1<0){
index = this.data.photoList.length-1;
}else{
index = index-1;
}
let photo = this.data.photoList[index];
let uploadTime = new Date(Number.parseInt(photo.uploadTimestamp));
let s = uploadTime.toLocaleString();
this.setData({
photo:photo,
index:index,
uploadTime:s,
startFlag:false,
startPoint:0
});
}else if(((point-e.touches[e.touches.length-1].clientX)<-50) && this.data.startFlag) {
let index = this.data.index;
console.log(index);
if(index+1>this.data.photoList.length-1){
index = 0;
}else{
index = index+1;
}
let photo = this.data.photoList[index];
let uploadTime = new Date(Number.parseInt(photo.uploadTimestamp));
let s = uploadTime.toLocaleString();
this.setData({
photo:photo,
index:index,
uploadTime:s,
startFlag:false,
startPoint:0
});
}
this.selectCover();
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
总结
作为一个微信小程序个人相册的开发者,我对这个项目有一些自我总结和反思。
首先,我认为成功的部分在于我能够充分理解用户的需求,并且提供了一个简单易用的相册功能。用户可以方便地上传照片、创建相册、查看照片信息等等。
其次,我采用了适当的设计和布局,使得小程序界面美观而且用户友好。我尽量减少了复杂的操作流程,让用户能够轻松地浏览和管理他们的照片。同时,我也在界面上保持了一定的一致性,使得用户可以很快熟悉和掌握小程序的使用方式。
然而,也存在一些需要改进的地方。首先,我认识到在小程序的功能方面还有一些局限性。例如,目前我只实现了基本的照片管理功能,但是还有很多其他可能的功能可以加入,比如照片编辑、滤镜效果、批量操作、分享照片、下载照片等等。这些功能可以提升用户的体验,并增加小程序的吸引力。
其次,我需要进一步改进小程序的性能和加载速度。尽管我尽力优化了代码和资源,但是随着用户上传的照片数量增加,小程序的加载速度可能会变慢。我需要继续寻找更好的方法来提高性能,以确保用户能够流畅地使用小程序。
最后,用户反馈对于改进小程序也非常重要。我会积极收集用户的意见和建议,并根据他们的反馈来改进小程序的功能和界面设计。通过不断改进和迭代,我相信我可以打造出一个更好的微信小程序个人相册。
总而言之,开发微信小程序个人相册是一次很有意义的经历。我学到了很多关于用户需求、设计和性能优化的知识,并且锻炼了我的开发技能。通过不断改进和学习,我希望能够为用户提供更好的相册体验,并且不断完善和扩展这个小程序。文章来源:https://www.toymoban.com/news/detail-774292.html
后端介绍
最后献上整体项目源码:个人相册(前端)文章来源地址https://www.toymoban.com/news/detail-774292.html
到了这里,关于微信小程序——个人相册(前端)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!