- 小程序调试新方案——使用WeConsole监控console/network/api/component/storage
[图片] 一、背景与简介 在传统的 PC Web 前端开发中,浏览器为开发者提供了体验良好、功能丰富且强大的开发调试工具,比如常见的 Chrome devtools 等,这些调试工具极大的方便了开发者,它们普遍提供查看页面结构、监听网络请求、管理本地数据存储、debugger 代码、使用 Console 快速显示数据等功能。 但是在近几年兴起的微信小程序的前端开发中,却少有类似的体验和功能对标的开发调试工具出现。当然微信小程序的官方也提供了类似的工具,那就是 vConsole,但是相比 PC 端提供的工具来说确实无论是功能和体验都有所欠缺,所以我们开发了 weconsole 来提供更加全面的功能和更好的体验。 基于上述背景,我们想开发一款运行在微信小程序环境上,无论在用户体验还是功能等方面都能媲美 PC 端的前端开发调试工具,当然某些(如 debugger 代码等)受限于技术在当前时期无法实现的功能我们暂且忽略。 我们将这款工具命名为[代码]Weimob Console[代码],简写为[代码]WeConsole[代码]。 项目主页:https://github.com/weimobGroup/WeConsole 二、安装与使用 1、通过 npm 安装 [代码]npm i weconsole -S [代码] 2、普通方式安装 可将 npm 包下载到本地,然后将其中的[代码]dist/full[代码]文件夹拷贝至项目目录中; 3、引用 WeConsole 分为[代码]核心[代码]和[代码]组件[代码]两部分,使用时需要全部引用后方可使用,[代码]核心[代码]负责重写系统变量或方法,以达到全局监控的目的;[代码]组件[代码]负责将监控的数据显示出来。 在[代码]app.js[代码]文件中引用[代码]核心[代码]: [代码]// NPM方式引用 import 'weconsole/init'; // 普通方式引用 import 'xxx/weconsole/init'; [代码] 引入[代码]weconsole/init[代码]后,就是默认将 App、Page、Component、Api、Console 全部重写监控!如果想按需重写,可以使用如下方式进行: [代码]import { replace, restore, showWeConsole, hideWeConsole } from 'weconsole'; // scope可选值:App/Page/Component/Console/Api // 按需替换系统变量或函数以达到监控 replace(scope); // 可还原 restore(scope); // 通过show/hide方法控制显示入口图标 showWeConsole(); [代码] 如果没有显式调用过[代码]showWeConsole/hideWeConsole[代码]方法,组件第一次初始化时,会根据小程序是否[代码]开启调试模式[代码]来决定入口图标的显示性。 在需要的地方引用[代码]组件[代码],需要先将组件注册进[代码]app/page/component.json[代码]中: [代码]// NPM方式引用 "usingComponents": { "weconsole": "weconsole/components/main/index" } // 普通方式引用 "usingComponents": { "weconsole": "xxx/weconsole/components/main/index" } [代码] 然后在[代码]wxml[代码]中使用[代码]<weconsole>[代码]标签进行初始化: [代码]<!-- page/component.wxml --> <weconsole /> [代码] [代码]<weconsole>[代码]标签支持传入以下属性: [代码]properties: { // 组件全屏化后,距离窗口顶部距离 fullTop: String, // 刘海屏机型(如iphone12等)下组件全屏化后,距离窗口顶部距离 adapFullTop: String, } [代码] 4、建议 如果不想将 weconsole 放置在主包中,建议将组件放在分包内使用,利用小程序的 分包异步化 的特性,减少主包大小 三、功能 1、Console 界面如图 1 实时显示[代码]console.log/info/warn/error[代码]记录; [代码]Filter[代码]框输入关键字已进行记录筛选; 使用分类标签[代码]All, Mark, Log, Errors, Warnings...[代码]等进行记录分类显示,分类列表中[代码]All, Mark, Log, Errors, Warnings[代码]为固定项,其他可由配置项[代码]consoleCategoryGetter[代码]产生 点击[代码]🚫[代码]按钮清空记录(不会清除[代码]留存[代码]的记录) [代码]长按[代码]记录可弹出操作项(如图 2): [代码]复制[代码]:将记录数据执行复制操作,具体形式可使用配置项[代码]copyPolicy[代码]指定,未指定时,将使用[代码]JSON.stringify[代码]序列化数据,将其复制到剪切板 [代码]取消置顶/置顶显示[代码]:将记录取消置顶/置顶显示,最多可置顶三条(置顶无非是想快速找到重要的数据,当重要的数据过多时,就不宜用置顶了,可以使用[代码]标记[代码]功能,然后在使用筛选栏中的[代码]Mark[代码]分类进行筛选显示) [代码]取消留存/留存[代码]:留存是指将记录保留下来,使其不受清除,即点击[代码]🚫[代码]按钮不被清除 [代码]取消全部留存[代码]:取消所有留存的记录 [代码]取消标记/标记[代码]:标记就是将数据添加一个[代码]Mark[代码]的分类,可以通过筛选栏快速分类显示 [代码]取消全部标记[代码]:取消所有标记的记录 [图片] 图 1 [图片] 图 2 2、Api 界面如图 3 实时显示[代码]wx[代码]对象下的相关 api 执行记录 [代码]Filter[代码]框输入关键字已进行记录筛选 使用分类标签[代码]All, Mark, Cloud, xhr...[代码]等进行记录分类显示,分类列表由配置项[代码]apiCategoryList[代码]与[代码]apiCategoryGetter[代码]产生 点击[代码]🚫[代码]按钮清空记录(不会清除[代码]留存[代码]的记录) [代码]长按[代码]记录可弹出操作项(如图 4): [代码]复制[代码]:将记录数据执行复制操作,具体形式可使用配置项[代码]copyPolicy[代码]置顶,未指定时,将使用系统默认方式序列化数据(具体看实际效果),将其复制到剪切板 其他操作项含义与[代码]Console[代码]功能类似 点击条目可展示详情,如图 5 [图片] 图 3 [图片] 图 4 [图片] 图 5 3、Component 界面如图 6 树结构显示组件实例列表 根是[代码]App[代码] 二级固定为[代码]getCurrentPages[代码]返回的页面实例 三级及更深通过[代码]this.selectOwnerComponent()[代码]进行父实例定位,进而确定层级 点击节点名称(带有下划虚线),可显示组件实例详情,以 JSON 树的方式查看组件的所有数据,如图 7 [图片] 图 6 [图片] 图 7 4、Storage 界面如图 8 显示 Storage 记录 [代码]Filter[代码]框输入关键字已进行记录筛选 点击[代码]🚫[代码]按钮清空记录(不会清除[代码]留存[代码]的记录) [代码]长按[代码]操作项含义与[代码]Console[代码]功能类似 点击条目后,再点击[代码]❌[代码]按钮可将其删除 点击[代码]Filter[代码]框左侧的[代码]刷新[代码]按钮可刷新全部数据 点击条目显示详情,如图 9 [图片] 图 8 [图片] 图 9 5、其他 界面如图 10 默认显示 系统信息 可通过[代码]customActions[代码]配置项进行界面功能快速定制,也可通过[代码]addCustomAction/removeCustomAction[代码]添加/删除定制项目 几个简单的定制案例如下,效果如图 11: [代码]import { setUIRunConfig } from 'xxx/weconsole/index.js'; setUIRunConfig({ customActions: [ { id: 'test1', title: '显示文本', autoCase: 'show', cases: [ { id: 'show', button: '查看', showMode: WcCustomActionShowMode.text, handler(): string { return '测试文本'; } }, { id: 'show2', button: '查看2', showMode: WcCustomActionShowMode.text, handler(): string { return '测试文本2'; } } ] }, { id: 'test2', title: '显示JSON', autoCase: 'show', cases: [ { id: 'show', button: '查看', showMode: WcCustomActionShowMode.json, handler() { return wx; } } ] }, { id: 'test3', title: '显示表格', autoCase: 'show', cases: [ { id: 'show', button: '查看', showMode: WcCustomActionShowMode.grid, handler(): WcCustomActionGrid { return { cols: [ { title: 'Id', field: 'id', width: 30 }, { title: 'Name', field: 'name', width: 70 } ], data: [ { id: 1, name: 'Tom' }, { id: 2, name: 'Alice' } ] }; } } ] } ] }); [代码] [图片] 图 10 [图片] 图 10 四、API 通过以下方式使用 API [代码]import { showWeConsole, ... } from 'weconsole'; showWeConsole(); [代码] replace(scope:‘App’|‘Page’|‘Component’|‘Api’|‘Console’) 替换系统变量或函数以达到监控,底层控制全局仅替换一次 restore(scope:‘App’|‘Page’|‘Component’|‘Api’|‘Console’) 还原被替换的系统变量或函数,还原后界面将不在显示相关数据 showWeConsole() 显示[代码]WeConsole[代码]入口图标 hideWeConsole() 隐藏[代码]WeConsole[代码]入口图标 setUIConfig(config: Partial<MpUIConfig>) 设置[代码]WeConsole[代码]组件内的相关配置,可接受的配置项及含义如下: [代码]interface MpUIConfig { /**监控小程序API数据后,使用该选项进行该数据的分类值计算,计算后的结果显示在界面上 */ apiCategoryGetter?: MpProductCategoryMap | MpProductCategoryGetter; /**监控Console数据后,使用该选项进行该数据的分类值计算,计算后的结果显示在界面上 */ consoleCategoryGetter?: MpProductCategoryMap | MpProductCategoryGetter; /**API选项卡下显示的数据分类列表,all、mark、other 分类固定存在 */ apiCategoryList?: Array<string | MpNameValue<string>>; /**复制策略,传入复制数据,可通过数据的type字段判断数据哪种类型,比如api/console */ copyPolicy?: MpProductCopyPolicy; /**定制化列表 */ customActions?: WcCustomAction[]; } /**取数据的category字段值对应的prop */ interface MpProductCategoryMap { [prop: string]: string | MpProductCategoryGetter; } interface MpProductCategoryGetter { (product: Partial<MpProduct>): string | string[]; } interface MpProductCopyPolicy { (product: Partial<MpProduct>); } /**定制化 */ interface WcCustomAction { /**标识,需要保持唯一 */ id: string; /**标题 */ title: string; /**默认执行哪个case? */ autoCase?: string; /**该定制化有哪些情况 */ cases: WcCustomActionCase[]; } const enum WcCustomActionShowMode { /**显示JSON树 */ json = 'json', /**显示数据表格 */ grid = 'grid', /** 固定显示<weconsole-customer>组件,该组件需要在app.json中注册,同时需要支持传入data属性,属性值就是case handler执行后的结果 */ component = 'component', /**显示一段文本 */ text = 'text', /**什么都不做 */ none = 'none' } interface WcCustomActionCase { id: string; /**按钮文案 */ button?: string; /**执行逻辑 */ handler: Function; /**显示方式 */ showMode?: WcCustomActionShowMode; } interface WcCustomActionGrid { cols: DataGridCol[]; data: any; } [代码] addCustomAction(action: WcCustomAction) 添加一个定制化项目;当你添加的项目中需要显示你自己的组件时: 请将 case 的[代码]showMode[代码]值设置为[代码]component[代码] 在[代码]app.json[代码]中注册名称为[代码]weconsole-customer[代码]的组件 定制化项目的 case 被执行时,会将执行结果传递给[代码]weconsole-customer[代码]的[代码]data[代码]属性 开发者根据[代码]data[代码]属性中的数据自行判断内部显示逻辑 removeCustomAction(actionId: string) 根据 ID 删除一个定制化项目 getWcControlMpViewInstances():any[] 获取小程序内 weconsole 已经监控到的所有的 App/Page/Component 实例 log(type = “log”, …args) 因为 console 被重写,当你想使用最原始的 console 方法时,可以通过该方式,type 就是 console 的方法名 on/once/off/emit 提供一个事件总线功能,全局事件及相关函数定义如下: [代码]const enum WeConsoleEvents { /**UIConfig对象发生变化时 */ WcUIConfigChange = 'WcUIConfigChange', /**入口图标显示性发生变化时 */ WcVisableChange = 'WcVisableChange', /**CanvasContext准备好时,CanvasContext用于JSON树组件的界面文字宽度计算 */ WcCanvasContextReady = 'WcCanvasContextReady', /**CanvasContext销毁时 */ WcCanvasContextDestory = 'WcCanvasContextDestory', /**主组件的宽高发生变化时 */ WcMainComponentSizeChange = 'WcMainComponentSizeChange' } interface IEventEmitter<T = any> { on(type: string, handler: EventHandler<T>); once(type: string, handler: EventHandler<T>); off(type: string, handler?: EventHandler<T>); emit(type: string, data?: T); } [代码] 五、后续规划 优化包大小 单元测试 体验优化 定制化升级 基于网络通信的界面化 weconsole 标准化 支持 H5 支持其他小程序平台(支付宝/百度/字节跳动) 六、License WeConsole 使用 MIT 协议. 七、声明 生产环境请谨慎使用。
2021-07-14 - 如何使用canvas绘制签名板?
场景分析在小程序业务中如需用户进行手写签名的场景如:寄快递,签约合同时需要在小程序中进行手写签名。 处理方法 实现原理运用 canvas 监听用户 touch 事件,然后在 canvas 上画出与 touch 事件相近的线模仿手写签名效果。 实现方法参考如下代码片段:https://developers.weixin.qq.com/s/MYDTQAmR7EIa [图片]
09-09 - We分析中的上报事件中如何导出具体数据?
项目的需求是,用户想要导出使用小程序的用户openid、unionid及进入的场景等信息,现用事件上报功能将openid、unionid上报,但在行为分析中选取这两个自定义字段并分组的话,就看不到进入场景的信息了。 所以想请问一下如何获取并导出相关的所有数据呢?
06-25 - 小程序中使用npm安装vant组件实现按需引入减少代码包大小,避免触发用户隐私协议
在小程序中使用 vant 组件库主要有以下两种方式: 下载源代码包放入项目中,可以自己删掉没用到的组件,不过后期只能自己手动更新,会不太好维护 通过 npm 的方式安装管理依赖,后期更新可以直接交给 npm 来管理,方便维护 正常项目中我们可能都会选择 npm 的方式,但是这种方式 vant 和小程序并不支持像我们一般的前端项目中的按需引入,小程序开发工具构建 npm 时会把整个 vant 的组件编译到 miniprogram_npm 目录中,即使我们在项目中没有通过 usingComponents 申明引用的组件也会被打包进代码包中。 [图片] 减少代码包大小 因为小程序主包有 2M 的限制,如果我们本身只用到了几个组件,最终却打包进了整个组件库,这样不仅不合理也额外占用了咱小程序的包大小。想要按需引入的办法只能自己手动去把 miniprogram_npm 目录中没用到的组件删掉,然后再打包上传。不过每次我们提交版本都要这样去操作的话,不光容易出错也很费时间,这里我们就可以借助 node 和 npm 的 script 脚本来自动处理。 大体思路就是,先直接用 node 去自动扫描读取项目中所有 json 文件 中的 usingComponents,找出项目中实际有用到的 vant 组件,然后再去 miniprogram_npm/@vant/weapp 目录下将没有用到的多余组件删除掉就行了,最后直接把相关代码放到项目中的 script 脚本中操作,这样通过类似 npm run vant 这样一条命令 1 秒钟就可以删除掉未使用到的多余组件,实现了按需引入了。 未使用到的 vant 组件也会触发隐私协议 除了减少代码包大小这一项外,其实还有一个更大的痛点,vant 的部分组件会自动触发小程序的隐私协议,比如上传组件 uploader 中用到的:收集你选中的照片或视频信息(wx.chooseImage、wx.chooseMedia、wx.chooseVideo)、收集你选中的文件(wx.chooseMessageFile),这类 api 会自动触发隐私协议授权。 即使你的项目中压根没使用这类组件,上传版本提审的时候小程序还是会自动扫描你 miniprogram_npm 目录下的所有文件,只要代码中有相关的 api 代码就会认为你用到了,然后霸道地强制要求你填写和更新相关隐私说明,随便瞎填一个 99% 会被拒,也不能填写项目中未使用,这样那你自己说未使用就会让你先去把项目中相关的代码删掉再来提审。 vant-tree-shaking 为了方便使用,脚本代码已经封装成了一个 npm 包 vant-tree-shaking(https://www.npmjs.com/package/vant-tree-shaking )上传到了 npm 公共仓库中,大家可以直接通过 npm 来下载使用: 全局安装 [代码]npm install -g vant-tree-shaking [代码] 在小程序开发者工具中上传小程序代码前,直接在项目根目录终端中运行命令:vant-tree-shaking,成功后会在控制台打印出:vant-tree-shaking success。 本地安装 [代码]npm install -D vant-tree-shaking [代码] 需要自己在 package.json 配置文件中配置 script 脚本命令,如直接配置自定义命令 vant: [代码]{ "name": "miniapp", "version": "1.0.0", "main": "app.js", "scripts": { "vant": "vant-tree-shaking", }, "dependencies": { "@vant/weapp": "^1.11.5" }, "devDependencies": { "vant-tree-shaking": "^1.0.0" } } [代码] 在小程序开发者工具中上传小程序代码前,直接在项目根目录终端中运行命令:npm run vant,成功后会在控制台打印出:vant-tree-shaking success。 核心代码 主要用到了 node 的 fs、path 这两个模块,来处理文件、目录读取删除和路径的处理,其实也很简单,完整的源码可以参考 github 仓库(https://github.com/cafehaus/vant-tree-shaking ),有什么使用问题或建议也欢迎大家积极反馈。 [代码]// 1、扫描项目中的所有 json 文件,找出项目中使用到的所有 vant 组件 function readFile(filePath) { if (!filePath || !fs.existsSync(filePath)) return if (fs.statSync(filePath).isDirectory()) { let files = fs.readdirSync(filePath) || [] files.map(m => { if (!IGNORE_DIRS.includes(m)) { let curPath = path.join(filePath, m) readFile(curPath) } }) } else { getUsingComponents(filePath, vantSet) } } // 2、项目中使用到的 vant 组件依赖的其他 vant 组件 function readVantDir() { const files = fs.readdirSync(VANT_PATH) || [] files.map(m => { if (!COMMON_DIRS.includes(m) && vantSet.has(`van-${m}`)) { const curPath = path.join(VANT_PATH, m, 'index.json') getUsingComponents(curPath, dependentSet) } }) // 3、删除未使用到 vant 组件目录 const usedVant = new Set([...vantSet, ...dependentSet]) for (let i = files.length - 1; i >= 0; i--) { const cur = files[i] if (!COMMON_DIRS.includes(cur) && !usedVant.has(`van-${cur}`)) { const curPath = path.join(VANT_PATH, cur) deleteDir(curPath) } } console.log('vant-tree-shaking success') } /** * 读取 json 文件中的 usingComponents 属性值 * @param {*} filePath 路径 * @param {*} setList 存符合条件的 vant 组件名的列表 */ function getUsingComponents(filePath, setList) { if (!filePath || !fs.existsSync(filePath) || !isJsonFile(filePath)) return try { const data = fs.readFileSync(filePath, 'utf8') const json = JSON.parse(data) const usingComponents = json.usingComponents || {} for (const key in usingComponents) { if (key.startsWith('van-')) { setList.add(key) } } } catch (err) { console.error(err) } } [代码] 实际测试的一个项目,其中只使用到了 vant 的自定义导航栏 van-nav-bar 组件,没有按需引入时整个代码包大小 544KB,按需引入之后只有 156KB。除了代码包减少了以外,也不用再担心其他未使用到的组件默认触发隐私协议而被拒审了。
05-25 - 在小程序中愉快的使用 Tailwind CSS 吧!
[图片] 在小程序中愉快的使用 Tailwind CSS 吧! 把 [代码]tailwindcss JIT[代码] 思想带入小程序开发吧! 在小程序中愉快的使用 Tailwind CSS 吧! Usage uni-app (vue2/3) uni-app for vite (vue3) Taro v3 (React/vue2/3) remax (react) rax (react) 原生小程序(webpack5 mina) Options 配置项 使用 arbitrary values Q&A 1. 我在 [代码]js[代码] 里写了 [代码]tailwindcss[代码] 的任意值,为什么没有生效? 2. 一些像 [代码]disabled:opacity-50[代码] 这类的 [代码]tailwindcss[代码] 前缀不生效? 3. 和原生组件一起使用注意事项 4. 编译到 h5 注意事项 Related projects 模板 template 预设 tailwindcss preset Bugs & Issues 笔者之前写了一个 tailwindcss-miniprogram-preset,可是那个方案不能兼容最广泛的 [代码]Just in time[代码] 引擎,在写法上也有些变体。 于是笔者又写了一个 [代码]weapp-tailwindcss-webpack-plugin[代码],这是一个 [代码]plugin[代码] 合集,包含 [代码]webpack/vite plugin[代码],它会同时处理类 [代码]wxml[代码] 和 [代码]wxss[代码] 文件,从而我们开发者,不需要更改任何代码,就能让 [代码]jit[代码] 引擎兼容微信小程序。 此方案可兼容 [代码]tailwindcss v2/v3[代码],[代码]webpack v4/v5[代码],[代码]postcss v7/v8[代码]。 随着 [代码]@vue/cli-service[代码] v5 版本的发布,uni-app 到时候也会转为 [代码]webpack5[代码] + [代码]postcss8[代码] 的组合,到时候,我会升级一下 [代码]uni-app[代码] 的示例,让它从 [代码]tailwindcss v2 jit[代码] 升级到 [代码]tailwindcss v3 jit[代码] Usage uni-app (vue2/3) 使用方式 | Demo 项目 uni-app for vite (vue3) 使用方式 | Demo 项目 Taro v3 (React/vue2/3) 使用方式 | React Demo 项目 | vue2 Demo 项目 | vue3 Demo 项目 remax (react) 使用方式 | Demo 项目 rax (react) 使用方式 | Demo 项目 原生小程序(webpack5 mina) 使用方式 | Demo 项目 Options 配置项 配置项 类型 描述 [代码]htmlMatcher[代码] [代码](assetPath:string)=>boolean[代码] 匹配 [代码]wxml[代码]等等模板进行处理的方法 [代码]cssMatcher[代码] [代码](assetPath:string)=>boolean[代码] 匹配 [代码]wxss[代码]等等样式文件的方法 [代码]jsMatcher[代码] [代码](assetPath:string)=>boolean[代码] 匹配 [代码]js[代码]文件进行处理的方法,用于 [代码]react[代码] [代码]mainCssChunkMatcher[代码] [代码](assetPath:string)=>boolean[代码] 匹配 [代码]tailwindcss jit[代码] 生成的 [代码]css chunk[代码] 的方法 [代码]framework[代码] ([代码]Taro[代码] 特有) [代码]react[代码]|[代码]vue2[代码]|[代码]vue3[代码] 由于 [代码]Taro[代码] 不同框架的编译结果有所不同,需要显式声明框架类型 默认[代码]react[代码] [代码]customRuleCallback[代码] [代码](node: Postcss.Rule, options: Readonly<RequiredStyleHandlerOptions>) => void[代码] 可根据 Postcss walk 自由定制处理方案的 callback 方法 [代码]cssPreflight[代码] [代码]Record<string,string>[代码]| [代码]false[代码] 在所有 [代码]view[代码]节点添加的 [代码]css[代码] 预设,可根据情况自由的禁用原先的规则,或者添加新的规则。 详细用法如下: [代码]// default 默认: cssPreflight: { 'box-sizing': 'border-box', 'border-width': '0', 'border-style': 'solid', 'border-color': 'currentColor' } // result // box-sizing: border-box; // border-width: 0; // border-style: solid; // border-color: currentColor // case 禁用所有 cssPreflight: false // result // none // case 禁用单个属性 cssPreflight: { 'box-sizing': false } // border-width: 0; // border-style: solid; // border-color: currentColor // case 更改和添加单个属性 cssPreflight: { 'box-sizing': 'content-box', 'background': 'black' } // result // box-sizing: content-box; // border-width: 0; // border-style: solid; // border-color: currentColor; // background: black [代码] 使用 arbitrary values 详见 tailwindcss/using-arbitrary-values 章节 | Sample Q&A 1. 我在 [代码]js[代码] 里写了 [代码]tailwindcss[代码] 的任意值,为什么没有生效? 详见 issue#28 A: 因为这个插件,主要是针对, [代码]wxss[代码],[代码]wxml[代码] 和 [代码]jsx[代码] 进行转义的,[代码]js[代码] 里编写的 [代码]string[代码] 是不转义的。如果你有这样的需求可以这么写: [代码]import { replaceJs } from 'weapp-tailwindcss-webpack-plugin/replace' const cardsColor = reactive([ replaceJs('bg-[#4268EA] shadow-indigo-100'), replaceJs('bg-[#123456] shadow-blue-100') ]) [代码] 你不用担心把代码都打进来导致体积过大,我在 ‘weapp-tailwindcss-webpack-plugin/replace’ 中,只暴露了2个方法,代码体积 1k左右,esm格式。 2. 一些像 [代码]disabled:opacity-50[代码] 这类的 [代码]tailwindcss[代码] 前缀不生效? 详见 issue#33,小程序选择器的限制。 3. 和原生组件一起使用注意事项 假如出现原生组件引入报错的情况,可以参考 issue#35 ,忽略指定目录下的文件,跳过插件处理,比如 [代码]uni-app[代码] 中的 [代码]wxcomponents[代码]。 如何更改?在传入的配置项 [代码]cssMatcher[代码],[代码]htmlMatcher[代码] 这类中过滤指定目录或文件。 4. 编译到 h5 注意事项 有些用户通过 [代码]uni-app[代码] 等跨端框架,不止开发成各种小程序,也开发为 [代码]H5[代码],然而 [代码]tailwindcss[代码] 本身就兼容 [代码]H5[代码] 了。此时你需要更改配置,我们以 [代码]uni-app[代码] 为例: [代码]const isH5 = process.env.UNI_PLATFORM === 'h5'; // 然后在 h5 环境下把 webpack plugin 和 postcss for weapp 给禁用掉 // 我们以 uni-app-vue3-vite 这个 demo为例 // vite.config.ts import { defineConfig } from 'vite'; import uni from '@dcloudio/vite-plugin-uni'; import { ViteWeappTailwindcssPlugin as vwt } from 'weapp-tailwindcss-webpack-plugin'; // vite 插件配置 const vitePlugins = [uni()]; !isH5 && vitePlugins.push(vwt()); export default defineConfig({ plugins: vitePlugins }); // postcss 配置 // 假如不起作用,请使用内联postcss const isH5 = process.env.UNI_PLATFORM === 'h5'; const plugins = [require('autoprefixer')(), require('tailwindcss')()]; if (!isH5) { plugins.push( require('postcss-rem-to-responsive-pixel')({ rootValue: 32, propList: ['*'], transformUnit: 'rpx' }) ); plugins.push(require('weapp-tailwindcss-webpack-plugin/postcss')()); } module.exports = { plugins }; [代码] Related projects 模板 template uni-app-vite-vue3-tailwind-vscode-template uni-app-vue3-tailwind-vscode-template uni-app-vue2-tailwind-vscode-template weapp-native-mina-tailwindcss-template 预设 tailwindcss preset tailwindcss-miniprogram-preset Bugs & Issues 目前这个插件正在快速的开发中,如果遇到 [代码]Bug[代码] 或者想提出 [代码]Issue[代码] 欢迎提交到此处 <!-- ## 关于其他小程序 处理了其他小程序的: [代码]/.+\.(?:wx|ac|jx|tt|q|c)ss$/[代码] 样式文件和 [代码]/.+\.(?:(?:(?:wx|ax|jx|ks|tt|q)ml)|swan)$/[代码] 各种 [代码]xxml[代码] 和特殊的 [代码]swan[代码] -->
2022-05-16 - 可以在小程序内判断用户是否关注公众号吗?
可以在用户使用小程序时判断用户有没有关注公众号,并进行提醒呢?
2017-12-26 - 小程序如何判断用户是否关注公众号?
[图片] 这个接口可以获取是否关注标识,但是openid怎么获取,能不能通过unionid获取? 求助!!!
2021-05-28 - 如何让微信小程序的全部页面重新加载一遍
比如我点击一个按钮,那么小程序中的全部页面,全部重新加载,就类似重新打开一次小程序
2019-02-26 - 修改小程序swiper组件面板指示点样式
修改当前显示眯的宽度及圆角值,记得加!important;如下所示: .wx-swiper-dot-active{ width:25px !important; border-radius: 5px !important; }
2021-07-16 - 小程序页面之间跳转的时候,怎么传递参数?
小程序页面之间跳转的时候,怎么传递参数?哪位大神知道?
2017-03-31 - 动态预加载 swiper-item 中的图片(延迟加载)
在使用 swiper 时,有些场景需要一次载入的图片太多,消耗资源的同时也会影响用户体验。 [图片] 自己的解决方法是这样的: 创建图片【地址数组】根据需要展示图片的总数创建一个空的数组,即用来页面绑定的【页面数组】初始化【页面数组】,为第 1、2 项赋值当向右滑动时,接着为【页面数组】的第 3 项赋值直到全部展示完毕[图片] 如果用户提前离开,就能避免未展示项图片的加载。 代码如下: <view class="s-page"> <swiper class="s-swiper" snap-to-edge="false" indicator-dots="true" bindchange="swiperChange"> <block wx:for="{{pageList}}" wx:key="*this"> <swiper-item class="s-swiper_item"> <view>{{item}}</view> </swiper-item> </block> </swiper> </view> Page({ /** * 页面的初始数据 */ data: { imageUrlList: ['url 1', 'url 2', 'url 3', 'url 4', 'url 5', 'url 6', 'url 7'], pageList: [], currentPageIndex: 0 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { // 根据图片总数,创建需要渲染的空数组 let pageList = new Array(this.data.imageUrlList.length).fill(''); // 初始化渲染数组,载入第1张,并且预载入下一张 pageList[this.data.currentPageIndex] = this.data.imageUrlList[this.data.currentPageIndex]; pageList[this.data.currentPageIndex + 1] = this.data.imageUrlList[this.data.currentPageIndex + 1]; // 渲染页面 this.setData({ pageList: pageList }) console.log(this.data.pageList); }, swiperChange: function (e) { let oldPageIndex = this.data.currentPageIndex, // 之前展示的页面索引 newPageIndex = e.detail.current; // 滑动后新展示的页面索引 // 判断是否由用户触摸引起的 if (e.detail.source == 'touch') { console.log(newPageIndex); // 判断滑动方向 if (oldPageIndex < newPageIndex) { console.log('向右滑动'); // 判断是否到最后一张,并且地址信息是否为空 if (newPageIndex < this.data.imageUrlList.length - 1 && !this.data.pageList[newPageIndex + 1]) { this.data.pageList[newPageIndex + 1] = this.data.imageUrlList[newPageIndex + 1] console.log('预加载成功'); } // 渲染页面 this.setData({ currentPageIndex: newPageIndex, pageList: this.data.pageList }) } else if (oldPageIndex > newPageIndex) { console.log('向左滑动'); this.data.currentPageIndex = newPageIndex; } } console.log(this.data.pageList); } }) 如果不显示“面板指示点”,那么【页面数组】直接创建一个空数组即可。 自己的笨办法,如果有更好的方法,请不吝赐教!
2021-07-07