前言
前段时间上线了自己学校的猫猫小程序:是小猫呀。今天来给大家分享下这个项目,希望大家在学习后也能给自己的学校开发一个猫猫小程序,或者将这个小程序用作毕设或者课设之类的,也可以作为前端找工作的项目。
下面是小程序的功能介绍,感兴趣的小伙伴可以去搜索下我的小程序,或者直接扫码
功能介绍
首页
添加猫咪
图片榜单
猫脸识别
核心代码
项目涉及的模块众多,我们举几个代表性的例子
添加猫咪
* 页面的初始数据
*/
data: {
tipText: '正在鉴权...',
pickers: {
gender: ['公', '母'],
sterilized: [false, true],
adopt: cat_status_adopt.map((x) => { return {desc: x} }),
to_star: [false, true],
},
picker_selected: {},
bottomShow: false,
text_cfg: text_cfg
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: async function (options) {
await this.loadPickers();
cat_id = options.cat_id;
if (await checkAuth(this, 2)) {
await this.loadCat();
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: async function () {
await this.loadMorePhotos();
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
},
// 没有权限,返回上一页
goBack() {
wx.navigateBack();
},
// 检查权限
添加图片
Page({
/**
* 页面的初始数据
*/
data: {
isAuth: false,
user: {},
uploading: false,
birth_date: '2008-01-01',
photos: [],
set_all: {},
canUpload: false,
text_cfg: config.text,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: async function (options) {
const db = await cloud.databaseAsync();
const cat_id = options.cat_id;
var catRes = await db.collection('cat').doc(cat_id).field({
birthday: true,
name: true,
campus: true,
_id: true
}).get();
this.setData({
cat: catRes.data,
birth_date: catRes.data.birthday || ''
});
//this.checkUInfo();
// 获取一下现在的日期,用在拍摄日前选择上
const today = new Date();
var now_date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
this.setData({
now_date: now_date
});
this.setData({
canUpload: await checkCanUpload()
});
// 获取一下手机平台
const device = await wx.getSystemInfoSync();
this.setData({
isIOS: device.platform == 'ios'
});
},
async onShow() {
await getPageUserInfo(this);
},
onUnload: async function (options) {
await this.ifSendNotifyVeriftMsg()
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
const pagesStack = getCurrentPages();
const path = getCurrentPath(pagesStack);
const share_text = `来给${this.data.cat.name}添加照片 - ${config.text.app_name}`;
return shareTo(share_text, path);
},
async chooseImg(e) {
var res = await wx.chooseMedia({
count: 20,
mediaType: ['image'],
sizeType: ["compressed"],
sourceType: ['album', 'camera'],
})
var photos = [];
for (var file of res.tempFiles) {
// 需要压缩
if (file.size > 512 * 1024) {
file.path = await compressImage(file.tempFilePath, 30);
console.log("compressed path:", file.path);
} else {
file.path = file.tempFilePath;
}
photos.push({
file: file
});
}
this.setData({
photos: photos,
set_all: {},
});
},
// 点击单个上传
async uploadSingleClick(e) {
if (this.data.uploading) {
console.log("uploading lock.");
return;
}
this.setData({
uploading: true
})
await requestNotice('verify');
wx.showLoading({
title: config.text.add_photo.success_tip_title,
mask: true,
});
const currentIndex = e.currentTarget.dataset.index;
const photo = this.data.photos[currentIndex];
await this.uploadImg(photo);
this.data.photos = this.data.photos.filter((ph) => {
// 把已上传的图片从图片列表中去掉
return ph.file.path != photo.file.path;
});
this.setData({
uploading: false,
photos: this.data.photos,
});
wx.hideLoading();
wx.showModal({
title: config.text.add_photo.success_tip_title,
content: config.text.add_photo.success_tip_content,
showCancel: false
});
},
// 点击多个上传
async uploadAllClick(e) {
if (this.data.uploading) {
console.log("uploading lock.");
return;
}
const photos = []; // 这里只会保存可以上传的照片
for (const item of this.data.photos) {
if (item.shooting_date && item.file.path) {
photos.push(item);
}
}
if (photos.length == 0) {
wx.showModal({
title: config.text.add_photo.unfinished_tip_title,
content: config.text.add_photo.unfinished_tip_content,
showCancel: false
});
return;
}
await requestNotice('verify');
for (let i = 0; i < photos.length; ++i) {
wx.showLoading({
title: '正在上传(' + (photos.length - i) + ')',
mask: true,
});
await this.uploadImg(photos[i]);
this.data.photos = this.data.photos.filter((ph) => {
// 把已上传的图片从图片列表中去掉
return ph.file.path != photos[i].file.path;
});
}
this.setData({
uploading: false,
photos: this.data.photos,
})
wx.hideLoading();
wx.showModal({
title: config.text.add_photo.success_tip_title,
content: config.text.add_photo.success_tip_content,
showCancel: false
});
},
async ifSendNotifyVeriftMsg() {
const db = await cloud.databaseAsync();
const subMsgSetting = await db.collection('setting').doc('subscribeMsg').get();
const triggerNum = subMsgSetting.data.verifyPhoto.triggerNum; //几条未审核才触发
// console.log("triggerN",triggerNum);
var numUnchkPhotos = (await db.collection('photo').where({
verified: false
}).count()).total;
if (numUnchkPhotos >= triggerNum) {
await sendNotifyVertifyNotice(numUnchkPhotos);
console.log("toSendNVMsg");
}
},
async uploadImg(photo) {
// multiple 表示当前是否在批量上传,如果是就不显示上传成功的弹框
this.setData({
uploading: true,
});
const cat = this.data.cat;
const tempFilePath = photo.file.path;
//获取后缀
const index = tempFilePath.lastIndexOf(".");
const ext = tempFilePath.substr(index + 1);
let upRes = await cloud.uploadFile({
cloudPath: cat.campus + '/' + generateUUID() + '.' + ext, // 上传至云端的路径
filePath: tempFilePath, // 小程序临时文件路径
});
// 返回文件 ID
console.log(upRes.fileID);
// 添加记录
const params = {
cat_id: cat._id,
photo_id: upRes.fileID,
user_id: this.data.user._id,
verified: false,
shooting_date: photo.shooting_date,
photographer: photo.pher
};
let dbAddRes = (await api.curdOp({
operation: "add",
collection: "photo",
data: params
})).result;
console.log("curdOp(add-photo) result:", dbAddRes);
},
pickDate(e) {
console.log(e);
const index = e.currentTarget.dataset.index;
this.setData({
["photos[" + index + "].shooting_date"]: e.detail.value
});
},
inputPher(e) {
const index = e.currentTarget.dataset.index;
this.setData({
["photos[" + index + "].pher"]: e.detail.value
})
},
// 下面是统一设置
setAllDate(e) {
const value = e.detail.value;
var photos = this.data.photos;
console.log(photos);
for (var ph of photos) {
ph.shooting_date = value;
}
this.setData({
"set_all.shooting_date": value,
photos: photos,
});
},
setAllPher(e) {
const photographer = e.detail.value;
var photos = this.data.photos;
for (var ph of photos) {
ph.pher = photographer;
}
this.setData({
"set_all.pher": photographer,
photos: photos,
});
},
// 移除其中一个
removeOne(e) {
const index = e.currentTarget.dataset.index;
const photos = this.data.photos;
const new_photos = photos.filter((ph, ind, arr) => {
// 这个photo是用户点击的photo,在上面定义的
return index != ind;
});
this.setData({
photos: new_photos
});
},
goBackIndex(e) {
wx.switchTab({
url: '/pages/genealogy/genealogy',
});
},
getUInfo(e) {
toSetUserInfo();
}
})
猫猫主界面
const default_png = undefined;
var catsStep = 1;
var loadingLock = 0; // 用于下滑刷新加锁
var pageLoadingLock = true; // 用于点击按钮刷新加锁
const tipInterval = 24; // 提示间隔时间 hours
// 分享的标语
const share_text = config.text.app_name + ' - ' + config.text.genealogy.share_tip;
Page({
/**
* 页面的初始数据
*/
data: {
cats: [],
filters: [],
filters_sub: 0, // 过滤器子菜单
filters_legal: true, // 这组过滤器是否合法
filters_show: false, // 是否显示过滤器
filters_input: '', // 输入的内容,目前只用于挑选名字
filters_show_shadow: false, // 滚动之后才显示阴影
filters_empty: true, // 过滤器是否为空
// 高度,单位为px(后面会覆盖掉)
heights: {
filters: 40,
},
// 总共有多少只猫
catsMax: 0,
// 加载相关
loading: false, // 正在加载
loadnomore: false, // 没有再多了
// 领养状态
adopt_desc: config.cat_status_adopt,
// 寻找领养的按钮
adopt_count: 0,
// 广告是否展示
ad_show: {},
// 广告id
ad: {},
// 需要弹出的公告
newsList: [],
newsImage: "",
text_cfg: config.text
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: async function (options) {
// 从缓存里读取options
var fcampus = options.fcampus;
if (!fcampus) {
fcampus = this.getFCampusCache();
}
// 从分享点进来,到跳转到其他页面
if (options.toPath) {
wx.navigateTo({
url: decodeURIComponent(options.toPath),
});
}
// 从扫描二维码扫进来,目前只用于猫猫二维码跳转
if (options.scene) {
const scene = decodeURIComponent(options.scene);
console.log("scene:", scene);
if (scene.startsWith('toC=')) {
const cat_No = scene.substr(4);
const db = await cloud.databaseAsync();
var cat_res = await db.collection('cat').where({
_no: cat_No
}).field({
_no: true
}).get()
if (!cat_res.data.length) {
return;
}
const _id = cat_res.data[0]._id;
this.clickCatCard(_id, true);
}
}
// 开始加载页面
const settings = await getGlobalSettings('genealogy');
if (!settings) {
console.log("no setting");
return
}
// 先把设置拿到
catsStep = settings['catsStep'];
// 启动加载
this.loadFilters(fcampus);
this.setData({
main_lower_threshold: settings['main_lower_threshold'],
adStep: settings['adStep'],
photoPopWeight: settings['photoPopWeight'] || 10
});
// 载入公告信息
this.newsModal = this.selectComponent("#newsModal");
await this.loadNews();
// 设置广告ID
const ads = await getGlobalSettings('ads') || {};
this.setData({
ad: {
banner: ads.genealogy_banner
},
})
},
onShow: function () {
showTab(this);
},
loadFilters: async function (fcampus) {
// 下面开始加载filters
var res = await loadFilter();
if (!res) {
wx.showModal({
title: '出错了...',
content: '请到关于页,清理缓存后重启试试~',
showCancel: false,
});
return false;
}
var filters = [];
var area_item = {
key: 'area',
cateKey: 'campus',
name: '校区',
category: []
};
area_item.category.push({
name: '全部校区',
items: [], // '全部校区'特殊处理
all_active: true
});
// 用个object当作字典,把area分下类
var classifier = {};
for (let i = 0, len = res.campuses.length; i < len; ++i) {
classifier[res.campuses[i]] = {
name: res.campuses[i],
items: [], // 记录属于这个校区的area
all_active: false
};
}
for (let k = 0, len = res.area.length; k < len; ++k) {
classifier[res.area[k].campus].items.push(res.area[k]);
}
for (let i = 0, len = res.campuses.length; i < len; ++i) {
area_item.category.push(classifier[res.campuses[i]]);
}
// 把初始fcampus写入,例如"011000"
if (fcampus && fcampus.length === area_item.category.length) {
console.log("fcampus exist", fcampus, area_item);
for (let i = 0; i < fcampus.length; i++) {
const active = fcampus[i] == "1";
area_item.category[i].all_active = active;
}
}
filters.push(area_item);
var colour_item = {
key: 'colour',
name: '花色',
category: [{
name: '全部花色',
items: res.colour.map(name => {
return {
name: name
};
}),
all_active: true
}]
}
filters.push(colour_item);
var adopt_status = [{
name: "未知",
value: null
}];
adopt_status = adopt_status.concat(config.cat_status_adopt.map((name, i) => {
return {
name: name,
value: i, // 数据库里存的
};
}));
var adopt_item = {
key: 'adopt',
name: '领养',
category: [{
name: '全部状态',
items: adopt_status,
all_active: true
}]
}
filters.push(adopt_item);
// 默认把第一个先激活了
filters[0].active = true;
console.log(filters);
this.newUserTip();
this.setData({
filters: filters,
});
await this.reloadCats();
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
this.getHeights();
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: async function () {
await this.loadMoreCats();
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
// 分享是保留校区外显filter
const pagesStack = getCurrentPages();
const path = getCurrentPath(pagesStack);
const fcampus = this.getFCampusStr();
const query = `${path}fcampus=${fcampus}`;
console.log(query);
return {
title: share_text,
path: query
};
},
// 获取二进制的campus filter字符串
getFCampusStr: function () {
var fcampus = [];
for (var item of this.data.filters[0].category) {
fcampus.push(item.all_active ? "1" : "0");
}
return fcampus.join("");
},
onShareTimeline: function () {
return {
title: share_text,
// query: 'cat_id=' + this.data.cat._id
}
},
代码
GitHub - sysucats/zhongdamaopu: 中大猫谱小程序(小程序更名为笃行猫谱)
项目由中山大学的学长学姐搭建,部署文档也非常全面,大家可以参考学长学姐的文档去部署属于自己的猫猫小程序呀。