垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别

功能列表

1.0.0版本:

  • 三个版本,一个APPID,登录页选择版本
  • 登录,openid判断
  • 社区居民用户注册,一房一主
  • 工作/清洁人员账号绑定,一人一号,一号一人
  • 用户首页,直接展示搜索功能,包括图片识别按钮
  • 工作/清洁人员首页,直接显示扫码按钮
  • 个人信息修改页(房主更换,自定义用户名,更换房间)
  • 积分页,扫码识别垃圾桶编号,拍照或上传图片用以记录垃圾数量和类别,即可积分
  • 商城页,CMS商品管理,list显示商品信息
  • 商品数据库
  • 垃圾分类数据库
  • 输入框清空按钮
  • 扫码获取垃圾桶编号
  • 调用摄像头或者选择本地图片上传
  • 上传图片预览
  • 图片识别
  • 识别结果分类显示
  • 垃圾溯源
  • 积分明细
  • 订单列表
  • 生成订单号,规则:日期(时间戳)+ 6 位随机数
  • 生成核验码
  • 订单核验

后续优化:

  • (bug)兑换成功后返回订单列表时,金币数及时更新——>增加下拉刷新
  • (bug)积分成功的提示持续时间过短,duration不起作用
  • (未完善)订单列表显示商品图片
  • (优化)获取楼栋等信息时使用addToSet(扫描全表需要用几次循环,每次循环结果拼接时不能保证不和之前的结果不重复,所以循环结束后还需要对结果再次去重以及排序)
  • (优化)垃圾识别结果分类显示时使用lookup(一次),取代group(一次)+ addToSet + where(type)(又一次),不过可能会出现有些垃圾类别本次没有投放的情况,传参显示页面如何处理
  • (优化)coin.js代码功能模块化(之前尝试过一次,可能是异步问题,导致运行结果有问题)
  • (优化)积分合并,一个房间内的住户累计积分在房间名下,而不是个人,这样积分累积速度会变快
  • (优化)用户搬家功能,搬离小区(积分不用更改,解绑房间信息),搬去小区内其他房间(提示用户积分带不走),新住户可以继承积分
  • (优化)强制分类投放,一个类别的垃圾桶是一个码,四个类别的垃圾桶要四个码
  • (优化)投放流程流程改进,可回收垃圾由垃圾桶称重后返回数据,将价格转换为金币发放给用户
  • 物业收到订单的提示(积分积累耗时较长,备货不建议很频繁,订单提醒功能适用于当天下单隔天提货的模式,便于及时备货)
  • 商家注册(事先电话联系,备案信息,商家登录时用openid记录身份,商家身份通过电话号码核实)
  • 商家管理商品列表(商家直接管理的话,流程麻烦,不如把兑换交给物业,小区统一向特定商户批量购买,再由居民兑换)

参考网页

  • 微信小程序弹窗组件,没用上
  • 微信小程序版本管理git,上传按他的步骤来,保存当前没毛病的版本的时候点“贮藏”,首次推送才要新建分支,后续推送就用之前建好的,推送人(作者)在版本管理的设置里,随便设置,不改就默认为unknown,换设备的时候,先拉取合并原分支,之后就可以两台设备都推送到一个分支了,更新版本:从远程仓库拉取合并分支,然后将本地恢复到那个分支,代码就同步了。
  • 微信小程序云开发实现一张或多张图片上传
  • 百度API——通用物体和场景识别
  • 鉴权认证机制获取Access Token
  • API调用的技术文档
  • 获取日期和时间
  • 类似distinct关键字的聚合操作符addToSet(数据不重复)
  • 聚合查询match,后面可接另外的聚合查询(如group)
  • 聚合查询——联表查询(lookup)
  • 云函数教程
  • 字符串转换为布尔类型
  • 聚合函数sort,对指定字段进行排序,可多个字段
  • orderby排序查找,可多个字段
  • 云函数查找数据总数大于100的全表查询教程
  • js数组去重的两种方法
  • sort方法
  • 正则表达式添加变量
  • 获取object对象的length

前端小记

  • var是函数作用域,而let是块作用域。在函数内声明了var,整个函数内都是有效的,在for循环内定义了一个var变量,实际上其在for循环以外也是可以访问的,而let由于是块作用域,所以如果在块作用域内(for循环内)定义的变量,在其外面是不可被访问的。
  • for循环用var声明循环变量会出现打印5个5的情况,let就是正常的12345。
  • promise是异步的,且有三个状态(等待、完成、失败),resolve()把状态变成完成(resolve所在函数执行完成后promise状态就会改变,在函数内部resolve和其他语句的先后顺序不重要),.then()在promise状态改变之后才会执行。
  • 调用数据库是异步的,调用成功之后再改变页面数据会导致页面原定数据先生效再被改变的情况,(如:页面二维码的显示,初始默认显示,当订单状态是已核验时,checked被更改,二维码不显示),解决办法:上一个页面传参时直接传递checked,本页面直接setData,就是同步的了。

报错记录

Open api qps request limit reached
  • 出现场景:一次性上传9张图片并识别
  • 错误原因:百度免费配额有qps限额,规定为2,超过就会报错
  • 解决办法:减少1秒内调用的次数,不要超过2次,如何降低QPS(错峰、限流、削峰)

代码记录

  • 水平居中:
display: flex;
justify-content: center;

或者

margin-left: auto;
margin-right: auto;

第一种效果更好
  • 垂直居中:
display: flex;
align-items: center;
  • 边框阴影:
box-shadow:0px 2px 5px 5px lightgrey;
  • 图片显示:
"/images/delin.png">


"../../images/delin.png">
  • 表单提交:
<form bindsubmit="Submit">
  <view name="something">view>
  <view name="anotherthing">view>
  <button form-type="submit">点击授权登录button>
form>
Submit: function(e){
  console.log('用户提交了数据:', e.detail.value)
  let {something, anotherthing} = e.detail.value
}
  • 页面跳转:
//关闭之前所有页面,跳转,没有返回键,但有Home键
wx.reLaunch({
	url: '/pages/home/home',
})

//关闭当前页面,跳转,会出现Home键
wx.redirectTo({
	url: '/pages/home/home',
})

//保留页面,跳转,会出现返回键
wx.navigateTo({
	url: '/pages/home/home',
})

//隐藏Home键
onShow: function () {
    wx.hideHomeButton()
},

云开发数据库

//数据权限要重视!否则会出现读不了的情况!


//查询
DB.collection('user').where({
    _openid: app.globalData.openId,
}).get({
    success: res => {
        console.log(res)
        if (res.data.length == 0) {
            // 没找到数据
            wx.reLaunch({
                url: '/pages/register/register',
            })
        } else {
            // 找到了数据
            app.globalData.userName = res.data[0].username
            app.globalData.building = res.data[0].building
            app.globalData.unit = res.data[0].unit
            app.globalData.door = res.data[0].door
            app.globalData.charactor = res.data[0].charactor
            wx.reLaunch({
                url: '/pages/home/home',
            })
        }
    }
})



//添加
DB.collection('user').add({
    data: {
        _id: app.globalData.openId,
        username: username,
        building: building,
        unit: unit,
        door: door,
        charactor: charactor
    }
})


//更新
DB.collection('user').doc(app.globalData.openId).update({
    data: {
        username: username,
        building: building,
        unit: unit,
        door: door,
        charactor: charactor
    }
})

拍照或上传图片

	// 拍摄或从手机相册中选择图片或视频
    wx.chooseMedia({
      // 最多可以选择的文件个数
      count: 9,
      // 文件类型
      mediaType: ['image'],
      // 图片和视频选择的来源
      sourceType: ['album', 'camera'],
      // 仅在 sourceType 为 camera 时生效,使用前置或后置摄像头
      camera: 'back',
      success(res) {
        // 上传图片暂存路径
        console.log(res.tempFiles[0].tempFilePath)
        // 图片大小
        console.log(res.tempFiles[0].size)
      }
    })


chooseMedia问题:只上传第一张

chooseImage没问题


	wx.chooseImage({
      count: n, // 默认9,设置图片张数
      sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
      sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
      success: function (res) {
        // console.log(res.tempFilePaths)
        // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
        var tempFilePaths = res.tempFilePaths
        if (imgbox.length == 0) {
          imgbox = tempFilePaths
        } else if (max >= imgbox.length) {
          imgbox = imgbox.concat(tempFilePaths);
        }
        that.setData({
          imgbox: imgbox
        });
        console.log('imgbox after add: ', imgbox.length)
      }
    })

扫码

let that = this
wx.scanCode({
      onlyFromCamera: true,
      success (res) {
        console.log(res)
        let dustbinCode = res.result
        console.log(dustbinCode)
        that.setData({
          haveDustbinCode: true,
          dustbinCode: dustbinCode
        })
        console.log('获取垃圾桶编码成功')
      }
    })

下载文件

可以根据文件 ID 下载文件,用户仅可下载其有访问权限的文件:

wx.cloud.downloadFile({
  fileID: '', // 文件 ID
  success: res => {
    // 返回临时文件路径
    console.log(res.tempFilePath)
  },
  fail: console.error
})

上传文件

在小程序端可调用 wx.cloud.uploadFile 方法进行上传:

wx.cloud.uploadFile({
  cloudPath: 'example.png', // 上传至云端的路径
  filePath: '', // 小程序临时文件路径
  success: res => {
    // 返回文件 ID
    console.log(res.fileID)
  },
  fail: console.error
})

CMS二次开发

  • 腾讯云7*24h在线支持

技术文档

  • 二次开发文档链接。(云开发文档)
  • CMS 内容管理系统介绍文档链接。(微信开放文档)
  • 微应用,自 2.10.0 版本起,CloudBase CMS 支持创建微应用,在项目视图中嵌入你的自定义 Web 页面。(云开发文档)
  • 提供了 Vue 和 React 的微应用开发模版,提供快速开发使用,你可以通过 CloudBase CLI 直接下载模版。(云开发文档)

创建微应用

入口如下,其他步骤参考微应用-快速开始。

垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第1张图片
垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第2张图片
垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第3张图片

开发微应用

安装CloudBase CLI

  • CLI工具-安装文档

下载模板

垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第4张图片

# 新建 Vue 微应用项目
tcb new vue-app cms-microapp-vue

下载之前需要用 小程序公众号登录 腾讯云,并且进行授权。(用微信登录会重新创建一个账号)

垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第5张图片

否则会提示需要现在腾讯云的控制台授权(会在浏览器弹出页面),并且授权之后不会立即更新授权状态,需要先退出登录。

  1. 关闭依然在尝试获取登录状态的命令行窗口
  2. 重新打开命令行窗口,退出登录,重新登录,再进行下载
# 退出登录
tcb logout

# 重新登录
tcb login

# 新建 Vue 微应用项目
tcb new vue-app cms-microapp-vue

垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第6张图片

如图下载的VUE模板在C盘目录下,可以找到之后剪切粘贴到其他合适的地方,也可以cd进入目标文件夹之后再执行下载命令。

模板适配

  • VUE项目适配(云开发文档)
  1. 创一个微应用,上传文件随意,这样就有了microAppID;

  2. 按照文档改好ID;

  3. 安装依赖

    # 先进入VUE项目的根目录(都在根目录操作)
    H:
    
    cd zfy\毕业论文\德邻\vue-app
    
    # 再安装依赖
    npm i
    

    垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第7张图片

  4. 部署

    npm run build
    

    垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第8张图片

  5. 打包压缩

    垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第9张图片

  6. 上传更新

    垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第10张图片

这样就是成功了,以后写项目在src里写

在微应用里引入云环境

  • 云开发——Web SDK API 参考——初始化
# 在终端里执行安装
npm install @cloudbase/js-sdk -S

微应用权限限制

  • 云开发——Web SDK API 参考——登录认证

  • 目的:分角色登录cms时限制用户是否可以使用微应用

  • 官方没有开发cms直接管理微应用权限的功能,需要自行限制权限

// VUE钩子函数
created() {

    let that = this;

    // 获取当前用户
    window.cloudbase
          .auth()
          .getLoginState()
          .then((res) => {
            console.log('current login state: ', res)
            console.log('current user: ', res.user.uid)
            that.uid = res.user.uid
            console.log('this.uid: ', that.uid)
            // 调用云函数查询用户信息
            window.cloudbase
                  .callFunction({
                    name: 'query_cms_users',
                    data: {
                      uid: that.uid
                    }
                  })
                  .then((res) => {
                    console.log('cms调用云函数query_cms_users结果:', res)
                    console.log('用户角色:', res.result.data[0].username)
                    that.username = res.result.data[0].username
                    console.log('this.username: ', that.username)
                  })
                  // .catch((ret) => (this.result = JSON.stringify(ret)))
            }
          )
  },

云函数query_cms_users/index.js 代码如下:

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init()

// 云函数入口函数
exports.main = async (event, context) => {
  const db = cloud.database()
  
  // 查询用户信息
  return  db.collection('wx-ext-cms-users')
            .where({
              uuid: event.uid
            })
            .get()
}

如果您采用cms系统的角色逻辑,并且代码固定某个角色有这个页面的权限。您可以参考一下我们cms系统的表结构获取角色信息

  • 用户表:wx-ext-cms-users里面的字段uuid就等于你获取到的uid
  • 角色和权限表:wx-ext-cms-user-roles里面的_id字段关联的用户表中role数组字段

根据这个关系,您创建一个云函数,然后传入uid,在云函数里面处理下逻辑,返回您需要的信息即可(不可以前端查库,默认私有读写,请用云函数查询这两个数据库信息后组合返回,前端调用云函数即可)

云数据库在微应用里的使用

  • 云开发——Web SDK API 参考——数据库

下面这种方式是使用不了的:

// 获取 `user` 集合的引用
import cloudbase from "@cloudbase/js-sdk";

const app = cloudbasae.init({
  env: "xxxx-yyy"
});

const db = app.database();

const collection = db.collection("user");

下面这种方式才可以使用:

// 测试数据库调用,可以,但是权限是仅创建者可读写,得用云函数才可以查询到
const db = window.cloudbase.database();
db.collection("imgForCoinPath").where({
  dustbinCode: 'MFBK-A-001'
}).get().then(
    res => {
      console.log('database: ', res)  // 所有console都要在then里面才可以打印得出来
  }
)

// 使用云函数查询
window.cloudbase.callFunction({
  name: 'query_garbageAnalys',   // 先去微信开发者工具创建并上传部署
})
.then((res) => {
  console.log('cms调用云函数query_gabageAnalys结果:', res)
})
// .catch((ret) => (this.result = JSON.stringify(ret)))

两种方式的本质区别就是:

  • window.cloudbase == app对象
  • 为什么只能用下面的方式,不知道,可能是因为微信小程序的CMS本身就默认了云环境路径,不需要再特别定义了(更新:VUE模板里的cloudbaserc.json文件里已经默认设置了云环境路径,所以无需再定义)
  • 我只试了把定义app对象的那一堆放在方法外面的方式,行不通,没有尝试放在方法内能不能行

echarts画图表

  • echarts画图官方文档
# 在终端里执行安装
npm install echarts --save

画图示例代码:

import * as echarts from 'echarts';

// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
console.log(myChart);

// 指定图表的配置项和数据
var option = {
  title: {
    text: 'ECharts 入门示例'
  },
  tooltip: {},
  legend: {
    data: ['销量']
  },
  xAxis: {
    data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
  },
  yAxis: {},
  series: [
    {
      name: '销量',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20]
    }
  ]
};
console.log(option);

// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);

elementUI时间选择器

  • 安装elementUI
# v3的
npm install element-plus --save

# v2的
npm i element-ui -S
  • npm版本过高会报错

垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第11张图片

# 安装低版本的npm
npm install npm@6.14.10 -g
  • 选择一段时间(v3版本的示例代码用的是typescript,vs老是报错,弃了,改用v2)

v3的:

<template>
  <div class="demo-date-picker">
    <div class="block">
      <span class="demonstration">Default</span>
      <el-date-picker
        v-model="value1"
        type="daterange"
        range-separator="To"
        start-placeholder="Start date"
        end-placeholder="End date"
      />
    </div>
    <div class="block">
      <span class="demonstration">With quick options</span>
      <el-date-picker
        v-model="value2"
        type="daterange"
        unlink-panels
        range-separator="To"
        start-placeholder="Start date"
        end-placeholder="End date"
        :shortcuts="shortcuts"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const value1 = ref('')
const value2 = ref('')

const shortcuts = [
  {
    text: 'Last week',
    value: () => {
      const end = new Date()
      const start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
      return [start, end]
    },
  },
  {
    text: 'Last month',
    value: () => {
      const end = new Date()
      const start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
      return [start, end]
    },
  },
  {
    text: 'Last 3 months',
    value: () => {
      const end = new Date()
      const start = new Date()
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
      return [start, end]
    },
  },
]
</script>
<style scoped>
.demo-date-picker {
  display: flex;
  width: 100%;
  padding: 0;
  flex-wrap: wrap;
}
.demo-date-picker .block {
  padding: 30px 0;
  text-align: center;
  border-right: solid 1px var(--el-border-color);
  flex: 1;
}
.demo-date-picker .block:last-child {
  border-right: none;
}
.demo-date-picker .demonstration {
  display: block;
  color: var(--el-text-color-secondary);
  font-size: 14px;
  margin-bottom: 20px;
}
</style>

v2的:

<template>
  <div class="block">
    <span class="demonstration">默认span>
    <el-date-picker
      v-model="value1"
      type="daterange"
      range-separator=""
      start-placeholder="开始日期"
      end-placeholder="结束日期">
    el-date-picker>
  div>
  <div class="block">
    <span class="demonstration">带快捷选项span>
    <el-date-picker
      v-model="value2"
      type="daterange"
      align="right"
      unlink-panels
      range-separator=""
      start-placeholder="开始日期"
      end-placeholder="结束日期"
      :picker-options="pickerOptions">
    el-date-picker>
  div>
template>

<script>
  export default {
    data() {
      return {
        pickerOptions: {
          shortcuts: [{
            text: '最近一周',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
              picker.$emit('pick', [start, end]);
            }
          }, {
            text: '最近一个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
              picker.$emit('pick', [start, end]);
            }
          }, {
            text: '最近三个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
              picker.$emit('pick', [start, end]);
            }
          }]
        },
        value1: '',
        value2: ''
      };
    }
  };
script>

不知道为什么,样式icon出不来,无解(才学浅薄,恳请走过路过的大神指教)

垃圾分类微信小程序——云开发+CMS+微应用+百度智能云图片识别_第12张图片
不管样式,直接开始实现功能算了。

  • 不会获取到选择的时间

你可能感兴趣的:(html,css,javascript,小程序)