生活中的Design.

钉钉H5应用踩坑(1)

字数统计: 1.9k阅读时长: 8 min
2022/06/22 Share

钉钉H5应用踩坑(1)

钉钉H5微应用开发踩坑,钉钉的Webview内核据说是UC浏览器内核,许多CSS/JS的兼容性问题,例如thead/tr不能使用position:sticky, 还有table标签的滚动行为两个端(iOS/Android)不一致,和table的宽度设置相关.在Android端一个overflow-y:auto的容器中,直接放个table(宽度比父容器大),是无法滚动的,而iOS端可以.

兼容全面屏iOS底部动作条

  1. 为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">
  1. 为需要适配的元素加padding或是margin,constant(safe-area-inset-bottom),env(safe-area-inset-bottom)在全面屏iOS设备上会获取底部动作栏的inset
    1
    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(() => {
//禁用iOS Webview弹性效果
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);
//非钉钉,回退到使用ClipboardJS
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()

1
npm i 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)
// install all modules under `modules/` 自动导入modules文件夹下的js模块,并且自动注册到vue实例中,需要暴露install方法方法,接收一个参数,包含vue实例
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 //点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);
//还剩3次时提示进入开发者模式
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差不多的
CATALOG
  1. 1. 钉钉H5应用踩坑(1)
    1. 1.1. 兼容全面屏iOS底部动作条
    2. 1.2. 禁用iOS的WebView回弹效果
    3. 1.3. 设置页面标题
    4. 1.4. 复制文字
    5. 1.5. 使用VConsole在线调试
      1. 1.5.1. 1.首先引入VConsole
      2. 1.5.2. 2.给VConsole加全局/临时开关