个人案例
- 琴房易约
商洛学琴房预约系统,高效省时省力解决琴房登记问题
琴房易约-琴房预约系统扫码体验
- 商图助理
商洛学院图书馆座位系统,高效省时省力解决图书馆座位利用率低,私人物品长时间占座问题。扫码落座,学习时间累加排名.
商图助理-图书馆座位系统扫码体验
- Skyline|小程序手势:让半屏弹窗更顺滑
在小程序页面开发中,我们经常用半屏弹窗来进来内容展示,例如:微信开放社区切换主页、加入购物车的选项页、文章留言区等等。 [图片] 常见的半屏弹窗展示逻辑是这样的: 打开弹窗:点击 “打开弹窗” 按钮展示弹窗关闭弹窗:点击“关闭按钮” or 遮罩层 关闭弹窗当我们想在半屏弹窗加一些交互动画时,可以监听节点的 touch 事件来做一些手势判断,进而处理拖拽事件。但是这种方式实现的滚动动画容易卡顿,出现延迟的情况,效果并不理想。 为了丰富小程序的交互体验,我们内置了一批手势组件,可以帮助开发者更好的实现交互动画的效果。 下图演示使用手势的半屏弹窗下拉效果与普通半屏下拉的对比。 当内部评论列表往下拉到顶部时,变为半屏的下拉,可直接下拉关闭弹窗。 [图片] 我们来看下这种操作是怎么实现的 在上面评论列表的半屏弹窗中会有一个 scroll-view 滚动组件,在 scroll-view 中会有滚动事件,当滚动到顶部时,我们希望有整个半屏的下拉事件。 所以我们需要在半屏的最外层放置一个拖动手势组件 pan-gesture-handler 由于拖动组件内部的 scroll-view 也是可以滚动的,所以这里需要进行一个手势协商的处理,就是什么条件下由哪个组件来响应手势。 当手势往下 ⬇️ 滚动时,此时判断内部 scroll-view 滚动条的位置 滚动条处于顶部:外层 pan-gesture-handler 响应滚动,此时半屏往下拖动至关闭半屏滚动条不处于顶部:内层 scroll-view 响应滚动,此时内部列表往上滚[图片] 当手势往上 ⬆️ 滚动时,此时判断半屏的位置 半屏不完全打开时:外层 pan-gesture-handler 响应滚动,此时半屏往上拖动至完全打开半屏半屏完全打开时:内层 scroll-view 响应滚动,此时内部列表往下滚[图片] 我们来看一下代码的实现,这里用到的手势组件 pan-gesture-handler(拖动时触发)和 vertical-drag-gesture-handler(纵向滑动时触发),手势组件有以下属性 on-gesture-event:手势回调事件should-response-on-move:是否响应当前手势的 move 阶段simultaneous-handlers:指定需要协商的手势是哪几个,下面演示表示 pan 和 scroll 协同触发。native-view:代理的原生节点,这里 scroll-view(scroll-y) 内有个 vertical-drag 手势,scroll-view 自身无法处理,需要被代理出来 ... 接着,我们看看在页面 js 中怎么处理手势。 在手势处理的回调中因为会改变半屏的状态值,所以这里的回调函数采用 worklet 函数,worklet 函数运行在 UI 线程,使得小程序可以做到类原生动画般的体验。 // page.js // shared 创建的变量为共享变量,可在 UI 线程和 JS 线程间同步 this.transY = wx.worklet.shared(1000) this.scrollTop = wx.worklet.shared(0) this.startPan = wx.worklet.shared(true) // shouldPanResponse 和 shouldScrollViewResponse 用于 pan 手势和 scroll-view 滚动手势的协商 shouldPanResponse() { 'worklet' return this.startPan.value }, shouldScrollViewResponse(pointerEvent) { 'worklet' // transY > 0 说明 pan 手势在移动半屏,此时 scroll-view 滚动不应生效 if (this.transY.value > 0) return false const scrollTop = this.scrollTop.value const { deltaY } = pointerEvent // deltaY > 0 是往上滚动,scrollTop <= 0 是滚动到顶部边界,此时 pan 开始生效,scroll-view 滚动不生效 const result = scrollTop <= 0 && deltaY > 0 this.startPan.value = result return !result }, // pan 手势处理 handlePan(gestureEvent) { 'worklet' if (gestureEvent.state === GestureState.ACTIVE) { const curPosition = this.transY.value const destination = Math.max(0, curPosition + gestureEvent.deltaY) // 改变半屏的位置 this.transY.value = destination } // 其他手势状态的处理,如滚动结束时计算半屏处于打开还是关闭的状态 } 目前,同程旅行 已经上线了手势结合半屏的效果 体验路径:酒店查询 - 选择酒店 - 选择入住人 - 新增入住人 [图片] 普通半屏结合手势代码片段:https://developers.weixin.qq.com/s/lx0RH1mD7rGj 手势除了在普通半屏的应用之外,也可以实现分段式半屏。下面演示的分段式半屏比普通半屏的判断条件更多一些。 判断条件同普通半屏类似,根据手势方向 和 分段式半屏当前的位置来判断是响应分段式半屏还是内部列表,响应分段式半屏是改变到哪一个位置。 [图片] 这里与普通半屏不同的是我们还改变了地图的缩放级别(scale) 因为 worklet 函数是在 UI 线程运行的,当要改变 data 值时,需要通过 wx.worklet.runOnJS 调回 JS 线程。 // page.js // 设置 map scale // 运行在 JS 线程 setMapScale(scale) { this.setData({ scale }) }, // worklet 函数,运行在 UI 线程 scrollTo(toValue) { 'worklet' let scale = 18 if (toValue > screenHeight / 2) { scale = 16 } // 从 UI 线程调回 JS 线程 wx.worklet.runOnJS(this.setMapScale.bind(this))(scale) this.transY.value = timing(toValue, { duration: 200 }) }, // 处理拖动半屏的手势 handlePan(gestureEvent) { 'worklet' // 滚动半屏的位置 if (gestureEvent.state === GestureState.ACTIVE) { // deltaY < 0,往上滑动 this.upward.value = gestureEvent.deltaY < 0 // 当前半屏位置 const curPosition = this.transY.value // 只能在 [statusBarHeight, screenHeight] 之间移动 const destination = clamp(curPosition + gestureEvent.deltaY, statusBarHeight, screenHeight) if (curPosition === destination) return // 改变 transY,来改变半屏的位置 this.transY.value = destination } if (gestureEvent.state === GestureState.END || gestureEvent.state === GestureState.CANCELLED) { if (this.transY.value <= screenHeight / 2) { // 在上面的位置 if (this.upward.value) { this.scrollTo(statusBarHeight) } else { this.scrollTo(screenHeight / 2) } } else if (this.transY.value > screenHeight / 2 && this.transY.value <= this.initTransY.value) { // 在中间位置的时候 if (this.upward.value) { this.scrollTo(screenHeight / 2) } else { this.scrollTo(this.initTransY.value) } } else { // 在最下面的位置 this.scrollTo(this.initTransY.value) } } }, 分段式页面代码片段:https://developers.weixin.qq.com/s/fw0U31mI7bGf 半屏的交互除了在页面内实现,也能跨页面实现,如常见的下沉式半屏交互。其中,半屏效果与上述实现类似,而前一页面的下沉实现需要结合自定义路由 后面的文章中我们会介绍自定义路由结合手势怎么去实现下沉式半屏效果,不仅如此,还有很多类原生的页面切换效果都能通过自定义路由实现 [图片]
2023-08-03 - Skyline|小程序页面转场动画
开发者A:小程序跳转时,页面切换效果可以自定义实现吗? 开发者B:搞个单页自己写呢 开发者A:这个写起来代码量有点大,而且放单页里面代码太复杂了🥹 官方:来啦来啦~小程序页面转场动画可以使用小程序自定义路由来实现,赶紧往下 ⬇️ 看~ 我们先来看下有无自定义路由的效果对比 没有自定义路由:只能使用默认路由切换效果,从右到左推入页面使用自定义路由:支持自定义转场动画,示例下沉式路由效果[图片] 在使用默认路由的时候,只需要调用 wx.navigateTo 即可,而自定义路由,则需要先声明 1、通过 wx.router.addRouteBuilder( 命名,builder 函数 ) 来声明自定义路由 2、页面跳转新增 routeType 参数,值为上一步的命名 [图片] 接着,我们来看下声明自定义路由中 builder 函数,这个函数主要是定义两个动画 定义页面推入动画:结合 推入进度、推入状态 等参数,由开发者自定义计算得来定义页面被推出动画:结合 被推出进度、被推出状态 等参数,由开发者自定义计算得来[图片] 当打开一个新页面的过程中,就会触发自定义路由动画,此时有两个动画 新页面:打开动画旧页面:隐藏动画下图演示在页面 A 打开 页面 B,打开过程的动画效果: 新页面:页面 B 半屏打开旧页面:页面 A 页面下沉[图片] 动画除了上述讲的动画效果之外,还有一个关键的点就是动画曲线,动画曲线可以让动画效果更加丰富。 👇 下面我们可以看出来,相同的动画效果,但是使用不同的动画曲线,展示出来的页面切换效果是很不一样的。worklet 支持了常见的动画缓动函数 Easing,开发者可以根据业务需求实现页面动画切换效果。 [图片] 那么我们就来看一下页面切换动画的代码是怎么实现的~ 页面的切换动画通过 worklet 函数来实现的,worklet 函数运行在 UI 线程,使得小程序可以做到 类原生动画般的体验。 我们先来看下这里设计的新页面打开时的动画,在页面打开的过程中,开发者可以收到一个 primaryAnimation 的参数,这个参数表示当前页面从 0 到 1 打开的进度,此时,我们通过 handlePrimaryAnimation 来实现页面打开过程我们希望页面展示的动画效果,例如:改变页面高度、圆角、与顶部的偏移距离等。 这样,就实现页面展示的动画效果。 // 新页面:页面 B 半屏打开 const handlePrimaryAnimation = () => { 'worklet' // primaryAnimation 为 builder 函数的参数,表示当前页面从 0 - 1 展示动画的进度 // primaryAnimation 为 sharedValue 类型,当 primaryAnimation 变化时,这个函数就会被执行 let t = primaryAnimation.value // 非手势触发时,可以通过动画曲线 easeInToLinear 来改变动画的进度值 // worklet 支持了常见的动画缓动函数,开发者可以根据业务需求实现需要的页面切换动画效果 if (!userGestureInProgress.value) { t = wx.worklet.Easing.bezier(0.35, 0.91, 0.33, 0.97).factory()(t) } const top = 0.12 // 半屏页面距离顶部的距离比例 const selfHeight = (1 - top) * screenHeight // 半屏页面高度 const marginTop = top * screenHeight // 半屏页面距离顶部的距离 const translateY = selfHeight * (1 - t) // 页面动画过程中的纵向偏移值 // 返回 AnimatedStyle,改变页面展示 return { marginTop: `${marginTop}px`, borderRadius: '10px', height: `${selfHeight}px`, transform: `translateY(${translateY}px)`, } } 同样的,页面隐藏跟页面展示是类似的,开发收到的是 secondaryAnimation 参数,表示的是页面从 1 到 0 的关闭进度,这里我们通过 [代码]handleSecondaryAnimation 来实现页面隐藏的效果。[代码] 注意:handleSecondaryAnimation 表示下一页面推入时,当前页面的隐藏动画,在下沉式动画这个案例中,handleSecondaryAnimation 是旧页面的,而 handlePrimaryAnimation 是新页面的。 // 旧页面:页面 A 页面下沉 const handleSecondaryAnimation = () => { 'worklet' // secondaryAnimation 为 builder 函数的参数,表示下一个页面推入时,当前页面从 1 - 0 隐藏动画的进度 // secondaryAnimation 为 sharedValue 类型,当 secondaryAnimation 变化时,这个函数就会被执行 let t = secondaryAnimation.value // 非手势触发时,可以通过动画曲线 fastOutSlowIn 来改变动画的进度值 if (!userGestureInProgress.value) { t = wx.worklet.Easing.bezier(0.4, 0.0, 0.2, 1.0).factory()(t) } const top = 0.1 // 页面距离顶部的距离比例 const scaleRatio = 0.08 // 缩放比例 const translateY = screenHeight * (top - 0.5 * scaleRatio) * t // 页面动画过程中的纵向偏移值 const scale = 1 - scaleRatio * t // 缩放过程中的比例 const radius = 12 * t // 页面圆角 // 返回 AnimatedStyle,改变页面展示 return { borderRadius: `${radius}px`, transform: `translateY(${translateY}px) scale(${scale})`, } } 实现完动画切换的函数之后,我们需要包装到 builder 函数中并注册这个 builder 函数。 // 注册 builder 函数 wx.router.addRouteBuilder("HalfScreenDialog", HalfScreenDialogRouteBuilder) wx.router.addRouteBuilder("ScaleTransition", ScaleTransitionRouteBuilder) // 实现页面 B 的 builder 函数:页面打开时半屏打开 const HalfScreenDialogRouteBuilder = ({ primaryAnimation }) => { return { handlePrimaryAnimation // 页面 B 打开时的动画,上文中实现的 handlePrimaryAnimation 函数 } } // 实现页面 A 的 builder 函数:下一个页面打开时,当前页面下沉 const ScaleTransitionRouteBuilder = ({ primaryAnimation, secondaryAnimation }) => { return { handlePrimaryAnimation, // 页面 A 打开时的动画 handleSecondaryAnimation // 页面 B 隐藏时的动画,上文中实现的 handleSecondaryAnimation 函数 } } 在文章开头我们知道,声明完自定义路由之后,需要在页面跳转时指定路由类型。 到这里,通过页面跳转,返回按钮已经达到我们要的效果了。 // home.js // 首页打开页面 A, wx.navigateTo({ url: 'pageA', routeType: 'ScaleTransition', }) // page.js // 页面 A 打开页面 B wx.navigateTo({ url: 'pageB', routeType: 'HalfScreenDialog', }) 我们在体验原生页面切换时,手势也是顺滑切换的重要组成部分,在上一篇 小程序手势:让半屏弹窗更顺滑 我们已经了解手势的使用,那么我们这里给页面绑定手势,支持向右、向下拖动页面返回。 我们在页面最外层嵌套一个手势组件 horizontal-drag-gesture-handler(横向滑动时触发) 当手势向右滑动时,根据触摸位置改变页面当前的状态 触摸中:页面随手指拖动触摸结束:根据手势速度和位置判断关闭还是打开页面// .wxml // .js // 根据手势状态改变页面展示状态 // this.customRouteContext 中包含当前页面定义路由 builder 时的全部变量 handleHorizontalDrag(gestureEvent) { "worklet"; if (gestureEvent.state === GestureState.BEGIN) { // 触摸开始 const { startUserGesture } = this.customRouteContext; startUserGesture(); } else if (gestureEvent.state === GestureState.ACTIVE) { // 触摸中,实现跟随手指拖动页面效果 const delta = gestureEvent.deltaX / windowWidth; const { primaryAnimation } = this.customRouteContext; const newVal = primaryAnimation.value - delta; primaryAnimation.value = clamp(newVal, 0.0, 1.0); } else if (gestureEvent.state === GestureState.END) { // 触摸结束 const { stopUserGesture, didPop } = this.customRouteContext; ... didPop(); // 退出页面调用 stopUserGesture(); // 结束必须调用 } else if (gestureEvent.state === GestureState.CANCELLED) { // 触摸取消 } } 添加完手势之后,就可以通过手势关闭页面了~ [图片] 除了案例中实现的下沉式半屏效果,自定义路由可以根据开发者需要自行定制动画。 目前,官方提供了几个常用的路由效果供大家使用,mark 这个 代码片段 即可使用。
2023-08-03 - Skyline|原生级卡片转场,小程序轻松实现
在上一篇文章《在小程序中实现原生相册》中,我们学习了自定义路由搭配共享元素实现的原生相册效果,共享元素可以让用户在体验小程序时视觉关联性更强。 除了相册实现之外,常见的卡片转场也非常适合。 [图片] ⬆️ 演示效果:默认动画 vs 卡片转场动画 👇 下面我们来看看卡片转场中通过 共享元素 + 自定义路由 来实现无痕跳转。 [图片] 这里的转场稍微有点复杂,涉及到以下 3 个点 旧卡片:图片放大、内容渐隐新页面:按比例放大、页面渐显手势搭配1、旧卡片:图片放大、内容渐隐 在本示例中,列表页采用的是 scroll-view 瀑布流布局的实现。 [图片] 这里我们的共享元素是卡片,即 grid-view 中的内容 card,卡片包括 图片、内容描述。 [图片] 默认情况下,共享元素是整个节点进行飞跃的,由于前后页面的图片元素一致但文本内容不一致, 导致在第一帧或者最后一帧会有跳动的效果。 为了让转场动画更加自然,我们需要在飞跃的过程中渐隐旧卡片的内容描述。 [图片] 在这里,我们需要先用 this.applyAnimatedStyle 来给对应的节点绑定 worklet 驱动动画。 .card_wrap 节点:整个卡片按比例放大.card_desc 节点:内容描述渐隐[图片] 关于动画执行的时机,我们可以通过配置项修改。 immediate:设置是否立即执行驱动动画flush:shareValue 更新时,applyAnimatedStyle 的 updater 函数刷新时机在本例中,需要保证共享元素的图片与目标页面图片位置重叠,所以 flush 设置 sync 在当前时间片刷新。 [图片] 绑定完驱动动画之后,我们需要给共享元素绑定帧回调事件,根据当前动画进度改变共享变量的值来驱动共享动画 [图片] 2、新页面:按比例放大、页面渐显 新页面在路由中的动画,需要在自定义路由中进行配置。关于自定义路由的更多介绍,可参考《小程序页面转场动画》 在路由动画过程中,我们将上一步的共享元素帧回调拿到 begin、end 的值,然后结合动画进度 t 计算得出新页面的位置、缩放比例。 还有根据动画进度,设置页面渐显,与前面的卡片渐隐承接。 [图片] 3、手势搭配 学习过我们前面的文章的同学都知道,自定义路由经常需要结合页面手势,来实现手势返回,关于手势的基础知识可参考《小程序页面转场动画》 [图片] 这里我们希望手势缩小整个当前页面,所以这里手势返回时只在当前页面做手势动画即可。 在页面详情页的最外层,嵌套一个手势组件 pan-gesture-handler,当手势拖动时根据手势的位置改变整个页面(通过 #fake-host 控制)的位置和大小来达到拖动的效果。 [图片] 同样绑定页面驱动动画,通过 applyAnimatedStyle 给 #fake-host 绑定驱动动画,当共享变量 transX、transY 等变化时则自动改变 transform 来驱动 #fake-host 缩小。 [图片] 接着绑定手势事件,根据手势拖动时拿到位置信息改变共享变量 transX、transY 的值。 [图片] 最后我们需要设置背景颜色透明,来达到类似把卡片拖回列表的视觉效果,更好的减少页面切换感~ [图片] 一个自定义路由的页面会有 3 层可以设置到背景色,要做到透明的效果需要将 3 个背景色都设置为透明。更多自定义路由背景色的详情参考官方文档。 [图片] 想要试试卡片转场的无恒效果~扫描 ⬇️ 下方小程序码即可体验。 如果你也想在小程序中实现卡片转场动画,mark 下这个 源码 直接接到到你的小程序吧~ [图片]
2023-08-03 - H5嵌套到小程序、APP,还能埋点统计?看完这篇便知晓
赠人玫瑰,手有余香。先点赞,再查看哇。 业务需求 最近产品需要做一个埋点,无论是预览商品还是预览企业信息都需要统计改用户停留的时长,但是这个里有个特点,就是商品和企业信息都是用H5写的,最终这个H5页面会嵌套自家产品小程序和自己产品安卓端和IOS端,由于嵌套的原因,需要处理的东西确实会比较复杂,并且还是嵌套到三个不同的宿主环境里,想想都会头疼,这样会导致有些事件H5是无法监听到的。 碰到的难题 上一篇文章,我有讲解在H5中,如何解决关闭浏览器发送统计信息及测试是否发送成功的问题;使用[代码]navigator.sendBeacon[代码]发送接口及[代码]window.addEventListener('unload', function(){}, false)[代码]来监听是否关闭浏览器,更多详情点我 这里将要讲解的难点有2个: 1.嵌套进去的H5页面,在APP或小程序中,用户把软件切换到后台我们都无法监听。 2.用户在使用嵌套的小程序和IOS宿主环境中点击左上角自带返回按钮,H5都无法监听到用户退出了当前H5页面。 解决思路 解决上面的难点之前,我想讲讲,统计时间预览怎么算,如果我不猜错,估计很多人都会和我一开始看见需求一样,什么统计预览时间?搞个定时器?这样会不会很消耗性能?我的代码会不会跑着跑着就蹦?一连串的疑问。 其实统计时间并没有那么难,我们只需要记录一次即可,我们在进入H5页面时,我们就new一个时间戳[代码](new Date).getTime()[代码]把它保存到全局,如何在要调接口统计时长时,我们在[代码](new Date).getTime()[代码]弄一个时间戳减去进来时保存的时间戳即可得到一个预览时间,这里相减是毫秒,自己转化下即可。 解决完时间统计问题,我们要来解决今天的难点问题。 解决这两个难点问题,我们H5主要使用一个监听事件[代码]visibilitychange[代码],这个时间可以监听页面是否隐藏,下面我们详细介绍: visibilitychange 当其选项卡的内容变得可见或被隐藏时,会在文档上触发 [代码]visibilitychange[代码] (能见度更改)事件。 MDN文档有些2点注意事项 当 visibleStateState 属性的值转换为 [代码]hidden[代码]时,Safari不会按预期触发[代码]visibilitychange[代码]; 因此,在这种情况下,您还需要包含代码以侦听 [代码]pagehide[代码] 事件。 出于兼容性原因,请确保使用 [代码]document.addEventListener[代码]而不是[代码]window.addEventListener[代码]来注册回调。 Safari <14.0仅支持前者。 重要代码 [代码]document.addEventListener("visibilitychange", function() { if (document.visibilityState === 'visible') { // 页面被显示 // backgroundMusic.play(); } else { // 页面被隐藏 // backgroundMusic.pause(); } }); [代码] 逻辑思路 全局设置一个变量用于记录初始时间,当页面隐藏(例如:切换后台)监听到[代码]visibilitychange[代码]下的[代码]document.visibilityState === 'hidden'[代码]时,我们就把时间统计发送到服务器;当[代码]document.visibilityState === 'visible'[代码]时,我们又开始新的统计时间,这样就可以精准把需要统计的时间完成到服务器。 此时新的问题开始出现了,就是嵌套H5后,在ios系统中,直接点击系统右上角的返回按钮,是无法触发[代码]visibilitychange[代码]事件的,这就让我们很困惑了,有人会提出,那不是可以监听关闭浏览器或者关闭网站事件,不就可以解决?我只能说这些都无法监听的,你想到的事件基本都不会触发H5页面中的所以事件安静的有点可怕,这种情况也只有ios才会出现,安卓手机和鸿蒙手机都不会出现这样的情况。 为了解决这种情况,我思索了很久很久差点把自己脑转晕,最后想到的解决方案是: H5嵌套APP 如果嵌套ios的APP里面,当用户点击左上角返回按钮,由APP生命周期里调用H5方法,在H5中window里挂载自己写好的一个函数,当调用自己挂载的函数时发送统计上传到服务器; H5嵌套小程序 如果嵌套到小程序的H5,我们不能这么写了,因为小程序没办法在页面销毁时调用H5页面的任何东西,这里不止页面销毁调不了H5方法而是任何时候都无法调用直接调H5里面的方法,所以这里我们需要使用微信提供的一个方法[代码]wx.miniProgram.postMessage[代码]文档详情,我们应该怎么合理使用这个方法呢?我的想法是,每此进入需要记录时间的页面使用该方法,把需要上传的参数及时间记录并赋值给[代码]wx.miniProgram.postMessage({data:{}})[代码],如何当用户点击小程序原生返回时,我们可以在原生里面写一个方法用于接收该参数; [代码] <web-view bindmessage="bindmessage"></web-view> // js bindmessage(){ // H5页面接收发送回来的参数 } wx.miniProgram.postMessage({ data: 'foo' }) [代码] 我们就可以把拿到的参数直接在小程序内完成发送统计; 总结 其实要解决这些问题也不难,难在怎么跳出思维,不能一成不变的认为只能在H5监听所有事件或者完成所有操作。 回顾一下:我们使用的主要代码有 [代码]// 在H5页面中,监听事件 document.addEventListener("visibilitychange", function() { if (document.visibilityState === 'visible') { // 页面被显示 // backgroundMusic.play(); } else { // 页面被隐藏 // backgroundMusic.pause(); } }); // 用于H5页面发送给小程序的内容 wx.miniProgram.postMessage({ data: 'foo' }); // 用于iosAPP端调用的方法 window.logData = function() {}; [代码] [代码]// 小程序原生代码绑定监听H5回传参数 <web-view bindmessage="bindmessage"></web-view> [代码] [代码]bindmessage(){ // H5页面接收发送回来的参数 } [代码] 就这些代码,我们就完成我们想要的效果了。 创作不易,喜欢记得点赞,如有错误请评论区留言。
2021-08-09 - 微信开发者工具下载的 sourcemaps 怎么用。
什么是 Sourcemaps uglifyjs、bable 等工具会对 源代码 进行编译处理生成编译后的代码(下称目标代码),而 sourcemaps 就是保留了目标代码在源代码中的 位置信息 --------- 大神分割线 --------- 如何解读 Sourcemaps Sourcemaps 是一个 json [代码]{ "version": 3, "sources": ["a.js", "b.js"], // 源文件列表,这个表示是由 a.js 和 b.js 合并生成 "names": ["myFn", "test"], // 如果开启了变量名混淆,这里会保留变量名在源文件中名字信息 "sourcesContent: [], // 可选项,保存源码信息,顺序与 sources 字段对应,chrome 的 sources 面板中源码使用了这个字段的内容进行展示 "sourceRoot": "", // 源文件所在的目录信息 "file": "dist.js", // 可选,编译后的文件名 "mappings": "" // 这个是重点,是目标代码和源文件的位置的映射关系 } [代码] mappings 目标文件"行"的信息 mappings 是使用 ; 分隔的,每个部分对应目标代码的行 如: “;AAAA;AAAA,BBBB;;” 本例子目标文件有 4 行 第 0 行和第 3 行没有源文件对应信息,所以这两行是编译过程中加入的代码 目标文件的"列"信息 如: “AAAA,CAEA,CAEA;” ‘,’ 表示行内的位置信息分隔符 本例表示目标文件的这一行有三个有效的位置信息。 位置信息的第一位表示目标文件的列的 偏移 信息 本例中,表示列的信息是 ‘A’、‘C’、‘C’,对应的数字为 0、+1、+1,(vlq 编码,在线编解码工具) 注意,这个是偏移信息; 列数从 0 开始,依次累加偏移值可以算出当前的位置信息对应的真正的列 所以本例中表示的是目标文件的第 n 行中的第 0 列,第 1 列,第 2 列(没错是第 2 列) 源文件的信息 如:‘AAAA;ACAA;ADAA;’ 位置信息的第二位表示源文件的信息,本例子中是 ‘A’、‘C’、‘D’,对应数字是 0、+1、-1 如果 sourcemaps 中的 sources 字段只有一个文件的话,那么位置信息中第二位一直是 A(不需要偏移) 假设 sourcemaps 中 sources: [‘a.js’, ‘b.js’] 本例的意思是 AAAA: 目标文件第 0 行第 0 列 对应 第 0 个文件 a.js ACAA; 目标文件第 1 行第 0 列 对应 第 1 个文件 b.js ADAA; 目标文件第 2 行第 0 列 对了 第 0 个文件 a.js (偏移是 -1 又回到了 a.js) 源文件的行信息 位置信息的第三位表示源文件中的行的信息, 理解了位置偏移的概念,我们很容易理解 如:‘AACA,CACA;AACA;‘ 那么 AACA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 1 行 CACA: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 1+1 行 AACA:目标文件的第 1 行第 0 列 对应 第 0 个文件的第 1 行 (注意:’;’ 后的行列偏移信息归 0) 源文件中的列信息 位置信息的第四位表示源文件中的列的信息 如:'AAAA,CAAC;' 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列 CAAC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列 位置信息的第五位 第五位表示变量的偏移,对应 sourcemaps 中的 names 字段,表示目标文件中的变量名对应域源文件中的变量 如:’AAAA,CAACC;AAAAD;' sourcemaps 中 names 字段是 [‘a’, ‘b’] 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,没有变量的信息 CAACC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列,有变量信息,变量在源文件中是 ‘b’ (0+1=1) AAAAD: 目标文件的第 1 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,有变量信息,变量在源文件中是 ‘a’ (1-1=0) --------- 大神分割线 --------- 怎么使用 Sourcemaps Q: 线上小程序报错,我怎么通过 sourcemaps 还原到源代码中? A: 如报错 appservice.js 1:15000, 表示目标文件第一行 第 15000 列位置报错。根据上文介绍的,通过 mappings 字段算。 Q: 不会。 A: 如果你会写代码的话,参考下边 [代码]import fs = require('fs') import {SourceMapConsumer} from 'source-map' async function originalPositionFor(line, column) { const sourceMapFilePath = '如果你不真的替换的成 sourcemaps 在硬盘中的位置,那你还是放弃自己写代码吧。 ' const sourceMapConsumer = await new SourceMapConsumer(JSON.parse(fs.readFileSync(sourceMapFilePath, 'utf8'))) return sourceMapConsumer.originalPositionFor({ line, column, }) } originalPositionFor(出错的行,出错的列) [代码] Q: 不会写代码 A: 下载最新版的开发者工具,菜单-设置-拓展设置-调试器插件 [图片] [图片] Q: 为啥都是 null? A: 每个小程序版本都应该对应一个sourcemap文件。 运营中心那里下载的 sourcemap 是对应线上最新的小程序版本。但运营中心的报错集合了多个小程序版本。拿旧小程序版本的报错信息,和最新版本的 sourcemap,是匹配不出的。开发者工具和ci 上传的时候,会提示下载对应版本的 sourcemap 信息,可以自助保存。 [图片] Q: 怎么确定有没有版本对应上 A: 下载的 sourcemap 中有个 wx 字段,标明了该 sourcemap 文件对应小程序版本号。 [图片] [图片] 前提 1.确保发生错误的小程序版本和下载回来的 sourcemap 版本是一致的。 a. 下载 sourceMap 文件,可在 mp 后台或开发者工具上传成功弹窗下载 2.确保 map 文件和发生错误的 js 文件是对应的。sourcemap 的目录和文件说明 a. APP 是主包,FULL 是整包(仅在不支持分包的低版本微信中使用),其他目录是分包 b. 每个分包下都有对应的 app-service.js.map 文件。 c. 如果是使用了按需注入特性(app.json中配置了lazyCodeLoading),那么每个分包下还会有 appservice.app.js.map(对应分包下非页面的js),和所有页面的 xxx.js.map 以上事情都确保正确之后,还是出现行列号匹配不出来的情况。那就需要进一步排查。 线上运行的小程序 sourcemap 文件是怎么生成的? 处理流程:源码 [ a.js a.js.map b.js b.js.map ] -> 开发者工具(JS转 ES5,压缩)-> 微信后台(合并 js 文件)[ appservice.app.js appservice.app.js.map]。 注意:如果源码在交给工具之前是经过了 webpack 等打包工具的处理,那源码这里需要有 map 文件。否则不需要存在 map 文件。 可以看出,map 文件经过三个步骤的处理,每个步骤都有可能导致出错,因此开发者需要先排查,是否是前两个步骤出错导致的 map 文件失效的。 如何排查前两个步骤产生的 map 文件是否有问题。 1.排查 a.js.map 文件是否有问题。 a. 可以在 a.js 的代码中写一下 throw new Error(‘test sourcemap’)。 b. 使用了 webpack 的情况下,要构建为生产环境的版本。 c. 在开发者工具模拟器中运行对应的页面,看看控制台中的报错,错误行列号是否能正常映射到源文件。 2.排查 开发者工具(JS转 ES5,压缩)步骤是否有问题。 在排查完第一步的基础上,点击预览,用微信上扫码预览,并打开调试 vConsole 功能,检查 vConsole 中是否有报错信息,检查报错信息中的行列号是否能正常映射到源文件。 如何排查 微信后台(合并 js 文件)是否有问题。 a. 一定要先排查完前两个步骤再来排查这一步,一般情况下,这一步是不会出错的。 b. 如果有问题,也只会导致 map 文件中的行号信息出现偏移。比如 Error 信息中显示报错地址是 100: 200,行号是 100。那么你可能直接用 100: 200 在 map 文件中搜索不出信息,但是如果 用 150: 200 就可以搜索出来,说明行号偏移了 50。那其他报错也可以偏移 50 后再进行搜索就找到结果。 c. 怎么排查偏移了多少?可以结合 error.message 的内容,初步判断大概错误的内容是什么。把对应的 map 文件放到这个网站上 source-map-visualization 进行搜索,找出哪些相同列号的地方。再结合 error.message 的内容进行判断。 d. 如果排查到是这一步导致的问题,请在社区上联系我们,我们会在后续版本进行修复。 依旧排查不出原因? 先整理一下按照上述步骤排查的结论,再在社区上联系我们协助
2023-02-10 - 如何从QQ群引流进微信群/关注公众号/视频号小商店/小程序?
[图片] 01什么是生成外链? 假如你创建了一个QQ群,因为业务发展想将这些成员倒流到微信群,将群二维码发送到群里太麻烦,用户还要保存二维码再微信相册扫码。 假如你在知乎有一群粉丝,你想将粉丝拉进自己的微信粉丝群,难道还是让用户保存二维码再微信相册扫码? 外链替你解决倒流的烦恼,使用【链接工具】小程序生成群二维码链接,再生成外链,将外链直接发送至QQ、知乎、头条、支付宝、浏览器等可访问链接的平台,用户只需点击链接可直接唤醒微信进入你需要倒流的群二维码页面,用户只需要长按识别二维码就可以进群。 直接进入【链接工具】小程序点这里 默认进入小程序首页,请用手机打开 以下我使用引流非微信平台用户关注我的视频号作为案例: 02生成外链如何使用? 1.使用【链接工具】首页点击【生成外链】 [图片] 2.输入文章链接与外链标题 文章链接可使用二维码转链后再添加 若使用自己公众号文章请先关联此小程序 [图片] 3.生成外链成功后一键复制 [图片] 4.发送至QQ(可发送至其他平台) [图片] 5.点击链接会直接唤醒微信 [图片] 6.直接触达引流界面 [图片]
2021-01-11 - 小商店商品ID优惠券ID怎么看?
01如何查看带货商品ID? 微信搜索打开【小商店助手】小程序进入首页 ↓ 带货收入->我带的货 ↓ 每个商品左上角编号为该商品ID [图片] [图片] [图片] 02如何查看优惠券ID? 个人版/企业版/组件版小商店 ↓ 电脑端打开https://shop.weixin.qq.com ↓ 营销中心->优惠券 ↓ 每个优惠券批次号就是该优惠券ID [图片] 03如何查看商品ID? 个人版小商店 ↓ 微信搜索打开【小商店助手】小程序进入首页 ↓ 我的商品->商品管理 ↓ 每个商品左上角编号为该商品ID [图片] [图片] 组件版/企业版小商店 电脑端打开https://shop.weixin.qq.com ↓ 商品管理->商品列表 ↓ 每个商品的Spuid就是该商品的ID [图片]
2020-12-31 - 链接工具——完美解决未群发内容无法添加
一键生成公众号文章群发链接、视频号扩展链接、商品链接、好友/群聊链接、短链接、二维码链接、淘客转链,简单好用的链接工具。 [图片] 01如何进行商品转链? [图片] 进入【商品转链】输入京东/拼多多商品导购链接 [图片] 点击引导词进入引导词条列表,你可选择官方提供的词条 也可自定义添加词条到个人词库方便下次使用 [图片] 点击作者进入作者条列表,你可选择官方提供的词条 也可自定义添加词条到个人词库方便下次使用 [图片] 配置完成后点击开始转链 转链成功即可得到已经群发的文章图文链接,可挂载至视频号 02如何进行小商店转链? 进入自己的小商店小程序点击右上角三个点 [图片] 点击底部弹窗中小商店头像 进入小商店信息主页点击更多资料 [图片] 长按复制账号原始ID,回到【链接工具】小程序 [图片] [图片] [图片] 进入【小商店转链】,先输入 粘贴小商店账号原始ID,添加至小商店列表。 [图片] 点击已经添加好的小商店进入小商店转链详情 [图片] 你可选择你需要转链的类型包含以下 7 种类型 小商店商品(自发货) 小商店带货商品(无货源) 小商店优惠券 小商店首页 [组件版]小商店商品(自发货) [组件版]小商店带货商品(无货源) [组件版]小商店优惠券 填写好商品ID或优惠券ID,点击开始转链即可 如何查看商品ID或优惠券ID:https://developers.weixin.qq.com/community/develop/article/doc/000060b0aacee81cb57bfdebf56413 03如何进行小程序转链? [图片] [图片] 进入【小程序转链】,先输入小商店账号原始ID,添加至小程序商店列表 [图片] 点击已经添加好的小程序进入小程序转链详情 填写好商品你需要营销的页面路径,点击开始转链即可 如何查看小程序路径:https://developers.weixin.qq.com/community/develop/article/doc/000404647d83303bb07b6a75651413 04如何进行二维码转链? [图片] 进入【二维码转链】,添加需要倒流的二维码图片 [图片] 配置好引导词与作者信息点击开始转链即可 05如何转换短链接? [图片] 进入【短链接】,输入你需要转短链接的网址,点击开始转链即可。 目前测试短链接有效期(30+天)本短链接仅可在微信内访问 06如何生成二维码? [图片] 进入【生成二维码】,输入你需要生成二维码的文字/链接。 [图片] 你可对二维码图标进行配置 也可点击前景色/角标色,对二维码进行美化 生成完成保存到本地即可 07如何文字转语音? [图片] 进入【文字转语音】,输入你需要生成语音的文字。 复制下载链接尽可能在PC端打开链接进行MP3文件下载 以上功能可在【链接工具】小程序中免费使用,让你的视频号带货路畅通无阻。
2021-01-04 - 如何查看小商店/小程序页面路径?
01如何获取小商店/小程序的url? 小商店获取URL 1.首先需要要注册一个公众号。 2.在公众号后台打开一篇文章,选择上方插入小程序。 [图片] 3.选择需要关联的小程序,可以根据名字或者appid搜索,之后会被记住了显示在常用小程序列表中。 [图片] 4.在小程序页面选择需要关联的微信小商店 点击获取更多页面路径,然后在右侧填入微信号,点击开启。 此时该微信小商店对该用户已开启获取页面路径权限,其他用户不可查看页面路径,不必担心。 [图片] 5.在手机上打开该小商店,点击你需要获取url路径的页面,点击右上角三个点,底部会弹出菜单,点击【复制页面链接】即可。 [图片] 6.拿到链接地址后可以嵌入到公众号、菜单栏、自己的小程序等。 小程序获取URL 1.登陆小程序后台,点击右上角工具,点击生成小程序码,输入小程序appid搜索,点击下一步,之后会被记住了显示在常用小程序列表中。 [图片] 2.点击获取更多页面路径,然后在右侧填入微信号,点击开启。 此时该微信小程序对该用户已开启获取页面路径权限,其他用户不可查看页面路径,不必担心。 [图片] 3.在手机上打开该小商店,点击你需要获取url路径的页面,点击右上角三个点,底部会弹出菜单,点击【复制页面链接】即可。 [图片] 02常用URL,可以直接复制使用 1、微信小商店首页: pages/index/index 2、优惠券页面: plugin-private://wx34345ae5855f892d/pages/couponDetail/couponDetail?couponId=18041086 //18041086为优惠券id 3、小商店商品: __plugin__/wx34345ae5855f892d/pages/productDetail/productDetail?productId=678236 //678236为商品ID 4、小商店带货商品: __plugin__/wx34345ae5855f892d/pages/cpsProductDetail/cpsProductDetail?productId=6201078 //6201078为带货商品ID 5、小商店订单页: __plugin__/wx34345ae5855f892d/pages/orderList/orderList?tabId=all //全部all,待付款pendingPay,待发货/待收货状态的订单pendingRecevied,所有售后单afterSale 6、小商店购物车 __plugin__/wx34345ae5855f892d/pages/shoppingCart/shoppingCart 7、【组件版】小商店商品: plugin-private://wx34345ae5855f892d/pages/productDetail/productDetail?productId=2575804 /*2575804为商品ID*/ 8、【组件版】优惠券页面: plugin-private://wx34345ae5855f892d/pages/couponDetail/couponDetail?couponId=17816280 /*17816280为优惠券id*/ 9、【组件版】小商店订单页: plugin-private://wx34345ae5855f892d/pages/orderList/orderList?tabId=all //全部all,待付款pendingPay,待发货/待收货状态的订单pendingRecevied,所有售后单afterSale 10、【组件版】小商店购物车: plugin-private://wx34345ae5855f892d/pages/shoppingCart/shoppingCart 11、如何查看商品的SpuId(商品编号) 微信搜索并打开【小商店助手】小程序,选择对应的小商店,点击带货收入->我的的货,进入商品列表每个商品左上角编号为该商品ID。 [图片] [图片] [图片] [图片] 03常见问题Q&A 1、为什么提供小程序更多页面路径获取的方式? a、默认显示的小程序首页路径不一定满足用户需求。 b、用户获取小程序其他页面路径的成本较高。 2、给指定的微信号“开启入口”失败怎么办? a、指定的微信号尚未关注该公众号,请先关注公众号。 b、如果已经关注公众号,请查看微信的隐私设置(在手机微信的"我-设置-隐私-添加我的方式"中),并开启"可通过以下方式找到我"的"微信号",否则可能无法复制小程序页面路径。 3、为什么微信号设置成功,却在小程序中找不到“复制页面路径”的菜单? a、“复制页面路径”仅出现在指定微信号(开启入口的微信号)中。 b、只有在第一步选择的小程序中才会出现“复制页面路径”的菜单。 c、复制功能仅保留10分钟,10分钟后功能消失。 d、微信版本较低,请确保手机微信版本为6.7.2及以上。
2021-01-04 - 首测微信小商店开放组件
目前微信小店开放组件已经申请成功,且第一时间去体验了一把。 想介入 微信小店开放组件的开发者可以点击下方链接提交申请 https://developers.weixin.qq.com/doc/ministore/minishopopencomponent/Introduction.html 官方在审核完成后会推送申请成功的模板消息,且拉入官方组件问题反馈群 你的微信小程序后台也会增加小商店店开放组件 [图片] 点击去管理会打开对应小程序的小商店后台,此后台是对应你小程序申请的插件版小店后台 与 单独申请的小店后台不互通,需要单独提交资质审核签约 官方提供了几个组件开发者可通过路由跳转进入对应的组件页面 例如下方商品详情 const productId = [商品id] // 填写具体的商品Id wx.navigateTo({ url: plugin-private://wx34345ae5855f892d/pages/productDetail/productDetail?productId=${productId}, }); 文档的接入方式我不多赘述,可自行看文档,这里说一下文档没得 鄙人使用的是uni-app框架开发小程序所以说一下uniapp介入方式 找到manifest.json文件源码视图找到微信小程序配置 "mp-weixin": { /* 小程序特有相关 */ "appid": "wx2afea6afe2d23263", "setting": { "urlCheck": false, "es6": true, "postcss": true, "minified": true }, "permission": { "scope.userLocation": { "desc": "你的位置信息将用于定位您是否位于图书馆范围内" } }, "requiredBackgroundModes": [ "audio" ], "usingComponents": true, "plugins": { "mini-shop-plugin": { "version": "1.0.63", // 必须是小程序购物组件最新版本号,微信开发者工具调试时可获取最新版本号(复制时请去掉注释) "provider": "wx34345ae5855f892d" // 必须填小程序购物组件appid,不要修改(复制时请去掉注释) } } }, 官方文档里提到的组件版本是1.1.0,介入后控制台报错提示找不到1.1.0,最终找到小程序插件信息最新版本才更新到1.0.63,不得不说这文档有点坑 附上插件的信息链接 https://mp.weixin.qq.com/wxopen/pluginbasicprofile?action=intro&appid=wx34345ae5855f892d&token=1836153220&lang=zh_CN 此时已经可以在小程序内使用插件了 去尝试一下 先在后台提交上架商品拿到SpuId [图片] /** * 商品详情 * @Author: wkiwi * @function: productDetail */ productDetail(productId){ uni.navigateTo({ url:`plugin-private://wx34345ae5855f892d/pages/productDetail/productDetail?productId=${productId}` }) }, 调用方法进入商品详情 [图片] 体验完美,可以说几分钟时间就集成了一个官方商城出来。 但是别高兴的太早,总结了本次介入的问题 1.文档问题,还在内测阶段可能不完善 存在版本问题 以及跳转说明问题 下方官方说默认不传tabId会进入全部分类 [图片] 可是并不如此,会toast提示tabId必须为 all/pendingPay/pendingRecevied/afterSale 中的一项 2.产品层面问题 在小程序内介入小店,大多应该就是充当官方商城的吧,但是商品列表组件竟然未提供,这是让开发者徒手再撸一个商品列表首页吗???? 目前用户想进入到商品列表必须通过商品详情左下方的店铺按钮才可以进入商品列表,造成入口较深,体验不佳 此时点击 店铺按钮 又造成另外一个问题,跳转进入商品列表也就是微信小商店的首页,此时头部没有返回按钮,点击手机的返回按钮会直接退出小程序 [图片] 猜测 店铺首页为小商店的tab页面,导致无法返回 此问题导致小程序退出率大大增加,运营辛辛苦苦留存的用户这么轻而易举让用户退出了?????? 此时小程序想浏览小程序本身的首页是必须通过清除小程序后台,重新点击进入才能回到小程序层级,否则你只能在插件版本的小商店内跳转!!! 本次体验整体感觉微信小商店 给小程序一个快速集成官方商城的一个解决方案,但是在产品上还有些问题,本阶段上述产品问题未解决我是不可能接入的,希望官方尽快完善上述问题 ----------------------------------接更新---------------------------------- 详情页点击首页按钮无法返回小程序首页解决方案 const miniShopPlugin = requirePlugin('mini-shop-plugin') miniShopPlugin.initHomePath('/pages/index/index') // /pages/index/index为自己小程序首页路径 进入小商店组件首页路径 wx.navigateTo({ url: `plugin-private://wx34345ae5855f892d/pages/home/home`, });
2020-10-30 - 使用BackgroundAudioManager背景音频实现一个音频播放器
说明 使用BackgroundAudioManager创建的实例,小程序切换到手机后台、小程序内页面间跳转,都不会影响音频的连续播放,可以很好的实现一个音频播放器。 BackgroundAudioManager是单实例,全局唯一,在任意页面任何位置调用wx.getBackgroundAudioManager()既可以获得。 效果 音频列表循环播放,支持上一首、下一首切换,实时进度展示,快进。 思路 将播放的音频列表放在app.globalData或本地做缓存,保证音频切换时找到对应列表。 将音频播放的实时状态放在app.globalData或本地做缓存,保证展示音频播放详情页的音频名称、实时进度等正确展示。 方法中BackgroundAudioManager.on*为监听事件,操作业务放在回调函数中处理。 BackgroundAudioManager的属性中,所有属性可以直接BackgroundAudioManager.获取值,非只读的属性可以通过BackgroundAudioManager. = ‘’ 方式赋值。 效果图 小程序界面 [图片] 手机后台,顶部下拉 [图片] 代码片段 详细代码请下载代码片段,可以直接运行demo。 https://developers.weixin.qq.com/s/VAmjRsmZ7090
2019-06-28