钉钉H5应用踩坑(1)
钉钉H5微应用开发踩坑,钉钉的Webview内核据说是UC浏览器内核,许多CSS/JS的兼容性问题,例如thead/tr
不能使用position:sticky
, 还有table
标签的滚动行为两个端(iOS/Android)不一致,和table的宽度设置相关.在Android端一个overflow-y:auto的容器中,直接放个table(宽度比父容器大),是无法滚动的,而iOS端可以.
兼容全面屏iOS底部动作条
- 为Html设置meta,主要是设置
viewport-fit
- Auto:默认值。这个值不影响初始布局视窗,整个 Web 页面是可视的,与Contain表现一致。
- Contain:最初的布局视窗和视觉布局视窗被设置为最大的矩形。
- Cover:初始布局视窗和视觉布局视窗被设置为设备物理屏幕的限定矩形。
在尺寸较大的设备中,在这些设备上,应用显示区域不一定是全屏的,viewport 是浏览器窗口的大小。
在大多数移动设备中,浏览器是全屏的,viewport 是整个屏幕的大小。
在全屏模式下,viewport 是设备屏幕的范围,窗口是浏览器窗口,浏览器窗口大小小于或等于视口的大小,并且文档是这个网站,文档的大小可比 viewport 长或宽。概括地说,viewport 基本上是当前文档的可见部分。
1
| <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no,viewport-fit=cover">
|
- 为需要适配的元素加padding或是margin,
constant(safe-area-inset-bottom)
,env(safe-area-inset-bottom)
在全面屏iOS设备上会获取底部动作栏的inset1 2 3 4
| .bottom-bar { padding-bottom: constant(safe-area-inset-bottom); //兼容 IOS<11.2 padding-bottom: env(safe-area-inset-bottom); //兼容 IOS>11.2 }
|
- constant(safe-area-inset-top):在Viewport顶部的安全区域内设置量(CSS像素)
- constant(safe-area-inset-bottom):在Viewport底部的安全区域内设置量(CSS像素)
- constant(safe-area-inset-left):在Viewport左边的安全区域内设置量(CSS像素)
- constant(safe-area-inset-right):在Viewport右边的安全区域内设置量(CSS像素)
禁用iOS的WebView回弹效果
禁用 主要是为了两端一致
1 2 3 4
| dd.ready(() => { dd.ui.webViewBounce.disable() })
|
设置页面标题
1 2 3 4 5 6 7 8
| try { await dd.biz.navigation.setTitle({ title: title, }) } catch (error) { document.title = title }
|
复制文字
1 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
| import dd from "gdt-jsapi"; import { Notify } from "vant"; import ClipboardJS from "clipboard";
export async function copyText(content) { try { await dd.version(); await dd.copyToClipboard({ text: content }); Notify({ message: "复制成功", type: "success", duration: 5000, }); } catch (e) { console.log(e); const text = document.createElement("p"); text.setAttribute("data-clipboard-text", content); const copy = new ClipboardJS(text); copy.on("success", function () { Notify({ message: "复制成功", type: "success", duration: 5000, }); }); copy.on("error", function (event) { console.log(event); Notify({ message: "复制失败", type: "danger", }); }); text.click(); } }
|
使用VConsole在线调试
因为钉钉没有好用的调试工具,而许多问题需要在线调试,所以最好有个线上环境可用的调试工具,VConsole可以当做一个简易的控制台来用.
1.首先引入VConsole
使用的版本是3.14.6,开启mini控制台很简单,只需要 new VConsole()
2.给VConsole加全局/临时开关
因为项目用的Vite + Vue3,全局的开关就放在.env
文件中,我们先在.env.production
文件里加个变量VITE_OPEN_CONSOLE
,不为空时表示默认打开mini控制台
1 2
| MODE=production VITE_OPEN_CONSOLE=1
|
然后,看下main.js,项目在初始化时会自动导入src/modules
下的模块,代码如下:
src/main.js
1 2 3 4 5 6
| const app = createApp(App) app.use(router)
Object.values(import.meta.globEager('./modules/*.js')).forEach(function (i) { return i.install?.({ app, router }) })
|
我们新建一个console.js
作为module引入,导出一个install方法,接收app,router参数. 这里我们通过import('vconsole')
动态导入VConsole,这样打包时会分成更小的chunk.然后把具体的逻辑放到composables
下. 这里的consoleInstance
是VConsole当前的实例Ref, isConsoleSupport
是初始化时从.env文件获取的开关值Ref<Boolean>
src/modules/console.js
1 2 3 4 5 6 7 8 9 10 11
| import { unref } from 'vue' import { useConsole } from '~/composables' export const install = ({ app, router }) => { const { consoleInstance, isConsoleSupport } = useConsole() import("vconsole").then(({ default: VConsole }) => { if (unref(isConsoleSupport)) { consoleInstance.value = new VConsole() } }) }
|
再看composables内的useConsole, isConsoleShow
是当前是否有打开mini控制台,其他模块如果需要打开,可以调用暴露的toggleConsole
方法
src/composables/console.js
1 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
| const consoleInstance = ref(null) const initValue = Boolean(import.meta.env.VITE_OPEN_CONSOLE); const isConsoleSupport = ref(initValue) const isConsoleShow = ref(initValue) export function useConsole() { function toggleConsole() { if (consoleInstance.value) { if (isConsoleShow.value) { consoleInstance.value?.hideSwitch() } else { consoleInstance.value?.showSwitch() } isConsoleShow.value = !isConsoleShow.value; } else { import("vconsole").then(({ default: VConsole }) => { consoleInstance.value = new VConsole() }) isConsoleShow.value = true } } return { consoleInstance, isConsoleSupport, toggleConsole, isConsoleShow } }
|
然后可以在设置界面加个隐藏的开关,用来打开mini控制台,比如点击版本号N次打开,这里我们学安卓的开发者模式,连续点了8次之后打开开发者模式,顺带打开控制台.
1 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
| import { Toast } from "vant" import { useConsole } from '~/composables' import { unref } from "vue" const isDeveloperModeOpen = useLocalStorage("DEVELOPMENT_MODE", false) const MAX_CLICK_COUNT = 8 export function useDeveloperKits() { const { vibrate, isSupported } = useVibrate({ pattern: [300, 100, 300] }) const tapCount = ref(0)
const { consoleInstance, isConsoleSupport, isConsoleShow, toggleConsole } = useConsole()
function doOpenDevelopmentMode(openConole) { if (!unref(isConsoleSupport)) { isConsoleSupport.value = true if (!openConole) { toggleConsole() } } } function doCloseDevelopmentMode() { consoleInstance.value?.destroy() isConsoleSupport.value = false isConsoleShow.value = false isDeveloperModeOpen.value = false }
const debouncedOpenDevelopMode = useDebounceFn((openConole) => { if (tapCount.value >= MAX_CLICK_COUNT) { isDeveloperModeOpen.value = true; console.log('开启开发者模式'); Toast('开启开发者模式'); doOpenDevelopmentMode(openConole) } tapCount.value = 0; if (isSupported) { vibrate(); } }, 1000) function openDeveloperMode(openConole) { if (isDeveloperModeOpen.value || tapCount.value >= MAX_CLICK_COUNT) { return } tapCount.value += 1; console.log('tapCount', tapCount.value); if (tapCount.value >= (MAX_CLICK_COUNT - 3) && tapCount.value < MAX_CLICK_COUNT) { Toast(`再按${MAX_CLICK_COUNT - tapCount.value}次进入开发者模式`); } debouncedOpenDevelopMode(openConole) } return { isDeveloperModeOpen, doOpenDevelopmentMode, openDeveloperMode, doCloseDevelopmentMode } }
|
这里给开发者模式也加了个本地开关的存储,我在App.vue中判断了下,true时直接调用doOpenDevelopmentMode
打开开发者模式,这样第一次打开开发者模式,下次重新进入时会自动打开
setting.vue
1 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
| <template> //....省略 <van-cell title="版本" @click="openDeveloperMode">ver 1.0.1</van-cell> <van-cell title="迷你控制台" v-if="isConsoleSupport"> <van-switch size="1rem" :modelValue="isConsoleShow" @update:model-value="toggleConsole" ></van-switch> </van-cell>
<van-cell title="开发者模式" v-if="isDeveloperModeOpen"> <van-switch size="1rem" :modelValue="isDeveloperModeOpen" @update:model-value="doCloseDevelopmentMode" ></van-switch> </van-cell> //....省略 </template> <script setup> import { useConsole, useDeveloperKits } from "~/composables"; //开关控制台 const { toggleConsole, isConsoleShow, isConsoleSupport } = useConsole(); //开发者模式 const { isDeveloperModeOpen, openDeveloperMode, doCloseDevelopmentMode, } = useDeveloperKits(); </script>
|
这里用到了VueUse的几个功能,useLocalStorage
,useVibrate
,useDebounceFn
:
useLocalStorage
,吧localStorage存储映射为一个RemovableRef,对Ref的修改会实时反映到localStorage上,反之亦然(默认会listenStorageChange:true)
useVibrate
,设备震动,具体看VueUse文档吧,挺简单的
useDebounceFn
,把方法转为防抖的,和lodash的debounce差不多的