个人案例
联想体验店
联想携手微盟智慧零售,实现门店可个性化经营,最大化提升坪效,造就商品流、资金流和数据流“三通”的未来OMO智慧商业模式。
联想OMO智慧零售带动2千余家门店“上云”,携手微盟实现资金商品数据三通扫码体验
梦洁官方商城
梦洁家纺牵手微盟上线 " 一屋好货 " 商城,616期间线上线下产生了1000万+的GMV,门店的进店率超过了300%。
进店率超过了300%,微盟小程序助力梦洁家纺获取私域流量扫码体验
韩食通WOWZOA
单月外卖销售额63万+,单月堂食/外卖总销售额170万+!小程序为韩国美食协会加足马力,聚拢近百个商户品牌,弯道超车!
增粉5万+,单月销售额170万+,韩食通借力小程序外卖聚拢近百个品牌商户扫码体验
- 移动端多端跨平台虚拟路由交互设计与实践
一、前言 路由是现如今移动端开发中不可或缺的功能,尤其是对于跨平台的大型移动端应用,不同平台之间的页面跳转更为复杂,好的路由设计方案可以提升开发效率,降低代码维护成本,达到事半功倍的效果。 二、背景 基于移动端业务动态化发版,还有提高人效的诉求,微盟商户助手接入了跨平台技术方案,为了可以更好的管理各个平台之间的页面跳转,需要用到路由框架,而目前市面上主流的路由框架功能都比较单一,只能够满足原生页面之间的跳转,同时也无法动态下发路由 URL,无法满足当前交互跳转的诉求,还有不同版本需要支持不同页面跳转等复杂的业务场景,基于此我们自研了一套涉及到前后端一体化的路由交互框架。 三、需求分析 为了保持三端(Android,IOS,小程序)页面路由跳转的统一性,支持不同平台页面之间的跳转,提升页面跳转的自由度以及降低真实 URL 的维护成本,路由设计需要满足以下几个条件: 1.路由URL需要根据不同的平台隔离配置。 2.应用不同版本单个触发事件可以支持路由到不同的页面。 3.不可以暴漏真实的页面URL给业务开发人员。 4.页面路由需要通过后端接口动态下发到移动端。 5.三端页面路由配置保持一致。 四、技术方案 4.1 整体技术方案 针对保持三端一致性以及复杂业务场景路由跳转的诉求,我们设计了虚拟路由的概念。 虚拟路由是什么? 虚拟路由就是遵循一套规则定义的一个文本字符串,代表了设计者的理念和思想,单个页面对应一个虚拟路由,可以在虚拟路由下面设置不同系统的真实路由,目前支持Android, iOS, 小程序三端,每个端根据不同实现类型,不同版本设置不同的真实路由虚拟路由的文本规范: page://{bosId}/{key}/产品/模块/页面名称?参数=xxx{bosId} 商户助手真实店铺的唯一标识 {key} 业务后端填充的一个字符串,通过字符串可以置换到业务相关的参数信息 产品 业务产品线标识 模块 业务线里的模块 虚拟路由交互跳转设计分为PC端和移动端 4.1.1 PC端 PC端主要负责虚拟路由的配置,配置虚拟URL以及对应的真实URL等信息,真实信息包括:端类型,业务产品版本号,真实URL地址,实现类型。 [图片] 真实的配置表如下图: [图片] 4.1.2 移动端 移动端通过接口下发拿到整个虚拟路由的配置表,然后在触发点击事件的同时,通过接口拿到当前事件对应的虚拟路由,去虚拟路由配置表中匹配到真实的URL和对应的平台类型,执行不同平台的跳转逻辑,实现页面的跳转。 4.2 具体实现 按照整体功能具体实现可以分为路由配置中心,路由管理类、路由配置表、路由跳转四部分。 [图片] 4.2.1 路由配置中心 路由配置中心主要负责虚拟路由的配置管理工作,配置虚拟路由以及对应的真实路由等信息。 新增路由配置页面 包括虚拟URL和路由规则,规则就是各个端真实路由信息。 [图片] 添加规则页面 添加各个端的真实URL,版本号,实现类型等信息。 [图片] 1、端类型目前支持Android,iOS,小程序三端。 2、组件标识版本号对应业务版本号。 3、真实URL对应不同端真实Page路径。 4、实现类型目前支持原生,H5,Donut等。 添加后的完整信息 [图片] 4.2.2 初始化路由管理类 路由管理类的作用是支持不同平台的页面路由跳转,应用初始化时期需要注册不同平台的实现类,比如:原生,H5等;每个平台实现类的跳转逻辑是不一样的,H5是通过 WebView 渲染打开页面,原生是通过原生的 API 打开页面,管理类最终以 Key 和 Value 的形式存储内容,Key 是实现平台的类型,Value 是具体实现的跳转类。 [图片] 4.2.3 初始化路由配置表 路由配置表是路由跳转的核心,路由映射管理信息都存储于路由配置表中 初始化路由配置表首先调用后端配置接口拉取路由配置,接口传参需要携带系统类型【比如:Android系统】和配置的版本号参数,接口会先通过系统类型筛选出当前系统下的所有路由配置信息,系统总共包括Android,iOS,小程序3端,以Android端为例,接口接收到请求,会立即匹配端类型为Android的所有配置信息,然后再根据版本号参数匹配版本信息,因为PC配置中心单个虚拟路由有可能会配置多个版本,需要筛选出符合当前版本的配置信息; 版本的匹配逻辑是:首先匹配和当前传参版本一致的版本,如果匹配不到就向下兼容去匹配最新的一个版本,以此类推匹配完所有的路由;APP获取到匹配好的完整路由表后,循环整个路由配置表,以Key和Value的形式存储于内存中,Key是虚拟路由,Value是对应的真实URL,版本号,实现类型等信息,至此整个初始化流程就完成了。 [图片] 4.2.4 路由跳转 路由跳转的触发事件主要在应用主页的菜单上 。 [图片] 所有菜单均有后端接口返回,菜单会携带对应的虚拟URL。 触发点击菜单的同时获取到虚拟URL,首先校验虚拟URL是否规范,因为虚拟URL是我们定义的一套标准,只有符合规范才可以触发后续的流程,不符合会终止流程,校验成功后会解析虚拟URL上是否有携带参数,目标页面的虚拟URL和真实URL都支持在URL上配置业务参数,参数解析完成后,去内存中的路由映射表中匹配到真实URL,检查真实URL上是否携带了业务参数,解析完成后合并所有的参数,根据真实URL对应的实现类型匹配到具体的跳转实现类,携带参数,执行跳转交互逻辑。 以下是匹配跳转类的核心代码: when (ImplType.valueOf(rb.implType)) { ImplType.NATIVE -> { val pr = NativePageRouter(rb) pr.routeUrl = routeUrl pr.processRoute() } ImplType.H5 -> { val pr = H5PageRouter(rb) pr.routeUrl = routeUrl pr.processRoute() } ImplType.DONUT -> { val pr = DefaultPageRouter(rb) pr.routeUrl = routeUrl pr.processRoute() } } [图片] 至此,整个交互流程已经完成,四个模块的职责依赖关系也清晰明了,如下图: [图片] 五、优势 相较于传统路由框架功能更加强大,带来的优势包括: 动态化 传统的路由框架路由都是在本地配置,局限性太强,此方案支持路由配置动态下发到移动端,跳转更加灵活,能够支持更加复杂多变的用户使用场景。 统一性 目前各个端都是各自为政,没有统一的标准,不成体系,此方案通过虚拟路由的概念,统一各个端的路由标准,更加易于维护管理,单个虚拟路由支持不同端不同版本的不同配置。 灵活性 传统的路由框架跳转修改需要强感知页面URL, 此方案跳转变动只需修改PC配置即可,业务开发人员无感知。 多平台 传统的路由框架仅支持原生页面之间的跳转,此方案可以支持不同平台页面的跳转,功能更加强大,新增业务平台只需实现对应的跳转类即可,易扩展。 容灾能力 如果新版本页面跳转有问题,可以降级到老版本页面,避免线上问题。 六、实践 微盟商户助手接入虚拟路由交互设计后,对业务迭代提供了很大的帮忙。 例如:企微小程序在接入虚拟路由之前,产品业务消息配置的页面都是小程序中的真实路径,由后端接口返回,但是如果单个业务消息跳转的页面需要变更,无法做到新老版本的兼容,老版本在点击消息触发跳转事件时就会提示找不到目标页面。 接入虚拟路由后所有消息只需配置一个通用的真实路径,目标页面的虚拟路由作为参数传递即可,点击消息触发跳转事件时,小程序会通过目标页面的虚拟路由匹配到当前小程序版本的真实路径,这样就可以实现消息页面的跳转,就解决了新老版本无法兼容的问题。 七、总结 移动端虚拟路由交互跳转设计运用到微盟助手商户中后发挥了极其重要的作用,业务开发人员无需关注真实的URL,只需要通过虚拟路由就可以跳转到不同平台的不同页面,跳转的页面可以通过服务端动态配置,单个页面支持不同版本跳转到不同的页面,业务功能遇到问题也可以降级处理,给业务提供了更多的灵活性和可能性。
2024-02-21 - 小程序JS也能做图像处理 - 会员卡主题色提取的方案解析
一、前言 主题色,简而言之,是界面设计中被用作基础配色方案的主要颜色。它是在设计中明确定义的一种核心色彩,用于塑造界面的整体氛围和风格。通过恰当选择和运用主题色,可以 提升界面的可视性和易用性,从而大幅地提升用户体验。 提到图像提取主题色,⼤家可能会想到 OpenCV 这种专业的计算机视觉库,其实Javascript 这种轻量级的 脚本语⾔也是可以被⽤来实现该功能。并且相较于其他语⾔,由于其实时性和动态性以及浏览器的原生支持,JS 具有更好的动态响应能⼒以及定制化能力。 二、背景 目前微盟 CRM 会员模块主要支持两种类型的会员卡,分别是等级会员卡以及权益会员卡。商户可以根据不同的应用场景去创建不同的会员方案(成长值等级类型、消费行为等级类型、权益卡以及付费会员),以及对它们配置、管理。满足丰富的业务场景,能帮助商家针对行业对会员进行个性化运营,帮助商家给会员提供更好的服务,提升会员的黏性和忠诚度。 [图片] 并且每⼀套等级/权益卡的卡面外观,包含了勋章图案、卡面背景、自定义图片、⽂字颜色和背景配色,商户可以对其进行自定义搭配用以塑造界面的整体氛围和风格,提升整体的视觉体验。 [图片] 等级卡装修页面 [图片] 等级卡背景色设置 [图片] 权益卡设置 当商家配置好背景/卡面图片后,其需要手动配置背景/卡面主题色,可能需要进行多次尝试才能找到最合适的配色方案。这无疑增加了配置成本。 此外,可供选择的主题色为固定的几个色板,这导致在设计页面时受到一定的局限性。为了解决这一问题,我们考虑在商户配置好图片后自动生成与其图片相匹配的主题色并自动应用,从而提升整体观感。 三、需求分析 解决该问题最重要的便是获取图片的主题色啦,那么我们该如何去获取到一张图片的主题色呢?市场上常用的提取主题色的方案主要为以下几种: 1.基于平均色的方案:这种方法计算图像中所有像素的RGB值的平均值,然后将这个平均值作为主题色。这种方法简单直接,但可能无法捕捉到图像中的主要色调。 2.颜色直方图:将图像的颜色分布表示为颜色直方图,然后选择直方图中的峰值作为主题色。这种方法对于多种颜色的图像效果较好,但总体效果较差 3.Opencv: 使用 OpenCV 这个强大的计算机视觉库来加载图像,然后使用颜色空间转换和阈值处理来提取特定颜色的部分。 但其实 Javascript 也可以被用来实现该功能,并且相较于其他方案,Js 处理的好处如下: 1.客户端控制:无需后端服务器支持,不用处理图像上传/下载等逻辑。 2.响应性:在处理尺寸较小的图片(CRM会员卡背景图片限制1M以内)时,具有更好的响应性。 3.交互性:可以直接在前端图像上实现交互,例如用户点击图片上的区域来提取特定颜色,或者通过滑块来调整颜色提取的敏感度。 四、需求分析 简而言之,我们的技术方案主要为以下几个步骤: 1.加载图像数据: 使用 Canvas 绘制图像并获取像素数据。 2.数据预处理: 对像素数据进行降噪/缩放/去皮等定制化处理。 3.生成主题色:采用切割算法(中位切分/八叉树)进行颜色提取。 [图片] 接下去我们会一步一步讲解具体的实现步骤。 1、加载图像数据 [图片] 前端开发者对于 Canvas 这个元素应该是再熟悉不过了,我们通过 Canvas 的像素渲染的特性,很轻松的就能获取到图片上的像素数据。但需要注意的是需要使用 IE8及之后版本哦。 首先我们需要通过创建一个 Canvas 元素并调用 Canvas 上下文的 DrawImage 方法将图片绘制到 Canvas 上,再通过 GetImageData 方法获取到图像数据,即ImageData 对象。 并且我们使用了 Web Workers 来对 Canvas 进行离屏渲染,使其在后台线程中处理图像,防止其阻塞主线程的执行。 现在我们已经获取到所有像素数据了,接下来我们便需要对其进行预处理,为我们之后的计算打好基础。 2、数据预处理 2.1 降噪 [图片] 这里的降噪不是指声音的降噪,而是图像处理中的一个专业术语,简单来说是为了去除图像中的干扰。常见的图像降噪处理有高斯滤波、均值滤波、中值滤波等方式。 我们使用高斯滤波(Gaussian Filter)来解释其工作原理,滤波器本质是一种对图像的卷积(image convolutions)。 什么?你说你不知道啥是卷积? 那你知道图像的模糊和锐化吧?那就是一个卷积。 用过P图软件的魔术棒抠图吗?没错那也是卷积。 用手机自拍开过美颜加过滤镜吗?恭喜你!这还是卷积。 卷积无处不在,而且也很好理解,用数学上的术语来表示卷积是对两个相同维度的矩阵进行逐元素相乘并求和。 如果把图片当做一个大矩阵,那么对图片进行模糊、锐化、边缘检测等处理的功能就是一个小矩阵我们称之为核(kernel),通常来说核的大小是MxM(M为奇数)如3x3,这样可以确保其有一个核心坐标(x, y)。 接着把核心坐标放在图像的起始坐标上,将他们重叠区域的元素进行逐个相乘后求和并将计算结果输出到和当前核心坐标相同的图像坐标上,按从左往右从上往下的顺序平滑核心坐标重复此步骤。 [图片] 弄明白卷积后我们回过头来讲高斯滤波就容易多了,万变不离其宗,高斯滤波其实就是创造一个符合高斯分布的卷积核并对图像进行卷积计算(对像素的R、G、B值分别计算得出结果),详细步骤不在这里过多阐述,来看下具体的运行效果。 [图片] 经过高斯滤波处理之后,图片看起来变模糊了。这就是像素被平滑处理之后的效果,为了减少之后计算的复杂度。 2.2 去皮 [图片] 经过降噪处理后,我们还需要剔除一部分边界颜色我们称之为去皮。因为有些配图是带有纯色背景色的比如白色,大面积的白色或黑色会影响统计结果,并且造成统计性能的下降。 在实际运行过程中会发现有些看似是白色的背景实际会有一定的色差。因此我们并不是简单的对颜色值为(255, 255, 255)的像素去除,而是选取一定的范围。这个范围不能过大,且尽量贴近黑、白色的边界。 如果我们选取RGB值小于(5, 5, 5)或大于(250, 250, 250)的像素进行去除的话,代码实现如下: function convertToPixelsArray(imgData) { const data = imgData.data; const pixels = []; const [min, max] = [5, 250]; //像素点RGB值不在此范围内的进行过滤 范围可选 for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // rgb值都在[5, 250]范围内,则不过滤 if (!(Math.min(r, g, b) >= max || Math.max(r, g, b) <= min)) { // 过滤透明度(可选) pixels.push([data[i], data[i + 1], data[i + 2]]); } } return pixels; } 3、生成主题色 在我们的项目中,我们主要采用了两种算法:中位切分法和八叉树。这些算法被用于不同的情境,以满足不同的图像处理需求。 对于那些需要快速获取主题色,并且对图像细节不太关心的场景,我们选择了中位切分法。这个简单且高效的算法能够帮助用户快速批量地获取图片的主题色。在这种情况下,我们的重点在于迅速处理大量图像,而不需要过多关注图像的微小差异。 在那些对细致和真实主题色有更高要求的场景中,八叉树无疑是更好的选择。比如: 渐变背景色:八叉树能够创造出更加复杂多样的背景效果,从而使渐变过程更加平滑自然,为视觉体验增添层次感。 细致的颜色匹配:八叉树具备实现对图像中不同区域进行精细颜色匹配的能力,从而更好地将主题色与整体设计融合。 下面就来介绍一下怎么用 JS 实现这两种算法。 4.1 中位切分法 中位切分其实非常好理解。首先需要各位读者发挥下空间想象能力,将R、G、B想象成三维空间的X、Y、Z轴,这样我们就得到了一个边长为256的立方体盒子,接着把每个像素点当作一个无体积的小球根据其坐标(r,g,b)放入盒子中。 [图片] 中位切分法的核心思想就是不断对某一根轴上的中位数进行切割,与日常生活中的折纸类似(把一张纸对折42次就可以上月球了,由此可见该算法的强大)。 不同的是,我们每次会选择像素密度最大子空间的最长边上的中位数进行切割,以确保每次切割过后的子空间中的像素数量大致相等。并在完成每次切分后使用插值排序法进行排序,从而找出密度最大子空间(或质量最大子空间)以进行下一次切割。 [图片] 某图像颜色在三维空间中的分布示意,可以看到部分区域的像素点非常密集 [图片] [图片] 沿R轴像素中位数处切割成左右2个子空间,右边空间包含的像素数量更多 当子空间边长小于设定的阀值时,我们便将其存入结果数组中。算法终止条件为切割次数或结果数组达到要求,此时密度排序靠前的几个子空间中包含的颜色即为我们所寻找的主题色。 以下是 JavaScript 的实现: function medianCut(pixels) { const colorRange = getColorRange(pixels);// 获取该颜色盒子的RGB范围 const colorBox = new ColorBox(colorRange, pixels.length, pixels); const divisions = 8; // 最大切割次数,在demo中写死 实际项目中使用者可自定义 let [box1, box2] = cutBox(colorBox); // 第一次切割 return queueCut([box1, box2], divisions); } function queueCut(queue, num) { let isSmallBox = false; while (queue.length < num && !isSmallBox) { const denselyBox = queue.shift(); const resultBox = cutBox(denselyBox); // 只返回一个盒子时,说明最长边已经小于阀值 if (resultBox.length === 1) { isSmallBox = true; queue.unshift(resultBox); return; } queue = insertValueInSortedArray(queue, resultBox); // 插值排序 } return queue; } function cutBox(colorBox) { const { colorRange, total, rgbArr } = colorBox; const cutSide = colorBox.getCutSide(colorRange); if (cutSide === -1) return colorBox; // 当切割边为-1即切割范围小于阀值时 停止切割 const colorInCutSide = rgbArr.map((item) => item[cutSide]); // 统计出各个值的数量 let medianColor = getMedianColor(colorInCutSide, total); // 此处做简易处理,实际处理中需考虑中位数值的像素数量是否过大 if (medianColor === colorRange[cutSide][0]) { medianColor++; } // 防止空切 const newRange = getCutRange(colorRange, cutSide, medianColor); // 获取切割后的两个盒子的RGB范围 const dividedPixels = dividePixel(rgbArr, cutSide, medianColor); // 分割像素到两个盒子中并返回... return [boxOne, boxTwo]; } 看到这里是否对中位切分算法有了更深的理解?在实际运用中,要注意切割次数和最小子空间的设定会影响到结果的准确性。 3.2 八叉树 我知道有些同学看到这个名字的时候已经开始头疼了。但请先别急,听我慢慢讲解 先用一句话简单概况,八叉树(Octree)是一种用于描述三维空间的树状数据结构,它是一种递归的树形数据结构,可以将三维空间划分为八个等分的小立方体(子节点),每个空间可以进一步细分为八个子空间,以此类推。 八叉树怎么和RGB颜色盒子连接起来呢? 首先,要将三维空间划分成八个等分的小立方体,我们需要沿着每条轴的中点切割,确保每个子空间的边长相等,从而确保每个立方体的体积相等。 比如将一座葫芦山(完整颜色盒子)用八叉树切分后会得到八种不同颜色的葫芦娃; [图片](没办法葫芦娃只有七个人,拿蛤蟆凑个数) 但是其颜色,比如蓝色的范围太广泛了,其分为天蓝色、宝蓝色、钢蓝色、葫蓝色、蔚蓝色、紫蓝色等等,天蓝色又分为淡蓝色、浅蓝色、鲜蓝色、翠蓝色、深蓝色等等。那么我们就需要对这些葫芦娃进行递归切割以获取这些更精确的颜色。 那么我们要切割到第几层才能获取到具体的像素点(1*1*1的颜色盒子)呢?我们可以观察到,每对颜色盒子进行一次切割,会导致子节点继承的RGB范围减半,而256=2^8,这意味着在对一个完整的颜色盒子划分到第八层时,我们就可以获得单个像素点啦。 我们知道了八叉树对于颜色盒子的划分规则后,我们该怎么使用八叉树来提取图像主题色呢? 上文中提到八叉树是按照三条轴的中点对空间进行递归切割的,也就是说在八叉树第一层结构中,各轴会被分成[0, 127], [128, 255]两个区间,颜色盒子被均分成八个小盒子。那我们该如何将这些小盒子其与八叉树中的第一层节点对应起来呢? 与二分查找法类似,我们需要将像素的RGB值分别与其轴中点进行比较并将结果用0、1来表示(0-小于中点,1-大于中点),在第一层中得到的对应关系如下图所示。 [图片] 以此类推,我们最终得到以下对应关系。 [图片] 以此类推,我们可以得到各像素点在八叉树中所挂载的节点(如下图所示)。八叉树将颜色数据按照层级关系组织与管理起来,从而实现高效的存储和查找操作。 [图片] 知道了怎么将像素数据存入八叉树结构后,我们就可以开始提取主题色啦。 首先,在八叉树最后一层结构中,每个节点表示的是一个具体的RGB值,我们称其为叶子节点。与此同时,在最后一层中每个兄弟节点的RGB差值范围为1,也就是说该父节点的所有子节点的颜色都十分类似。我们可以发现,在八叉树较深层级中,兄弟节点的RGB差值范围较小,公式为diff=2^(8-n),其中diff为RGB差值,n为层数。 那么我们可以利用这个特性来合并相似元素从而更有效地表示数据,当一个颜色盒子中存有过多小盒子(子节点)与葫芦娃(像素)的时候,丢弃其中所有小盒子,并保留所有葫芦娃至父盒子中,从而进行空间压缩,减少空间复杂度。 为什么要进行合并呢?在介绍中位切分法的篇幅中,我们提到,提取主题色其实就是找到密度最集中的几个簇,被合并的盒子的像素都为那些像素集中且密度较大的盒子。这便是我们所需找到的簇,也就是主题色。 如果将所有像素全都储存在八叉树结构后,才进行合并叶子节点的步骤的话,这无疑极大的增加了空间复杂度,所以我们设定了一个阀值N(可自定义),一般来说,N越大,得到的结果越精确,但时间复杂度与空间复杂也会同步上升。 在将像素插入八叉树结构的过程中,当叶子节点数量超过N时,我们便会将挂有最多叶子节点的父节点进行合并操作。合并后,将该父节点设为叶子节点,且之后所有在该分支上的像素均挂载至该父节点上。 当所有的像素数据遍历结束后,我们就可以得到N个叶子节点了。 我们需要根据每个叶子节点上的像素数量对其进行排序,排名前列的便是我们所需要的主题色啦。 以上介绍了中值切分法和八叉树两种算法的实现 中位切分法适用于资源有限且需要快速提取图像主题色的场景,尤其适合实时或大规模处理需求。它的简单性和计算效率使其在这些场景下成为很好的选择,尤其是当主要目标是从图像中快速捕捉主题颜色而不需要过多考虑细节和渐变色时。比如当商户只需要快速批量地获取图片的主题色,且不关心主题色的精度时 而八叉树在动态适应性和处理颜色渐变方面具有优势,适用于需要保持细节不和平滑度的图像颜色提取任务,但其算法复杂度较高,内存消耗较大。主要应用场景如下: 并且该两种算法我们都会放在 Web Worker 中处理,以提高页面性能和用户体验,同时保持主线程的渲染进程不受影响。 五、成果 以上就是这篇文章的总体内容了,通过 Canvas,可以在 Web 前端对图像进行操作和渲染。结合切割算法,可以实现类似的图像颜色提取功能。这种方法尤其适合需要在用户界面中实时显示颜色提取结果的交互式应用。并且通过对于 Web Worker的一个使用,避免了对主线程渲染的影响,从而提升了用户体验。 接下来我们看一下会员卡接入该功能之后的一个整体效果吧。 会员权益卡接入该功能后,当用户上传完卡面图片,便会自动提取其中的主题色。这样,用户可以直接选择提取出的色彩作为主题色应用,以确保卡面图片和主题色的协调性。具体效果如下所示: [图片] 权益卡效果展示 会员等级卡接入该功能后的效果图如下所示: [图片] 等级卡效果展示 我们还可以利用该工具,当加载一些比较耗时的大图片时使用提取出的主题色进行渐变填充,实现一种"模糊渐变加载"的过渡效果。这种过渡场景可以增加页面加载的视觉吸引力和平滑性。如下图所示: [图片]
2024-02-21 - 微盟移动端组件库 Titian Mobile 对外开源
消费互联网时代,纷繁的应用需求催生了产品和功能的指数级增长。在复杂多元的研发场景下,高效快速、保质保量、可持续交付的研发模式,方能支撑高速扩张的业务需求。 01. Titian 是什么? 近日,微盟重磅推出多渠道移动端组件库——Titian。作为移动端前台的“底层”能力,它伴随了“微盟 WOS -新商业操作系统”的研发历程,经历了从无序到有序,从稚嫩到成熟的蜕变,并助力了需要不断实现的“产品一体化、体验一体化、服务一体化、数据一体化和开放一体化”的 WOS 终极形态。 [图片] 经过一年多的不断进化,Titian 取得了大量核心业务应用及有效性验证。Titian 提供了清晰统一的 API ,满足多渠道小程序与多前端框架能力,极大的降低了研发成本与提升需求流转效率。同时,Titian 也是体验良好的重要保障,遵循用户习惯、维持用户心智,为业务提供了有序一致的产品体验。 [图片] 02.设计理念体验设计远不止于核心任务,更需要沉淀与延续设计模式和理念,为后续的设计产出质量与效率提升做基奠。为此,微盟Titian在完成核心任务的同时并行落地了“移动端设计体系”。包含以下三部分:分别为“设计理念”、“视觉语言”、“设计规范”。体系是设计在执行时的规则、标准与指导。 [图片] 针对微盟产研、微盟生态开发者、外部行业开发者3类用户群体,微盟Titian在提升效率、普适通用的核心思路下,基于“有序、普适、多元”的设计理念与价值观,打造具有自身特色的移动端组件库。 · 体验有序一致 即内外的有序,内在拥有统一的标准、规则及模式。外在有统一的设计语言让用户体验有序一致。 · 场景应用普适性 沉淀于微盟核心的 SaaS 业务,对“交易场景”拥有良好的普适性。既能满足当下需求,也能拓展延伸至更广泛的应用场景。 · 品牌调性多元化 丰富多元的场景选择性,满足不同品牌调性展现。贴合用户心智,维持品牌认知。在赋能品牌的同时,开发者更能探索出无限可能。 03.Titian 提供的能力 3.1 完善的基础组件 Titian 目前提供了 60+ 移动端基础组件,足以支撑绝大多数的业务需求。 [图片] 3.2 更丰富的多端小程序支持 借力微盟自研的小程序多渠道转码工具(即将开放),Titian小程序组件库可在微信、支付宝、小红书、快手等渠道的小程序顺畅运行。 3.3 React 和 Vue 同步支持 Titian H5 基于 Web Components 能力,Titian H5 提供 React 和 Vue 3.0 两套组件库。该两套组件库底层互通, API 一致,满足业务方丰富的使用场景。 3.4 H5 与小程序 API 统一小程序与 H5 组件库两者采用统一的 API 设计,方便开发者接入。 3.5 二次开发能力 在 Titian 丰富的基础组件上,用户可以快速开发满足自身需求的定制组件。在微盟已经有大量的案例落地,满足了丰富的业务需求。 [图片] 3.6 多维度主题切换 在确保品牌风格统一的前提下,Titian基于品牌调性提炼了具有共性的视觉特征,分别为颜色、图标、圆角。并用这些特征组合为“通用”、“潮流”、“亲和”套风格(后续会持续增加),让开发者随心选择,让产品视觉更贴合品牌调性,保持品牌识别度,维持C端用户对品牌的心智认知。 3.7 开箱即用的体验 Titian 提供交互式的示例,对于组件最常用的功能,可以直接配置相关属性。业务方可以直接上手操作,简单直观。 04. 开发工具 · 自研打包工具基于 esbuild , Titian 自研了小程序打包工具,让编译流程更加快速,开发体验更加友好。 · VS Code 提示插件 帮助用户在 VS Code 中快捷使用 Titian 组件。 · WXML 格式化工具一个 prettier 格式化插件,方便统一代码风格。 · Figma 设计组件Titian提供了一整套完整的设计组件资源,接入引用即可立即使用,方便快速搭建页面,设计组件覆盖代码能实现的所有能力,做到了设计-研发闭环。 · UED 验收工具对于 Titian 组件,打开验收工具配置后,长按即可变得半透明。可以帮助 UI 快速分辨出 Titian 组件。 05. 优秀案例 经过1年的迭代与不断调优,Titian已为微盟企业内部及微盟商城、CRM、CMS、商户助手、微盟客等客户,累积提供了5000+的移动端场景的应用及验证。 [图片] [图片] [图片] [图片] [图片] [图片] [图片] 未来,微盟将在 Titian 组件库的基础上做更多的探索,开放优化可视化建站平台、D2C 设计图转代码工具、物料市场等新工具新平台,敬请大家多多关注~ 反馈和共建请访问Titian UI - 多渠道移动端组件库PC端官网,进行体验! https://titian.design.weimob.com/ github链接:https://github.com/weimob-tech/titian-design 微盟云开发者可通过微盟云开发者工具中的Titian使用文档指引,查找使用方式! https://doc.weimobcloud.com/word?menuId=53&childMenuId=58&tag=3764
2023-02-07 - 微信小程序真机调试报错fail-109:net::ERR_ADDRESS_UNREACHABLE ?
在已配置https的情况下,开发者工具中运行正常,真机调试及体验版 部分接口出现 fail-109:net::ERR_ADDRESS_UNREACHABLE 错误, 部分接口可以正常调用。求大佬告知原因及解决方法!
2020-01-15 - 页面生命周期钩子、PerformanceObserver 监听,数据可采集顺序确认的?
通过打日志查看,在打开一个页面,并且页面首次渲染完成时:页面生命周期钩子(onLoad, onShow, onReady)都触发完后,通过 PerformanceObserver 监听方式的回调才触发。即,数据可采集的顺序为: page.onLoadpage.onShowpage.onReadyperformance 的 routeperformance 的 evaluateScriptperformance 的 firstRender疑问: performance 的evaluateScript 代码注入阶段(记为A)、page.onReady 首次渲染完成(记为B),实际发生顺序是 A ➜ B,但真实数据获可采集顺序却是 B ➜ A,这个是什么原因?两者可采集顺序是固定的吗?
2022-02-16 - 小程序插件开发里面怎么使用 tabBar 呢, 有没有示例呢还是不支持?
想开发一个插件,里面需要使用到 tabbar, 试了下配置在 plugin.json 里后跳转报错,是不是不支持?如果不支持的话有什么好的替代方法呢?谢谢。
2021-06-30 - 企业微信可以通过扫一扫二维码访问关联的小程序吗?
企业微信可以通过扫一扫二维码访问关联的小程序吗
2021-04-19 - 小程序模板消息能力调整通知
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019-10-13 - 小程序模板消息订阅功能需要根据商户的行业类目找到对应模板,但是类目不同,能否做到模板统一?如何破解
问题痛点描述: 某系统有过万个后台开通了微信支付的商户,现有功能 -》支付后触发模板发送。我们要将模板发送统一改成消息订阅方式,那么依腾讯的规则,消息订阅方式需要严格根据【小程序类目】约束来选择模板。也就是说,不同的【小程序类目】会对应不同的模板,但是我们的功能是固定的。 举个例子说: 任何商户下的用户产生了一次支付,我们的系统都要推送【订单支付成功通知】模板消息给该用户的微信,但是如果该商户所属的【小程序类目】下不包含该模板,那我们就会推送失败。 功能实现诉求: 1、能否有一个通用的跨全类目的模板发送解决方案呢?至少我想保证例如【订单支付成功通知】这种模板在我系统内所有高级商户都可以下发用户 2、如果不能有跨全类目发送方案,能否根据我们不同商户有不同类目,但是不同商户需要发送相同模板的诉求,给一些实现建议呢?
2019-12-05 - 关于订阅消息:作为SaaS服务商,是不是要为每个行业都配置消息模板?
关于新的订阅消息,模板需要限定要具体的行业类目。 那么作为 SaaS 服务商,我们的服务涉及众多行业,是不是要为每个行业都配置消息模板?有没有对照表可以参考呢? 场景类似的模板消息每个行业之间有共性么?作为产品经理,感觉是一个蛮大的工程量啊 T T 哪位能出来分享下经验,谢谢!
2019-11-26 - 小程序可发送的订阅消息模板与二级类目相关,作为第三方开发者需要对264个类目的小程序分别添加模板吗?
已知小程序二级类目有264个,不同的二级类目决定了小程序可选择的订阅消息模板不同。 https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/Sub_Template_Message_Setting.html 以上接口文档支持通过接口获取不同类目的模板及对应关键词库,但通过接口获取到264个类目的所有模板及关键词库后,是否需要对264个类目分别配置个人模板库? 是否有什么方式可统一配置模板库,针对不同类目的小程序也可发送统一的订阅消息? 因为我们的业务面向不同类型的企业是一致的,需要发送的订阅消息也是一致的,但是目前根据已知的条件判断似乎是面对不同类目的小程序需要配置不同的消息模板库,如我的理解有误,请指正。 期待官方的回应,感谢。
2019-12-04 - 从源码看微信小程序启动过程
一、写作背景 接触小程序一年多,真实体验就是小程序开发门槛相对而言确实比较低。不过小程序的开发方式,一直是开发者吐槽的,如习惯了 Vue,React 开发的开发者经常会吐槽小程序一个 Page 必须由多个文件组成,组件化支持不完善或者说不能非常愉快的开发组件。在以前小项目中没太大感觉,从加入有赞,参与有赞微商城小程序的开发,是真切的体会到对于大型小程序项目开发的复杂性。 有赞从微信小程序内测就开始开发小程序,在不支持自定义组件的时代,只能通过 import 的形式拆分模块或实现组件。在业务复杂的页面,可能会 import 非常多的模块,而相应的 wxss 也需要 import 样式,除了操作繁琐,有时候也难免遗漏。 作为开发者,我们当然希望可以让工作更简单,更愉快,也希望改善我们的开发方式。所以希望能够更了解微信小程序框架,减少不必要的试错,于是有了一次对小程序框架的 debug 之旅。(基础库 1.9.93) 通过三周空余时间的 debug,也算对小程序框架有了一些浅显的认识,达到了最初的目的;对小程序启动,实例,运行等有了真切的体会。这篇文章记录了小程序框架的基本代码结构,启动流程,以及程序实例化过程。 本文的目的是希望把我看到的分享给对小程序感兴趣或者正在开发小程序的读者,主要解答“框架对传入的对象等到底做了什么”。 二、从启动流程一窥小程序框架细节 在开发者工具中使用 help() 方法,可以查看一些指令和方法。使用其中的 openVendor 方法可以打开微信开发者工具在小程序框架所在目录。其中以包括以基础库命名的目录和其他帮助文件,如其中有两个工具 wcc,wcsc。wcc 可把 wxml 转换为对应的 JS 函数 —— $gwx(path, global),wcsc 可将 wxss 转换为 css。而基础库目录包括 WAService.js 和 WAWebview.js 文件。小程序框架在开发者工具中以 WAService.js 命名(WAWebview.js 不知其作用,听说在真机环境使用该文件)。 在开发中工具命令行使用 document.head 可以查看到小程序的启动流程大致如下: [图片] 以小节的方式分别介绍这些流程,小程序是如何处理的(小节编号与图中编号相同)。 1、初始化全局变量 下图是小程序启动是初始化的一些全局的变量: [图片] 那些使用“__”开头,未在文档中提及可使用变量是不建议使用的,wxAppCode 在开发者工具中分为两类值,json 类型和 wxml 类型。以 .json 结尾的,其 key 值为开发者代码中对应的 json 文件的内容,.wxml 结尾的,其 key 值为通过调用 $gwx(’./pages/example/index.wxml’) 将得到一个可执行函数,通过调用这个函数可得到一个标识节点关系的 JSON 树。 [图片] 2、加载框架(WAService.js) 使用工具对 WAService.js 进行格式化后进行 debug。可以发现小程序框架大致由: WeixinJSBridge、 NativeBuffer、 wxConsole、 WeixinWorker、 JavaScript兼容(这部分为猜测)、 Reporter、 wx、 exparser、 virtualDOM、 appServiceEngine 几部分组成。 其中除了 wx 和 WeixinJSBridge 这两个基础 API 集合, exparser, virtualDOM, appServiceEngine 这三部分作为框架的核心, appServiceEngine 提供了框架最基本的接口如 App,Page,Component; exparser 提供了框架底层的能力,如实例化组件,数据变化监听,view 层与逻辑层的交互等;而 virtualDOM 则起着链接 appServiceEngine 和 exparser 的作用,如对开发者传入 Page 方法的对象进行格式化再传入 exparser 的对应方法处理。 框架对外暴露了以下API:Behavior,App,Page,Component,getApp,getCurrentPages,definePlugin,requirePlugin,wx。 3、业务代码的加载 在小程序中,开发者的 JavaScript 代码会被打包为 [代码]define('xxx.js', function(require, module, exports, window, document, frames, self, location, navigator, localStorage, history, Caches, screen, alert, confirm, prompt, fetch, XMLHttpRequest, WebSocket, webkit, WeixinJSCore, Reporter, print, WeixinJSBridge) { 'use strict'; // your code }) [代码] 这里的 define 是在框架中定义的方法,在框架中提供了两个方法:require 和 define 用来定义和使用业务代码。其方式有些像 AMD 规范接口,通过 define 定义一个模块,使用 require 来应用一个模块。但是也有很大区别,首先 define 限制了模块可使用的其他模块,如 window,document;其次 require 在使用模块时只会传入 require 和 module,也就是说参数中的其他模块在定义的模块中都是 undefined,这也是不能在开发者工具中获取一些浏览器环境对象的原因。 在小程序中,JavaScript 代码的加载方式和在浏览器中也有些不同,其加载顺序是首先加载项目中其他 js 文件(非注册程序和注册页面的 js 文件),其次是注册程序的 app.js,然后是自定义组件 js 文件,最后才是注册页面的 js 代码。而且小程序对于在 app.js 以及注册页面的 js 代码都会加载完成后立即使用 require 方法执行模块中的程序。其他的代码则需要在程序中使用 require 方法才会被执行。 下面详细介绍了 app.js,自定义组件,页面 js 代码的处理流程。 4、加载 app.js 与注册程序 在 app.js 加载完成后,小程序会使用 require(‘app.js’) 注册程序,即对 App 方法进行调用,App 方法是对 appServiceEngine.App 方法的引用。 下图是框架对于 App 方法调用时的处理流程: [图片] App 方法根据传入的对象实例化一个 app 实例,其生命周期函数 onLaunch 和 onShow 因为使用不同的方式获取 options的参数。在有些需要根据场景值来实现需求的,或许使用 onShow 中的场景值更合适。 在实际开发过程中发现,在微信顶部唤起小程序和在小程序列表唤起的 options 也是不一样的。在该案例中通过点击分享的小程序进入后,关闭小程序,再通过不同方式进入小程序,通过顶部唤起的还是 options 的 path 属性还是分享出来的 path,但是通过列表中打开直接回到了首页,这里 App 中的 onShow 就会获取到不同的 options。 5、加载自定义组件代码以及注册自定义组件 自定义组件在 app.js 之后被加载,小程序会在这个过程中加载完所有的自定义组件(分包中自定义组件没有有测试过),并且是加载完成后自动注册,只有注册完成后才会加载下一个自定义组件的代码。 下图是框架对于 Component 方法处理流程: [图片] 图中介绍了框架如何对传入 Component 方法的对象的处理,其后面还有很多深入的对于组件实例化的步骤没有在图中表示出来,具体可以在文章最后的附件中查看。 自定义组件在小程序中越来越完善,其拥有的能力也比 Page 更强大,而后面会提到在使用自定义组件的 Page 中,Page 实例也会使用和自定义组件一样的实例化方式,也就是说,他拥有和自定义组件一样的能力。 6、加载页面代码和注册页面 加载页面代码的处理流程和加载自定义组件一样,都是加载完成后先注册页面,然后才会加载下一个页面。 下图是注册一个页面时框架对于 Page 方法的处理流程: [图片] Page 方法会根据是否使用自定义组件做不同的处理。使用自定义组件的 page 对象会被处理为和自定义组件的结构,并在页面实例化时使用不同的处理流程进行实例化。当然对于开发而言没任何不同。 从图中可以发现 Page 传入的(生命周期)代码并不会在这里被执行,可以通过下面小节了解 Page 实例化的详细过程。 7、等待页面 Ready 和 Page 实例化 还记得上面介绍的启动流程中最后一步等待页面 Ready?严格来讲是等待浏览器 Ready,小程序虽然有部分原生的组件,不过本质上还是一个 web 程序。 在小程序中切换页面或打开页面时会触发 onAppRoute 事件,小程序框架通过 wx.onAppRoute 注册页面切换的处理程序,在所有程序就绪后,以 entryPagePath 作为入口使用 appLaunch 的方式进入页面。 下图是处理导航的程序流程: [图片] 从图中可以看出页面的实例化是在进入页面时进行,下图是具体的实例化过程: [图片] 下图是最终可得到 Page 实例: [图片] 可以发现其中多了 onRouteEnd API,实际该接口不会被调用。其中以 component 标记的表示只有在使用了自定义组件时才会有的方法和属性。在前面第 5 小节提到了对于使用自定义组件的页面会按照自定义组件方式解析,这些属性和方法与自定义组件表现一致。 8、关于 setData 小程序框架是一个以数据驱动的框架,当然不能少了对他如何实现数据绑定的探索,下图是 Page 实例的 setData 执行流程: [图片] 其中 component:setData 表示使用自定义组件的 Page 实例的 setData 方法。 三、写在最后 这是一次不完全的小程序框架探索,是在微信开发工具中 debug 的结果。虽然对于实际开发没有什么太大的帮助,但是对框架如何对开发的 js 代码进行处理有了一个很明确的认识,在使用一些 js 特性时可以有明确的感知。如果你还疑惑“小程序框架对传入的对象等到底做了什么”那一定是我表达能力太差,说声对不起。 通过这一次 debug ,也给我引入了新的问题,还希望能够有更多的讨论: · 自定义组件太多启动时会耗时处理自定义组件 · 文件太多会耗时读文件 · 合理的设计分包很重要 当然最后对于框架中已有的能力,还是非常希望微信可以开放更多稳定的接口,并在文档中告知开发者,让开发变得简单一些。
2019-03-05 - 小打卡|如何组件化拆分一个200+页面的小程序
大家好,我是小打卡的前端唐驰。刚才金轩正同学分享了基于原生小程序底层架构,在此基础上我为大家分享下如何拆分一个200+页面的小程序,主要通过以下几点来聊一聊小打卡在组件化路上的一些实践 1.背景 2.组件与方案 3.组件间通讯 4.基于组件我们做了哪些事 [图片] [图片] 1. 其实一开始小打卡是没有引入组件化的,因为微信最开始是不支持组件化的。当时js代码已经4k+行了,各种功能代码,有用的没有用的,不知道干什么的代码就躺在那里,一动不动。举个例子,一个头像点击跳转的逻辑搜索了下,遍布在各个页面。修改起来可想而知的胆战心惊。另一个原因就是当时由于业务功能直线上升,很快我们就遇到了代码包超包了。在微信还没有实现分包之前,我们就只能一个一个页面的去review剔除代码,效率极低。这也是促成我们决定寻求出路的原因之一。可是删代码删功能是不能解决问题,期间我们也考虑过h5的方式,跑了demo之后却发现h5方式的多次渲染, 与加载首页白屏,尽管有各种服务端渲染方案,但是我们一致觉得为了用户体验,放弃了。 [图片] 2. 对于小打卡来说,我们不能再任由项目裸奔了,需要一种开发方式来进行约束,主要是有几个诉求: 在之前的项目上,为了方便。功能与功能之间的耦合程度极其的高,各种为了使用方便而随意修改某一个方法。 1.降低页面上各个功能点的耦合程度 我们不希望同一个功能点同样的代码在页面肆意copy,这样带来了极高的维护成本。以至后面无法维护。并且功能的复用不希望是copy,前端与后端不同的是不仅是单单的逻辑复用、更有布局、样式等。 2.提供代码的可复用性、可维护性 对于一个程序员来说,如果你打开一个代码文件。映入眼帘的是密密麻麻的代码,行数达到好几千K行,我相信大家的第一反应是抗拒的,更别说去修改代码,天知道会改出什么问题。 3.降低单一文件的复杂度 4.如果是公共功能的化我们还希望它能够有自己的作用域,保持自己的独立性。 [图片] 3. 根据以上几点,我们用一个页面举例,如何去拆分一个页面,首先我们需要有以下几点认识: 决定一个页面如何组件化的前提是该页面的功能是否是有全局都需要的功能模块 功能模块是否需要与页面其他模块强耦合 单个功能模块逻辑是否过于复杂(占用代码空间过大)——>单纯是为了页面代码的可读性。 不是全拆成组件就是最好的,不能为了组件而组件化 [图片] 4. 说了这么多,其实我们应该首先应该了解下,组件的特性? 专一性(一个组件只干一件事情,或者某一类事情。)功能的高度内聚 比如说右侧的feed集上的头像、它是一个组件、就负责显示头像跟跳转,其他的事它都不参与 可配置(能够适应通过设置属性值的方式来输出不同的东西)输入影响部分输出 然后我们同时可以设置头像组件上的size属性来设置头像在不同页面下的大小样式 生命周期(组件可以在自身或者说所在页面的生命周期内可以做不同的事情)比如可以在组件生成的时候进行数的初始化、属性值的类型校验、组件销毁时并同时销毁定时器等其他任务 事件传递 (既然要让组件与页面保持独立性,那么组件与页面的通讯交互就得需要一个标准) 右侧的feed组件其实是一个组件集合、我们通过组合不同的组件然后就形成了feed组件。就跟搭积木一样、只需要引入组件就行了。特别方便。 [图片] 5. 说到组件,那么小程序早期的不支持自定义组件开发这就很让人头疼、同样的feed组件我们经历了几乎三个版本的大改动、从最开始的直接写在页面里,后台使用template方式、再到后来的自定义组件方式。所以我们的演进步骤就成了page->template->component, 这儿列了一个表格对比了下几种组件化方式的对比。 可以看到,include的方式其实是最鸡肋的,include的方式其实实际意义上我理解成更多的是代码的切割,并且还不能将(template、wxs)分出去、所以这种方式我们直接pass掉了, 而template的方式其实是我们曾经主力使用的方式、到现在我们也还在使用、相对于include来说,template有了独立的作用域、虽然css、跟js还是与页面共享的。但是已经可以做一些比较简单的事情了。 对于component来说,完完全全的组件,满足了组件的所有要求。 [图片] 6. 先说说template的方式吧,举个列子,这个是我们的使用template构建的头像组件。跟写页面的方式很像、同样是js、wxss、wxml组成。用名称来命名。但是由于微信当时没有很方的方式去引用这些文件,或者说没有一种方法可以打包供我们很方便的使用。但是比起之前直接copy代码的方式、这样通过引用的方式使用其实感觉已经好了很多了。 [图片] [图片] 7. 具体的使用方式我画了张图,对应组件内文件与页面文件的对应方式、这里对于js的引用其实我们是做了一些小动作, 我们在调用Page方法前做了一次page方法与组件方法的check,因为在page代码里我们不能保证所有的方法名不会跟组件内的方法名不会冲突,所以我们做了这个一个检查、 然后mix函数还做了另一个事情就是将page方法与组件方法合并。然后对于mix函数其实我们还可以做很多事情、、比如规范生命周期回调函数放在一个对象内,然后我们自己定义的方法放在另一个对象里,就跟vue一样。 But,在经历了一段template组件化的时间后,我们又觉得这个方式还是有点烂,为什么呢?在使用时仍然不能避免引入众多的文件、虽然我们对js文件做了处理,但是wxss的样式仍然会被污染、js与page仍然共享作用域。并不能成为一个真正的标准组件。好在后来,微信上了自定义组件的功能,接下来聊聊这个标准的微信自定义组件吧。 8. 微信提供了自定义组件的功能后我们也第一时间跟进了,相对于template这种方式来说,现在是真正的独立于页面存在。使用也比之前更为方便与简洁,右图是我们对component的一个项目目录划分。我们将component划分为了公共组件与页面组件、为什么会有页面组件, 1.是为了降低页面代码的复杂度 2.为了好看。 公共组件就不说了,一定是最基础、最通用的组件。 [图片] 9. 转向component方式后有一个问题逐渐便凸显出来了,由于组件的独立作用域,组件间的通讯就成了一个问题,接下来聊一聊组件的事件传递。微信最开始的时候提供了一种triggerEvent的方式,可是这样的方式似乎并不能满足我们某些场景下的需求。后来又提供了page下selectComponent方法来直接操作组件内部的属性与方法。然后还有就是基于我们自己的事件广播机制。这几种方式构成了小打卡现目前最主要的组件与page、组件与组件间的数据交互方式 [图片] 10. 先来说说triggerEvent模式,微信在自定义组件上可以自定义监听函数。我们在组件内将需要向外抛出的事件统一通过this.triggerEvent(‘invoke’,{handler:’fun’,data:{}})这个方法来执行。其中invoke对应了我们绑定在组件标签上的监听函数。而将需要外部执行的方法与数据通过数据的方式传给监听函数。而在page上面我通过统一的监听回调函数去自动执行需要执行的方法、这里的trigger与event都不要我们去手写在组件与page创建的时候底层就已经帮我们预置了,我们只需要关注业务开发就行。这是对于一部分需要page与组件交互的模式。而对于我们想直接操作组件方法而不需要反馈的模式就得使用selectComponent的模式 [图片] 11. 一个简单的列子:全局的toast组件。在需要弹出toast的时候我们想直接调用就行、不用在通过传值给组件、然后由组件来执行显示或隐藏。这类组件我们在组件目录里新增了一个lib的文件。在page里只需要引入这个lib文件然后就可以直接调用toast组件。lib主要是对this.selectCompent与执行逻辑的一个封装。 [图片] 12. 事件发布订阅模式:基于底层的eventBus。简化后我们用在了组件与组件之间的通讯上、特点是简单。 [图片] 13. 解决了组件间的通讯问题,可是对于公共组件的引用仍然让我们觉得麻烦与不畅快、所以我们构建了全局通用模版、它是干什么的呢。它提供给了一些基础的全局组件、比如自定义导航头、toast、loading等等。小打卡所有的页面都通过slot的方式插入到这个模版组件x-page下面。这样就解决了我们需要在每个页面引入公共组件的问题。另一个问题使用自定义导航栏的时定位起点会有状态栏下移动到屏幕左上方。会造成布局的错误。通过x-page可以很好解决这个问题而不用重新布局。并且通信问题也不用担心,都是由x-page组件作为中台来对内对外进行分发与执行。 [图片] [图片] [图片] 14. 通过以上小打卡的开发模式就基本形成。要做的事情还有很多,更多组件的玩儿法,对于现在或者将来我们正在做的。 是构建小打卡的组件与基础sdk的仓库。 拆分组件开发与业务开发。 通过npm包管理的方式来应对越来越多的小程序平台的开发。 或者通过形成小程序插件的方式供其他小伙伴使用。 [图片] [图片] 以上就是我今天分享的内容。谢谢。
2019-04-26 - 微信第三方平台问题,获取类目,设置类目
我们是一家微信第三方平台,在使用过程中,很多商家的小程序已经自行申请好了,但是没有设置基本信息 其中类目大多数60%都不会添加,需要引导用户添加 幸运的是我们在open开放平台看到可以添加设置类目 可惜的是,我们调用接口,发现这个添加设置只能对第三方平台上创建的小程序进行操作 这个很不利于我们后面的操作,没有类目用户就不能提交版本,如果加上肯定事半功倍 .
2018-11-28 - 第三方模板库的模板ID全部没了
- 第三方模板库的模板ID为什么不增加了?我原来提交的小程序模板库ID全没了,然后有从1开始了,然后再提交新的小程序上去,每次都是覆盖那个模板ID,不增加新的小程序模板库ID。是什么原因呀。 - 预期表现 原来原创开发的小程序模板库都没了~提交应该新增,也不新增了
2018-11-11 - 越过山丘-微盟小程序开发问题的解决之道
本文整理了微盟小程序开发过程中遇到的一些问题,快看看你遇到的问题这里有没有答案吧! 问题一: iOS时间转换问题怎么解决?pc端调试、安卓机都能正常执行,iOS上time的结果是null var resData = '2017-3-14 10:03:45' console.log("返回时间:" + resData) var time = Date.parse(new Date(resData)) ; console.log(time); 解决方案: 将 '-' 替换成 '/' resData = resData.replace(/-/g, '/'); 问题二: 直播过程中可能会开始、暂停、继续、结束,客户端需要相应的做暂停和播放、结束,我们是希望通过消息的发送来通知到客户端,但是腾讯云IM的API调接口发群消息,限制 100次/秒,满足不了我们的需求 解决方案: 两点。 1、客户端轮询我们的接口,查询播放状态,舍弃。因为客户端数量过大时,服务端扛不住。 2、操作管理后台界面做轮询,一旦暂停,发送消息。客户端接口通过特定code跟普通消息做区分。 问题三: storage不可以共用,小程序和插件之间数据共享有问题怎么解决呢? 解决方案: 插件使用小程序的共用方法(该方法由插件初始化时从构造函数传入),可以通过此方法从小程序发起请求、存储数据等。 问题四: 小程序的10层跳转限制怎么破? 解决方案: 判断一下当前页面长度是否达到10级,达到10级用redirectTo,未达到用navigateTo var jumpUrl = function (data) { let pages = getCurrentPages(); if (pages.length >= 10) { wx.redirectTo({ url: data }) } else { wx.navigateTo({ url: data }) } } 问题五: 浮点运算的精度问题一般怎么解决? 解决方案: 先转成整数,再进行运算,最后进行除法运算。 列举一个减法运算: function accSub(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)); //动态控制精度长度 n = (r1 >= r2) ? r1 : r2; return ((arg1 * m - arg2 * m) / m).toFixed(n); } 问题六: 小程序二维码scene的长度限制最多32位可见字符,这个有什么妙招吗 解决方案: 1、中间页 + 短参数 新建一个中间空白跳转页面,每次生成的二维码都是这个页面, 访问这个页面时,将参数中的scene的值,去指定接口获取完整的 带参数的链接, 然后跳转过去,适用于一个解决方案中有很多个页面需要生成二维码来跳转; 2、短参数 二维码指向到特定页面,scene值为短参数,进入页面时请求接口获取完整的参数(json格式); 问题七: 还有一个图片预加载问题,有些活动图片太多太大,且不确定使用哪张图片,接口返回数据时即刻显示图片可能出现短暂空白的问题 解决方案: 分三步,你需要建立一个图片预加载器 1、js /** * 图片预加载组件 */ class ImgLoader { /** * 初始化方法,在页面的 onLoad 方法中调用,传入 Page 对象及图片加载完成的默认回调 */ constructor(pageContext, defaultCallback) { this.page = pageContext this.defaultCallback = defaultCallback || function () { } this.callbacks = {} this.imgInfo = {} this.page.data.imgLoadList = [] //下载队列 this.page._imgOnLoad = this._imgOnLoad.bind(this) this.page._imgOnLoadError= this._imgOnLoadError.bind(this) } /** * 加载图片 * * @param {String} src 图片地址 * @param {Function} callback 加载完成后的回调(可选),第一个参数个错误信息,第二个为图片信息 */ load(src, callback) { if (!src) return; let list = this.page.data.imgLoadList, imgInfo = this.imgInfo[src] if (callback) this.callbacks[src] = callback //已经加载成功过的,直接回调 if (imgInfo) { this._runCallback(null, { src: src, width: imgInfo.width, height: imgInfo.height }) //新的未在下载队列中的 } else if (list.indexOf(src) == -1) { list.push(src) this.page.setData({ 'imgLoadList': list }) } } _imgOnLoad(ev) { let src = ev.currentTarget.dataset.src, width = ev.detail.width, height = ev.detail.height //记录已下载图片的尺寸信息 this.imgInfo[src] = { width, height } this._removeFromLoadList(src) this._runCallback(null, { src, width, height }) } _imgOnLoadError(ev) { let src = ev.currentTarget.dataset.src this._removeFromLoadList(src) this._runCallback('Loading failed', { src }) } //将图片从下载队列中移除 _removeFromLoadList(src) { let list = this.page.data.imgLoadList list.splice(list.indexOf(src), 1) this.page.setData({ 'imgLoadList': list }) } //执行回调 _runCallback(err, data) { let callback = this.callbacks[data.src] || this.defaultCallback callback(err, data) delete this.callbacks[data.src] } } module.exports = ImgLoader 2、wxml <template name="img-loader"> <image mode="aspectFill" wx:for="{{ imgLoadList }}" wx:key="*this" src="{{ item }}" data-src="{{ item }}" bindload="_imgOnLoad" binderror="_imgOnLoadError" style="width:0;height:0;opacity:0" /> </template> 3、使用 let images = [ 'http://cdn.weimob.com/saas/activity/bargain/images/arms/shoulie.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/shandian.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/fengbao.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/yingren.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/leiming.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/caidao.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/liehen.png' ] //初始化图片预加载组件,并指定统一的加载完成回调 this.imgLoader = new ImgLoader(this, null); images.forEach(item => { this.imgLoader.load(item) }) 问题八: 服务端时间和客户端时间问题,实际项目中有很多倒计时存在,服务端和客户端时间不对应会引起错乱。 解决方案: 将服务器时间通过接口下发给客户端,计算出二者的差值,计算倒计时的时候,可以将客户端时间加上这个差值来,再跟开始时间或结束时间计算倒计时。 如果你还有什么问题,欢迎在下方留言,我们会尽力为你解答。
2018-10-09 - 微盟小程序性能优化实践(下)
在上一篇分享中,给大家分享了启动性能加载的相关实践,详情可戳右方链接:微盟小程序性能优化实践(上) 接下来和大家聊一聊首屏加载的体验建议和渲染性能优化。 二、首屏加载的体验建议 · 提前请求 异步数据请求不需要等待页面渲染完成。 · 利用缓存 利用storage API对异步请求数据进行缓存,二次渲染页面,再进行后台更新。 · 避免白屏 先展示页面骨架和基础内容。 三、渲染性能优化 · 每次 setData 的调用都是一次进程间通信过程,通信开销与 setData 的数据量正相关 · setData 会引发视图层页面内容的更新,这一耗时操作一定时间中会阻塞用户交互 · setData 是小程序开发使用最频繁,也是最容易引发性能问题的 · 在页面列表中使用懒加载+动态移除非可视区域范围内的内容,让dom小下去 · 耗时比较长的js做到异步,不要阻塞进程(js属于单线程) · 少使用scroll-view,这个组件对性能的影响太大,单纯的只是需要一块区域滚动,可以使用view+css的方式实现 · 在页面频繁滚动触发回调函数,会导致页面卡顿,这时必须和防抖动函数或者节流函数相结合做一些处理 · 页面中的图片可以使用懒加载的方式(添加lazy-load属性,只针对page与scroll-view下的image有效) · 页面跳转要做一下限制,如果页面快速点击会出现跳转多次的情况 避免不正当的使用setData · 使用data在方法间共享数据,可能增加setData传输的数据量。data 应该仅仅包含与页面渲染相关的数据 · 使用setData 传输大量的数据,通讯耗时与数据量成正比,导致页面更新延迟 可能造成页面更新开销增加。所以setData 仅传输页面需要的数据,使用setData 的特殊Key 实现局部更新 · 短时间内频繁调用setData (操作卡顿、交互延迟 阻塞通信、页面渲染延迟),对连续的setData 调用进行合并 · 后台进行页面setData (抢占前台页面的渲染资源) 例如 活动定时器 再页面切入后台时应该将关闭 避免不正当的使用onPageScroll · 只在必要的时候监听pageScroll 事件 · 避免在onPageScroll 中执行复杂的逻辑 · 避免在onPageScroll 中频繁调用setData · 避免频繁查询节点信息(SelectQuery) 部分场景建议使用节点布局相交状态 · 监听( IntersectionObserver) 替代 使用自定义组件 在需要频繁更新的场景下,自定义组件的更新只在组件内部进行,不受页面部分内容的复杂性的影响。 使用体验评分功能 在开发过程中使用体验评分可以测试出代码中一些需要优化的点,准备定位到影响性能的原因,很大程序提高页面的性能。
2018-09-25 - 微盟小程序性能优化实践(上)
微盟小程序性能优化要分享的内容分为三部分,启动性能加载、首屏加载的体验建议和渲染性能优化。 今天主要讲启动性能加载的性能优化实践,先看启动加载过程的流程: [图片] · 公共库注入 · 资源准备(基础UI创建,代码包下载) · 业务代码注入和渲染 · 渲染首屏 · 异步请求 优化方案 1、控制代码包大小 · 开启开发者工具中的 “ 上传代码时自动压缩 ” · 及时清理无用代码和资源文件 · 减少代码包中的图片等资源文件的大小和数量 · 将图片等资源文件放到CND中 · 提取公共样式 · 代码压缩,图片格式,压缩,或者外联 · 公共组件提取,代码复用 2、 分包加载 分包加载过程流程 [图片] 在开发小程序分包项目时,会有一个或者多个分包,其中没有分包小程序必须包含一个主包,即放置启动页面或者tabBar页面,以及一些分包都需要用到的公共资源脚本。 在小程序启动时,默认会下载主包并且启动主包内页面,如果用户打开分包内的页面,客户端会把分包下载下来,下载完之后再进行展示。 · 分包加载流程 [图片] 使用分包加载的优点: · 能够增加小程序更大的代码体积,开发更多的功能 · 对于用户,可以更快地打开小程序,同时不影响启动速度 使用分包加载有哪些限制: · 整个小程序所有分包不能超过8M · 单个主包/分包不能超过2M 3、 运行机制优化 · 代码中减少立即执行的代码数量 · 避免高开销和长时间阻塞代码 · 业务代码都写入页面的生命周期中 · 做好缓存策略 4、 数据管理优化 · 首屏请求数量尽量不能超过5个,超过的可以做接口合并(node层,服务端都可以处理) · 对多次提交的数据可以做合并处理 首屏加载的体验建议和渲染性能优化这两部分的内容,将在下次分享给大家。微盟小程序性能优化实践(下)
2018-09-25