写点什么

从 0 到 1:不文明现象随手拍小程序开发日记(一)

作者:CC同学
  • 2025-06-28
    广东
  • 本文字数:5430 字

    阅读完需:约 18 分钟

从0到1:不文明现象随手拍小程序开发日记(一)

前期调研

不文明现象随手拍小程序:在城市的快速发展进程中,不文明现象时有发生,为了有效解决这一问题,提升城市文明程度, 市民若发现不文明行为,如乱扔垃圾、随地吐痰、破坏公共设施、违规停车等,只需点击“上报不文明现象”按钮,即可将这些不文明行为记录下来,并附上简短的文字描述,如事件发生的具体地点、时间以及对周围环境或他人造成的影响等。市民可以在小程序的“我的上报记录”页面中随时查看自己的上报; 为了进一步调动市民参与的积极性,小程序设置了积分激励机制。每当市民成功上报一条不文明现象并被后台审核通过后,即可获得相应的积分奖励。 同时提供积分商城模块,可以让市民使用通过参与任务所获得的积分来兑换各种奖励或福利。

功能规划

数据设计

ActivityModel.DB_STRUCTURE = {  _pid: 'string|true',  ACTIVITY_ID: 'string|true',
ACTIVITY_TITLE: 'string|true|comment=标题', ACTIVITY_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中', ACTIVITY_CHECK_REASON: 'string|false|comment=审核理由',
ACTIVITY_CATE_ID: 'string|true|default=0|comment=分类', ACTIVITY_CATE_NAME: 'string|false|comment=分类冗余',
ACTIVITY_CANCEL_SET: 'int|true|default=1|comment=撤销设置 0=不允,1=允许,2=仅上报截止前可撤销',
ACTIVITY_MAX_CNT: 'int|true|default=20|comment=每人次数上限 0=不限', ACTIVITY_START: 'int|false|comment=项目时间', ACTIVITY_START_DAY: 'string|false',
ACTIVITY_BEGIN: 'int|true|default=0|comment=开始时间', ACTIVITY_STOP: 'int|true|default=0|comment=截止时间',

ACTIVITY_ADD_MONTH: 'string|false',
ACTIVITY_ORDER: 'int|true|default=9999', ACTIVITY_VOUCH: 'int|true|default=0',
ACTIVITY_FORMS: 'array|true|default=[]', ACTIVITY_OBJ: 'object|true|default={}',
ACTIVITY_JOIN_FORMS: 'array|true|default=[]',
ACTIVITY_ADDRESS: 'string|false|comment=详细地址', ACTIVITY_ADDRESS_GEO: 'object|false|comment=详细地址坐标参数',
ACTIVITY_QR: 'string|false', ACTIVITY_VIEW_CNT: 'int|true|default=0', ACTIVITY_JOIN_CNT: 'int|true|default=0', ACTIVITY_COMMENT_CNT: 'int|true|default=0',
ACTIVITY_ADD_TIME: 'int|true', ACTIVITY_EDIT_TIME: 'int|true', ACTIVITY_ADD_IP: 'string|false', ACTIVITY_EDIT_IP: 'string|false',};ActivityJoinModel.DB_STRUCTURE = { _pid: 'string|true', ACTIVITY_JOIN_ID: 'string|true', ACTIVITY_JOIN_ACTIVITY_ID: 'string|true|comment=上报PK', ACTIVITY_JOIN_ACTIVITY_TITLE: 'string|true',
ACTIVITY_JOIN_IS_ADMIN: 'int|true|default=0|comment=是否管理员添加 0/1',
ACTIVITY_JOIN_USER_ID: 'string|true|comment=用户ID', ACTIVITY_JOIN_SCORE: 'int|true|default=0|comment=获取积分',
ACTIVITY_JOIN_FORMS: 'array|true|default=[]|comment=表单', ACTIVITY_JOIN_OBJ: 'object|true|default={}',
ACTIVITY_JOIN_STATUS: 'int|true|default=0|comment=状态 1=成功, 99=系统撤销', ACTIVITY_JOIN_REASON: 'string|false|comment=撤销理由',
ACTIVITY_JOIN_ADD_MONTH: 'string|false',
ACTIVITY_JOIN_ADD_TIME: 'int|true', ACTIVITY_JOIN_EDIT_TIME: 'int|true', ACTIVITY_JOIN_ADD_IP: 'string|false', ACTIVITY_JOIN_EDIT_IP: 'string|false',};
复制代码

核心实现

class ActivityService extends BaseProjectService {
// 获取当前项目状态 getJoinStatusDesc(activity) { let timestamp = this._timestamp;
if (activity.ACTIVITY_STATUS == ActivityModel.STATUS.UNUSE) return '项目停止'; else if (activity.ACTIVITY_START > timestamp) return '项目未开始'; else if (activity.ACTIVITY_STOP <= timestamp) return '项目结束'; else return '进行中'; }
/** 浏览信息 */ async viewActivity(userId, id) {
let fields = '*';
let where = { _id: id, ACTIVITY_STATUS: ActivityModel.STATUS.COMM, } let activity = await ActivityModel.getOne(where, fields); if (!activity) return null;
ActivityModel.inc(id, 'ACTIVITY_VIEW_CNT', 1);

return activity; }
/** 取得分页列表 */ async getActivityList(type = 'run', { cateId, //分类查询条件 search, // 搜索条件 sortType, // 搜索菜单 sortVal, // 搜索菜单 orderBy, // 排序 page, size, isTotal = true, oldTotal }) {
orderBy = orderBy || { 'ACTIVITY_ORDER': 'asc', 'ACTIVITY_START': 'asc', 'ACTIVITY_ADD_TIME': 'desc' }; let fields = 'ACTIVITY_ADDRESS,ACTIVITY_STOP,ACTIVITY_JOIN_CNT,ACTIVITY_OBJ,ACTIVITY_VIEW_CNT,ACTIVITY_TITLE,ACTIVITY_MAX_CNT,ACTIVITY_START_DAY,ACTIVITY_START,ACTIVITY_ORDER,ACTIVITY_STATUS,ACTIVITY_CATE_NAME,ACTIVITY_OBJ.cover,ACTIVITY_OBJ.score';
let where = {};
if (cateId && cateId !== '0') where.ACTIVITY_CATE_ID = cateId;
where.ACTIVITY_STATUS = ActivityModel.STATUS.COMM; // 状态
// 进行状态 let day = timeUtil.time('Y-M-D'); if (type == 'run') { where.ACTIVITY_STOP = ['>=', this._timestamp]; } else { where.ACTIVITY_STOP = ['<=', this._timestamp]; orderBy = { 'ACTIVITY_ORDER': 'asc', 'ACTIVITY_START': 'desc', 'ACTIVITY_ADD_TIME': 'desc' }; }
if (util.isDefined(search) && search) { where['ACTIVITY_TITLE'] = ['like', search];
} else if (sortType && util.isDefined(sortVal)) {
// 搜索菜单 switch (sortType) { case 'cateId': { if (sortVal) where.ACTIVITY_CATE_ID = String(sortVal); break; } case 'sort': { // 排序 orderBy = this.fmtOrderBySort(sortVal, 'ACTIVITY_ADD_TIME'); break; } } }
let ret = await ActivityModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal); if (ret) ret.type = type; return ret; }

/** 取得我的上报分页列表 */ async getMyActivityJoinList(userId, { search, // 搜索条件 sortType, // 搜索菜单 sortVal, // 搜索菜单 orderBy, // 排序 page, size, isTotal = true, oldTotal }) { orderBy = orderBy || { 'ACTIVITY_JOIN_ADD_TIME': 'desc' }; let fields = 'ACTIVITY_JOIN_REASON,ACTIVITY_JOIN_ACTIVITY_ID,ACTIVITY_JOIN_ACTIVITY_TITLE,ACTIVITY_JOIN_STATUS,ACTIVITY_JOIN_ADD_TIME';
let where = { ACTIVITY_JOIN_USER_ID: userId };
if (util.isDefined(search) && search) { where['activity.ACTIVITY_TITLE'] = { $regex: '.*' + search, $options: 'i' }; } else if (sortType) { // 搜索菜单 switch (sortType) { case 'timedesc': { //按时间倒序 orderBy = { 'activity.ACTIVITY_START': 'desc', 'ACTIVITY_JOIN_ADD_TIME': 'desc' }; break; } case 'timeasc': { //按时间正序 orderBy = { 'activity.ACTIVITY_START': 'asc', 'ACTIVITY_JOIN_ADD_TIME': 'asc' }; break; } case 'status': { where.ACTIVITY_JOIN_STATUS = Number(sortVal) break; } } }

let result = await ActivityJoinModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);
return result; }
/** 取得我的上报详情 */ async getMyActivityJoinDetail(userId, activityJoinId) {
let fields = '*';
let where = { _id: activityJoinId, ACTIVITY_JOIN_USER_ID: userId }; let activityJoin = await ActivityJoinModel.getOne(where, fields);
return activityJoin; }

async statActivityJoin(id) { // 上报数 let where = { ACTIVITY_JOIN_ACTIVITY_ID: id, ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC } let cnt = await ActivityJoinModel.count(where);

await ActivityModel.edit(id, { ACTIVITY_JOIN_CNT: cnt }); }
/** 上报前获取关键信息 */ async detailForActivityJoin(userId, activityId) { let fields = 'ACTIVITY_JOIN_FORMS, ACTIVITY_TITLE';
let where = { _id: activityId, ACTIVITY_STATUS: ActivityModel.STATUS.COMM, } let activity = await ActivityModel.getOne(where, fields); if (!activity) this.AppError('该项目不存在');
if (activity.ACTIVITY_MAX_CNT > 0) { let cnt = await ActivityJoinModel.count({ ACTIVITY_JOIN_USER_ID: userId }); if (cnt >= activity.ACTIVITY_MAX_CNT) this.AppError('该项目您已经上报' + cnt + '次,已超过可提交上限~'); }

let myForms = [];
if (myForms.length == 0) {
let user = await UserModel.getOne({ USER_MINI_OPENID: userId, USER_STATUS: UserModel.STATUS.COMM }); if (!user) this.AppError('用户异常');
// 取得我的上报信息 myForms = [ { mark: 'name', type: 'text', title: '姓名', val: user.USER_NAME }, { mark: 'phone', type: 'mobile', title: '手机', val: user.USER_MOBILE }, ]
}
activity.myForms = myForms;
return activity; }
/** 撤销我的上报 只有成功可以撤销 取消即为删除记录 */ async cancelMyActivityJoin(userId, activityJoinId) { let where = { ACTIVITY_JOIN_USER_ID: userId, _id: activityJoinId, ACTIVITY_JOIN_STATUS: 0 }; let activityJoin = await ActivityJoinModel.getOne(where);
if (!activityJoin) { this.AppError('未找到可撤销的记录'); }
let activity = await ActivityModel.getOne(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID); if (!activity) this.AppError('该项目不存在');
if (activity.ACTIVITY_STATUS == ActivityModel.STATUS.UNUSE) this.AppError('该项目已停止,不能撤销');
if (activity.ACTIVITY_CANCEL_SET == 0) this.AppError('该项目设置了不能撤销');
if (activity.ACTIVITY_CANCEL_SET == 2 && activity.ACTIVITY_STOP < this._timestamp) this.AppError('该项目已经截止上报,不能撤销');
await ActivityJoinModel.del(where);
// 上报数量统计 await this.statActivityJoin(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID);
}

/** 按天获取上报项目 */ async getActivityListByDay(day) { let start = timeUtil.time2Timestamp(day); let end = start + 86400 * 1000 - 1; let where = { ACTIVITY_STATUS: ActivityModel.STATUS.COMM, ACTIVITY_START: ['between', start, end], };
let orderBy = { 'ACTIVITY_ORDER': 'asc', 'ACTIVITY_ADD_TIME': 'desc' };
let fields = 'ACTIVITY_TITLE,ACTIVITY_START,ACTIVITY_OBJ.cover';
let list = await ActivityModel.getAll(where, fields, orderBy);
let retList = [];
for (let k = 0; k < list.length; k++) {
let node = {}; node.timeDesc = timeUtil.timestamp2Time(list[k].ACTIVITY_START, 'h:m'); node.title = list[k].ACTIVITY_TITLE; node.pic = list[k].ACTIVITY_OBJ.cover[0]; node._id = list[k]._id; retList.push(node);
} return retList; }
/** * 获取从某天开始可报名的日期 * @param {*} fromDay 日期 Y-M-D */ async getActivityHasDaysFromDay(fromDay) { let where = { ACTIVITY_START: ['>=', timeUtil.time2Timestamp(fromDay)], };
let fields = 'ACTIVITY_START'; let list = await ActivityModel.getAllBig(where, fields);
let retList = []; for (let k = 0; k < list.length; k++) { let day = timeUtil.timestamp2Time(list[k].ACTIVITY_START, 'Y-M-D'); if (!retList.includes(day)) retList.push(day); } return retList; }

}
复制代码

UI 设计









后台管理








git 代码下载

点击下载

用户头像

CC同学

关注

CC同学的小程序开发笔记 2021-06-13 加入

大鹅厂的小小程序媛,vx: cclinux0730

评论

发布
暂无评论
从0到1:不文明现象随手拍小程序开发日记(一)_CC同学_InfoQ写作社区