钉钉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设备上会获取底部动作栏的inset| 12
 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回弹效果
禁用 主要是为了两端一致
| 12
 3
 4
 
 | dd.ready(() => {
 dd.ui.webViewBounce.disable()
 })
 
 | 
设置页面标题
| 12
 3
 4
 5
 6
 7
 8
 
 | try {await dd.biz.navigation.setTitle({
 title: title,
 })
 } catch (error) {
 
 document.title = title
 }
 
 | 
复制文字
| 12
 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控制台
| 12
 
 | MODE=productionVITE_OPEN_CONSOLE=1
 
 | 
然后,看下main.js,项目在初始化时会自动导入src/modules下的模块,代码如下:
src/main.js
| 12
 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
| 12
 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
| 12
 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次之后打开开发者模式,顺带打开控制台.
| 12
 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
| 12
 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差不多的