三、微信公众平台
开发者通过公众号向用户提供咨询和服务的平台!!!
Mp.weixin.qq.com
订阅号
订阅号:为媒体和个人提供一种新的信息传播方式,主要功能是在微信侧给用户传达资讯;(功能类似报纸杂志,提供新闻信息或娱乐趣事)
适用人群:个人、媒体、企业、政府或其他组织。
群发次数:订阅号(认证用户、非认证用户)1天内可群发1条消息。
所在位置:
服务号
服务号:为企业和组织提供更强大的业务服务与用户管理能力,主要偏向服务类交互(功能类似12315,114,银行,提供绑定信息,服务交互的);
适用人群:媒体、企业、政府或其他组织。
群发次数:服务号1个月(按自然月)内可发送4条群发消息。
所在位置:
直接显示在微信好友对话列表中
企业微信(企业号)
企业的专业办公管理工具。与微信一致的沟通体验,提供丰富免费的办公应用,并与微信消息、小程序、微信支付等互通,助力企业高效办公和管理。
小程序
小程序是一种新的开放能力,开发者可以快速地开发一个小程序。小程序可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
微信小程序,小程序的一种,英文名Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。
全面开放申请后,主体类型为企业、政府、媒体、其他组织或个人的开发者,均可申请注册小程序。微信小程序、微信订阅号、微信服务号、微信企业号是并行的体系。
类似于app一样的产品,嵌入到微信客户端的
1.2缺点
工程大小有限制 (只有2M,使用分包,16m)
入口比较深 (依托微信)
不能直接分享到朋友圈 【更新 安卓beta测试版,】
2. 应用场景/发展前景
应用场景
电商、社交、娱乐、家政服务等等
发展前景
微信生态圈
接入微信小程序
3.1 注册
3.2 完善信息
3.3 开发
3.4 发布上线
第四步:
邮箱激活 ,点击激活链接
点击上传按钮,生成的版本
2.2审核版本
点击开发版本提交审核按钮之后生成的版本,此版本也是微信审核团队正在审核的版本
审核版本通过审核之后,点击发布按钮生成的版本,此版本就是线上正常运行的版本
2.4体验版本
在开发版本点击选为体验版本即可生成
3.2 项目程序
协助管理员进行小程序开发及运营维护,同样能够使用体验版小程序
3.3 体验成员
使用体验版小程序
服务器域名:
服务器、域名、备案、域名绑定和解析
域名: 必须要提前进行配置(上线之前)
域名一定要加https
app.js 小程序入口文件(逻辑文件js),相当于vue的 main.js ==> new Vue({})
app.json 全局的配置文件
app.wxss 全局样式文件 (参考css)
Project.config.json 项目的配置文件【项目针对于编辑器的配置文件】
Sitemap.json 站点地图( 当前小程序页面,允不允许在微信客户端进行索引[搜索] )
每一个页面都是由4个文件构成:
[page].wxml 页面结构 —》html
[page].js 页面逻辑 --》js
[page].wxss 页面样式 --》css
[page].json 页面配置 --》json
Utils 工具目录
Tips:
Pages:
“entryPagePath”: “pages/c/c”, 默认首页
Pages 页面的路径列表
未指定 entryPagePath 时,数组的第一项代表小程序的初始页面(首页)。
注意事项:
Pages 路径原生前面不要加任何东西
每一个元素是以逗号分隔,最后一个元素后面不能加逗号
Json文件使用双引号
Json配置文件,不能够加注释
所有的页面必须在pages属性中提前定义好
Window
用于设置小程序的状态栏、导航条、标题、窗口背景色。
"navigationBarBackgroundColor": "#ffff00",
"navigationBarTitleText": "小程序",
"navigationBarTextStyle": "black",
"navigationStyle": "default"
"enablePullDownRefresh": true, 开启全局下拉刷新
"backgroundColor": "#123456",
"backgroundTextStyle": "light"
Tabbar底部tab
最多5个,最少2个
List: 底部tab Array
"tabBar": {
"color": "#000000",
"selectedColor": "#f00",
"backgroundColor": "#654321",
"borderStyle": "white",
"position": "bottom",
"custom": false,
"list":[
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "tabs/index.png",
"selectedIconPath": "tabs/indexFull.png"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "tabs/cart.png",
"selectedIconPath": "tabs/cartFull.png"
},
{
"pagePath": "pages/my/my",
"text": "我的",
"iconPath": "tabs/my.png",
"selectedIconPath": "tabs/myFull.png"
}
]
},
页面配置只能配置window; 页面配置的优先级要高于全局配置
页面中配置项在当前页面会覆盖 app.json 的 window 中相同的配置项。
如果不加任何的设置,默认所有的页面都会被索引(搜索)
场景值用来描述用户进入小程序的路径,也就是如何进入小程序的!!!
应用场景:
KFC :
获取场景值的方式:
App({
// 生命周期函数
onLaunch(options){ //初始化
// 获取场景值
// console.log(options.scene,'onLaunch')
if(options.scene == 1011){
// 扫描二维码进入小程序的,点餐页面
console.log('我是直接点餐')
}else if(options.scene == 1001){
// 搜索进来,外卖页面
console.log('我是外卖页面')
}else{
// 直接进入首页
console.log('进入首页')
}
}
})
“page”: “pages/index/index?id=XXX”
Inclusive:包含 --当前页面传递的参数 ,包含params里面的值
Exact: 与params参数完全一致,即可命中
Exclusive 与params参数交集为空的时候,即可命中
Partial 与params参数交集不为空的时候,即可命中
{
"rules": [
{
"action": "allow",
"params": ["id","name"],
"matching": "partial",
"page": "pages/index/index"
},
{
"action": "disallow",
"page":"*"
}
]
}
Priority: 改变当前规则匹配的优先级的问题!!
谁的数大,就先匹配谁,如果没有,则从上到下依次进行匹配
{
"rules": [
{
"action": "disallow",
"params": ["id","name"],
"page": "pages/index/index"
},
{
"action": "allow",
"params": ["id","name"],
"page": "pages/index/index",
"priority":1 // 优先执行
},
{
"action": "disallow",
"page":"*"
}
]
}
小程序开发框架的逻辑层使用 JavaScript 引擎为小程序提供开发者 JavaScript 代码的运行环境以及微信小程序的特有功能。
在 JavaScript 的基础上,我们增加了一些功能,以方便小程序的开发:
• 增加 App 和 Page 方法,进行程序注册和页面注册。
• 增加 getApp 和 getCurrentPages 方法,分别用来获取 App 实例和当前页面栈。
• 提供丰富的 API,如微信用户数据,扫一扫,支付等微信特有能力。
• 提供模块化能力,每个页面有独立的作用域。
注意:小程序框架的逻辑层并非运行在浏览器中,因此 JavaScript 在 web 中一些能力都无法使用,如 window,document 等。
2. 注册小程序
App.js 文件中 App() 方法
App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。
// 在app中,使用this获取app内的值
App({
onShow(){
// console.log('进入小程序或者切前台','onShow')
// 获取userinfo
console.log(this.userInfo) //this是当前app实例
this.say()
},
// 只能加载一次
onLaunch(){
// console.log('小程序初始化','onLaunch')
},
onHide(){
console.log('切后台','onhide')
},
// 全局监听
onError(error){
console.log('错误信息',error)
},
// 当访问的页面不存在的时候,执行此函数
onPageNotFound(){
// console.log('次页面不存在')
// 如果当前页面不存在,可以跳转到404页面
wx.redirectTo({
url: '/pages/page404/page404',
})
},
userInfo:{
name:"后羿",
role:"射手"
},
say(){
console.log('我正在说话')
},
run:function(){
console.log('我正在跑步')
}
})
在页面获取全局唯一的应用实例:
const app = getApp();
// console.log(app,'全局的唯一的应用实例')
console.log(app.userInfo);
注册小程序中的一个页面。接受一个 Object 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。
// 注册页面
Page({
Data:{ }
})
Data:
data 是页面第一次渲染使用的初始数据。
字符串,数字,布尔值,对象,数组。
获取: 在page中获取data的值使用 this.data.属性
更新: // 更新 视图会发生改变
this.setData({
message:"0511python"
})
//视图不会发生改变
this.data.message = "0511python";
生命周期函数:
// 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
onLoad(options){
// 获取参数
console.log('onload',options)
},
// 页面显示
onShow(){
console.log('onshow')
},
// 页面隐藏
onHide(){
console.log("onhide")
},
onReady(){
console.log('onready')
}
页面监听函数:
// 1.页面下拉刷新
onPullDownRefresh(){
// console.log(123)
// 改变当前data中message的值为0511python
// 先获取
// console.log(this.data.message)
// 更新
// this.setData({
// message:"0511python"
// })
this.data.message = "0511python";
},
// 2.上拉加载
onReachBottom(){
console.log('上拉加载')
}
// 3.监听页面滚动的
onPageScroll(e){
console.log(e)
}
Any: 自定义
username:"admin",
// 自定义函数 -- 不要是用 methods
fn(){
return "我是自定义fn函数"
},
getCurrentPages()
获取当前页面栈(页面层级类似于 A(首页)-B-C-D(当前页))
4. 生命周期
进入小程序:
onLaunch - onShow - onLoad - onShow - onReady
离开小程序:
页面 onhide – 小程序的onhide
五、视图层
框架的视图层由 WXML 与 WXSS 编写,由组件来进行展示。
将逻辑层的数据反映成视图,同时将视图层的事件发送给逻辑层。
WXML(WeiXin Markup language) 用于描述页面的结构。
WXS(WeiXin Script) 是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。
WXSS(WeiXin Style Sheet) 用于描述页面的样式。
组件(Component)是视图的基本组成单元。
Vue – :属性名称=“值”
关键字
True/false
true
使用时,双引号 + 花括号 + 关键字即可
属性绑定
属性绑定
true
{{ age >= 18 ? '成年' : '未成年' }}
1+2
{{ 1+2+age }}
{{ 1+2+age+'10' }}
{{ arr[1] }}
{{ obj.name }}
使用 wx:for-item 可以指定数组当前元素的变量名,
使用 wx:for-index 可以指定数组当前下标的变量名:
============
wx:key 与vue里面的key的作用完全相同
如不提供 wx:key,会报一个 warning, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略
使用key:
*this
唯一的属性
1.字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
2. 保留关键字 *this 代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字。
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
{{ item }}
及格
及格
不及格
不及格
及格
优秀
成绩不合格
======
一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。
wx:if
hidden
{{ item }}
4.2 使用
Template is
4.3传参
Data
概念
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
样式导入
/app.wxss/
@import “/wxss/header.wxss”;
新单位 rpx
正常宽度的问题:
规定: 6 7 8 小米 三星 、 平板 ,屏幕宽度都是750rpx
实际的元素的大小/设备的大小 750rpx = 设计稿元素的大小 20rpx/设计搞的宽度 750prpx
view{
width: 750rpx;
height: 20px;
background-color: red;
}
4.选择器
目前支持的选择器有:
选择器 样例 样例描述
.class .intro 选择所有拥有 class="intro" 的组件
#id #firstname 选择拥有 id="firstname" 的组件
element view 选择所有 view 组件
element, element view, checkbox 选择所有文档的 view 组件和所有的 checkbox 组件
::after view::after 在 view 组件后边插入内容
::before view::before 在 view 组件前边插入内容
5.静态样式/动态样式
静态样式 使用的wxss样式文件操作
动态样式 使用的style进行操作
笔记03
一、今日内容
// 简写方式
_bindtap(){
// console.log("_bindtap",this)
},
// key +val
_bindtapKey:function(){
// console.log("key+val",this)
// let _this = this;
// setInterval(function(){
// _this.data.num++; //自己本身加1
// // 获取自己本身付给自己,渲染到视图层
// _this.setData({
// num:_this.data.num
// })
// },1000)
setInterval(()=>{
this.data.num++; //自己本身加1
// 获取自己本身付给自己,渲染到视图层
this.setData({
num:this.data.num
})
},1000)
},
// 箭头函数
_bindtapJt:()=>{
// this == undefined
// console.log("箭头函数",this)
},
_catchtap(){
console.log("_catchtap")
}
Data-*
data-item-id 复合类型传参 itemId
接参:
fun1(e){
// console.log(e)
console.log(e.target.id);
console.log(e.currentTarget.id);
},
Target 属性内的值 ,存的是当前事件事件源(点击谁触发了当前的函数)上面的值;
currentTarget 只获取当前事件绑定到的组件上的值
三、作业(开灯关灯)
四、组件
• 组件是视图层的基本组成单元。
• 组件自带一些功能与微信风格一致的样式。
• 一个组件通常包括 开始标签 和 结束标签,属性 用来修饰这个组件,内容 在两个标签之内。
Content goes here …
注意:所有组件与属性都是小写,以连字符-连接
公共属性 :所有的组件都有的属性
所有组件都有以下属性:
属性名 类型 描述 注解
id String 组件的唯一标示 保持整个页面唯一
class String 组件的样式类 在对应的 WXSS 中定义的样式类
style String 组件的内联样式 可以动态设置的内联样式
hidden Boolean 组件是否显示 所有组件默认显示
data-* Any 自定义属性 组件上触发的事件时,会发送给事件处理函数
bind* / catch* EventHandler 组件的事件 详见事件
2.组件使用
2.1基础组件视
2.1.1 text
hello wolrd
hello wolrd
hello>wolrd
2.1.2 icon
2.1.3 rich-text 富文本 v-html (了解)
2.2 视图组件
2.2.1 view 视图
box
2.2.2 swiper滑块视图容器
封装面板指示点:
Js:
data: {
banners:[
"/logo/1.jpg",
"/logo/2.jpg",
"/logo/3.jpg",
],
current:0,
},
_change(e){
// console.log(e)
let current = e.detail.current;
this.setData({
current,
})
},
Detail : 组件私有的事件,里面默认的值都在detail里面获取
2.2.3 scroll-view 滚动视图
案例:
Wxml:
手机
电脑
冰箱
洗衣机
电视
小米手机
苹果手机
华为手机
小米电脑
苹果电脑
华为电脑
小米冰箱
苹果冰箱
华为冰箱
小米洗衣机
苹果洗衣机
华为洗衣机
小米电视
苹果电视
华为电视
js:
Page({
data: {
cateid:"cateid0"
},
change(e){
this.setData({
cateid : e.target.dataset.cateid
})
}
})
3.媒体组件
3.1 image
src
Bindload //当图片载入完毕时触发
Binderror //当错误发生时触发
show-menu-by-longpress //开启长按图片显示识别小程序码菜单
lazy-load 懒加载 ****
4.表单组件
请填写一下信息
Js:
// pages/form/form.js
Page({
/**
* 页面的初始数据
*/
data: {
},
_input(e){
console.log(e)
},
_radioChange(e){
console.log(e)
},
_checkboxChange(e){
console.log(e)
},
_switchChange(e){
console.log(e)
},
formReset(){
console.log('重置')
},
formSubmit(e){
console.log(e)
}
})
笔记04
一、今日安排
二、导航组件
Navigator /api
Target:
Api:
_jumpCart(){
// 使用api进行跳转
wx.navigateTo({
url: '/pages/cart/cart',
})
},
_jumpMy(){
wx.switchTab({
url: '/pages/my/my',
success:(res)=>{
console.log(res)
}
})
}
1.2传参和接参
wx.switchTab 是不能够传递参数的!!!!
传参:
url="/pages/cart/cart?id=100&num=99"
wx.navigateTo({
// url: `/pages/cart/cart?username=${username}&password=${password}`,
url: "/pages/cart/cart?username="+username+"&password="+password,
})
接参:
在对应的页面的生命周期函数接: onLoad
onLoad: function (options) {
console.log(12345678)
console.log(options)
},
wx8fc369471215e8ae
跳转到其他小程序
三、模块化
// 定义配置变量
const BaseUrl = "http://localhost:3000";
// 对外暴露
// module.exports = BaseUrl;
module.exports = {
BaseUrl,
};
// 引入
const Config = require('../../utils/config');
console.log(Config.BaseUrl)
3.es6规范
暴露:
// export {
// BaseUrl,
// username,
// password
// }
export default BaseUrl;
导入:
// import {BaseUrl,username,password} from "../../utils/config"
// console.log(BaseUrl,username,password)
import Config from "../../utils/config"
console.log(Config)
四、组件化
文件:
Json文件:
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})
properties: {
size:{
type:String, //当前size的数据类型
value:"default"// 当前size的默认值
},
name:{
type:String,
value:"admin"
},
color:{
type:String,
value:"red"
}
},
// data 就是当前组件自己私有的属性,不能再调用处进行传值改变
data: {
// size:"default"
age:"30"
},
// 自定时事件函数都在methods中进行操作。之前在page中的所有的操作,在当前js中都可以正常使用!!!
methods: {
click(){
console.log(this.properties.size)
// this.setData({
// age:40
// })
}
}
五、Api
同步api
Sync ,有返回值的,和try catch 合用
异步api
Callback 回调函数
Promise 对象 (不写回调函数,返回一个promise对象)
.Then()
//1. 获取设备信息的
// let system = wx.getSystemInfoSync();
// console.log(system)
wx.getSystemInfo({
// success: (result) => {
// console.log(result)
// },
}).then(res=>{
console.log(res)
})
2.wx.showToast(Object object)
显示消息提示框
// 请求有返回结果之后
wx.showToast({
title: '您未登录,请先去登录,有更好的体验!!',
// icon:"success"
icon:"none",
image:"/logo/bulb.png",
duration:3000
})
// wx.hideToast({})
showloading
// 数据请求时,使用提示
wx.showLoading({
title: '数据加载中',
})
setTimeout(()=>{
wx.hideLoading()
},3000)
showActionSheet
wx.showActionSheet({
itemList: ["首页","确定","联系客服"],
itemColor:"#ff0",
success(res){
// console.log(res)
if(res.tapIndex == 1){
//执行确定
}
}
})
动态设置标题
wx.setNavigationBarTitle({
title:options.name
})
隐藏 返回首页按钮
wx.hideHomeButton({
success: (res) => {},
})
// 执行缓存存储
// let username = "root";
// 同步存储
// 函数 : 没有返回值的函数
// wx.setStorageSync('username', username);
let carts = [
{id:10,name:"abc"},{id:11,name:"bcd"}
]
wx.setStorage({
data: carts,
key: 'carts',
success(res){
console.log(res)
}
})
3.2获取缓存
_getStorage(){
// 同步获取
// const storage = wx.getStorageSync('cart') || [];
// var newCarts = storage.map((item)=>{
// return item.name;
// })
// console.log(newCarts)
wx.getStorage({
key:"carts",
success(res){
console.log(res)
}
})
}
3.3.删除缓存
_removeStorage(){
// 同步删除 (没有返回值)
// const res = wx.removeStorageSync('username');
// console.log(res)
// 异步删除
// wx.removeStorage({
// key:"username",
// success(res){
// console.log(res)
// }
// })
}
3.4清空缓存
_clearStorage(){
// wx.clearStorageSync();
wx.clearStorage({
success(res){
console.log(res)
}
})
}
Wx.request({
Url:””,网络请求的地址
Method:“get/post”,
Headers:{},
Data:{},
Success(){
}
})
报错信息:
// get
getRequest(){
let _this = this;
wx.request({
url: 'http://localhost:3000/products',
method:"get", // get 也是默认值
success(res){
// console.log(res)
_this.setData({
products:res.data.result
})
}
})
}
Post请求
// post
postRequest(){
let _this = this;
wx.request({
url: 'http://localhost:3000/login',
method:"post", // get 也是默认值
header:{
"Content-Type":"application/json"
},
data:{
username:"admin",
password:123456
},
success(res){
console.log(res)
}
})
}
Api
Request.js
import {_login,_getProducts} from “…/…/utils/api”
生成 package.json文件
第四步:
工具-- 构建npm
导航:
返回 | 首页
订单查询
Tabbar:
data: {
list:[
{
text:"订单查询",
iconPath:"/icon/ddFull.png",
selectedIconPath:"/icon/dd.png"
},
{
text:"收获地址",
iconPath:"/icon/ddFull.png",
selectedIconPath:"/icon/dd.png"
},
{
text:"会员等级",
iconPath:"/icon/ddFull.png",
selectedIconPath:"/icon/dd.png"
},
{
text:"客服帮助",
iconPath:"/icon/ddFull.png",
selectedIconPath:"/icon/dd.png",
badge:"2"
}
]
},
changeIndex(e){
// swicth case
// console.log(e)
if(e.detail.index == 0){
wx.switchTab({
url: '/pages/index/index',
})
}else if(e.detail.index == 1){
}else if(e.detail.index == 2){
}else{
wx.redirectTo({
url: '/pages/cart/cart',
})
}
},
笔记05
一、今日内容
主包:
每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面
目前小程序分包大小有以下限制:
• 整个小程序所有分包大小不超过 16M
• 单个分包/主包大小不能超过 2M
不使用分包结构,所有的页面程序都会直接编译和下载;(缺点)
"subpackages":[
{
"root": "module/packageA", // 分包根路径
"pages": [ // 分包的页面
"cate/cate",
"dog/dog"
]
},
{
"root": "module/packageB",
"pages": [
"tree/tree"
]
}
]
当使用分包结构时,第一次进行小程序的时候,进入主包只会加载主包的内容,不会加载分包的内容。只要进入当前分包结构,就是加载分包内容。
直接进入分包结构,那么主包也会进行加载!!!
在分包中,同样可以使用主要下面的js文件的内容!!!
2. 独立分包
独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。
获取不到app.js中的内容!!!
console.log( getApp({allowDefault: true}) ) 可以获取到 app实例
三、性能优化(了解)
2.1 使用api
wx.getUserInfo({
success(result){
that.setData({
userInfo:result.userInfo
})
}
})
2.2 button
Wxml:
Js:
_login(e){
// console.log(e)
if( e.detail.errMsg == "getUserInfo:fail auth deny" ){
wx.showToast({
title: '请登录!!!',
icon:"none"
})
return;
}
let userInfo = e.detail.userInfo;
wx.setStorageSync('userInfo', userInfo)
this.setData({
userInfo,
})
wx.showToast({
title: '登录成功',
})
}
第一次,进入小程序,获取用户信息,必须使用button’进行操作,授权
3.小程序登录
Wx.login()
let that = this;
// 检测是否登录的
wx.checkSession({
success () {
//session_key 未过期,并且在本生命周期一直有效
// console.log('已经登录的效果')
},
fail () {
// wx.login() 服务端 (php)
// console.log('未登录的')
wx.login({
success(res){
console.log(res.code);
let code = res.code;
let s = "5bc1d0659334e0acf22c0ae04513e92e";
let appid = "wx1b41fefe4f9411d6";
// 错误的案例
wx.request({
url: `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${s}&js_code=${code}&grant_type=authorization_code`, // url 服务端接口
success(result){
console.log(result)
}
})
}
})
}
})
四、云开发
1.什么是云开发?
开发者可以使用云开发开发微信小程序、小游戏,无需搭建服务器,即可使用云端能力。
云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。
云开发: 微信封装的api + (存储空间/数据库/函数)
3.怎么去开通?
3.1 创建云开发项目:
新建文件夹,导入微信开发者工具,选择云开发,生成文件;
必须要使用真实的appid,不能使用测试账号!!!选择云开发选项;
miniprogramRoot : 小程序的根目录
cloudfunctionRoot: 云端代码的根路径
4.具体操作
注意:
1) 小程序云能力初始化 ,告诉小程序使用哪一个环境
4.1 数据库
_openid: 当前用户的唯一标识-------- oMSJe5QCOMv-19fn6YASICmC3EtQ
增删改查!!
4.1.1 添加
add(){
// 添加username password pic (_id)自动生成的
let data = {
username:"德玛西亚",
password:123456,
pic:null
}
// 数据添加
db.collection('user').add({
data,
success:(res)=>{
// console.log(res)
if(res._id){
wx.showToast({
title: '添加成功',
})
}
}
})
},
4.1.2 删除
(1)id
remove(){
// 根据id进行删除 id作为条件使用doc()
db.collection('user').doc('b5416b755f562f3e0150ddf02435cc2d').remove({
success: function(res) {
// console.log(res.data)
wx.showToast({
title: '删除成功',
})
}
})
注意:
删除多条数据,不允许在小程序端删除,要在云端进行删除???
4.1.3 获取
1) 获取一条数据
// 获取内容
get(){
// 1. 根据id获取一条数据
// 65825b355f55e28800f09dc23f4cfbf3
db.collection('users').doc('65825b355f55e28800f09dc23f4cfbf3').get({
success(res){
console.log(res)
}
})
}
2) 获取多条
// 2.根据条件获取多条数据
let where = { password:123456 }
db.collection('users').where(where).get({
success(res){
console.log(res)
}
})
注意事项:
在小程序端,一次性获取最多20条数据,云端最多一次100条
可以使用 promise all 进行循环获取
4.1.4修改
update(){
// 1.根据id修改
// 65825b355f55e28800f09dc23f4cfbf3
db.collection('users').doc('65825b355f55e28800f09dc23f4cfbf3').update({
data:{
username:"张三疯"
},
success(res){
console.log(res)
}
})
// 2.根据条件修改 (在小程序端也是不能运行的,和删除多条是一样的)
}
4.2云函数(云端)
创建: cloudfunctions目录,右键 ,选择创建node.Js函数,起一个名字即可
编写:
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {
// event 接受参数
return await db.collection(event.tables).where(event.where).remove()
}
找到编写之后的云函数,右键上传部署到云端即可!!
wx.cloud.callFunction({ // 要调用的云函数名称 name: login, // 传递给云函数的event参数 data: { x: 1, y: 2, }}).then(res => { // output: res.result === 3}).catch(err => { // handle error})
删除/修改多条 !!!
4.3存储
4.3.1s上传
(1)选择图片
uplaodImage(){
// api
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
// 临时的图片地址
const tempFilePaths = res.tempFilePaths
// console.log(tempFilePaths[0])
// 执行上传到云端存储空间
// 1.获取拓展名 根据.分割成数组
let extName = tempFilePaths[0].split(".").pop();
let fileName = new Date().getTime()+"."+extName;
// console.log(extName)
wx.cloud.uploadFile({
cloudPath: fileName, // 上传至云端的路径
filePath: tempFilePaths[0], // 小程序临时文件路径
success: res => {
// 返回文件 ID
// console.log(res.fileID,'上传成功')
// 添加数据库
let data = {
username: "admin",
password: 123456,
pic: res.fileID
}
// 执行添加
db.collection('users').add({
// data:data, // 要插入的数据
data,
success: (res) => {
// console.log(res)
if (res._id) {
wx.showToast({
title: '插入成功',
})
}
}
})
},
fail: console.error
})
}
})
}
4.3.2下载
// 下载图片
download(e){
let fileID = e.currentTarget.dataset.fileid;
// 执行下载即可
wx.cloud.downloadFile({
fileID, // 文件 ID
success: res => {
// 返回临时文件路径
// console.log(res.tempFilePath)
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath
})
},
fail: console.error
})
}
五、项目
1.导入项目
笔记06–项目01
1.初始化项目
1.1. 更改 appid
1.2切换环境id
2.登录操作(个人中心)
2.1.进入当前个人中心页面,判断是否登录
onShow(){
// 1.检测是否登录
this._checkSession();
},
// 2.检测是否登录的函数
_checkSession(){
let _this = this; //保存页面this实例
wx.checkSession({
success () {
// 已经登录
_this.setData({
isLogin:true,// 设置登录状态
})
},
fail () {
// 还未登录
wx.showToast({
title: '登录才能有想不到体验',
icon:"none"
})
_this.setData({
isLogin:false, //设置未登录状态
})
}
})
}
2.2执行登录
逻辑!!!
用户登录:
创建表 c-users
字段:
_id 唯一标识 自动生成
_openid 唯一用户标识 自动生成
userInfo 存储用户信息(对象)
在utils下面创建配置文件config.js
登录代码总结:
// pages/personal/personal.js
// 导入配置文件和api
import Api from "../../utils/api"
import Config from "../../utils/config"
Page({
data: {
isLogin:false, //判断当前用户是否登录
userInfo:{}, //存储用户信息
},
onShow(){
// 1.检测是否登录
this._checkSession();
},
// 2.检测是否登录的函数
_checkSession(){
let _this = this; //保存页面this实例
wx.checkSession({
success () {
// 已经登录
_this.setData({
isLogin:true,// 设置登录状态
})
},
fail () {
// 还未登录
wx.showToast({
title: '登录才能有想不到体验',
icon:"none"
})
_this.setData({
isLogin:false, //设置未登录状态
})
}
})
},
// 3.执行登录
_login(e){
// console.log(e)
let _this = this;
// 用户未同意授权登录
if(e.detail.errMsg == "getUserInfo:fail auth deny"){
wx.showToast({
title: '请先登录!!',
icon:"none"
})
return;
}
// 用户同意授权
// 1。获取到当前的用户信息 , 2. _openid
wx.login({
success(){
// 不要code来获取openid,直接使用云开发函数即可
// code --- 用户登录凭证
wx.cloud.callFunction({
name:"login", // 云函数的名字,在控制台云函数列表中查询
async success(res){
// console.log(res)
let _openid = res.result.openid; //获取自己的openid
let userInfo = e.detail.userInfo;// 获取自己的用户信息
// 4.查询当前用户是否在用户表中,如果在,直接什么都不做
let allUsers = await Api.findAll( Config.tables.userTable, { _openid } )
if(allUsers.data.length <=0){
// 没有
// 3.先去添加用户信息
const addres = await Api.add( Config.tables.userTable , {userInfo} )
}
// 把openid , userinfo 插入到缓存中
wx.setStorageSync('_openid', _openid);
wx.setStorageSync('userInfo', userInfo);
wx.showToast({
title: '登录成功',
})
// 渲染页面
_this.setData({
isLogin:true,
userInfo,
})
}
})
},
fail(){
wx.showToast({
title: '由于网络原因,登录失败!!!',
icon:"none"
})
}
})
}
})
// 1.初始化数据库
const db = wx.cloud.database()
// 1.添加api
const add = (collectionName, data = {}) => {
// 返回一个promise对象
return db.collection(collectionName).add({
data
});
}
// 2.查询api
const findById = () => {
}
// 2.根据条件进行查询(分页)
const find = () => {
}
// 3.根据条件查询
const findAll = async ( collectionName, where={} ) => {
const MAX_LIMIT = 20;
const countResult = await db.collection(collectionName).count()
const total = countResult.total //获取数据库的总数据的个数
// 计算需分几次取
const batchTimes = Math.ceil(total / 20) // 2
// 承载所有读操作的 promise 的数组
const tasks = []; //用来存储所以得返回的promise对象
// skip = (page - 1) * limit
for (let i = 0; i < batchTimes; i++) {
const promise = db.collection(collectionName).skip(i * MAX_LIMIT).limit(MAX_LIMIT).get()
// 把所有promise对象都放入数组中
tasks.push(promise)
}
// 当没有数据的时候。直接返回一个和有数据相同数据结构的对象,只不过返回的data是一个空的数组
if((await Promise.all(tasks)).length <=0){
// 没有数据的
return {data:[]};
}
// 等待所有
return (await Promise.all(tasks)).reduce((acc, cur) => {
return {
data: acc.data.concat(cur.data),
errMsg: acc.errMsg,
}
})
}
export default {
add,
findAll
}
2.4判断是否为管理员登录
3.管理员菜谱分类管理页面
3.1创建数据表
c-recipeType 菜谱分类表
字段:
_id 唯一标识 自动生成
_openid 唯一用户标识 自动生成
typeName 菜谱名称
3.4类别删除
// 3.执行删除操作
async _removeRecipeType(e){
let id = e.currentTarget.dataset.id; //获取条件id
// splice(index,1)
let res = await Api.removeById( Config.tables.recipeTypeTable, id );
// console.log(res,'删除操作')
if(res.stats.removed == 1){
wx.showToast({
title: '删除成功!',
})
this._getRecipeTypes()
}
}
4菜谱发布
c-recipes 菜谱表
字段:
_id 唯一标识 自动生成
_openid 唯一用户标识 自动生成
recipeName 菜谱名称
recipeTypeId 分类ID
fields:[] 图片地址
recipeMakes:string 菜谱做法
follows: 收藏个数
views: 浏览次数
(不存)nickName : 发布菜谱的用户 【可以存储,还有头像问题,也可以不存储,因为有openid,到时候可以利用openid,使用promise.all去用户表查询】
status: 是否删除 (1正常 2删除)
time: 添加时间
难点: 多图上传
4.1获取分类信息
4.2处理图片问题
//2.选择图片
_bindselectImage(e){
// console.log(e)
let tempFilePaths = e.detail.tempFilePaths; //获取图片临时路径
// [{url:"XXX.jog"},{url:"xxx.jpg"}]
let files = tempFilePaths.map((item)=>{
return {url:item};
})
// console.log(files)
files = this.data.files.concat(files) //拼接
this.setData({
files,
})
},
// 3.删除图片
_deleteImage(e){
// console.log(e)
let index = e.detail.index; //获取要删除的索引
let files = this.data.files;
files.splice(index,1); //删除当前图片
this.setData({
files,
})
},
4.3发布菜谱
// 发布菜谱
async _doRecipes(e){
// console.log(e)
// 获取菜谱信息
let {recipeName,recipeTypeId,recipeMakes} = e.detail.value;
if(recipeName =="" ||recipeTypeId=="" || recipeMakes=="" ||this.data.files.length <= 0 ){
wx.showToast({
title: '请补全信息!!',
icon:"none"
})
return;
}
// fields 图片的上传路径
const fields = await this._uploaderFile(this.data.files);
let data = {
follows:0,views:0,status:1,time:new Date().getTime(),recipeName,recipeMakes,recipeTypeId,fields
}
// 执行添加
let result = await Api.add( Config.tables.recipeTable,data );
// console.log(result,'插入成功')
if(result._id){
wx.showToast({
title: '菜谱发布成功',
})
setTimeout(()=>{
wx.navigateBack({
delta: 1,
})
},1500)
}
},
//多图文件上传
async _uploaderFile(files){
// [{url:"xxx"},{url:"xxx"}]
let allFilesPromise = []; // 全部的promise对象
files.forEach((item,index)=>{
let extName = item.url.split('.').pop(); //获取拓展名
let fileName = new Date().getTime()+'_'+index+'.'+extName; //文字
let promise = wx.cloud.uploadFile({
cloudPath: "c-recipes/"+fileName, // 上传至云端的路径
filePath: item.url, // 小程序临时文件路径
})
allFilesPromise.push( promise )
})
return await Promise.all( allFilesPromise )
}
3.获取自己发布的菜谱信息
条件: _openid , status = 1 orderBy (time,desc)
获取: 不使用分页, 加排序 (findAll( 表,条件, ))
// 获取自己发布的菜谱信息
async _getSelfRecipes(){
let where = {
_openid : wx.getStorageSync('_openid'),
status:1
}
// orderBy={field:"_id",sort:"desc"}
let orderBy = { field:"time",sort:"desc" };
let result = await Api.findAll( Config.tables.recipeTable,where,orderBy );
this.setData({
selfRecipeLists:result.data
})
}
4.菜谱删除
修改 status =0, 根据id修改
// 删除菜谱
_doRemoveRecipe(e) {
let id = e.currentTarget.dataset.id;
let index = e.currentTarget.dataset.index;
let selfRecipeLists =this.data.selfRecipeLists;
let _this = this;
wx.showModal({
title: "危险提示",
content: "您确定要删除么?",
async success(res) {
// console.log(res)
if (res.confirm) {
//执行删除
let result = await Api.updateById(Config.tables.recipeTable, id, {
status: 0
});
if (result.stats.updated == 1) {
// 1.不请求数据库,直接在视图上操作
selfRecipeLists.splice(index,1)
_this.setData({
selfRecipeLists
})
// _this._getSelfRecipes();
}
}
}
})
}
5.首页数据加载
5.1 热门菜谱
Status = 1 , views 排序 desc排序 4个
通过菜谱的openid作为条件,获取用户信息:
(1)获取到opneid
(2)根据openid去users表查询
(3)重新将用户信息添加到菜谱列中
// 获取热门菜谱
async _getHotRecipes(){
let result = await Api.find( Config.tables.recipeTable,{status:1},4,1,{field:"views",sort:"desc"} );
let usersAllPromise = []; //用来存放所以得promise用户对象的
result.data.forEach((item,index)=>{
// console.log(item._openid)
// item._openid
let usersPromise = Api.find(Config.tables.userTable, {
_openid: item._openid
});
usersAllPromise.push(usersPromise)
})
let users = await Promise.all( usersAllPromise )
// 利用map函数,给result.data数据添加新的内容
result.data.map((item,index)=>{
item.userInfo = users[index].data[0].userInfo
})
this.setData({
hotRecipeLists:result.data
})
}
5.2获取分类导航
// 获取首页分类
async _getRecipeType(){
let result = await Api.find( Config.tables.recipeTypeTable,{},2 )
// console.log(result,4567890)
this.setData({
recipeTypesList:result.data
})
}
6.加载菜谱分类列表
第一步:从首页进行跳转
// 进入菜谱分类列表页面
_goRecipeTypePage(){
wx.navigateTo({
url: '../typelist/typelist',
})
}
第二部:在当前页面
// 获取所以得类别
async _getRecipeTypes(){
let result = await Api.findAll( Config.tables.recipeTypeTable)
// console.log(result,4567890)
this.setData({
recipeTypeLists:result.data
})
}
7.菜谱列表
分页 5个
7.1 普通分类进入菜谱列表
条件: 类别id, status = 1 , 时间time 排序, desc
顶部window显示内容: title tag=”ptfl”
7.2 热门菜谱进入菜谱列表
条件: status = 1, views 进行desc排序即可
tag=”rmcp”
7.3推荐菜谱进入菜谱列表
条件: status = 1, follows进行desc排序即可
Tag=”tjcp”
7.4搜索进入菜谱列表
条件:where (搜索的关键字) status = 1, 根据time desc,
Tag:search
Js:
where={
status:1,
// 正则匹配
recipeName:Api.db.RegExp({
regexp: title,
options: 'i',
})
};
orderBy={field:"time", sort:"desc"}
// 当第一次进入当前页面,获取数据为空时,显示没有发布过信息
if(result.data.length <= 0 && page ==1){
// 没有数据
this.setData({
tips:true,
})
return;
}
// 加载数据时,没有更多数据了
if(result.data.length < limit){
// 没有更多数据了
this.setData({
tip:true,
})
}
总结代码:
// pages/recipelist/recipelist.js
import Api from "../../utils/api"
import Config from "../../utils/config"
Page({
/**
* 页面的初始数据
*/
data: {
page:1, // 默认显示页
recipeLists:[], //存放所有的菜谱
tips:false, //判断当前菜谱下是否有菜谱
tip:false, //没有更多数据
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
let {id,title,tag} = options;
wx.setNavigationBarTitle({
title,
})
this.data.id = id; //
this.data.tag = tag;
this.data.title = title;
this._getRecipes(tag);
},
// 根据条件,获取菜谱信息
async _getRecipes(tag){
// 核心代码
// console.log(tag)
let where = {}, orderBy = {},limit=5,page=this.data.page,id=this.data.id;
let title =this.data.title;
switch(tag){
case "ptfl": // 普通分类
// 设置搜索条件
where = {recipeTypeId:id,status:1};
orderBy={field:"time",sort:"desc"}
break;
case "rmcp": // 热门菜谱
// 设置搜索条件
where = {status:1};
orderBy={field:"views",sort:"desc"}
break;
case "tjcp": // 推荐
// 设置搜索条件
where = {status:1};
orderBy={field:"follows",sort:"desc"}
break;
case "search": // 搜索
// 设置搜索条件
where = {
status:1,
recipeName:Api.db.RegExp({
regexp: title,
options: 'i',
}),
}
orderBy={field:"time",sort:"desc"}
break;
}
// 链接数据库,进行查询
let result = await Api.find( Config.tables.recipeTable,where,limit,page,orderBy )
// 当第一次进入当前页面,获取数据为空时,显示没有发布过信息
if(result.data.length <= 0 && page ==1){
// 没有数据
this.setData({
tips:true,
})
return;
}
// 加载数据时,没有更多数据了
if(result.data.length < limit){
// 没有更多数据了
this.setData({
tip:true,
})
}
let usersAllPromise = []; //用来存放所以得promise用户对象的
result.data.forEach((item,index)=>{
// console.log(item._openid)
// item._openid
let usersPromise = Api.find(Config.tables.userTable, {
_openid: item._openid
});
usersAllPromise.push(usersPromise)
})
let users = await Promise.all( usersAllPromise )
// 利用map函数,给result.data数据添加新的内容
result.data.map((item,index)=>{
item.userInfo = users[index].data[0].userInfo
})
// 把新获取到的数据和原来的数据拼接在一个数组中
result.data = this.data.recipeLists.concat(result.data);
this.setData({
recipeLists:result.data
})
},
onReachBottom(){
// console.log('加载数据')
// 下一页
this.data.page++;
// 继续调用获取菜谱方法
this._getRecipes(this.data.tag)
}
})
笔记08
2.详情页面
2.1获取内容
通过菜谱的id,
// 获取菜谱详情的方法
async _getRecipeDetail(){
let _id = this.data.id; //获取条件id
let result = await Api.findById( Config.tables.recipeTable, _id );
// 根据当前菜谱的openid,去用户表中查询对应发布人的信息
let users = await Api.find( Config.tables.userTable,{_openid:result.data._openid} );
// console.log(users)
result.data.userInfo = users.data[0].userInfo;
this.setData({
recipe:result.data
})
}
2.2 views的变化
步进为1,只要进入菜谱详情页面,就加+1
// 修改views热度值,每次+1
let updateViews = await Api.updateById( Config.tables.recipeTable, _id, { views: _.inc(1) } )
// 操作视图
result.data.views++;
2.3follows关注
(1)先去followTable查询已经关注
a) 条件
菜谱的id,自己的openid
// 判断一下,当前菜谱,当前用户是否关注了
// 查询followTable
let where = {
_openid:wx.getStorageSync('_openid'), //自己的openid
recipeID:_id
}
// 查询
let followResult = await Api.find( Config.tables.followTable,where )
console.log(followResult)
this.setData({
recipe:result.data,
// 如果 关注表中查询的结果数组的长度大于0,证明已经关注该菜谱
// 反之,没有关注
isFollows:followResult.data.length > 0 ? true : false
})
(2)关注/未关注
A) 先判断是否登录/登录之后才可以进行关注与取消关注的操作
// 使用缓存中的openid进行判断是否登录
let _openid = wx.getStorageSync('_openid') || null;
if(_openid == null){
wx.showToast({
title: '您还未登录,关注请先去登录!',
icon:"none",
})
setTimeout(()=>{
wx.switchTab({
url: '../personal/personal',
})
},1500)
// 未登录
return;
}
B)进行关注
recipeID
// 进行关注
// recipeID
// 插入follow表
let addres = await Api.add( Config.tables.followTable,{recipeID: this.data.id} )
// 菜谱表更新 follows字段
let updateViews = await Api.updateById( Config.tables.recipeTable, this.data.id, { follows: _.inc(1) } )
wx.showToast({
title: '关注成功!',
})
this.setData({
isFollows:true
})
C)取消关注
//取消关注
// 删除follows表中的对应的数据
// 删除条件 (删除多条数据,不能再小程序端进行,必须在运行)
let where = {
_openid: wx.getStorageSync('_openid'), //自己的openid
recipeID: this.data.id
}
wx.cloud.callFunction({
name: "remove",
data: {
table: Config.tables.followTable,
where,
},
success: async (res) => {
// console.log(res)
if (res.result.stats.removed == 1) {
// 更新 recipr菜谱表中的字段 -1
let updateViews = await Api.updateById(Config.tables.recipeTable, this.data.id, {
follows: _.inc(-1)
})
this.setData({
isFollows:false
})
}
}
})
// let removeRes = await Api.removeByWhere(Config.tables.followTable,where)
// console.log(removeRes)
注意事项:
如果根据where条件进行删除,条件只能查处一条数据,那么也可以进行删除!!!
3.搜索页面
3.1搜索框
根据 关键词进行模糊搜索,进入菜谱列表页面
(2)跳转直接参考近期搜索操作即可
3.2热门搜索
根据菜谱的view字段,排序,前九个显示,进入详情页面
(1)获取热门搜索
async _getHotSearch(){
let result = await Api.find( Config.tables.recipeTable, {status:1},4,1,{field:"views",sort:"desc"});
// console.log(result);
this.setData({
hotSearch:result.data
})
},
(2)跳转页面,存入近期搜索缓存
// 跳转到详情页面
_goToRecipeDetailPage(e){
let { id ,recipeName} = e.currentTarget.dataset;
// 讲搜索内容 recipename 存入缓存
// ["123",“345,”5678“]
let search = wx.getStorageSync('search') || [];
let findIndex = search.findIndex((item)=>{
return item == recipeName;
})
if(findIndex == -1){
//不存在
search.unshift(recipeName) //插入到数组的最前面
}else{
// 存在
search.splice(findIndex,1);
search.unshift(recipeName) //插入到数组的最前面
}
wx.setStorageSync('search', search);
wx.navigateTo({
url: '../recipeDetail/recipeDetail?id='+id+"&recipeName="+recipeName,
})
}
3.3近期搜索
根据 关键词进行模糊搜索,进入菜谱列表页面
(1)获取近期搜索数据
// 获取近期搜索记录
_getJinSearch(){
let jinSearch = wx.getStorageSync('search') || [];
this.setData({
jinSearch,
})
},
(2)添加点击事件
(3)跳转页面,存储
// 跳转到菜谱列表页面
_recipePage(e){
let {id,title,tag} = e.currentTarget.dataset;
this.setData({
inputVal:""
})
// 讲搜索内容 recipename 存入缓存
// ["123",“345,”5678“]
let search = wx.getStorageSync('search') || [];
let findIndex = search.findIndex((item)=>{
return item == title;
})
if(findIndex == -1){
//不存在
search.unshift(title) //插入到数组的最前面
}else{
// 存在
search.splice(findIndex,1);
search.unshift(title) //插入到数组的最前面
}
wx.setStorageSync('search', search);
// console.log(id,title,tag)
wx.navigateTo({
url: `../recipelist/recipelist?id=${id}&title=${title}&tag=${tag}`,
})
},
4.个人中心页面(菜谱分类的获取)
4.1 去菜谱表中,根据自己的openid status =1 获取自己所有的发布的菜谱;(recipeTypeId)(可以忽略)
// 获取自己发布的菜谱 (已经存在data中了,直接获取即可)
let selfRecipeLists = this.data.selfRecipeLists;
4.2将重复的去除
// 获取所有的类别id
let typeIds = selfRecipeLists.map((item)=>{
return item.recipeTypeId
})
// 进行去重
// new set() 数组去重,返回的不是一个真正的数组
// Array.from() 转换为真正的数组
let newTypeIds = Array.from(new Set(typeIds)) ;
4.3根据recipeTypeId去分类表中获取对应的菜谱分类信息
let typeAllPromise = []; //存放所以得分类的数组
newTypeIds.forEach((item,index)=>{
let typePromise = Api.findById( Config.tables.recipeTypeTable,item )
typeAllPromise.push(typePromise)
})
// Promise.all 等待所有都完成(或第一个失败)。
let recipeTypes = await Promise.all(typeAllPromise)
5.个人中心页面(获取自己关注的菜谱)
获取菜谱:!!!
5.1根据自己的openid ,获取自己关注的菜谱信息
// openid
let _openid = wx.getStorageSync('_openid');
let follows = await Api.findAll( Config.tables.followTable,{_openid} );
let recipeID = follows.data.map((item)=>{
return item.recipeID
})
// console.log(recipeID)
let recipeAllPromise = []; //存放所以得分类的数组
recipeID.forEach((item,index)=>{
let recipePromise = Api.findById( Config.tables.recipeTable,item)
recipeAllPromise.push(recipePromise)
})
let recipes = await Promise.all(recipeAllPromise)
// console.log(recipes)
this.setData({
followSelRecipes:recipes
})
6.上线部署
http://localhost:3000/api/getbanner
https://www.zhaoyunuo.net/data.json
上线:
(1)修改自己的appid,换成真实的appid
(2)把所以得设计的服务器域名都要换成https开头
(3)所以得服务器域名都要在后台进行配置
Uni-app
开启msyql数据库:
Api接口:
Npm install
Npm start 运行即可
后台系统:
Npm install
Npm start
默认管理员: admin 123123
笔记09
https://dcloud.io/
一、原生app、混合app的概念
1.原生app
Ios: 官方提供的语言 object-c
Android: java
2.混合app
a) vue,react 前端框架 ,结合自己独有的渲染引擎,根据app的特性,包装出来的
Uni-app – vue
react-native—React
3.多端
Ios、安卓、H5、微信小程序、百度小程序、支付宝小程序、qq小程序、头条小程序。。。。
二、uni-app
1.uni-app介绍
Dcloud 流应用 2012
uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台
uni-app在手,做啥都不愁。即使不跨端,uni-app也是更好的小程序开发框架(详见)、更好的App跨平台框架、更方便的H5开发框架。不管领导安排什么样的项目,你都可以快速交付,不需要转换开发思维、不需要更改开发习惯。
、
编辑器下载:
解压:使用即可(无需安装)
2.项目创建
2.1 HbuilderX编辑器 (重要)
(1)文件-新建-项目
选择好项目目录,填写好项目名称,编辑器会自动创建项目!!!
(2)运行
H5:
运行-运行到浏览器–选择浏览器即可
说明: 普通版的编译器,默认没有 scss/sass插件,需要安装
工具-插件安装-新插件 - 导入即可
小程序:
2.2 vue-cli脚手架(了解)
npm install -g @vue/cli
vue create -p dcloudio/uni-preset-vue 项目名称
Npm run serve 直接运行(默认H5)
npm run dev:%PLATFORM% 运行
npm run build:%PLATFORM% 发布
2.3二者的区别
(1)编译器
位置不同/升级不同
(2) 开发者工具
D.ts (cli自带)
D.ts(H 不带的)
Uni.scss uni-app内置的常用样式变量(设置当前项目标准)
Pages.json 项目的配置文件(路由/导航样式)
Manifest.json 项目的应用配置(app头像,名字)
Main。Js vue挂载文件
App.vue 跟组件 (生命周期,全局样式)
Static 静态文件(只能放置图片,视频等资源)
Pages 页面目录(vue)
Common 公共目录(js/css)
Components 公共组件
4.底部tab和配置
4.1创建页面
4.2创建tab
"tabBar":{
"list":[
{
"pagePath":"pages/index/index",
"text":"首页",
"iconPath":"static/index.png",
"selectedIconPath":"static/indexFull.png"
},
{
"pagePath":"pages/cart/cart",
"text":"购物车",
"iconPath":"static/cart.png",
"selectedIconPath":"static/cartFull.png"
},
{
"pagePath":"pages/my/my",
"text":"我的",
"iconPath":"static/my.png",
"selectedIconPath":"static/myFull.png"
}
]
}
4.3局部配置
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"navigationBarBackgroundColor":"#4CD964",
// "navigationBarTitleText":"white"
"navigationBarTextStyle":"white"
}
}
三、语法操作
1.开发规范
• 页面文件遵循 Vue 单文件组件 (SFC) 规范
• 组件标签靠近小程序规范,详见uni-app 组件规范
• 接口能力(JS API)靠近微信小程序规范,但需将前缀 wx 替换为 uni,详见uni-app接口规范
• 数据绑定及事件处理同 Vue.js 规范,同时补充了App及页面的生命周期
• 为兼容多端运行,建议使用flex布局进行开发
2.回顾
基本操作:
{{ message }}
三元运算:
{{ age >=18 ? "成年" :"未成年" }}
在js中,获取和设置data中的值得时候,不需要再加data
3.组件的创建
Inject privide
建议在根目录创建 components目录,右键选择创建组件选项!!
3.2引入组件
// 引入子组件
// import vChild from "../../components/child.vue"
import child from "@/components/child.vue"
// 注册组件
components:{
child,
},
3.3组件传值
父传子
Parent组件:
全局:
Main。js
//全局的模块绑定
import baseUrl from “./utils/utils.js”;
Vue.prototype.baseUrl = baseUrl;
笔记10
一、小U商城
服务端:
Node + express + mysql
前端:
Uni-app
目的:
熟练使用uni-app框架,
掌握开发流程
二、开始相关配置
数据库mysql
端口号 3306
接口运行
Npm install
Npm start
后台系统
Npm install
Npm start
三、UNI-app前端
1.导入静态页面
直接创建空目录,导入静态页面
2.设置api.js/config.js/http.js
// 获取秒杀活动
async _getseckill(){
let result = await this.api._getseckill();
// 处理图片
result.data.list[0].img = this.baseUrl + result.data.list[0].img;
this.seckill = result.data.list[0];
this._setSeckill(this.seckill.endtime) //传入活动结束时间
},
// 处理倒计时的
_setSeckill(endtime){
if(this.timer) clearInterval(this.timer);
this.timer = setInterval(()=>{
console.log('234567890')
//倒计时时间差计算
let t = parseInt((endtime - new Date().getTime()) / 1000); // s 秒
let h,m,s;
h = parseInt(t/3600); // 小时
m = parseInt(t%3600 / 60) //分钟
s = t % 60; // 秒数
// 设置格式
h = h <10? "0"+h : ""+h;
m = m <10? "0"+m : ""+m;
s = s <10? "0"+s : ""+s;
this.timeObj = {h,m,s};
},1000)
},
7.首页一级分类跳转商品页面
获取商品数据:
methods:{
async _getcategoodPage(){
// fid一级分类id,必填page请求页码,必填size分页偏移量,必填
let {fid,page,size} = this;
// let data ={
// fid:this.fid,
// page:this.page
// }
uni.showLoading({
title:"请求中···"
})
let result = await this.api._getcategoodPage({fid,page,size})
// 获取到的数据为null,证明没有商品了,直接返回,不进行处理图片路径问题
if(result.data.list[1] == null){
uni.hideLoading()
return;
}
// 请求成功之后,隐藏加载loading图标
if(result.data.list[1].length > 0){
uni.hideLoading()
}
// 获取总页数
this.totalPage = result.data.list[0];
// 获取商品信息
let products = result.data.list[1];
// 处理图片路径
products.map((item,index)=>{
item.img = this.baseUrl + item.img
})
// 分页数据的拼接
products = this.products.concat(products)
this.products =products;
console.log(result)
}
},
获取更多数据
9.获取商品详情
10.手机验证码
第一次登陆:
注册+登陆
第二次:
登陆
Code: 正常流程是不需要给前端返回的,(因为手机已经有了),需要后端人员吧code存入到session中,设置过期时间;
等前端人员点击登陆的时候,把输入的验证码和后端存储的session中验证码进行比较!!!
data() {
return {
codeMsg:"获取手机验证码",
tel:null, //手机号
timer:null,
isSend:false, //false 没有发送 true 已经发送 (开关)
}
},
async _getPhoneCode(){
if(this.isSend){
// 已经发送了
return;
}
// console.log(this.tel)
let tel = this.tel; //获取手机号
// let regExp = /^1[34859]{9}$/
let regExp = /^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/;
if(!regExp.test( tel )){
uni.showToast({
title:"请输入正确的手机号",
icon:"none"
})
return;
}
// 发起请求,获取验证码
let result = await this.api._sms({phone:tel});
// console.log(result)
if(result.data.list != null){
// 获取到了验证码
let num = 10;
if(this.timer)clearInterval(this.timer)
this.timer = setInterval(()=>{
num--;
this.codeMsg = num +"秒之后,重新获取验证码"
if(num <=0){
this.codeMsg = "获取手机验证码"
this.isSend = false;
clearInterval(this.timer)
}
},1000)
this.isSend = true; //发送之后,改变发送状态
uni.setStorageSync("code",result.data.list.code) //存储验证码
}
}
笔记11
1.登录 send页面
// 执行登录
async _doLogin(){
let code = this.code;
let tel = this.tel;
// 再一次使用正则匹配
let regExp = /^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/;
if(!regExp.test( tel )){
uni.showToast({
title:"请输入正确的手机号",
icon:"none"
})
return;
}
let sCode = uni.getStorageSync('code');
if(code != sCode){
uni.showToast({
title:"验证码错误",
icon:"none"
})
return;
}
// 执行登录
// console.log('执行登录')
let result = await this.api._wxDoLogin({phone:tel})
if(result.data.list != null){
//登录成功
let {token,uid,phone} = result.data.list;
uni.setStorageSync('token',token)
uni.setStorageSync('uid',uid,)
uni.setStorageSync('phone',phone)
// 登录成功之后,跳转到个人中心页面
uni.showToast({
title:"登录成功"
})
setTimeout(()=>{
uni.switchTab({
url:"../mine/mine"
})
},1500)
}else{
uni.showToast({
title:"登录失败,检测网络",
icon:"none"
})
}
// console.log(result,'登录')
}
2.进入项目,判断是否登录
// 项目初始化 (大门)
async onLaunch() {
// 检测是否登录
let token = uni.getStorageSync('token');
// token 不存在
if(!token){
uni.setTabBarItem({
index:2,
text:"未登录"
})
return;
}
// 存在
let result = await this.api._checkToken({"authorization":token});
// result.data.code == 200 成功的 500 失败
if(result.data.code == 200){
//登录成功的状态
uni.setTabBarItem({
index:2,
text:"我的"
})
}else{
uni.setTabBarItem({
index:2,
text:"未登录"
})
}
}
4.封装检测登录状态的方法utils.js
// 1检测是否登录
const _checkToken= async (_this,token)=>{
// token 不存在
if(!token){
uni.setTabBarItem({
index:2,
text:"未登录"
})
return false; // false 未登录的
}
// 存在
let result = await _this.api._checkToken({"authorization":token})
// result.data.code == 200 成功的 500 失败
if(result.data.code == 200){
//登录成功的状态
uni.setTabBarItem({
index:2,
text:"我的"
})
return true; //登录
}else{
uni.setTabBarItem({
index:2,
text:"未登录"
})
return false;
}
}
export default _checkToken;
5.加入购物车操作
async _cartAdd(){
/*
uid用户编号,必填项
goodsid商品编号,必填项
num数量,必填项
checked是否选中,必填项 ,默认1 选中 0 不选中
authorization header头中需要添加token后台验证条件
*/
let uid = uni.getStorageSync("uid");
let goodsid = this.id; //商品id
let num = this.num; //购买数量
let checked = 1; // 选中状态 0 非选中状态
// 用来检测是否登录的
let authorization = uni.getStorageSync('token');
// 执行添加
let result = await this.api._cartAdd({uid,goodsid,num,checked},{authorization})
if(result.data.code == 500){
//登录过期了
uni.showToast({
title:"登录已过期",
icon:"none"
})
setTimeout(()=>{
// uni.switchTab({
// })
uni.navigateTo({
url:"../send/send"
})
},1500)
}else{
//插入成功 (跳转到购物车页面)
}
// console.log(result)
},
6.购物车操作
注意:
(1)有没有登录
(2)登录了有没有值
6.1购物车数据的获取
// 获取购物车信息
async _getCarts() {
let uid = uni.getStorageSync("uid");
// 用来检测是否登录的
let authorization = uni.getStorageSync('token');
let result = await this.api._cartlist({
uid
}, {
authorization
});
if (result.data.code == 500) {
//登录状态过期
this.loginStatus = false;
} else {
//正常的登录章台
this.loginStatus =true;
// 遍历数据
if(result.data.list == null) return;
result.data.list.forEach((item)=>{
item.img = this.baseUrl + item.img
item.checked = item.checked == 1 ? true : false;
})
this.carts = result.data.list;
}
}
6.2获取统计类的数据
(1)总数
// 购买的总件数
_totalNum(){
// 所有的被选中的商品的数量累计
let total = 0;
this.carts.forEach((item,index)=>{
// if(item.checked){
// total += item.num;
// }
item.checked ? total += item.num : "";
})
return total;
}
(2)总价
// 总价格
_totalPrice(){
// 所有的被选中的商品的数量累计
let total = 0;
this.carts.forEach((item,index)=>{
item.checked ? total += item.num * item.price : "";
})
return total;
}
(3)全选状态
// 全选状态
_allCheckedStatus(){
// every some map foreach find findIndex filter
// 必须全部选中 为真 ,有一个为false 就是false
let checked = this.carts.every((item)=>{
return item.checked == true ; // ( 3>2, 4<5 )
})
// console.log(checked)
return checked;
}
7.购物车事件操作
7.1 加减问题
// 添加
asc(index){
this.carts[index].num++; //不做库存判断了
},
desc(index){
this.carts[index].num--;
if(this.carts[index].num <= 0){
this.carts[index].num=1;
}
},
7.2全选状态
// 全选状态
changeAllChecked(e){
// console.log(e)
this.carts.forEach((item)=>{
item.checked = e.detail.value;
})
}
7.3,单个状态
// 单个状态属性改变
changeChecked(e,index){
this.carts[index].checked = e.detail.value;
},
7.4修改全部状态
// 全选状态
changeAllChecked(e){
// console.log(e)
this.carts.forEach((item)=>{
item.checked = e.detail.value;
})
let authorization =uni.getStorageSync('token');
// this._editCart(index);
this.carts.forEach((item,index)=>{
let checked = item.checked ==true ? 1 : 0; // 处理每一个的值
this.api._editCart({id:item.id,num:item.num,checked},{authorization})
})
},
7.5执行删除
// 执行删除操作
deleteCarts(index,id){
let authorization =uni.getStorageSync('token');
uni.showModal({
title:"危险提示",
content:"您确定要删除么?",
success: (res) => {
if(res.confirm){
this.api._cartdelete({id},{authorization})
this.carts.splice(index,1);
}
}
})
},
7.6.跳转到确认订单页面
// 跳转到确认订单页面
_confirm(){
// 获取被选中的所有的购物车商品
let carts = this.carts.filter((item)=>{
return item.checked == true;
})
// 将数据存入缓存
uni.setStorageSync("carts",carts)
if(carts.length <= 0){
uni.showToast({
title:"请至少选中一项",
icon:"none"
})
return;
}
// 进行跳转
uni.navigateTo({
url:"../confirm/confirm"
})
},
8.订单页面
computed:{
// 总价格
_totalPrice(){
// 所有的被选中的商品的数量累计
let total = 0;
this.cartsInfo.forEach((item,index)=>{
item.checked ? total += item.num * item.price : "";
})
return total;
},
_confirmPrice(){
let confirmPrice = this._totalPrice + this.yhq;
return confirmPrice;
}
},
_getCarts(){
let cartsInfo = uni.getStorageSync("carts");
this.cartsInfo = cartsInfo
},
// 添加
asc(index){
this.cartsInfo[index].num++; //不做库存判断了
this._editCart(index);
},
desc(index){
this.cartsInfo[index].num--;
if(this.cartsInfo[index].num <= 0){
this.cartsInfo[index].num=1;
}
this._editCart(index);
},
// 封装修改cart数据的方法
async _editCart(index){
let { id,num,checked } = this.cartsInfo[index];
checked = checked ==true ? 1 : 0;
let authorization =uni.getStorageSync('token');
let result = await this.api._editCart({id,num,checked},{authorization})
if (result.data.code == 500) {
//登录状态过期
this.loginStatus = false;
}
},
笔记12
1.订单查询
// 获取全部信息
async _getOrders(){
let uid = uni.getStorageSync('uid');
let authorization = uni.getStorageSync('token');
let result = await this.api._orders({uid},{authorization});
if(result.data.list ==null){
// 没有数据
this.onOff =false;
}else{
result.data.list.map((item,index)=>{
item.child.map((val,ind)=>{
val.img = this.baseUrl + val.img;
})
})
console.log(result)
this.onOff = true;
this.orders = result.data.list;
}
}
},
2.支付宝小程序/百度小程序(百度小程序不支持个人开发)(了解)
配置路径:
3.发布
H5, 微信小程序, 支付宝小程序, 安卓app
证书: