微信小程序基础

1.介绍及安装

1.1介绍

小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。它的主要开发语言是 JavaScrip。

1.2安装及使用

小程序进行开发时,需要先安装开发者工具,申请账号等操作,具体详见https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html

2.小程序入门

2.1创建并预览

在安装完成微信开发者工具后,新建项目选择小程序项目,选择代码存放的硬盘路径,填入刚刚申请到的小程序的 AppID,点击顶部菜单编译在微信开发者工具中预览第一个小程序,预览图如左图,目录结构如右图:

                    

除了可以在开发者工具中预览之外,还可以在手机上进行预览。点击菜单的真机调试,微信扫描二维码后即可查看。另外,在开发过程中,如果涉及到修改配置文件等信息时,推荐使用微信开发工具(有提示),而对于页面的开发推荐使用其他的开发工具。比如需要新建一个页面时,可在微信开发者工具中编辑app.json,在里面添加页面路径,那么就会自动创建页面相关的文件;再例如配置tarbar时都会出现提示。

2.2JSON 配置

JSON 是一种数据格式,并不是编程语言,在小程序中,JSON扮演的静态配置的角色。在小程序运行之前就决定了小程序一些表现,需要注意的是小程序是无法在运行过程中去动态更新JSON 配置文件从而发生对应的变化的。相比于XML ,JSON格式最大的优点是易于人的阅读和编写,通常不需要特殊的工具,就能读懂和修改,是一种轻量级的数据交换格式。

2.2.1 小程序配置app.json

app.json 是当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。其部分内容如下:

{
  "pages":[
    "pages/index/index",
    "pages/logs/logs"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "Weixin",
    "navigationBarTextStyle":"black"
  }
}

除了以上属性外,还要其他的属性,说明如下:

属性 类型 必填 说明
pages String[] 描述当前小程序所有页面路径,也就是指定小程序页面在哪个目录
window Object 定义小程序所有页面的顶部背景颜色,文字颜色定义等
 tarBar Object  底部tab栏的设计
entryPagePath
String 小程序的默认启动路径,如果不填,将默认使用pages中的第一个页面
useExtendedLib
Object 引入第三方的插件

(1)window常用的设置

1)backgroundTextStyle:下拉 loading 的样式,仅支持 dark / light。需要和enablePullDownRefresh共同使用才能生效

 "window":{
    "backgroundTextStyle":"dark",
    "enablePullDownRefresh": true
 }

2)navigationBarBackgroundColor:导航栏背景颜色,仅支持rgb(如#fff),不支持颜色变量(如red)

"navigationBarBackgroundColor": "#00BFFF",

效果图如下:

 3)navigationBarTextStyle:导航栏标题颜色,仅支持 black / white,默认是black

"navigationBarTextStyle":"white"

4)navigationBarTitleText:导航栏的标题文字

"navigationBarTitleText": "我的微信小程序"

(2)tarBar常用设置

一般情况下,这个导航栏放在底部。tarBar的有一个list,其中 list 接受一个数组,只能配置最少 2 个、最多 5 个 tab。tab 按数组的顺序排序,每个项都是一个对象

"tabBar": {
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath":"img/icon/index.png",
        "selectedIconPath": "img/icon/index-active.png"
      },
      {
        "pagePath": "pages/my/my",
        "text": "我的",
        "iconPath":"img/icon/my.png",
        "selectedIconPath": "img/icon/my-active.png"
      }
    ]
  }

效果图如下:

 

pagePath:指定页面的路径;text:显示的文字;iconPath:未激活时的图标,需自定义图片。图片存放路径img文件夹与pages文件夹必须同级;selectedIconPath:激活时的图标。

除了list属性外,还有其他的属性,如需要则可自行设置,列举如下:

属性 说明
color tab 上的文字默认颜色,仅支持十六进制颜色
selectedColor tab 上的文字选中时的颜色,仅支持十六进制颜色
backgroundColor tab 的背景色,仅支持十六进制颜色
borderStyle tabbar 上边框的颜色, 仅支持black/white,默认是black
position tabBar 的位置,仅支持bottom/top,默认是bottom

2.2.2工具配置 project.config.json

小程序开发者工具在每个项目的根目录都会生成一个 project.config.json,在工具上做的任何配置都会写入到这个文件,当重新安装工具或者换电脑工作时,只要载入同一个项目的代码包,开发者工具就自动会帮你恢复到当时开发项目时的个性化配置。

2.2.3页面配置 page.json

2.3WXML 模板

WXML 全称是 WeiXin Markup Language,是小程序框架设计的一套标签语言,结合小程序的基础组件、事件系统,可以构建出页面的结构。WXML就相当于html页面,但是它里面只能写标签,不能包含js和css。WXML 要求标签必须是严格闭合的,没有闭合将会导致编译错误,其属性是大小写敏感的。打开pages/index/index.wxml,其内容如下:

<view class="container">
  <view class="userinfo">
    <button wx:if="{{!hasUserInfo && canIUse}}"> 获取头像昵称 </button>
    <block wx:else>
      <image src="{{userInfo.avatarUrl}}" background-size="cover"></image>
      <text class="userinfo-nickname">{{userInfo.nickName}}</text>
    </block>
  </view>
  <view class="usermotto">
    <text class="user-motto">{{motto}}</text>
  </view>
</view>

对比html页面可以看出,这里的标签是不一样的,小程序采用的标签是view、button等,这些都是已经包装过的。

2.4WXSS 样式

WXSS 具有 CSS 大部分的特性,但也有扩充。新增了尺寸单位rpx,提供了全局的样式和局部样式。可以写一个 app.wxss 作为全局样式,会作用于当前小程序的所有页面,局部页面样式 page.wxss 仅对当前页面生效,此外 WXSS 仅支持部分 CSS 选择器。

2.5JS 逻辑交互

 编写 JS 脚本文件来处理用户的操作,请看下面的代码:

<view>{{ msg }}</view>
<button bindtap="clickMe">点击我</button>

点击 button 按钮的时候,我们希望把界面上 msg 显示成 "Hello World",于是我们在 button 上声明一个属性: bindtap ,在 JS 文件里边声明了 clickMe 方法来响应这次点击操作:

Page({
  clickMe: function() {
    this.setData({ msg: "Hello World" })
  }
})

以上先简单介绍,具体的语法见下章节。

2.6小程序宿主环境

微信客户端给小程序所提供的环境为宿主环境。小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程。

3.WXML详解

3.1数据绑定

1)数据的呈现通过两个大括号包裹起来,数据的值在js的data中定义。下面是一个简单的例子:

pages/index/index.wxml的内容替换如下:

<text>当前时间:{{time}}</text>

pages/index/index.js的内容替换如下:

const app = getApp()

Page({
  data: {
      time: (new Date()).toString()
  }
})

在js中定义的变量直接在wxml中显示。

2)除了直接使用data中的对象外,在大括号中还可以进行简单的逻辑运算,且看例子:

<text>{{ a === 10? "变量 a 等于10": "变量 a 不等于10"}}</text>

这是一个三元运算符,结果可想而知。

3)字符串拼接

<view>{{"hello " + name}}</view>

4)放置数字、字符串或者是数组

<text>{{[1,2,3]}}</text>

<!-- 输出 1,2,3 -->



<text>{{"hello world"}}</text>

<!-- 输出 hello world -->

3.2属性绑定

属性值也可以动态的去改变,但属性值必须被包裹在双引号中。

<text data-test="{{test}}"> hello world</text>

需要注意,没有被定义的变量的或者是被设置为 undefined 的变量不会被同步到 wxml 中,即不会显示在页面上。

3.3条件逻辑

WXML 中,使用 wx:if="{{condition}}" 来判断是否需要渲染该代码块,使用wx:elif 和 wx:else 来添加一个 else 块

1)只用if

wxml内容

<view wx:if="{{sex==0}}">男生</view>

js内容

Page({
    data: {
        sex:0
    },
})

当把sex的值改为1时页面就不会显示任何的内容。

2)if和else都使用

计算逻辑是先判断是否符合if的条件,若不符合就else,或者继续判断是否符合elif的条件,若还不符合就直接进入else。

wxml内容else

<view wx:if="{{age<20}}">年龄小于20岁</view>
<view wx:else>年龄不小于20岁</view>

wxml内容elif

<view wx:if="{{age<20}}">年龄小于20岁</view>
<view wx:elif="{{age>20}}">年龄大于20岁</view>
<view wx:else>年龄是20岁</view>

js内容

Page({
    data: {
        age:20
    },
})

3)block的使用

因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 <block/> 标签将多个组件包装起来,并在上边使用 wx:if 控制属性。

<block wx:if="{{id==1}}">
  <view> view1 </view>
  <view> view2 </view>
</block>

3.4列表渲染

在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item。index和item均可省略。

wxml内容

<view wx:for="{{user}}" wx:key="key">
    <text>用户名:{{item.name}}</text>
    <text>年龄:{{item.age}}</text>
</view>

js内容

Page({
    data: {
        user: [{
                name: '张三',
                age: 20
            },
            {
                name: '李四',
                age: 30
            },
            {
                name: '王五',
                age: 22
            },
            {
                name: '赵柳',
                age: 20
            },
            {
                name: '李琦',
                age: 18
            },
        ]
    },
})

输入内容如下:

当然,如果不使用默认的item,也可自定义。使用 wx:for-item 指定数组当前元素的变量名,使用 wx:for-index 指定数组当前下标的变量名,把上述的wxml内容修改如下:

<view wx:for="{{user}}" wx:for-item="myuser" wx:for-index="i" wx:key="key">
    <text>{{i+':'}}用户名:{{myuser.name}}</text>
    <text>年龄:{{myuser.age}}</text>
</view>

如果需要指定数据的唯一标识,可以使用wx:key,但值必须是列表中唯一的字符串或数字,且不能动态改变。

3.5模板

WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。使用 name 属性,作为模板的名字。然后在 <template/> 内定义代码片段。

 wxml内容

<template name="msgItem">
  <view>
    <text> 消息内容: {{msg}} </text>
    <text> 时间: {{time}} </text>
  </view>
</template>

<!-- 引用模板 -->
<template is="msgItem" data="{{...item}}"/>

js内容

Page({
    data: {
        item: {
            msg: '你吃饭了吗',
            time: '2020-11-18'
        }
    },
})

上述就是使用 is 属性,声明需要的使用的模板,然后将模板所需要的 data 传入。

对应is属性,还可以动态决定具体需要渲染哪个模板,wxml内容如下:

<template name="odd">
  <view> odd </view>
</template>


<template name="even">
  <view> even </view>
</template>


<block wx:for="{{[1, 2, 3, 4, 5]}}">
  <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>

输出结果如图:

.

3.6引用

WXML 提供两种文件引用方式import和include。

3.6.1import引用

import 可以在该文件中使用目标文件定义的 template。

1)在 item.wxml 中定义了一个叫 item的 template

<template name="item">
  <text>{{text}}</text>
</template>

2)在 index.wxml 中引用item.wxml并使用模板

<import src="item.wxml"/>

<template is="item" data="{{text: 'forbar'}}"/>

需要注意的是 import 有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件中 import 的 template,简言之就是 import 不具有递归的特性。且看下面的例子:

若C 引用 B,B 引用A,在C中可以使用B定义的 template,在B中可以使用A定义的 template ,但是C不能使用A定义的template,代码如下:

A.wxml

<!-- A.wxml -->
<template name="A">
  <text> A template </text>
</template>

B.wxml

<!-- B.wxml -->
<import src="a.wxml"/>

<template name="B">
  <text> B template </text>
</template>

C.wxml

<!-- C.wxml -->
<import src="b.wxml"/>

<template is="A"/>  <!-- -->

<template is="B"/>

引用A模板时会触发一个警告,因为 b 中并没有定义模板 A。

3.6.2include引用

include 可以将目标文件中除了 <template/>外的整个代码引入,相当于是拷贝到 include 位置。

test.wxml

<view> header </view>

 index.wxml

<!-- index.wxml -->
<include src="test.wxml"/>

<view> body </view>

输出结果如下:

3.7共同的属性和事件绑定

1)hidden

组件是否显示,默认是false,如果不显示设置为true即可。

<view hidden="{{false}}">我是显示的</view>
<view hidden="{{true}}">我是隐藏的</view>

2)data-*

自定义的组件属性,组件上触发的事件时,会把此属性的值发送给事件处理函数,事件处理函数通过e.target.dataset.name获取。

<view data-name="张三"></view>

需要注意的是,data-name不是固定的,可以自定义,如果是data-data,那么就通过e.target.dataset.data来获取,也就是通过data-后指定的名字来获取属性的值。具体的介绍见后面章节介绍。

3)bind*/catch*

组件的事件绑定,具体看下章节。

3.8组件-图片 cover-image

覆盖在原生组件之上的图片视图,类似于image,只不过它是绝对定位的,会悬浮在原生组件之上。

<cover-image src="/img/images/xy1.jpg"></cover-image>

3.9组件-视图 view

它是原生的视图,类似于div标签,但在小程序中就使用view,不建议使用div。

<view>
    <view>123</view>
    <view>456</view>
</view>

3.10组件-图标 icon

图标有几种类型,使用时需指定type,而size默认值是23,可调整;color也是可设置。text是文本标签。

<view class="icon-box">
    <view class="inline">
        <icon type="success"></icon>
    </view>
    <view class="title">成功
        <text class="title-info">success</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="download"></icon>
    </view>
    <view class="title">下载
        <text class="title-info">download</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="info"></icon>
    </view>
    <view class="title">提示
        <text class="title-info">info</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="warn" color="#C9C9C9"></icon>
    </view>
    <view class="title">普通警告
        <text class="title-info">warn</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="warn"></icon>
    </view>
    <view class="title">强烈警告
        <text class="title-info">warn</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="waiting"></icon>
    </view>
    <view class="title">等待
        <text class="title-info">waiting</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="success_no_circle" size="33"></icon>
    </view>
    <view class="title">已选择
        <text class="title-info">success_no_circle</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="circle"></icon>
    </view>
    <view class="title">未选择
        <text class="title-info">circle</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="info_circle"></icon>
    </view>
    <view class="title">表单提示
        <text class="title-info">info_circle</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="cancel"></icon>
    </view>
    <view class="title">停止或关闭
        <text class="title-info">cancel</text>
    </view>
</view>
<view class="icon-box">
    <view class="inline">
        <icon type="search"></icon>
    </view>
    <view class="title">搜索
        <text class="title-info">search</text>
    </view>
</view>

样式:

.icon-box {
    text-align: center;
    margin-top: 20px;
}

.inline {
    margin-left: 50px;
    float: left;
}

.title-info {
    color: #9C9C9C;
}

效果图:

 

3.11组件-按钮 button

按钮属性如下表:

属性 默认值 说明
type default 按钮类型,default白色、primary绿色、warn红色
size default 按钮尺寸大小,default默认,mini小尺寸
plain false 按钮是否透明,设置true即可透明
disabled false 是否禁用,true是禁用
loading false 名称前是否带 loading 图标
 form-type    用于 form 组件,点击分别会触发 form 组件的 submit/reset 事件

 代码:

<view>
    <view class="my-button" >
        <button type="primary" disabled="{{disabled}}">按钮</button>
    </view>
    <view class="my-button" >
        <button type="primary" plain="{{plain}}" >透明按钮</button>
    </view>
    <view class="my-button" >
        <button size="mini" loading="{{loading}}">小按钮</button>
    </view>
</view>

效果图:

3.12组件-多选框 checkbox

多选框分为默认样式和推荐样式:

1)默认样式

页面:

<view>
    <view class="page-section-title">默认样式</view>
    <label class="checkbox" wx:for="{{hobby}}" wx:key="key">
        <checkbox value="{{item.value}}" checked="{{item.checked}}">{{item.label}}</checkbox>
    </label>
</view>

js:

Page({
    data: {
        hobby:[
            {value:'1',label:'看书',checked:true},
            {value:'2',label:'打游戏'},
            {value:'3',label:'跑步'},
            {value:'4',label:'骑自行车',checked:true}
        ]
    },
})

效果图:

 2)推荐样式

页面:

<view>
    <view class="page-section">
        <view class="page-section-title">默认样式</view>
        <view class="weui-cells weui-cells_after-title">
            <checkbox-group bindchange="checkboxChange">
                <label class="weui-cell weui-check__label" wx:for="{{hobby}}" wx:key="key">
                    <view class="weui-cell__hd">
                        <checkbox value="{{item.value}}" checked="{{item.checked}}" />
                    </view>
                    <view class="weui-cell__bd">{{item.label}}</view>
                </label>
            </checkbox-group>
        </view>
    </view>
</view>

js:

Page({
    data: {
        hobby:[
            {value:'1',label:'看书',checked:true},
            {value:'2',label:'打游戏'},
            {value:'3',label:'跑步'},
            {value:'4',label:'骑自行车',checked:true}
        ]
    },
    checkboxChange(e){
        console.log('选中的值为:', e.detail.value)
    }
})

效果图:

推荐样式样式更好看,但要注意一个坑。其样式采用的weui样式,因此必须引入此依赖:在app.json中引入扩展组件

 "useExtendedLib": {
      "weui": true
  },

3.13组件-单选框 redio

只说明推荐样式。

页面:

<view>
    <view class="page-section">
        <view class="page-section-title">推荐样式</view>
        <view class="weui-cells weui-cells_after-title">
            <radio-group bindchange="radioChange">
                <label class="weui-cell weui-check__label" wx:for="{{hobby}}" wx:key="key">
                    <view class="weui-cell__hd">
                        <radio value="{{item.value}}" checked="{{item.checked}}" />
                    </view>
                    <view class="weui-cell__bd">{{item.label}}</view>
                </label>
            </radio-group>
        </view>
    </view>
</view>

js:

Page({
    data: {
        hobby:[
            {value:'1',label:'看书'},
            {value:'2',label:'打游戏'},
            {value:'3',label:'跑步'},
            {value:'4',label:'骑自行车',checked:true}
        ]
    },
   radioChange(e){
        console.log('选中的值为:', e.detail.value)
    }
})

效果图:

3.14组件-输入框 input和textarea

文本框和多行输入框类型,其部分属性如下:

属性 默认值 说明
value   文本框初始内容
type text 文本框类型,text文本输入键盘、number数字输入键盘、idcard身份证输入键盘、digit小数输入键盘。textarea无此属性
password false 是否是密码框,true是。textarea无此属性
placeholder   文本框占位符或提示文字
disabled false 是否禁用,true禁用
maxlength 140 最大输入长度,设置为 -1 的时候不限制最大长度,若限制了长度,当达到最大长度后就不能继续输入了
focus false 是否自动获取焦点,true是
bindinput   输入事件触发,详见事件绑定
bindblur   文本框失去焦点时触发事件,详见事件绑定

input页面:

<view class="page-body">
    <view class="page-section">
        <view class="weui-cells__title">自动聚焦</view>
        <view class="weui-cells weui-cells_after-title">
            <view class="weui-cell weui-cell_input">
                <input class="weui-input" focus placeholder="将会获取焦点" />
            </view>
        </view>
    </view>
    <view class="page-section">
        <view class="weui-cells__title">用户名:{{name}}</view>
        <view class="weui-cells weui-cells_after-title">
            <view class="weui-cell weui-cell_input">
                <input class="weui-input" value="{{name}}" bindinput="bindKeyInput" />
            </view>
        </view>
    </view>
    <view class="page-section">
        <view class="weui-cells__title">密码</view>
        <view class="weui-cells weui-cells_after-title">
            <view class="weui-cell weui-cell_input">
                <input class="weui-input" placeholder="密码" maxlength="10" />
            </view>
        </view>
    </view>
    <view class="page-section">
        <view class="weui-cells__title">数字输入</view>
        <view class="weui-cells weui-cells_after-title">
            <view class="weui-cell weui-cell_input">
                <input class="weui-input" type="number" placeholder="数字输入" maxlength="5" />
            </view>
        </view>
    </view>
</view>

input的js:

Page({
    data: {
        name: '张三'
    },
    bindKeyInput(e) {
        this.setData({
            name: e.detail.value
        })
    }
})

现象是页面打开时,光标自动在第一个输入框闪烁,输入用户名是,文本框上面的值也会发生变化,密码是隐藏不可见的,数字输入5位就不能输入了。这里的文本框包括后面的几乎都是为表单做铺垫。

textarea页面:

<view class="page-section">
        <view class="weui-cells__title">备注</view>
        <view class="weui-cells weui-cells_after-title">
            <view class="weui-cell weui-cell_input">
                <textarea class="weui-input" placeholder="备注"/>
            </view>
        </view>
</view>

3.15组件-日期时间组件 picker

从底部弹起的滚动选择器。它最重要是属性是mode,用来指定选择器的类型,其值有selector、multiSelector、time、date和region,具体说明且看后续。

对于不同的mode,picker拥有不同的属性,请看介绍。

1)selector:普通选择器

属性 类型 默认值 描述
range array/object array [] 选择的范围
range-key string   当 range 是一个 Object Array 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容
value number 0 表示选择了 range 中的第几个(下标从 0 开始)
bindchange     value 改变时触发 change 事件

下面的页面使用了两种类型的数组呈现:

<picker mode="selector" bindchange="bindPickerChange" value="{{index}}" range="{{array}}">
    <view class="picker">
        当前选择的是1:{{array[index]}}
    </view>
</picker>

<picker mode="selector" bindchange="bindPickerChange2" value="{{objectArray[objIndex].id}}" range="{{objectArray}}" range-key="name">
    <view class="picker">
        当前选择的是2:{{objectArray[objIndex].name}}
    </view>
</picker>

js:

Page({
    data: {
        array: ['美国', '中国', '巴西', '日本'],
        index: 1,
        objectArray: [{
                id: 0,
                name: '2016年'
            },
            {
                id: 1,
                name: '2017年'
            },
            {
                id: 2,
                name: '2018年'
            },
            {
                id: 3,
                name: '2019年'
            }
        ],
        objIndex: 3
    },
    bindPickerChange(e) {
        console.log('picker发送选择改变,值为', e.detail.value)
        this.setData({
            index: e.detail.value
        })
    },
    bindPickerChange2(e) {
        console.log('picker发送选择改变,值为', e.detail.value)
        this.setData({
            objIndex: e.detail.value
        })
    },
})

2)multiSelector:多列选择器

属性 类型 默认值 描述
range array/object array [] 选择的范围
range-key string   当 range 是一个 Object Array 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容
value number 0 表示选择了 range 中的第几个(下标从 0 开始)
bindchange     value 改变时触发 change 事件
bindcolumnchange     列改变时触发

 页面:

<picker mode="multiSelector" bindchange="bindMultiPickerChange" value="{{multiIndex}}"
 range="{{multiArray}}">
    <view class="picker">
        当前选择:{{multiArray[0][multiIndex[0]]}},{{multiArray[1][multiIndex[1]]}},{{multiArray[2][multiIndex[2]]}}
    </view>
</picker>

js:

Page({
    data: {
        multiArray: [
            ['无脊柱动物', '脊柱动物'],
            ['扁性动物', '线形动物', '环节动物', '软体动物', '节肢动物'],
            ['猪肉绦虫', '吸血虫']
        ],
        multiIndex: [0, 0, 0],
    },
     bindMultiPickerChange: function (e) {
        console.log('picker发送选择改变,携带值为', e.detail.value)
        this.setData({
          multiIndex: e.detail.value
        })
      },
})

3)time:时间选择器

属性 类型 默认值 描述
value string   选中的时间,格式为"hh:mm"
start string   有效时间范围的开始,字符串格式为"hh:mm"
end string 0 有效时间范围的结束,字符串格式为"hh:mm"
bindchange     value 改变时触发 change 事件

对于时间选择器,只能选择到分钟,若不指定有效开始时间和结束时间,那么默认从00:00到23:59可选。且看实例:

页面:

<picker mode="time" bindchange="bindPickerChange" value="{{time}}" start="{{start}}" end="{{end}}">
    <view class="picker">
        当前选择时间:{{time}}
    </view>
</picker>

js:

Page({
    data: {
        time:'14:20',
        start:'10:30',
        end:'20:59'
    },
     bindPickerChange: function (e) {
        console.log('picker发送选择改变,携带值为', e.detail.value)
        this.setData({
          multiIndex: e.detail.value
        })
      },
})

4)date:日期选择器

属性 类型 默认值 描述
value string 当天 选中的日期,格式为"YYYY-MM-DD"
start string   有效日期范围的开始,字符串格式为"YYYY-MM-DD"
end string   有效日期范围的结束,字符串格式为"YYYY-MM-DD"
fields string day 有效值 year,month,day,表示选择器到哪一粒度,如指定month则只选择到月份
bindchange     value 改变时触发 change 事件

有两个示例,一个使用默认值,一个指定了fields到月份,具体如下:

页面:

<picker mode="date" bindchange="bindDateChange" value="{{date}}" start="{{start}}" end="{{end}}">
    <view class="picker">
        当前选择日期:{{date}}
    </view>
</picker>

<picker mode="date" bindchange="bindDateChange2" value="{{date1}}" fields="month">
    <view class="picker">
        当前选择日期:{{date1}}
    </view>
</picker>

js:

Page({
    data: {
        date: '2020-12-08',
        start: '2020-01-01',
        end: '2020-12-30',
        date1: '2020-12',
    },
    bindTimeChange: function(e) {
        console.log('picker发送选择改变,携带值为', e.detail.value)
        this.setData({
            time: e.detail.value
        })
    },
    bindDateChange: function(e) {
        console.log('picker发送选择改变,携带值为', e.detail.value)
        this.setData({
            date: e.detail.value
        })
    },
    bindDateChange2: function(e) {
        console.log('picker发送选择改变,携带值为', e.detail.value)
        this.setData({
            date1: e.detail.value
        })
    },
})

5)region:省市区选择器

属性 类型 默认值 描述
value array [] 选中的省市区,默认选中每一列的第一个值
bindchange     value 改变时触发 change 事件

省市区选择时使用微信的数据,只需存储文字即可。

页面:

<picker mode="region" bindchange="bindRegionChange" value="{{region}}" >
    <view class="picker">
        当前选择区:{{region[0]}}{{region[1]}}{{region[2]}}
    </view>
</picker>

js:

Page({
    data: {
        region: ['广东省', '深圳市', '龙华区']
    },
    bindRegionChange(e) {
        console.log('picker发送选择改变,携带值为', e.detail.value)
        this.setData({
            region: e.detail.value
        })
    }
})

3.16组件-开关 switch

开关主要说明一下类型,有switch和checkbox类型。

页面:

<view class="section__title">type="switch"</view>
<view class="body-view">
    <switch checked="{{switch1Checked}}" bindchange="switch1Change" />
</view>

<view class="section__title">type="checkbox"</view>
<view class="body-view">
    <switch type="checkbox" checked="{{switch2Checked}}" bindchange="switch2Change" />
</view>

js:

Page({
    data: {
        switch1Checked: true,
        switch2Checked: false,
    },
    switch1Change(e){
        console.log('按钮1改变,值是:'+e.detail.value)
        this.setData({
            switch1Checked:e.detail.value
        })
    },
    switch2Change(e){
        console.log('按钮2改变,值是:'+e.detail.value)
        this.setData({
            switch2Checked:e.detail.value
        })
    }
})

效果:

 

3.17组件-表单 form

 对于表单,主要就是表单提交事件bindsubmit,详见事件章节。另外,需要给每个输入框(包括单选框、多选框等)添加name属性,原因是在表单提交时会根据name属性去获取值。提交按钮只需要指定formType类型是submit即可。示例如下:

页面:

<view class="container">
    <form bindsubmit="formSubmit" bindrest="formRest">
        <view class="inbox">
            <label class="label">姓名<span style="color: red;">*</span></label>
            <input placeholder="姓名" value="{{form.name}}" name="name" maxlength="50"/>
        </view>
        <view class="inbox">
            <label class="label">密码<span style="color: red;">*</span></label>
            <input placeholder="密码" value="{{form.password}}" password="true" name="password" maxlength="50"/>
        </view>
        <view class="inbox">
            <label class="label">性别<span style="color: red;">*</span></label>
            <radio-group bindchange="radioChange" name="sex">
                <radio class="radio-group-col" value="{{item.value}}" wx:for="{{sexOption}}" wx:key="key" checked="{{item.checked}}">
                    <text>{{item.label}}</text>
                </radio>
            </radio-group>
        </view>
        <view class="inbox">
            <label class="label">爱好</label>
            <checkbox-group bindchange="checkboxChange" name="hobby">
                <checkbox class="checkbox-group-col" wx:for="{{hobbyOption}}" wx:key="key" value="{{item.value}}" checked="{{item.checked}}">
                    <text>{{item.label}}</text>
                </checkbox>
            </checkbox-group>
        </view>
        <view class="inbox">
            <label class="label">出生日期<span style="color: red;">*</span></label>
            <picker mode="date" bindchange="bindDateChange" value="{{form.brith}}" end="{{brithEnd}}">
                <input placeholder="出生日期" name="brith" value="{{form.brith}}" />
            </picker>
        </view>
        <view class="inbox">
            <label class="label">输入时间<span style="color: red;">*</span></label>
            <picker mode="time" bindchange="bindTimeChange" value="{{form.nowTime}}">
                <input placeholder="输入时间" name="nowTime" value="{{form.nowTime}}" />
            </picker>
        </view>
        <view class="inbox">
            <label class="label">所在地区<span style="color: red;">*</span></label>
            <picker mode="region" bindchange="bindRegionChange" value="{{region}}">
                <input placeholder="所在地区" name="region" value="{{form.region}}" />
            </picker>
        </view>
        <view class="inbox">
            <label class="label">详细地址</label>
            <textarea class="textarea" placeholder="详细地址" name="addr" value="{{form.addr}}" maxlength="2000"/>
            </view>
        <view class="btn-group">
            <button type="primary" formType="submit">提交</button>
            <button formType="reset">取消</button>
        </view>
    </form>
</view>

js:

const now = new Date()
//导入工具类
const util = require('../../utils/util.js');

Page({
    data: {
        form: {},
        sexOption: [{
                label: '',
                value: 0
            },
            {
                label: '',
                value: 1
            }
        ],
        hobbyOption: [{
                value: '1',
                label: '看书',
                checked: true
            },
            {
                value: '2',
                label: '打游戏'
            },
            {
                value: '3',
                label: '跑步'
            },
            {
                value: '4',
                label: '骑自行车',
                checked: true
            }
        ],
        brithEnd: now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate(),
        region: []
    },

    //日期发生变化
    bindDateChange(e) {
        this.setData({
            'form.brith': e.detail.value
        })
    },
    //时间发生变化
    bindTimeChange(e) {
        this.setData({
            'form.nowTime': e.detail.value
        })
    },
    //地区发生变化
    bindRegionChange(e) {
        const region = e.detail.value
        this.setData({
            'region': region,
            'form.region': region[0] + region[1] + region[2]
        })
    },
    //多选按钮发生变化
    checkboxChange(e) {
        this.setData({
            'form.hobby': e.detail.value
        })
    },
    //单选按钮发生变化
    radioChange(e) {
        this.setData({
            'form.sex': e.detail.value
        })
    },
    //表单提交
    formSubmit(e) {
        const form = e.detail.value
        let isChecking = false
        if (!util.isNotBlank(form.name)) {
            isChecking = true
        } else if (!util.isNotBlank(form.password)) {
            isChecking = true
        } else if (!util.isNotBlank(form.sex)) {
            isChecking = true
        } else if (!util.isNotBlank(form.brith)) {
            isChecking = true
        } else if (!util.isNotBlank(form.nowTime)) {
            isChecking = true
        } else if (!util.isNotBlank(form.region)) {
            isChecking = true
        }
        if (isChecking) {
            wx.showToast({
                title: "带*号为必填项",
                icon: 'none',
                duration: 2000
            })
        } else {
            this.data.form = form
            wx.showModal({
                title:'表单内容' ,
                icon: 'none',
                content:JSON.stringify(form)
            })
        }
    },

})

页面样式:

.checkbox-group-col {
    padding-left: 15px;
}

.radio-group-col{
    padding-right: 30px;
}

全局样式:

/* *app.wxss* */
.container {
    height: 100%;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    padding: 10rpx 0 0 0;
    box-sizing: border-box;
}

.inbox {
    display: flex;
    margin: 20px 0 0 20px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--weui-FG-3);
    font-size: 18px;
}

.inbox .label {
    width: 30%;
}

.textarea {
    height: 80rpx;
    margin-left: 20rpx;
}

.btn-group {
    display: flex;
    margin: 10px;
    /* 样式居中 */
    align-items: center;
    justify-content: center;
}

button {
    width: 30% !important;
}

工具类方法:

//是否为空
const isNotBlank = val => {
    return val != null && val != ''
}

module.exports = {
  isNotBlank: isNotBlank
}

效果图:

对于picker组件,当里面存放的input标签时,作为表单的输入,需要使用disabled把输入框设置为禁用,只能通过选择时间来输入,不能手动输入。原因是如果不禁用那么在选择时会同时弹出选择框和输入框,界面很不友好。

4.js详解

4.1App.js

1)文件包裹

app中,使用App构造器把整个文件包裹起来,所有的方法和变量都写在里面。对于方法,可直接按照ES6语法定义即可,而对于变量,需要放到data的属性中。

App({
  data: {
      msg:'今天天气真好'  
  },
  test(){
    console.log(123)  
 }
})

2)系统方法

另外在app.js中有一些默认的方法提供使用,如onLaunch方法,此方法是在页面加载完成会自动调用的,可以在里面进行数据的初始化。

App({
  data: {
      onLoad: function (option){
          
       },
})

其方法中有一个参数,是一个对象,当进行页面跳转进行参数拼接传递时,就可以使用对应的参数来获取。示例如下:

--index.js
wx.navigateTo({
      url:'/pages/test/test?name=zys&age=20'
})

--test.js
Page({
    onLoad:function(option){
        console.log(option.name)
        console.log(option.age)
    }
})

3)全局参数

有些参数需要很多地方使用,就可以设置为全局的,需要在App.js中设置。其中有个对象globalData,里面是用来存放全局参数的。设置参数后在其他页面文件通过导入的方法,就可以使用其中的变量了。

App.js设置全局参数:

App({
   globalData: {
        userInfo: null
   }
})

index.js导入并使用参数:

//获取应用实例
const app = getApp()

Page({
      onLoad: function (option){
          app.globalData.userInfo = 'admin'
          //console.log(app.globalData.userInfo)
      },
})

不仅可以直接使用这个值,还可以修改这个值。

4.2页面js

页面js就拿index.js为例说明。与app.js不同的是,它是通过Page构造器包裹的,也没有globalData参数。page构造器用来注册一个小程序页面。

Page({
      onLoad: function (option){
      ...
      }
})

4.2.1使用setData设置值

在页面js中获取变量的值,使用this.data来获取。但是要修改值,不能全部使用this.data,在很多情况下需要使用this.setData来设置值,this.setData将数据从逻辑层发送到视图层,同时改变对应的data的值。简单的说,就是保证js和界面的数据是同步的。

Page({
    data: {
        name: 'admin'
    },
    onLoad: function() {
        //获取值
        console.log(this.data.name)
        //设置值:方法一
        this.data.name = '张三'
        //设置值:方法二
        this.setData({
            name:'李四'
        })
    },
})

若设置值,推荐使用第二种方法。

需要注意的是,小程序中,当使用setData对对象或属性赋值时,用法是不一样的:

(1)给对象直接赋值

const myuser = {name:'张三',age:20}
this.setData({
    user: myuser
})

(2)给对象的某个属性赋值

const myuser = {name:'张三',age:20}
this.setData({
    'user.name': myuser.name
})

注意区别就是给对象的某个属性赋值时,需要用引号括起来

4.2.2系统内置的方法

onLoad:页面加载完成时调用。页面初次加载的时候,微信客户端就会给Page实例派发onLoad事件,Page构造器参数所定义的onLoad方法会被调用,onLoad在页面没被销毁之前只会触发1次。

onShow:小程序启动或切前台时调用。

5.事件

事件绑定分为两种,bind绑定和catch绑定,区别是catch能阻止事件冒泡。具体事件绑定以bind绑定为例,在bind后加上对应的事件名即可,也可以通过冒号的方式进行绑定,bindtap和bind:tap是一样的效果。以下只介绍常用的事件。

5.1事件对象

当事件回调触发的时候,会收到一个事件对象,对象的部分属性如下:

type:事件类型
target:触发事件的组件的一些属性值集合
currentTarget:当前组件的一些属性值集合
detail:额外的信息

 额外的信息主要用于事件获取参数值,一般使用event.detail.value来获取。

5.1.1target和currTarget的区别

对于target和currTarget,它们作用是不一样的,currentTarget为当前事件所绑定的组件,而target则是触发该事件的源头组件。下面的例子就是很好的说明:

<!-- wxml -->
<view catchtap="handleTap" data-name="outer">
  <view data-name="inner">点击我</view>
</view>

<!-- js -->
Page({
    handleTap(e){
        console.log(e.target.dataset.name)//inner
        console.log(e.currentTarget.dataset.name)//outer
    }
})

5.1.2target和currentTarget的属性

一般通过dataset来获取data-*的属性值,且看后续的章节介绍。推荐使用currentTarget,它在小程序中使用的很多,原因是小程序中事件的方法不能传递参数,只能通过属性值的方式传递并获取。

5.2 tap

绑定点击事件,点击时会触发指定的方法,会给方法传递一个事件对象。

<!--wxml-->
<view>
    <button bindtap="myTest">我是点击事件,点我触发</button>
</view>

<!--js-->
Page({
    data: {},
    myTest(e){
       console.log(e)
       console.log(e.currentTarget.dataset)
    }
})

5.3 input

绑定文本框事件,结合detail属性用于在逻辑层获取文本框输入的值。

<!-- wxml -->
<input type="text" bindinput="input" data-name="username" placeholder="请输入用户名"/>
<input password='true' bindinput="input" data-name="password" placeholder="请输入密码"/>
<button bindtap="getValue" type="primary">点我获取内容</button>

<!-- js -->
Page({
    data: {
        user:{
            username:'',
            password:''
        }
    },
    input(e){
        //获取data-name的值
        const key = e.currentTarget.dataset.name
        //获取文本框的值
        const value = e.detail.value
        this.data.user[key] = value
    },
    getValue(e){
        console.log(this.data.user)
    }
})

当输入数据后,点击按钮即可在输出对应的内容。输入结果如下:

5.4 longpress

手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发,如果不超过350ms就会触发tap事件,一般情况下只使用tap事件进行点击触发。

<!-- wxml -->
<button bindtap="tapHandle" bindlongpress="longpressHandle" type="primary">点我获取内容</button>

<!-- js -->
Page({
    tapHandle(e){
        console.log(111)
    },
    longpressHandle(e){
        console.log(222)
    }
})

5.5 submit

绑定表单,绑定后通过detail来获取表单的具体数据。

<!-- wxml -->
<form bindsubmit="formSubmit">
    <text>账号:</text>
    <input type="text" placeholder='请输入账号' name="username" />

    <text>密码:</text>
    <input password='true' placeholder='请输入密码' name="password" />

    <button type='primary' form-type='submit'>登录</button>
</form>

<!-- js -->
Page({
    formSubmit(e) {
        //获取表单数据的值
        const obj = e.detail.value
        console.log(obj)
    },
})

输入数据点击按钮即可看到数据。

5.6confirm

回车事件,点击回车按钮时触发。可用于输入框或按钮。

<!-- wxml -->
<input type="text" bindinput="input" bindconfirm="searchSubmit" placeholder="搜索"/>

<!-- js -->
Page({
    data: {
        username: ''
    },
    onLoad() {},
    input(e) {
        this.setData({
            username: e.detail.value
        })
    },
    searchSubmit(e) {
        console.log(this.data.username)
    }
})

输入数据后回车就可以看到打印的结果。

 

6.WXSS

WXSS全称是WeiXin Style Sheets,它是一套用于小程序的样式语言,用于描述WXML的组件样式的文件。

6.1WXSS尺寸和位置

上图是wxss的存放位置,其中公共样式放在app.wxss中,页面自己的也是放到页面目录下。

在WXSS中,引入了rpx尺寸单位。目的是,适配不同宽度的屏幕,解决同一px在不同屏幕下的占比问题。小程序编译后,rpx会做一次px换算。换算是以375个物理像素为基准,也就是在一个宽度为375物理像素的屏幕下,1rpx = 1px。

6.2WXSS引用

在小程序中,需要引入其他的样式文件,引入方式是@import 样式文件路径,例子:

引入当前目录下的test样式文件

@import './test.wxss'

7.常用API

7.1路由

路由的几种类型对比如下表:

方法 说明 使用
wx.reLaunch 关闭所有页面,打开到应用内的某个页面 wx.reLaunch({url:'url'})
wx.redirectTo

关闭当前页面,跳转到应用内的某个页面,

但是不允许跳转到 tabbar 页面

wx.redirectTo({url:'url'})
wx.navigateTo

保留当前页面,跳转到应用内的某个页面,

但是不能跳到 tabbar 页面。

页面栈最多十层,使用navigateBack返回上一层

wx.navigateTo({url:'url'})
wx.navigateBack 关闭当前页面,返回上一页面或多级页面 wx.navigateBack()

前3个方法都可以传递参数,在跳转后的页面onload方法中获取参数的值,以navigateTo为例说明:

index.wxml:

<button type="primary" bindtap="goNext">进入下一页</button>

index.js:

Page({
    goNext(e) {
        wx.navigateTo({
            url: '/pages/test2/test2?name=zss&age=20'
        })
    }
})

test.wxml:

<view>
    传递的参数:{{user.name+","+user.age}}
</view>

test.js:

Page({
    data: {
        user: {}
    },
    onLoad: function(options) {
        console.log(options)
        this.setData({
            user: options
        })
    },

})

7.2消息框

先看下表的分类:

类型 消息提示框 模态对话框  loading 提示框 操作菜单
方法 wx.showToast wx.showModal wx.showLoading wx.showActionSheet

且看详细说明:

1)wx.showToast

属性 类型 说明
title string 提示的内容
icon string

默认值是success。提示的图标。

success:成功图标,此时 title 文本最多显示 7 个汉字长度

none:不显示图标,此时 title 文本最多可显示两行

duration number 默认值是number,提示显示的时间,但是是毫秒。

下面包含有图标和无图标的例子:

wx.showToast({
     title:'成功提示',
     duration:2000
})
wx.showToast({ title:
'无图标提示', icon:'none' })

2)wx.showModal

属性 类型 说明
title string 提示的标题
context string

提示的内容

showCancel boolean

默认值是true。是否显示取消按钮。

cancelText string 默认值是'取消',取消按钮的文字,最多 4 个字符。
confirmText string 默认值是'确定',取消按钮的文字,最多 4 个字符.
success function

接口调用成功的回调函数。函数的参数有两个属性:

confirm为true时表示用户点击了确定按钮,cancel为true时表示用户点击了取消按钮

下面包含有删除按钮和无删除按钮的例子:

wx.showModal({
    title:'消息提示',
    content:'您确定要删除吗?',
    success:function(res){
        if(res.confirm){
            console.log('点击了确定')
        }else if(res.cancel){
            console.log('点击了取消')
        }
    },
})

wx.showModal({
    title: '消息提示',
    content: '操作成功',
    showCancel: false,
    success: function(res) {
        console.log('点击了确定')
    },
})

3)wx.showLoading

其最重要的属性就是title,加载时提示的文字。如果不关闭则会一直加载,关闭使用wx.hideLoading()。

下面例子是加载弹框显示5秒后自动关闭,在实际情况中是数据加载完成后关闭。

wx.showLoading({
    title: '加载中',
})
setTimeout(function() {
    //关闭加载框
    wx.hideLoading()
}, 5000)

4)wx.showActionSheet

属性 类型 说明
itemList Array,元素是string 按钮的文字数组,数组长度最大为 6
success function

接口调用成功的回调函数。函数的参数有属性tapIndex:

表示用户点击的按钮序号,从上到下的顺序,从0开始

下面例子是选择了不同的菜单就显示不同的值

const arr = ['A', 'B', 'C']
    wx.showActionSheet({
    itemList: arr,
    success(res) {
        console.log("您选择了:" + arr[res.tapIndex])
    },
})

7.3网络

小程序请求后台时,必须要联网,没有网络是请求不到后台的。另外,部分域名需要先通国小程序后台设置为信任域名才能去请求访问。需要注意是,如果需要在请求后使用this,那么必须在请求之前先定义一个变量存储this,然后在请求后使用定义的变量,不能直接使用this,此时this的null。请求的方式如下表:

属性 说明
url 请求的路径
method 请求的方法,包括GET(默认)、POST、DELETE、PUT
data 传递的参数,必须是一个对象
header

请求头信息,属性content-type的默认值是application/json。可在其中添加token等认证信息

success

接口调用成功的回调函数,有参数response,数据是其中的data,即response.data

1)get请求

wx.request({
    url:'https://autumnfish.cn/api/joke/list',
    data:{
        num: 5
    },
    success(res){
        console.log(res.data)
    }
}),

2)post请求

post直接传递对象,后台通过@RequestBody接收;

先看第一种:

wx.request({
    url:'https://autumnfish.cn/api/user/reg',
    data:{
        username:'123456556'
    },
    method:'POST',
    success(res){
        console.log(res.data)
    }
})

3)请求的封装

post请求的另一种方式是把参数拼接在请求路径后面,后台通过@RequestParam接收。

const app = getApp();

function get(url, callback) {
    wx.request({
        method: 'GET',
        url: url,
        header: {
            'token': app.globalData.token
        },
        success(res) {
            callback(res.data);
        }
    })
}

function post(url, data, callback) {
    wx.request({
        method: 'POST',
        url: url,
        data: data,
        header: {
            'token': app.globalData.token
        },
        success(res) {
            callback(res.data);
        }
    })
}

function postParam(url, data, callback) {
    const keys = Object.keys(data)
    let params = '?'
    for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        let value = data[key]
        if (value != null && value != '') {
            if (i != 0) {
                params += '&'
            }
            //对特殊字符进行转义
            value = encodeURIComponent(value)
            params += key + '=' + value
        }

    }
    wx.request({
        method: 'POST',
        url:url + params,
        header: {
            'token': app.globalData.token
        },
        success(res) {
            callback(res.data);
        }
    })
}

7.4数据缓存

数据缓存分为存数据、取数据和删除数据。缓存就是把数据存到本地,允许存储的最大长度是1M,最大数据是10M。主要介绍同步操作。

1)setStorageSync:对指定的key存储value

wx.setStorageSync("username","zys")

2)getStorageSync:获取指定key的value

const username = wx.getStorageSync("username")

3)removeStorageSync:删除指定key的value

wx.removeStorageSync("username")

7.5用户登录

小程序登录时需要先给微信发送请求,获取此用户唯一的openid,后期用户的唯一性验证就需要它,详细的配置请看官网或参考demo,代码地址https://github.com/zhongyushi-git/mini-program.git。

7.6图片的下载

图片下载时,一般需要保存到系统相册中。实例如下:

wxml:

<view>
    <button type="warn" bindtap="download">下载图片</button>
</view>

js:

Page({
    download(){
        const url ='http://192.168.43.123:8887/image/5.jpg'
        //下载文件到本地
        wx.downloadFile({
            url:url,
            success(res){
                //把图片保存到系统相册
                wx.saveImageToPhotosAlbum({
                    filePath:res.tempFilePath,
                    success(resp){
                        wx.showToast({
                            title:'保存成功',
                            icon:'success',
                            duration:2000
                        })
                    },
                    fail(err){
                        console.log('保存错误',err)
                    }
                })
            },
            fail(err){
                console.log('下载错误',err)
            }
        })
    },
    
})

点击按钮就可以把图片保存到本地相册了,不过需要注意的是,url必须是图片的地址,不能是字节流。

8.自定义组件

8.1创建组件

组件的结构和普通页面的结构类似,但也不完全相同,在js中,它使用的是Component构造器,其中是属性也有所不同;另外在json文件中需要指定是组件。

1)在项目根目录下新建一个component目录,再创建子目录mycon目录

2)在微信开发者工具中选择mycon目录,然后右键新建component,就会自动生成组件的相关配置

3)mycon.json的内容:指定是组件

{
  "component": true,
  "usingComponents": {}
}

4)编辑mycon.js内容:

Component({
    /**
     * 组件的属性列表
     */
    properties: {
        titles:{
            type:Array,
            value:[]
        }
    },
    /**
     * 组件的方法列表
     */
    methods: {
        handleChange(e) {
            const index = e.currentTarget.dataset.index
            const {
                titles
            } = this.data
            titles.forEach((item, i) => i == index ? item.isActive = true : item.isActive = false)
            this.setData({
                titles
            })
            //子组件调用父组件的方法,可传递参数
            this.triggerEvent('itemChange',{index})
        }
    }
})

在组件中,使用了slot标签,他相当于一个占位符,当传值时会将其替换为传递的内容。

5)wxss样式:

.title{
    display: flex;
    padding: 10px;
}

.title-item{
    flex:1;
    display: flex;
    /*垂直水平居中*/
    justify-content: center;
    align-items: center;
}

.active{
    color: red;
    border-bottom: 1px solid currentColor;
}

8.2声明组件

在需要使用组件的页面的json中声明组件

{
  "usingComponents": {
      "mycon":"../../components/mycon/index"
  }
}

是以键值对的形式声明的,key是指定的组件的名称,value是组件的路径,一般使用相对路径。

8.3使用组件

声明之后,在wxml中直接使用即可。

wxml内容:

<mycon titles="{{titles}}" binditemChange="handleItemChange">
    <view>
        <block wx:if="{{titles[0].isActive}}">我是首页内容</block>
        <block wx:elif="{{titles[1].isActive}}">我是分类内容</block>
        <block wx:else>我是原创内容</block>
    </view>
</mycon>

js内容:

Page({
    data: {
        titles: [{
                name: '首页',
                isActive: true
            },
            {
                name: '分类',
            },
            {
                name: '原创',
            }
        ]
    },
    handleItemChange(e){
        const {
            titles
        } = this.data
        titles.forEach((item, i) => i == e.detail.index ? item.isActive = true : item.isActive = false)
        this.setData({
            titles
        })
    }
})

  效果图如下:

8.4组件传值说明

在上述自定义组件中,存在父子组件之间的传值,在此讲解。

1)父组件向子组件传值

父组件向子组件传递时,只需要在子组件的标签上添加对应的属性,然后在子组件中获取即可。在此实例中,父组件给子组件传递了标题数组titles,子组件在properties中接收并指定类型和默认值。

       

2)子组件向父组件传递方法

子组件给父组件传值时,需要通过方法的方式传递,即调用父组件的方法,把值放到参数中。在此实例中,子组件通过triggerEvent方式调用父组件的方法,并传递了参数,父组件在标签上定义了方法。

   

 

 图片上传

<view>
    <mp-uploader title="提示    " tips="图片上传提示" delete="false" max-size="{{10*1024*1024}}" max-count="5" files="{{files}}"
     upload="{{uplaodFile}}" bindfail="uploadError" bindsuccess="uploadSuccess"></mp-uploader>
     <button bindtap="saveImg">保存</button>
</view>

 

Page({
    data: {
        files: [],
        filePath: []
    },
    onLoad() {
        this.setData({
            uplaodFile: this.uplaodFile.bind(this)
        })
    },
    //文件上传的函数,返回一个promise
    uplaodFile(files) {
        this.data.files = this.data.files.concat(files.tempFilePaths);
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({
                    urls: files.tempFilePaths
                })
            }, 1000)
        })
    },
    //上传失败的回调
    uploadError(e) {
        console.log('图片上传失败', e.detail)
    },
    //上传成功的回调
    uploadSuccess(e) {
        console.log('图片上传成功', e.detail)
    },
    //把图片上传到服务器
    saveImg() {
        //一次上传一张图片
        const files = this.data.files
        for (var i = 0; i < files.length; i++) {
            wx.uploadFile({
                url: 'http://localhost:8080/file/upload',
                filePath: files[i],
                name: 'file',
                complete: (res) => {
                    this.data.filePath.push(res.data)
                    if (i === files.length) {
                        wx.showToast({
                            title: '上传成功'
                        })
                        console.log('图片路径:' + this.data.filePath)
                    }
                }
            })
        }


    }
})

8.5组件异步传值

如果在父组件中传给子组件的数据,是异步获取的,比如是从接口获取的,一般在子组件的ready里面是不能直接拿到值的。直接从ready里面打印的值要么是空对象或者空数组或者nul。想要取到值,就得在计算属性observers里面去获取,就是监听到值变化的时候再去取值。

父组件

<child-component list="{{data}}"/>

子组件

Component({
    /**
     * 组件的属性列表
     */
    properties: {
        list: {
            type: Array,
            value: []
        }
    },
    lifetimes: {
        //组件初始化
        ready: function() {
            console.log(this.data.list) //取不到值
        },
    },
    //使用计算属性监听数据变化
    observers: {
        'list': function() {
            console.log(this.data.list) //可取到异步的值
        }
    }
}

这个计算属性其实和vue的计算属性很相似。

8.6父组件调用子组件方法

上述已说明子组件如何调用父组件的方法,再补充父组件调用子组件方法

parent.xml

<child-component id="child134"/>

child.js

Component({
    methods: {
         tttt() { 
             console.log('我是子组件方法');
        },
    }
})

parent.js

// 页面获取自定义组件实例
let child = this.selectComponent('#child134'); 
// 通过实例调用组件事件
child.tttt(); 

通过这种方式还能进行值的传递,父组件将值动态传递给子组件。

posted @ 2020-12-08 22:52  钟小嘿  阅读(833)  评论(0编辑  收藏  举报