那些好用的新属性,那些头疼的问题,持续更新!
# 更新记录
- 2020-06-10 : 多图上传、换肤
- 2019-10-02 : 新增属性记录
- 2018-12-10 : 基础样式
# 问题
# 去掉button的边框
小程序旧版工具找不到before和after伪类,而它很多的边框都是使用伪类完成,去掉边框时使用伪类
< button > 点击 < /button>
button: after {
border: none;
}
2
3
4
5
# 双向数据绑定
小程序的数据响应使用 setData
,类似react的 setState
的写法,分别绑定。
# 工作原理
小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript
所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。
而 evaluateJavascript
的执行会受很多方面的影响,数据到达视图层并不是实时的。
# 常见的 setData 操作错误
1. 频繁的去 setData
在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去 setData
,其导致了两个后果:
- Android 下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;
- 渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;
2. 每次 setData 都传递大量新数据
由 setData
的底层实现可知,我们的数据传输实际是一次 evaluateJavascript
脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程,
3. 后台态页面进行 setData
当页面进入后台态(用户不可见),不应该继续去进行 setData
,后台态页面的渲染用户是无法感受的,另外后台态页面去 setData
也会抢占前台页面的执行。
< input
class = "weui-input"
type = 'number'
maxlength = "11"
placeholder = "请输入手机号码"
value = '{{guiderTel}}'
bindinput = "bindPhoneInput" / >
Page({
data: {
tel: ""
},
bindPhoneInput: function(e) {
this.setData({
tel: e.detail.value
})
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 下拉刷新ios出现一块空白
在 iOS 中,手指按住屏幕上下拖动,会触发 touchmove 事件。这个事件触发的对象是整个 webview 容器,容器自然会被拖动,剩下的部分会成空白。
- 单页
// 外层div
position: fixed;
height: 100 % ;
// 在 onPullDownRefresh 事件里加setTimeout事件延迟下下拉刷新的事件。
setTimeout(function() { // 这里写刷新要调用的函数,比如:
_this.pullRefresh();
}, 500);
// app.json
"disableScroll": true
2
3
4
5
6
7
8
9
10
11
- 多页
使用scroll-view进行滑动
< scroll - view scroll - y style = "height:{{wh}}px;"
bindscrolltolower = "onBottom" >
<
/scroll-view>
2
3
4
5
# 模态对话框怎么换行
使用\r\n,微信开发者工具不生效但是真机可生效
wx.showModal({
title: 'showModal换行',
content: '你好\r\n我叫切图',
success(res) {}
})
2
3
4
5
# 如何阻止冒泡
bindtap改成catchtap,这样就只会触发goHome不会触发goShop 小程序事件
< view class = 'ad-container'
bindtap = "goShop" >
<
view class = 'ad-txt'
catchtap = 'goHome' > 跳过 {
{
lastSecond
}
}
s < /view> < /
view >
2
3
4
5
6
7
8
9
10
11
# 跳转其他小程序失效
版本更新之后需要在app. json中加入如下代码
"navigateToMiniProgramAppIdList": [
其他小程序的appid
],
2
3
# ios的时间问题
小程序上 Date.parse(new Date(data ))
在ios手机拿不到正确的时间,这是因为ios系统不支持2010-10-10这样格式的时间导致出现的这个问题,IOS只识别2010/10/10这样的格式
var data = '2010-10-10 10:10:10'
var datatime = data.replace(/\-/g, "/")
var newdata = new Date(datatime).getTime()
var format = data.replace(/-/g, '/')
var newdata2 = Date.parse(new Date(format))
2
3
4
5
6
# 未登录前闪烁首页的问题
首页中调用接口判断是否已经登录,此时未登录时会跳转到登录页面。但调用接口但瞬间会显示首页的布局。 解决方案:调用首页接口前使用空白背景的loading,拿到结果后再根据返回值选择停留在首页还是跳转登录页
# 原生组件层级问题
微信小程序原生组件camera、canvas、input(仅在focus时表现为原生组件)、live-player、live、pusher、map、textarea、video的层级是最高的,页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上。 https://blog.csdn.net/DeepLies/article/details/81511722 解决方案:
使用cover-view,它是覆盖在原生组件之上的文本视图。
- 可覆盖的原生组件包括 map、video、canvas、camera、live-player、live-pusher; 只支持嵌套 cover-view、cover-image,可在 cover-view 中使用 button
- cover-view和cover-image的
aria-role
仅可设置为button
,读屏模式下才可以点击,并朗读出“按钮”;为空时可以聚焦,但不可点击 - 文本建议都套上cover-view标签,避免排版错误。
- 建议子节点不要溢出父节点
<
view class = "container" >
<
view class = "page-body" >
<
view class = "page-section page-section-gap" >
<
map
style = "width: 100%; height: 300px;"
latitude = "{{latitude}}"
longitude = "{{longitude}}" >
<
cover - view class = "cover-view" >
<
cover - view class = "container" >
<
cover - view class = "flex-wrp"
style = "flex-direction:row;" >
<
cover - view class = "flex-item demo-text-1" > < /cover-view> <
cover - view class = "flex-item demo-text-2" > < /cover-view> <
cover - view class = "flex-item demo-text-3" > < /cover-view> < /
cover - view > <
/cover-view> < /
cover - view > <
/map> < /
view > <
/view> < /
view >
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 特定场景下,可以使用wx:if将该原生组件在遮挡的场景下进行隐藏。
# 低版本兼容
# 1. 版本号比较
微信客户端和小程序基础库的版本号风格为 Major. Minor. Patch(主版本号. 次版本号. 修订版本号)。 文档中会在组件,API等页面描述中带上各个功能所要求的最低基础库版本号。 开发者可以在小程序中通过调用 wx. getSystemInfo 或者 wx. getSystemInfoSync 获取到当前小程序运行的基础库的版本号。通过版本号比较的方式进行运行低版本兼容逻辑。 版本号比较适用于所有情况。部分场景下也可以使用后面提到的方法完成。 注意:不要直接使用字符串比较的方法进行版本号比较。 版本号比较可以参考以下代码:
function compareVersion(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i])
const num2 = parseInt(v2[i])
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
compareVersion('1.11.0', '1.9.9') // 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const version = wx.getSystemInfoSync().SDKVersion
if (compareVersion(version, '1.1.0') >= 0) {
wx.openBluetoothAdapter()
} else {
// 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示
wx.showModal({
title: '提示',
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
})
}
2
3
4
5
6
7
8
9
10
# 2. API 存在判断
对于新增的 API,可以通过判断该API是否存在来判断是否支持用户使用的基础库版本。例如:
if (wx.openBluetoothAdapter) {
wx.openBluetoothAdapter()
} else {
// 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示
wx.showModal({
title: '提示',
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
})
}
2
3
4
5
6
7
8
9
# 3. wx. canIUse
除了直接通过版本号判断,也可以通过 wx. canIUse 来判断是否可以在该基础库版本下直接使用。例如: ** API 参数或返回值 对于 API 的参数或者返回值有新增的参数,可以判断用以下代码判断。
wx.showModal({
success: function(res) {
if (wx.canIUse('showModal.success.cancel')) {
console.log(res.cancel)
}
}
})
2
3
4
5
6
7
** 组件 对于组件,新增的组件或属性在旧版本上不会被处理,不过也不会报错。如果特殊场景需要对旧版本做一些降级处理,可以这样子做。
Page({
data: {
canIUse: wx.canIUse('cover-view')
}
})
2
3
4
5
< video controls = "{{!canIUse}}" >
<
cover - view wx: if = "{{canIUse}}" > play < /cover-view> < /
video >
2
3
4
canIUse 的数据文件随基础库进行更新,新版本中的新功能可能出现遗漏的情况,建议开发者在使用时提前测试。
# 隐藏回到首页
内部工具登录页面不需要导航上的返回首页 解决方案: onShow 中调用 hideHomeButton 进行隐藏
# bindinput拿不到输入框中数据
华为自带键盘输入时会在虚拟键盘上显示提示的字符,当用户通过点击这里输入文字而不是正常输入时,该输入框拿不到值。 解决方法:输入框同时绑定bindblur
< input class = "weui-input"
placeholder = "请输入账号名"
value = '{{agentAccount}}'
bindinput = "bindAccountInput"
bindblur = "bindAccountInput" / >
2
3
4
5
# 小程序登录页审核无法通过
- 内部使用工具不能显示首页的情况下
登录页加上改行文字 该程序为xx内部使用,如忘记账号密码请联系本公司,谢谢!
- 首页可预览,新增登录按钮,点击进入登录页面
https://developers.weixin.qq.com/community/operate/doc/000640bb8441b82900e89f48351401
# 小程序拿不到图片
- 本地调试时会打开不检验https证书的配置。导致真的上线时拿不到http的图片。
建议上线前去掉该配置测试。
- 未配置合法域名
开发=>开发设置=>服务器域名/业务域名 服务器域名:是 wx. request 请求去拉取数据的域名。 一般返回为JSON字符串 业务域名:小程序的webview组件要引入的其他H5地址的URL的域名或者网页里面的iframe的域名
# 第一次加载组件不触发onShow
组件第一次加载的时候不能执行onShow里面的内容,只有在隐藏又显示后,才会显示。而页面层级却每次进入都会触发onShow。 例如:
onLoad() {
console.log('onLoad')
},
onShow() {
console.log('onShow')
},
mounted() {
console.log('mounted')
},
2
3
4
5
6
7
8
9
实际输出:
onLoad
mounted
2
# 页面栈太多导致报错
errMsg :"navigateTo:fail webview count limit exceed"
页面栈超过五层时会报错,合理使用弹框减少页面跳转
# 导航跳转失效
小程序只能打开10个页面,使用wx. navigateTo时会保存页面,,因此滥用可能导致导航跳转失效。
# 怎么复制文字
# text添加selectable属性
<text selectable bindlongtap='copy'>{{ item.question }}</text>
# 一键复制
使用setClipboardData复制
wx.setClipboardData({
data: 'xxx',
success: function(res) {
wx.showToast({
title: '复制成功',
});
}
})
2
3
4
5
6
7
8
getClipboardData获取内容
wx.getClipboardData({
success(res) {
console.log(res.data)
}
})
2
3
4
5
# 无法使用async等语法
https://developers.weixin.qq.com/community/develop/doc/00066877c54eb0ff5488b54885b801
新版工具增加了 增强编译
的选项来增强 ES6转ES5
的能力,启用后会使用新的编译逻辑以及提供额外的选项供开发者使用。
启用 增强编译
后的编译能力的对比:
特性 | 原有逻辑 | 增强编译 |
---|---|---|
Babel版本 | babel6 | babel7 |
Presets | es2015、stage0 | env {chrome:53, ios:8} |
Helpers | 单文件内联 | 跨文件共享 |
Async/Await | 不支持 | 支持 |
严格模式开关 | 不支持 | 支持 |
忽略文件目录 | 不支持 | 支持 |
代码压缩 | uglify-js | terser |
Babel插件 | - | 一系列proposal * |
polyfill | 大部分es6 | 新增三个polyfill * |
使用
preset-env
, 支持最新的ECMAScript
语法共享helpers函数,默认放在项目
@babel/runtime
目录,可通过项目配置文件
配置支持async/await语法,按需注入
regeneratorRuntime
,目录位置与helpers函数一致文件首行是
// use strict disable;
时,即可禁用文件严格模式可通过
项目配置文件
指定任意文件、目录不经过编译(如:miniprogram_npm)原有逻辑是支持
stage0
语法的,为了向前兼容,引入了一系列proposal插件关于polyfill,基础库中已经引入了大量的
es6
相关的polyfill 可参考文档,增强编译下,新增:Array. prototype. includes(es7)
、Object. entries(es8)
、Object. values(es8)
# mpvue出现脚本错误或者未正确调用 Page()
在当前的js文件添加Page({}),文件不能为空
# mpvue的sitemap配置
小程序根目录下的 sitemap.json
文件用于配置小程序及其页面是否允许被微信索引,文件内容为一个 JSON 对象,如果没有 sitemap.json
,则默认为所有页面都允许被索引。但在mpvue中没有该文件会报错,只需要在外层加上sitemap. json文件,内容如下:
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}
2
3
4
5
6
7
微信现已开放小程序内搜索,开发者可以通过 sitemap.json
配置,或者管理后台页面收录开关来配置其小程序页面是否允许微信索引。当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。 爬虫访问小程序内页面时,会携带特定的 user-agent: mpcrawler
及场景值: 1129
。需要注意的是,若小程序爬虫发现的页面数据和真实用户的呈现不一致,那么该页面将不会进入索引中。
# mpvue生命周期
所有页面里面的created生命周期函数 都会在小程序加载的时候一次性执行,而不是每进入一个页面执行一次。建议替换成mounted/onLoad/onShow
# mpvue页面新增不实时
新增的页面需要重新 npm run dev
来进行编译
# 新鲜好用
# match-media
可以指定一组 media query 规则,满足时这个节点才会被展示。
< match - media min - width = "300"
max - width = "600" >
<
view > 当页面宽度在 300~500 px 之间时展示这里 < /view> < /
match - media >
<
match - media min - height = "400"
orientation = "landscape" >
<
view > 当页面高度不小于 400 px 且屏幕方向为纵向时展示这里 < /view> < /
match - media >
2
3
4
5
6
7
8
9
10
11
12
# movable-view/movable-area
可以实现播控等移动等小球功能
< movable - area >
<
movable - view x = "{{x}}"
y = "{{y}}"
direction = "all" > text < /movable-view> < /
movable - area >
2
3
4
5
6
# mut-bind互斥事件绑定
自基础库版本 2. 8. 2 起,除 bind
和 catch
外,还可以使用 mut-bind
来绑定事件。一个 mut-bind
触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind
绑定函数不会被触发,但 bind
绑定函数和 catch
绑定函数依旧会被触发。
换而言之,所有 mut-bind
是“互斥”的,只会有其中一个绑定函数被触发。同时,它完全不影响 bind
和 catch
的绑定效果。
例如在下边这个例子中,点击 inner view 会先后调用 handleTap3
和 handleTap2
,点击 middle view 会调用 handleTap2
和 handleTap1
。
< view id = "outer"
mut - bind: tap = "handleTap1" >
outer view <
view id = "middle"
bindtap = "handleTap2" >
middle view <
view id = "inner"
mut - bind: tap = "handleTap3" >
inner view <
/view> < /
view > <
/view>
2
3
4
5
6
7
8
9
10
11
12
# wxs
WXS 代码与js的书写大同小异,主要是作为文件放在wxml中实现方法复用等。代码可以编写在 wxml 文件中的 <wxs>
标签内,或以 .wxs
为后缀名的文件内。通过 module.exports
实现导出内部等私有变了和函数, 使用require来引入其他文件。
/pages/tools. wxs文件:
var foo = "'hello world' from tools.wxs";
var bar = function(d) {
return d;
}
module.exports = {
FOO: foo,
bar: bar,
};
module.exports.msg = "some msg";
2
3
4
5
6
7
8
9
< wxs src = "./../tools.wxs"
module = "tools" / >
<
view > {
{
tools.msg
}
} < /view> <
view > {
{
tools.bar(tools.FOO)
}
} < /view>
2
3
4
5
6
7
8
9
10
11
12
13
# 模板
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。
# 定义
使用 name 属性,作为模板的名字。然后在内定义代码片段
< template name = "msgItem" >
<
view >
<
text > {
{
index
}
}: {
{
msg
}
} < /text> <
text > Time: {
{
time
}
} < /text> < /
view > <
/template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 使用
使用 is 属性,声明需要的使用的模板,然后将模板所需要的 data 传入。类似vue中的动态组件。
< template is = "msgItem"
data = "{{...item}}" / >
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-09-15'
}
}
})
2
3
4
5
6
7
8
9
10
11
12
# 代码片段
https://www.cnblogs.com/niejunchan/p/8564146.html
# 分包
https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages.html 某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。 在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。 在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。 目前小程序分包大小有以下限制:
- 整个小程序所有分包大小不超过 16M
- 单个分包/主包大小不能超过 2M
对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
├──
app.js├── app.json├── app.wxss├── packageA│└── pages│├── cat│└── dog├── packageB│└── pages│├── apple│└── banana├── pages│├── index│└── logs└── utils
2
在app. json中使用subpackages字段进行分包
{
"pages": [
"pages/index",
"pages/logs"
],
"subpackages": [{
"root": "packageA",
"pages": [
"pages/cat",
"pages/dog"
]
}, {
"root": "packageB",
"name": "pack2",
"pages": [
"pages/apple",
"pages/banana"
]
}]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 打包原则
- 声明
subpackages
后,将按subpackages
配置路径进行打包,subpackages
配置路径外的目录将被打包到 app(主包) 中 - app(主包)也可以有自己的 pages(即最外层的 pages 字段)
subpackage
的根目录不能是另外一个subpackage
内的子目录tabBar
页面必须在 app(主包)内
# 引用原则
packageA
无法 requirepackageB
JS 文件,但可以 requireapp
、自己 package 内的 JS 文件packageA
无法 importpackageB
的 template,但可以 requireapp
、自己 package 内的 templatepackageA
无法使用packageB
的资源,但可以使用app
、自己 package 内的资源
# 低版本兼容
由微信后台编译来处理旧版本客户端的兼容,后台会编译两份代码包,一份是分包后代码,另外一份是整包的兼容代码。 新客户端用分包,老客户端还是用的整包,完整包会把各个 subpackage
里面的路径放到 pages 中。
#
# 封装使用
# 跳转/回退
< table >
<
thead >
<
tr >
<
th > 方法 < /th> <
th > 描述 < /th> < /
tr > <
/thead> <
tbody >
<
tr >
<
td > navigateTo < /td> <
td > 保留当前页面, 跳转到应用内的某个页面(注意: 目前页面路径最多只能十层。) < /td> < /
tr > <
tr >
<
td > redirectTo < /td> <
td > 关闭当前页面, 跳转到应用内的某个页面 < /td> < /
tr > <
tr >
<
td > reLaunch < /td> <
td > 关闭所有页面, 打开到应用内的某个页面 < /td> < /
tr > <
tr >
<
td > switchTab < /td> <
td > 跳转到 tabBar 页面, 并关闭其他所有非 tabBar 页面(跳转的 tabBar 页面的路径, 不能带参数) < /td> < /
tr > <
tr >
<
td > navigateBack < /td> <
td > 关闭当前页面, 返回上一页面或多级页面, 通过delta设定返回的页面数( 如果 delta 大于现有页面数, 则返回到首页) < /td> < /
tr > <
tr >
<
td > navigator < /td> <
td > 上面都为js中设定, navigator是微信提供的组件 < /td> < /
tr > <
/tbody> < /
table >
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
wx.navigateTo({
url: 'test?id=1'
})
wx.redirectTo({
url: 'test?id=1'
})
wx.reLaunch({
url: 'test?id=1'
})
wx.switchTab({
url: '/test'
})
wx.navigateBack({
delta: 2
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
< view class = "btn-area" >
<
navigator url = "/page/test?id=1" > 对应 wx.navigateTo < /navigator> <
navigator url = "/page/test?id=1"
open - type = "redirect" > 对应 wx.redirectTo < /navigator> <
navigator url = "/page/test?id=1"
open - type = "reLaunch" > 对应 wx.reLaunch < /navigator> <
navigator url = "/page/test"
open - type = "switchTab" > 对应 wx.switchTab < /navigator> < /
view >
2
3
4
5
6
7
8
9
10
wx. navigateTo 和 wx. redirectTo 不允许跳转到 tabbar 页面,只能用 wx. switchTab 跳转到 tabbar 页面
获取跳转参数:
Page({
onLoad: function(option) {
console.log(option.id)
}
})
2
3
4
5
# 跳转其他小程序
- navigator:基础库 2.0.7 开始支持
// envVersion:develop(开发版),trial(体验版),release(正式版)
<
navigator
target = "miniProgram"
open - type = "navigate"
app - id = "XXXXXXXX"
path = "pages/invite/invite?id={{id}}"
extra - data = ""
version = "release"
hover - class = "none" >
跳转小程序 <
/navigator>
2
3
4
5
6
7
8
9
10
11
12
- wx.navigateToMiniProgram:基础库 1.3.0 开始支持,此接口即将废弃
openChat: function() {
wx.navigateToMiniProgram({
appId: 'XXXXXXXX',
path: 'pages/invite/invite?id=' + id,
extraData: '',
envVersion: 'release',
success(res) {}
})
}
2
3
4
5
6
7
8
9
# 拨打电话
< text data - phone = "{{phone}}"
bindtap = "calling" > {
{
phone || '暂无'
}
} < /text>
calling: function(e) {
const phone = e.currentTarget.dataset.phone
if (!this.verifyPhone(phone)) {
return;
}
wx.makePhoneCall({
phoneNumber: phone,
success: function() {
console.log("拨打电话成功!")
},
fail: function() {
console.log("拨打电话失败!")
}
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 倒计时
export var CutDownTimer = function(config) {
this.defaultSettings = {
ms: true,
withDay: true,
eleId: '',
timer: null,
startTime: '',
endTime: '',
endCallBack: function() {},
callback: function() {}
};
this.config = Object.assign(this.defaultSettings, config);
this.config.time_distance = otherUntil.getTimeDistance(this.config.startTime, this.config.endTime);
}
CutDownTimer.prototype = {
// 运行主函数
run: function() {
var self = this;
var time_distance, int_day, int_hour, int_minute, int_second;
time_distance = otherUntil.getTimeDistance(this.config.startTime, this.config.endTime);
// 递减
this.config.time_distance = time_distance - 1000;
this.config.isBeforeStart = otherUntil.checkTimeStart(this.config.startTime)
if (time_distance > 0) {
int_day = Math.floor(time_distance / 86400000);
time_distance -= int_day * 86400000;
int_hour = Math.floor(time_distance / 3600000);
time_distance -= int_hour * 3600000;
int_minute = Math.floor(time_distance / 60000);
time_distance -= int_minute * 60000;
int_second = Math.floor(time_distance / 1000);
if (int_day < 10)
int_day = "0" + int_day;
if (int_hour < 10)
int_hour = "0" + int_hour;
if (int_minute < 10)
int_minute = "0" + int_minute;
if (int_second < 10)
int_second = "0" + int_second;
// 执行回调
self.config.callback({
isBeforeStart: this.config.isBeforeStart,
timeOver: false,
d: int_day,
h: int_hour,
m: int_minute,
s: int_second
})
self.config.timer = setTimeout(function() {
self.run();
}, 1000);
} else {
// 执行结束回调
self.config.endCallBack({
isBeforeStart: this.config.isBeforeStart,
timeOver: true,
d: '00',
h: '00',
m: '00',
s: '00'
})
return;
}
},
clear: function() {
clearTimeout(this.config.timer)
}
}
export var otherUntil = {
checkTimeStart: function(startTime) {
var en = startTime.replace('-', '/').replace('-', '/')
var e = new Date(en)
var now = new Date()
if (e - now >= 0) {
// 未开始
return true
} else {
// 已开始
return false
}
},
checkTimeOver: function(endTime) {
var en = endTime.replace('-', '/').replace('-', '/')
// var en = startTime.replace(' ', 'T')
var e = new Date(en)
var now = new Date()
if (e - now >= 0) {
// 未结束
return false
} else {
// 已结束
return true
}
},
getTimeDistance: function(start, end, now) {
var reg = /-/g;
var startTime = new Date(start.replace(reg, '/')).getTime();
var endTime = new Date(end.replace(reg, '/')).getTime();
var nowTime = null;
if (now) {
nowTime = new Date(now.replace(reg, '/')).getTime();
} else {
nowTime = new Date().getTime();
}
// 活动尚未开始
if (startTime > nowTime) {
return startTime - nowTime
}
// 已开始未结束
if (startTime < nowTime && nowTime < endTime) {
return endTime - nowTime
}
// 已结束
if (nowTime > endTime) {
return 0
}
},
accSub: function(arg1, arg2) {
var r1, r2, m, n;
try {
r1 = arg1.toString().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度
n = (r1 >= r2) ? r1 : r2;
return ((arg1 * m - arg2 * m) / m).toFixed(n);
},
accAdd: function(arg1, arg2) {
var r1, r2, m, c;
try {
r1 = arg1.toString().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
c = Math.abs(r1 - r2);
m = Math.pow(10, Math.max(r1, r2));
if (c > 0) {
var cm = Math.pow(10, c);
if (r1 > r2) {
arg1 = Number(arg1.toString().replace(".", ""));
arg2 = Number(arg2.toString().replace(".", "")) * cm;
} else {
arg1 = Number(arg1.toString().replace(".", "")) * cm;
arg2 = Number(arg2.toString().replace(".", ""));
}
} else {
arg1 = Number(arg1.toString().replace(".", ""));
arg2 = Number(arg2.toString().replace(".", ""));
}
return (arg1 + arg2) / m;
},
accDiv: function(arg1, arg2) {
var t1 = 0,
t2 = 0,
r1, r2;
try {
t1 = arg1.toString().split(".")[1].length;
} catch (e) {}
try {
t2 = arg2.toString().split(".")[1].length;
} catch (e) {}
r1 = Number(arg1.toString().replace(".", ""));
r2 = Number(arg2.toString().replace(".", ""));
return (r1 / r2) * pow(10, t2 - t1);
},
GetQueryString: function(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
},
formateUserName: function(name) {
let nameAfterReplace = '';
let len = name.length;
if (len < 3 && len > 0) {
nameAfterReplace = name[0] + '*'
} else {
let num2 = Math.ceil(len / 3);
let num1 = Math.floor(len / 3);
let num3 = len - num1 - num2;
let star = '*'.repeat(num2);
nameAfterReplace = name.substr(0, num1) + star + name.substr(len - num3);
}
return nameAfterReplace
},
ft_c: function(c) {
if (typeof c !== "number") {
c = parseFloat(c);
}
if (c === NaN) {
return '';
}
return c.toFixed(2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# picker选择
结合weui
<view class="weui-cell weui-cell_access">
<view class="weui-cell__hd">
<image src="//imgw.pospal.cn/pospalUnion/images/p13.png"></image>所属行业:
</view>
<view class="weui-cell__bd">
<picker bindchange="bindIndustryChange" value="{{industryIndex}}" range="{{industryArray}}" class="{{industry=='请输入'?'phcolor':''}}">
<view class="picker">
{{industry}}
</view>
</picker>
</view>
<view class="weui-cell__ft weui-cell__ft_in-access"></view>
</view>
2
3
4
5
6
7
8
9
10
11
12
13
Page({
data: {
industryArray: ['零售', '餐饮', '烘焙', '宠物'],
industryIndex: 0,
industry: '请输入'
},
bindIndustryChange: function(e) {
this.setData({
industryIndex: e.detail.value,
industry: this.data.industryArray[e.detail.value]
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
# 下拉框选择
https://developers.weixin.qq.com/s/poy4UbmX7wkV
# 下拉刷新
// json文件
{
"enablePullDownRefresh": true,
}
// js文件
onPullDownRefresh: function() {
that.XXX();
},
2
3
4
5
6
7
8
9
# 原生页面间传值
# globalData
在 app.js
文件中定义全局变量 globalData
, 将需要存储的信息存放在里面。通过getApp()方法来获取之前赋值过的全局变量.
// app.js
App({
// 全局变量
globalData: {
name: 'qietu'
}
})
//使用
var app = getApp();
Page({
onLoad: function() {
console.log(app.globalData.name);
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# navigateTo/redirectTo
不允许跳转到 tab 所包含的页面
//pageA.js
// Navigate
wx.navigateTo({
url: '../pageD/pageD?name=qietu&gender=male',
})
// Redirect
wx.redirectTo({
url: '../pageD/pageD?name=qietu&gender=male',
})
// pageB.js
...
Page({
onLoad: function(option) {
console.log(option.name + 'is' + option.gender)
this.setData({
option: option
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 数据缓存
https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorageSync.html
持原生类型、Date、及能够通过 JSON.stringify
序列化的对象。
// 异步
wx.setStorage({
key: "key",
data: "value"
})
wx.getStorage({
key: 'key',
success(res) {
console.log(res.data)
}
})
wx.removeStorage({
key: 'key',
success(res) {
console.log(res)
}
})
wx.clearStorage()
// 同步
try {
wx.setStorageSync('key', 'value')
} catch (e) {}
try {
var value = wx.getStorageSync('key')
if (value) {
// Do something with return value
}
} catch (e) {
// Do something when catch error
}
try {
wx.removeStorageSync('key')
} catch (e) {
// Do something when catch error
}
try {
wx.clearStorageSync()
} catch (e) {
// Do something when catch error
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 页面栈
小程序中页面之间时以栈的形式进行维护,可以使用getCurrentPages()函数获取到之前到页面
let pages = getCurrentPages(); // 获取当前页面栈
let prevPage = pages[pages.length - 2]; // length - 2 是上一页的数据
// 直接操作上一个页面的 index数据 之后返回
prevPage.setData({
name: 'qietu'
}, function() {
wx.navigateBack()
})
2
3
4
5
6
7
8
# 按钮样式
# 双按钮
<view class="weui-btn-area weui-btn-double">
<button class="weui-btn weui-btn-left">取消</button>
<button class="weui-btn weui-btn-right">确定</button>
</view>
2
3
4
.weui-btn-area.weui-btn-double {
position: relative;
height: 72rpx;
font-size: 0;
margin: 0 60rpx 26rpx 60rpx;
}
.weui-btn {
display: inline-block;
vertical-align: middle;
height: 72rpx;
line-height: 72rpx;
background: #3e8af0;
font-size: 30rpx;
color: #fff;
}
.weui-btn.weui-btn-left {
width: 50%;
border-radius: 100rpx 0rpx 0 100rpx;
background: #5d9af7;
}
.weui-btn.weui-btn-right {
width: 50%;
border-radius: 0rpx 100rpx 100rpx 0rpx;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 短阴影
<view class='yb-btn'>
<view class='btn-shadow'></view>
<button class="weui-btn" bindtap='bindEnter'>确定</button>
</view>
2
3
4
.yb-btn {
position: relative;
}
.btn-shadow {
position: absolute;
width: 534rpx;
height: 48rpx;
background: #3e8af0;
box-shadow: 0 2px 10px 0 #3e8af0;
border-radius: 24rpx;
left: 50%;
margin-left: -267rpx;
bottom: 0rpx;
}
.weui-btn {
width: 646rpx;
height: 94rpx;
line-height: 94rpx;
background: #3e8af0;
border-radius: 100rpx;
font-size: 36rpx;
color: #fff;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 表格型列表
<view class='detail-record'>
<view class='titleTxt'>消费历史记录</view>
<view class='detail-record__header'>
<view class='detail-record__item f3'>名称</view>
<view class='detail-record__item'>购买时间</view>
<view class='detail-record__item'>到期时间</view>
</view>
<view class='detail-record__content'>
<block wx:for="{{detailInfo.versionModels}}" wx:for-index="index" wx:for-item="item" wx:key="item.id">
<view class='detail-record__item-content'>
<view class='detail-record__item f3'>{{item.name}}</view>
<view class='detail-record__item'>{{item.createdTime}}</view>
<view class='detail-record__item'>{{item.expiredTime||'-'}}</view>
</view>
</block>
<view class="weui-loadmore weui-loadmore_line" hidden="{{detailInfo!=''}}">
<view class="weui-loadmore__tips weui-loadmore__tips_in-line">暂无数据</view>
</view>
</view>
</view>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.detail-record {
padding: 0 10px 10px 10px;
}
.detail-record__header {
height: 36rpx;
line-height: 36rpx;
padding: 12rpx 0;
display: flex;
flex-direction: cloumn;
justify-content: center;
align-items: center;
background: #f7f7f7;
}
.detail-record__item {
flex: 2;
font-size: 13px;
color: #bbb;
}
.detail-record__item.f3 {
flex: 3;
}
.detail-record__item-content {
display: flex;
flex-direction: cloumn;
justify-content: center;
align-items: center;
padding: 32rpx 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 二维码
getSystemInfoSync获取系统信息 qrcodejs weapp-qrcode
< view class = 'invite-QRCode' >
<
canvas canvas - id = "mycanvas" / >
<
/view>
2
3
4
5
const app = getApp();
const weui = require('../../modules/weui/weui.js');
var QR = require("../../utils/qrcode.js");
var WINDOW_WIDTH_IN_RPX = 750;
Page({
/**
* 页面的初始数据
*/
data: {
codeUrl: '',
registerUrl: '',
url: '',
partner: encodeURIComponent(wx.getStorageSync('partner') || '')
},
onLoad: function(options) {
QR.qrApi.draw("https://qietuniu.com", "mycanvas", this.rpx2px(650), this.rpx2px(650));
},
rpx2px: function(val) {
var res = wx.getSystemInfoSync();
return res.windowWidth * val / WINDOW_WIDTH_IN_RPX;
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 圆形区域内移动小球
https://developers.weixin.qq.com/s/UwEnOkmZ71hU
const app = getApp()
const RADIUS = 65
const MIN_DISTANCE = 5 //至少位移到5才触发摇杆
Page({
data: {
transform: ""
},
startPoint: "",
startFn(e) {
let touch = e.touches[0]
this.startPoint = [touch.pageX, touch.pageY]
},
moveFn(e) {
let touch = e.touches[0]
let point = [touch.pageX, touch.pageY]
let differ = [point[0] - this.startPoint[0], point[1] - this.startPoint[1]]
let distance = Math.sqrt(differ[0] * differ[0] + differ[1] * differ[1])
if (distance > MIN_DISTANCE) {
// 摇杆始终切到边缘,实际位移距离无关紧要,只关心移动方向
let rate = RADIUS / distance
let position = [differ[0] * rate, differ[1] * rate]
this.setData({
transform: "transform: translate(" + position[0] + "px, " + position[1] + "px)"
})
}
},
endFn(e) {
this.setData({
transform: ""
})
},
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
< view class = "out"
bindtouchstart = "startFn"
bindtouchmove = "moveFn"
bindtouchend = "endFn" >
<
view class = "in"
style = "{{transform}}" > < /view> < /
view >
2
3
4
5
6
7
8
page {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.out {
width: 200px;
height: 200px;
border-radius: 50%;
border: 1px solid;
display: flex;
align-items: center;
justify-content: center;
}
.in {
width: 70px;
height: 70px;
background-color: gray;
border-radius: 50%;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 监听页面滚动停止
使用监听器
onPageScroll: function(e) {
clearTimeout(this.timeoutId);
this.isNotScroll = false;
// 设计时器以监听页面停止滚动
this.timeoutId = setTimeout(
function() {
this.isNotScroll = true;
delete this.timeoutId;
}.bind(this),
100
);
},
2
3
4
5
6
7
8
9
10
11
12
13
14
# 消息订阅流程
消息订阅使用场景具有局限性,无法订阅一次后无数次推送。只能限制在一段时间内具有推送能力。
# 微信授权
getCode() {
let _this = this
wx.login({
success: function(res) {
if (res.code) {
bindCode({
jsCode: res.code
}).then(res => {
if (res.status.code === 0) {
// 成功获取
} else {}
})
} else {
wx.showToast({
title: '获取用户登录态失败' + res.errMsg,
icon: 'none',
duration: 5000
})
}
}
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 消息模板
// 封装消息方法
subscribeMessage(data) {
subscribeMessage(data).then(res => {
if (res.status.code === 0) {
console.log('通知成功')
}
})
}
// 具体参数调用时传入
this.subscribeMessage({
type: 103,
data: {
thing7: {
value: wx.getStorageSync('tenantName') || '无'
},
date4: {
value: getDay('dayTime')
}
},
page: 'pages/order/main?code=' + code,
templateId: 'xxxx' // 公众号模板配置中获得
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 选择订阅
orderMsg() {
wx.requestSubscribeMessage({
tmplIds: ['xxx'],
success: function(res) {
if (res.errMsg === 'requestSubscribeMessage:ok') {
wx.showToast({
title: '您已订阅!',
icon: 'none',
duration: 2000
})
}
console.log('success')
},
fail(err) {
console.error(err)
}
})
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 多图上传(一)
正常图片上传,添加多张图片获得路径后统一提交
# 多图上传(二)
后端希望通过上传一个接口实现多图上传,做的时候很别扭,删除时多次调用接口性能不好,仅供参考。 痛点分析:
- upload上传必须有文件,可以拿一张假图片进行冒充
- 图片链接转成微信内的tempFilePath本地临时路径形式可以采用downloadFile
- 选择照片chooseImage,sizeType可以设置压缩,通过count属性实时计算能上传几张
- 上传使用uploadFile,多图采用uploadFile的complete方法,对比当前是否全部上传,完成则给当前列表赋值,未完成则递归调用直至完成
- 预览使用previewImage
wxml:
< view class = "container" >
<
view class = "subTitle" >
<
text > (图片数量不超过十张) < /text> < /
view > <
view class = "imgWrapper clearfix" >
<
!-- < view class = 'uploader-img flex justify-content-start'
wx: if = "{{pics}}" > -- >
<
view class = 'uploader-list'
wx: for = "{{pics}}"
wx: key = "item.length"
wx: if = "{{item}}" >
<
image class = "weui-uploader__img"
src = "{{item}}"
data - index = "{{index}}"
mode = "scaleToFill"
bindtap = 'previewImg' / >
<
view class = 'weui-uploader__delete'
bindtap = "deleteImg"
data - index = "{{index}}" > x < /view> < /
view > <
view class = "weui-uploader__input-box"
wx: if = "{{pics.length < maxNum }}" >
<
view class = "weui-uploader__input"
bindtap = "chooseImg" >
<
image src = "//imgw.pospal.cn/pospalUnion/images/camera.png" > < /image> <
view > 上传图片 < /view> < /
view > <
/view> < /
view > <
/view>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
js:
const app = getApp();
Page({
data: {
pics: [],
maxNum: 3,
showTxt: '上传'
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
this.getList()
},
// 获取图片
getList: function() {
wx.showLoading();
app.authRequest('xxxx', {},
(res) => {
wx.hideLoading();
if (res.status.code == 0) {
let {
imageUri1,
imageUri2,
imageUri3
} = res.userContractModel
let pics = []
if (imageUri1 != null) pics.push(imageUri1)
if (imageUri2 != null) pics.push(imageUri2)
if (imageUri3 != null) pics.push(imageUri3)
this.setData({
pics
})
} else {
app.showError(res.status.message);
}
}
);
},
// url=>tempFilePath,不是微信本地数据的就进行转换
changeUrl: function(data) {
if (data.indexOf('qietuniu.cn') == -1) {
return Promise.resolve(data)
} else {
return new Promise((resolve, reject) => {
wx.downloadFile({
url: data,
success: function(res) {
resolve(res.tempFilePath)
},
fail: function(err) {
reject(err)
}
})
})
}
},
//选择图片
chooseImg: function(e) {
var that = this,
pics = [...this.data.pics],
index = this.data.pics.length;
if (pics.length < that.data.maxNum) {
wx.chooseImage({
count: this.data.maxNum - pics.length, // 最多可以选择的图片张数,默认9
sizeType: ['compressed'], // original 原图,compressed 压缩图,默认二者都有
sourceType: ['album', 'camera'], // album 从相册选图,camera 使用相机,默认二者都有
success: function(res) {
var imgs = res.tempFilePaths;
// 判断是否达到总上限
for (var i = 0; i < imgs.length; i++) {
if (pics.length < that.data.maxNum) {
pics.push(imgs[i]);
}
}
that.setData({
pics: pics,
showTxt: '上传'
})
that.uploadImg(pics, index, pics.length)
},
});
} else {
wx.showToast({
title: `最多上传${this.data.maxNum}张图片` ,
icon: 'none',
duration: 3000
});
}
},
/**
*
* @param {*} data 数据
* @param {*} i 从i开始
* @param {*} endIndex 截止到这个长度,最后的index
*/
uploadImg: function(data, i = 0, endIndex = data.length) {
wx.showLoading({
title: `${this.data.showTxt}中...` ,
mask: true,
})
console.log('aaa', {
'updateImage1': i == 0 ? data[i] == null ? 2 : 1 : 0,
'updateImage2': i == 1 ? data[i] == null ? 2 : 1 : 0,
'updateImage3': i == 2 ? data[i] == null ? 2 : 1 : 0,
'updateImage4': i == 3 ? data[i] == null ? 2 : 1 : 0
})
var that = this
this.changeUrl(data[i] || 'https://imgc.pospal.cn/agent_api/attachment/null.jpg').then((filePath) => {
wx.uploadFile({
url: 'https://crmapi.pospal.cn/agent_api/api/wx/user/user/contract/update',
filePath: filePath,
name: `image${i+1}` ,
header: {
"Content-Type": "multipart/form-data",
'accessToken': wx.getStorageSync('SESSIONTOKEN') || null,
"apiKey": "xxxx",
'PSPLVISITORAUTO': 'API'
},
// 0表示不变,1表示更新,2表示抹除
formData: {
'updateImage1': i == 0 ? data[i] == null ? 2 : 1 : 0,
'updateImage2': i == 1 ? data[i] == null ? 2 : 1 : 0,
'updateImage3': i == 2 ? data[i] == null ? 2 : 1 : 0
},
success: function(res) {
if (JSON.parse(res.data).status.code !== 0) {
app.showError(JSON.parse(res.data).status.message);
}
},
fail: function(err) {
console.log('fail', err)
wx.hideLoading();
},
complete: () => {
i++;
if (i == endIndex || i > endIndex) { //当图片传完时,停止调用
console.log('执行完毕');
wx.showToast({
title: `操作成功!` ,
duration: 1500
});
let pics = [...this.data.pics]
this.setData({
pics
})
wx.hideLoading();
} else { //若图片还没有传完,则继续调用函数
that.uploadImg(data, i, endIndex); //递归,回调自己
}
}
})
})
},
// 删除图片
deleteImg: function(e) {
var that = this;
var pics = [...this.data.pics];
var index = e.currentTarget.dataset.index;
pics.splice(index, 1);
var endIndex = this.getLen(this.data.pics)
this.setData({
pics: pics,
showTxt: '删除'
})
that.uploadImg(pics, index, endIndex)
},
getLen: function(data) {
return (data.filter((item) => item != null)).length
},
// 预览
previewImg: function(e) {
const index = e.currentTarget.dataset.index;
const pics = [...this.data.pics];
wx.previewImage({
current: pics[index],
urls: pics
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
.container {
position: relative;
top: 0px;
left: 0px;
height: 100%;
padding: 40rpx 10rpx;
box-sizing: border-box;
}
.imgWrapper {
padding: 50rpx 0 0 60rpx;
}
.clearfix:after {
content: "";
display: block;
clear: both;
}
.subTitle text {
padding: 0rpx 40rpx;
font-size: 28rpx;
color: #aaa;
}
.uploader-img {
float: left;
}
.weui-uploader__input-box:before,
.weui-uploader__input-box:after {
display: none;
}
.weui-uploader__img {
width: 260rpx;
height: 260rpx;
}
.weui-uploader__input-box {
border: 1px solid #e6e7fb;
border-radius: 2px;
margin-right: 50rpx;
margin-bottom: 20rpx;
width: 260rpx;
height: 260rpx;
box-sizing: border-box;
}
.weui-uploader__input {
text-align: center;
opacity: 1;
}
.weui-uploader__input image {
width: 64px;
height: 48px;
margin: 20px 0 10px 0;
}
.weui-uploader__input view {
font-size: 18px;
color: #757575;
height: 20px;
line-height: 20px;
}
.weui-uploader__delete {
position: absolute;
height: 30px;
line-height: 28px;
width: 30px;
text-align: center;
background: rgba(0, 0, 0, 0.70);
color: #fff;
font-size: 16px;
border-radius: 50%;
top: -10px;
right: -10px;
}
.weui-uploader__file {
position: relative;
margin-top: 9px
}
.uploader-list {
position: relative;
float: left;
margin-right: 50rpx;
margin-bottom: 50rpx
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93