通用代码段

通用代码段

1. 置剪切板

1
2
3
4
5
6
7
8
9
10
// 置剪切板
Copy(val, msg = "复制成功") {
var input = document.createElement("input"); //创建input元素
input.value = val;
document.body.appendChild(input);
input.select();
document.execCommand("Copy"); //复制
document.body.removeChild(input); // 删除临时实例
this.$message.success(msg);
},
1
2
3
4
5
6
7
8
9
10
// 置剪切板
export const Copy = (val: any, msg = "复制成功") => {
let input = document.createElement("input"); //创建input元素
input.value = val;
document.body.appendChild(input);
input.select();
document.execCommand("Copy"); //复制
document.body.removeChild(input); // 删除临时实例
showToast(msg)
}

2. 文本超出使用省略号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//一行
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;

//两行
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;

// 微信小程序
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;

3. 隐藏滚动条

1
2
3
4
5
6
7
8
&::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
color: transparent;
}

4. H5 调起拨号盘

1
2
3
call (phone) {
window.location.href = `tel://${phone}`
}

5. dayjs

1
2
3
4
5
6
7
8
9
10
import dayjs from 'dayjs'
import isLeapYear from 'dayjs/plugin/isLeapYear' // 导入插件
import 'dayjs/locale/zh-cn' // 导入本地化语言

dayjs.extend(isLeapYear) // 使用插件
dayjs.locale('zh-cn') // 使用本地化语言

import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(relativeTime)
// console.log(dayjs().to(dayjs('2024-3-15T15:00')));

6. vant 函数形式组件样式导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Vant 中有个别组件是以函数的形式提供的,包括 Toast,Dialog,Notify 和 ImagePreview 组件。
* 在使用函数组件时,unplugin-vue-components 无法解析自动注册组件,导致 @vant/uto-import-resolver 无法解析样式,因此需要手动引入样式。
* */

// Toast
// import { showToast } from 'vant';
import 'vant/es/toast/style';

// Dialog
// import { showDialog } from 'vant';
import 'vant/es/dialog/style';

// Notify
// import { showNotify } from 'vant';
import 'vant/es/notify/style';

// ImagePreview
// import { showImagePreview } from 'vant';
import 'vant/es/image-preview/style';

7. axios 网络请求封装+pinia

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
58
59
60
61
62
63
64
65
import axios from 'axios'
import router from '@/router/index'
import { storeToRefs } from 'pinia'
import { useUserStore } from "@/stores/login";

// 登录时的 token 时间戳,用于前端判断 token 是否过期
const TokenTimestampKey: string = import.meta.env.VITE_TOKEN_TIMESTAMP_KEY
// 前端设置的 token 过期时间
const tokenTimeOut: number = Number(import.meta.env.VITE_TOKEN_TIMEOUT)

// 创建一个axios的实例
const request = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: Number(import.meta.env.VITE_APP_TIMEOUT)
})

// 请求拦截器
request.interceptors.request.use(
config => {
const userStore = useUserStore();
const user = storeToRefs(userStore); // 响应式

if (location.pathname === '/login') { // 1.如果是登录页,免校验token
console.log('登录页免检tk');
config.headers['token'] = `${user.token}`

} else if (user.token.value) { // 2.如果有token,检测token是否过期
if (IsCheckTimeOut()) {
userStore.logout();
router.push('/login')
return Promise.reject(new Error('有tk,但tk过期了'))
}
config.headers['token'] = `${user.token}`
} else { // 3.如果没有token
userStore.logout();
router.push('/login')
return Promise.reject(new Error('没有tk,未登录'))
}
return config
}, error => {
return Promise.reject(error)
}
)

// 响应拦截器
request.interceptors.response.use(response => {
if (response.data.status !== 200) {
return Promise.reject(response.data)
} else {
return response.data
}
}, error => {
if (error && error.response && error.response.status === 401) { // 当等于401时候,表示后端告诉我们token超时了
const userStore = useUserStore();
userStore.logout(); // 调用退出登录,删除token
router.push('/login')
}
return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入catch
})

function IsCheckTimeOut() {
const currentTimeStamp = Date.now();
return (currentTimeStamp - Number(localStorage.getItem(TokenTimestampKey))) / 1000 > tokenTimeOut
}
export default request // 导出axios实例

pinia中:

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
const TokenKey: string = import.meta.env.VITE_TOKEN_KEY

// 创建组合式api仓库
import { defineStore } from "pinia"
import { ref } from "vue"

// 创建 & 导出仓库
export const useUserStore = defineStore('userStore', () => {
const token: any = ref(localStorage.getItem(TokenKey) || null)

const setToken = (tk: any) => {
token.value = tk // 将数据设置给pinia
localStorage.setItem(TokenKey, tk) // 将数据同步给缓存
}

const logout = () => {
token.value = null // 将pinia的token数据置空
localStorage.clear() // 清空所有缓存
}
return {
token,
setToken,
logout
}
})

8. 数字千分位逗号分隔

1
2
3
4
5
6
7
// 数字千分位逗号分隔
export const toThousands = function formatNumberWithCommas(number: number): string {
const numberString: string = number.toString();
// 使用正则表达式在数字字符串中插入千分位逗号
const formattedNumber: string = numberString.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return formattedNumber;
}

9. window.open()

9.1. 常用参数值

参数值 作用
_blank 新窗口打开(默认)
_self 当前窗口打开
_parent 在父框架中打开
_top 在顶层窗口打开
自定义名称 在指定名称的窗口中打开

9.2. 参数使用示例

9.2.1. _parent 和 _top 示例

javascript

1
2
3
4
// 在父框架中打开链接(适用于iframe嵌套场景)
window.open('https://example.com', '_parent');
// 在顶层窗口打开链接(跳出所有框架)
window.open('https://example.com', '_top');

9.2.2. _blank 示例

javascript

1
2
// 在新窗口打开(默认行为)
window.open('https://example.com', '_blank');

9.2.3. _self 示例

javascript

1
2
// 在当前窗口打开(替换当前页面)
window.open('https://example.com', '_self');

9.2.4. 自定义窗口名称示例

javascript

1
2
// 在名为"myWindow"的窗口中打开链接
window.open('https://example.com', 'myWindow');

9.3. 实际应用场景

javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 聊天客服窗口 - 确保只打开一个客服窗口
function openChat() {
window.open('/chat', 'customerServiceWindow', 'width=400,height=500');
}

// 商品比较功能 - 在同一窗口中打开不同商品
function compareProduct(productId) {
window.open(`/product/${productId}`, 'comparisonWindow');
}

// 在父框架中打开外部链接(适用于嵌入式应用)
function openInParentFrame() {
window.open('https://payment.com/checkout', '_parent');
}

9.4. 运行效果

  • _blank:创建新窗口
  • _self:替换当前页面
  • _parent:在直接父框架中打开(适用于iframe嵌套)
  • _top:在顶层窗口打开(跳出所有框架)
  • 自定义名称
  • 首次调用:创建新窗口并使用指定名称
  • 再次调用同名窗口:在原窗口中加载新URL,不会创建新窗口
  • 适用于需要复用同一窗口的场景

9.5. 注意事项

  • 名称应使用字母数字,避免特殊字符(如连字符)
  • 现代浏览器可能会忽略部分窗口特性参数
  • 非用户触发的弹窗可能被浏览器拦截
  • **_parent_top**主要用于处理框架嵌套场景

这种命名窗口的方式适合需要控制窗口复用的场景,如客服聊天、商品比较、嵌入式应用导航等功能。

10. 封装一个准确判断数据类型的函数

1
2
3
4
5
6
7
8
// 封装一个准确判断数据类型的函数
export const getDataType = (obj: any): string => {
let type = typeof obj;
if (type != "object") {
return type;
}
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}

11. 手机号前端脱敏

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
// 手机号前端格式化为188****8888
export const formatPhoneNumber = (cardNumber: string) => {
if (cardNumber.length >= 11) {
let firstDigits = cardNumber.slice(0, 3);
let lastDigits = cardNumber.slice(-4);
// 中间位数11-7=4
let middleDigitsCount = cardNumber.length - 7
// 构造星号的字符串
const middleStars = '*'.repeat(middleDigitsCount);
return firstDigits + middleStars + lastDigits;
} else {
return '1** **** ****'
}
}

// 固定11位手机号
export const formatPhoneNumber = (number: string | number) => {
const phoneNumber = String(number)
if (phoneNumber.length === 11) {
let firstDigits = phoneNumber.slice(0, 3);
let lastDigits = phoneNumber.slice(-4);
return firstDigits + ' **** ' + lastDigits;
} else {
return '1** **** ****'
}
}

12. vant 分页上拉加载更多

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<script setup lang="ts">
import router from '@/router';
import moment from 'moment';
// import { findSalesmanIncomeRecord } from '@/api/my';
const toPage = () => {
router.back()
}
const list: any = ref([])
const total = ref(0)
const page = ref(1)
const size = ref(10)
const loading = ref(false)
const error = ref(false)
const finished = ref(false);
const onLoad = async () => {
try {
const res: any = { result: 1, data: [{ record: '一念永恒' }, { record: '一念永恒' }, { record: '一念永恒' }, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}] }
// const res: any = await findSalesmanIncomeRecord({ page: page.value, size: size.value })
if (res.result === 1) {
total.value = res.total;
list.value = [...list.value, ...res.data]
// 所有数据加载完成, finished 设为 true
if (page.value * size.value >= total.value) {
finished.value = true;
} else {
finished.value = false;
}
page.value += 1
} else {
error.value = true
}
} catch (err) {
error.value = true
} finally {
// 加载状态结束
loading.value = false;
}
};
</script>

<template>
<nav-bar colors="#ffffff" @back="toPage" title="中奖记录"></nav-bar>
<main>
<div class="top">
<div class="time">中奖时间</div>
<div class="record">所中奖品</div>
</div>
<van-list v-model:loading="loading" v-model:error="error" error-text="加载失败,点击重新加载" :finished="finished"
finished-text="没有更多数据了" @load="onLoad">
<div class="row" v-if="list.length > 0" v-for="(item, index) in list" :key="index">
<div class="time">{{ moment(item.create_time).format('YYYY-MM-DD HH:mm') }}</div>
<div class="record">获得{{ item.record }}</div>
</div>
<van-empty v-else image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png" image-size="80"
description="" />
</van-list>
</main>
</template>
<style lang="less" scoped>
main {
background-color: #fcf8ed;

.top {
height: 50px;
line-height: 50px;
display: flex;
align-items: center;
font-weight: bold;
font-size: 16px;

.time {
width: 50%;
text-align: center;
color: #9270eb;
}

.record {
width: 50%;
text-align: center;
color: #cb4e6f;
}
}

.row {
margin: 0 10px;
height: 45px;
line-height: 45px;
display: flex;
align-items: center;
border-top: 1px solid #ccc;

.time {
width: 50%;
text-align: center;
font-size: 14px;
color: #777;
}

.record {
width: 50%;
text-align: center;
font-size: 14px;
color: #cb4e6f;
}
}
}
</style>

带有切换:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<script setup lang="ts">
import router from '@/router';

const toPage = () => {
router.back()
}
const active = ref(0)
const changeActive = (val: string) => {
console.log(val);
error.value = false
page.value = 1
list.value = []
onLoad();

}
const list: any = ref([])
const total = ref(0)
const page = ref(1)
const size = ref(10)
const loading = ref(false)
const error = ref(false)
const finished = ref(false);
const onLoad = async () => {
console.log(page.value);

try {
const res: any = { result: 1, data: [{}, {}, {}, {}] }
// const res: any = await findSalesmanIncomeRecord({ page: page.value, size: size.value })
if (res.result === 1) {
total.value = res.total;
list.value = [...list.value, ...res.data]
// 所有数据加载完成, finished 设为 true
if (page.value * size.value >= total.value) {
finished.value = true;
} else {
finished.value = false;
}
page.value += 1
} else {
error.value = true
}
} catch (err) {
error.value = true
} finally {
// 加载状态结束
loading.value = false;
}
};
</script>

<template>
<nav-bar colors="#ffffff" @back="toPage" title="店务订单"></nav-bar>
<van-tabs v-model:active="active" type="card" color="#4a70f6" @change="changeActive">
<van-tab name="1" title="待确认"></van-tab>
<van-tab name="2" title="已完成"></van-tab>
</van-tabs>
<van-list v-model:loading="loading" v-model:error="error" error-text="加载失败,点击重新加载" :finished="finished"
finished-text="没有更多数据了" @load="onLoad">
<div class="order" v-if="list.length > 0" v-for="(item, index) in list" :key="index">
<div class="row">
<div class="left">
<div class="label">订单号:</div>
<div>11111</div>
</div>
</div>
<div class="row">
<div class="left">
<div class="label">客户姓名:</div>
<div>11111</div>
</div>
</div>
<div class="row">
<div class="left">
<div class="label">开单时间:</div>
<div>2024-02-02 15:15:15</div>
</div>
</div>
<div class="row">
<div class="left">
<div class="label">员工:</div>
<div>11111</div>
</div>
</div>
<div class="row">
<div class="left">
<div class="label">尾款:</div>
<div>11111</div>
</div>
<div class="btn">详情</div>
</div>
</div>
<van-empty v-else image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png" image-size="80"
description="暂无数据" />
</van-list>
</template>

<style scoped lang="less">
.order {
margin: 12px;
padding: 12px;
border-radius: 10px;
background-color: #f3f7ff;
font-size: 16px;
font-weight: 300;

.row {
display: flex;
justify-content: space-between;
align-items: center;

.left {
display: flex;
align-items: center;

.label {
width: 80px;
}

div {}
}

.btn {
color: #4a70f6;
}
}
}
</style>

13. Element-plus视频/图片上传

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<script setup>
import { Plus } from '@element-plus/icons-vue'
const upload_img_api = import.meta.env.VITE_UPLOAD_IMG_API
const loading = ref(false)
// 上传视频
const beforeAvatarUpload = (res, uploadFile) => {
loading.value = true
}
const handleAvatarSuccess1 = (res, uploadFile) => {
form.value.video = img_path + res.serverImagePath
loading.value = false
}
// 上传图片
const handleAvatarSuccess2 = (res, uploadFile) => {
form.value.img = img_path + res.serverImagePath
}

</script>
<template>
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="品项视频:">
<el-upload class="avatar-uploader" :action="upload_img_api" :show-file-list="false"
:before-upload="beforeAvatarUpload" :on-success="handleAvatarSuccess1">
<video v-if="form.video" :src="form.video" class="avatar"></video>
<div v-if="loading" class="loading">视频上传中</div>
<el-icon v-if="!form.video" class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="品相详情图:">
<el-upload class="avatar-uploader" :action="upload_img_api" :show-file-list="false"
:on-success="handleAvatarSuccess2">
<img v-if="form.img" :src="form.img" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
</el-form>
</template>

<style scoped lang="less">
:deep(.avatar-uploader) {
position: relative;

.loading {
position: absolute;
z-index: 999;
background-color: rgba(0, 0, 0, 1);
width: 100%;
height: 100%;
top: 0;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
}

.avatar {
width: 178px;
height: 178px;
display: block;
}

.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}

.el-upload:hover {
border-color: var(--el-color-primary);
}
}

.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>

14. webview app 手机状态栏适配

https://uniapp.dcloud.net.cn/component/web-view.html#web-view

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>

<body>
<script>
var statusBarHeight
// 只有H5+APP 才会执行这段代码
document.addEventListener("plusready", () => {
//状态栏高度,判断是否是数字,不是使用默认高度28
statusBarHeight = Number.isFinite(window.plus.navigator.getStatusbarHeight()) ? window.plus.navigator.getStatusbarHeight() : 28;
// 设置状态栏
document.getElementById('app').style.paddingTop = statusBarHeight + 'px'
}, false);
</script>
<div id="app" style="background: linear-gradient(180deg, #C0D3FF 0%, #f6f8fa 150px);min-height: 100vh;">
</div>
<script type="module" src="/src/main.ts"></script>
<script type="text/javascript" src="https://gitee.com/dcloud/uni-app/raw/dev/dist/uni.webview.1.5.4.js"></script>
</body>

</html>
1
2
3
4
5
6
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom" // 隐藏系统导航栏
}
}
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<template>
<web-view :src="webviewUrl" @message="getMessage"></web-view>
</template>

<script>
export default {
data() {
return {
webviewUrl: 'https://xxxxxxxxx'
}
},
onLoad() {

},
methods: {
// 将base64 图片保存到本地系统相册
saveBaseImgFile(base64) {
const bitmap = new plus.nativeObj.Bitmap('base64')
bitmap.loadBase64Data(base64, () => {
const url = '_doc/' + new Date().getTime() + '.png'
bitmap.save(
url, {
overwrite: true // 是否覆盖
// quality: 'quality' // 图片清晰度
},
(i) => {
uni.saveImageToPhotosAlbum({
filePath: url,
success: () => {
uni.showToast({
title: '保存成功',
duration: 1000,
icon: 'success'
})
bitmap.clear()
}
})
},
(e) => {
uni.showToast({
title: '保存失败',
duration: 1000,
icon: 'fail'
})
bitmap.clear()
}
)
},
(e) => {
uni.showToast({
title: '保存失败',
duration: 1000,
icon: 'fail'
})
bitmap.clear()
}
)
},

getMessage(data) {
let that = this
let value = data.detail.data[0].value
let type = data.detail.data[0].type
if (type == 1) {
this.saveBaseImgFile(value)
} else if (type == 2) {
uni.downloadFile({ //下载文件资源到本地,返回文件的本地临时路径
url: value,
success: (res) => {
uni.saveImageToPhotosAlbum({ //保存图片到系统相册
filePath: res.tempFilePath,
success: (res) => {
uni.showToast({
title: '保存成功',
duration: 1000,
icon: 'success'
})
},
fail: (err) => {
uni.showToast({
title: '保存失败',
duration: 1000,
icon: 'fail'
})
}
})
}
})
} else if (type == 3) {
let that = this;
uni.scanCode({
success: function(res) {
const _funName = 'msgFromUniapp'
const _data = {
msg: res.result
};
const currentWebview = that.$scope.$getAppWebview().children()[0];
currentWebview.evalJS(`${_funName}(${JSON.stringify(_data)})`);
}
});
}
}
}
}
</script>

<style>

</style>

15. H5打包app的手机物理返回键问题

vue2:在 main.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
const { Toast } = require("vant");

document.addEventListener('plusready', function (a) {
plus.key.addEventListener('backbutton', function () {
//获取地址栏目中的url
let urls = location.hash.split('/')[1]
if (urls === 'data' || urls === 'home' || urls === 'book') {//判断当前是否无后退页
var first = null;
plus.key.addEventListener('backbutton', function () {
//首次按键,提示‘再按一次退出应用’
if (!first) {
first = new Date().getTime();
Toast('再按一次退出应用')
setTimeout(function () {
first = null;
}, 1500);
} else {
plus.runtime.quit();
}
}, false);
} else {
history.go(-1); // 返回到上一页
}
}, false);
})

vue3:在 main.ts 中引入:

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
import { showToast } from "vant";

document.addEventListener('plusready', (a) => {
let first: number = 0;
(window as any).plus.key.addEventListener('backbutton', () => {
//获取地址栏目中的url
const urls = location.pathname
if (urls === '/home' || urls === '/team' || urls === '/my') {//判断当前是否无后退页
//首次按键,提示‘再按一次退出应用’
if (first === 0) {
first = 1;
showToast('再按一次退出应用')
setTimeout(function () {
first = 0;
}, 1500);
} else {
(window as any).plus.runtime.quit();
}
} else {
history.go(-1); // 返回到上一页
}
}, false);
})

// 下方代码具有同样效果

// uni-app安卓app嵌套web-view 物理返回键处理,下面这些代码放在打包的H5页面的app.vue下就能实现手机自带按键一级一级返回,前提是纯套壳H5,不能有app页面

// var plusReady = (callback: any) => {
// if ((window as any).plus) {
// callback();
// } else {
// document.addEventListener('plusready', callback);
// }
// };

// plusReady(() => {
// let firstBack = 0;
// const handleBack = () => {
// let urls = location.pathname
// if (urls === '/home' || urls === '/team' || urls === '/my') {//判断当前是否无后退页
// if (firstBack === 1) {
// (window as any).plus.runtime.quit();
// } else {
// firstBack = 1
// showToast('再按一次退出应用')
// setTimeout(function () {
// firstBack = 0;
// }, 1500);
// }
// } else {
// history.go(-1); // 返回到上一页
// }
// };
// (window as any).plus.key.addEventListener('backbutton', handleBack);
// })

16. echart在vue3中的一个bug

加上markRaw即可

1
2
3
import { markRaw } from 'vue';

myChart.value = markRaw(echarts.init(document.getElementById("myChart")));

17. 视频video标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<video
id="video"
src="video.mp4" /*视频地址*/
controls = "true" /*是否显示播放控件*/
poster="images.jpg" /*视频封面*/
preload="auto" /*预加载*/
webkit-playsinline="true" /*IOS中让视频在小窗内播放而不是全屏播放*/
playsinline="true" /*IOS微信浏览器支持小窗内播放*/
x-webkit-airplay="allow" /*是否支持iosAirPlay功能*/
x5-video-player-type="h5" /*启用H5播放器,是wechat安卓版特性*/
x5-video-player-fullscreen="true" /*设置为true防止横屏*/
x5-video-orientation="portraint" /*播放器的方向,landscape横屏,portraint竖屏,默认值为竖屏*/
style="object-fit:fill">
</video>

18. 禁用鼠标右键

1
2
3
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
});

19. 滚动条持续保持在最顶部,点击到顶部或底部

1:在滚动条所属 div 上加上 ref=“mianscroll” (mianscroll名称随意起)

1
2
3
<div ref="mianscroll">
//中间内容
</div>

2:点击事件到顶部

1
2
3
4
5
6
topScrollClick() {
this.$nextTick(() => {
let scrollEl = this.$refs.mianscroll;
scrollEl.scrollTo({ top: 0, behavior: 'smooth' });
});
},

3:点击事件到底部

1
2
3
4
5
6
bottomScrollClick() {
this.$nextTick(() => {
let scrollEl = this.$refs.mianscroll;
scrollEl.scrollTo({ top: scrollEl.scrollHeight, behavior: 'smooth' });
});
}

注意点:一定要是给滚动条所属 div 添加如上方法,不然无法生效,滚动的 div 的样式是 overflow:auto

20. 移动端打开调试控制台

在index.html中加上:

1
2
3
4
5
6
<script src="https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js"></script>
<script>
// 初始化
var vConsole = new VConsole();
console.log('Hello world');
</script>

21. 消除 input 原生样式

1
2
3
4
5
input {
outline: none;
border: none;
padding: 0;
}

22. 全屏

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
fullScreen () {
var docElm = document.documentElement;
//W3C
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
}
//FireFox
else if (docElm.mozRequestFullScreen) {
docElm.mozRequestFullScreen();
}
//Chrome等
else if (docElm.webkitRequestFullScreen) {
docElm.webkitRequestFullScreen();
}
//IE11
else if (docElm.msRequestFullscreen) {
docElm.msRequestFullscreen();
}
if (document.exitFullScreen) {
document.exitFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (element.msExitFullscreen) {
element.msExitFullscreen();
}
},

23. 多选全选

computed 计算属性内:

1
2
3
4
5
6
7
8
9
10
11
checkAll: {
set(check) {
this.obj.list.forEach((item) => (item.checked = check));
},
get() {
return (
this.obj.list.length !== 0 &&
this.obj.list.every((item) => item.checked === true)
);
},
},

24. validator 校验

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
data () {
const checkMobile = function (rule, value, callback) {
// 校验的是value
// 第三位必须是9
value.charAt(2) === "9" ? callback() : callback(new Error("手机号第三位必须是9"))
}
return {
// 数据对象
loginForm: {
// 校验的字段
mobile: '',
password: ''
},
// 校验规则
// { key: value }
loginRules: {
mobile: [{ required: true, message: '手机号不能为空', trigger: 'blur' }, {
trigger: 'blur',
message: '手机号格式不正确',
pattern: /^1[3-9]\d{9}$/ // 校验手机号
}, {
trigger: 'blur',
validator: checkMobile
}],
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }, {
min: 6, max: 16, trigger: 'blur', message: '密码的长度为6-16位'
}]
}
}
},

25. 解决 js 数据精度的问题

1
npm install --save big.js
1
import Big from 'big.js'
1
2
3
const number1 = new Big(.3)
const number2 = number1.plus(.1) // 0.4
const number3 = number1.minus(.1) // 0.2

26. github 拉取代码需要代理,避免频繁设置和清除代理,需要系统代理

在 系统用户(例如 zs) 目录下新建 bin/g.cmd,写入以下代码:

添加用户级环境变量即可:C:\Users\zs\bin

拉取命令:g clone https://github.com/xxxxxx.git

需要系统开启系统代理(科学上 使系统能访问 github),端口 7897

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion

:: 🔧 修改 Windows 10 检测,防止意外错误
ver | findstr "10" >nul
if not errorlevel 1 (
reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1 /f >nul
)

:: 定义 ANSI 颜色
set "COLOR_RESET=[0m"
set "COLOR_GREEN=[92m"
set "COLOR_YELLOW=[93m"
set "COLOR_CYAN=[96m"

:: 代理地址
set "PROXY=http://127.0.0.1:7897"
set "USE_PROXY=0"
set "IS_GITHUB=0"
set "PROXY_RUNNING=0"

:: 🔍 检测 Windows 系统代理
for /f %%A in ('powershell -Command "[int](Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -Name ProxyEnable).ProxyEnable"') do (
if "%%A"=="1" set "PROXY_RUNNING=1"
)

:: 遍历参数,查找 GitHub 仓库地址
for %%A in (%*) do (
echo %%A | findstr /B /C:"https://github.com" >nul
if not errorlevel 1 (
set "IS_GITHUB=1"
set "URL=%%A"
)
)

if "%IS_GITHUB%"=="1" goto :PROMPT_USER
goto :RUN_GIT

:PROMPT_USER
echo.
echo %COLOR_CYAN%=================================================%COLOR_RESET%
echo %COLOR_YELLOW%当前使用的是 GitHub 仓库%COLOR_RESET%
echo %COLOR_YELLOW%默认情况下不使用代理%COLOR_RESET%
echo.
echo %COLOR_YELLOW%如果要使用代理(%PROXY%),请输入 y 并回车%COLOR_RESET%
echo %COLOR_CYAN%=================================================%COLOR_RESET%
echo.

set /p CONFIRM=输入 y 以使用代理(直接回车跳过):

if /I "!CONFIRM!"=="y" (
echo %COLOR_GREEN%[信息] 你选择了使用代理...%COLOR_RESET%
set "USE_PROXY=1"

if "%PROXY_RUNNING%"=="0" (
echo %COLOR_YELLOW%[提示] 系统代理(127.0.0.1:7897)可能未开启,请检查 Windows 代理设置!%COLOR_RESET%
exit /b
) else (
echo %COLOR_GREEN%[信息] 系统代理已开启,继续执行。%COLOR_RESET%
)
) else (
echo %COLOR_YELLOW%[提示] 你选择了不使用代理%COLOR_RESET%
)

:SET_PROXY
if "%USE_PROXY%"=="1" (
set "https_proxy=%PROXY%"
set "http_proxy=%PROXY%"
echo %COLOR_GREEN%[信息] 已设置代理: %PROXY%%COLOR_RESET%
) else (
set "https_proxy="
set "http_proxy="
)

:RUN_GIT
echo %COLOR_CYAN%[执行] git %*%COLOR_RESET%
git %*

27. 旋转图片

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
/**
* 旋转图片
* @param base64 图片base64
* @param degrees 旋转角度
*/
const rotateImage = (base64: string, degrees: number = 0): Promise<string> => {
if (typeof degrees !== 'number') return Promise.resolve(base64)
return new Promise(resolve => {
const img = new Image()
img.src = base64
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) return resolve(base64)

// 计算旋转后的画布尺寸
const radians = degToRad(degrees)
const sin = Math.abs(Math.sin(radians))
const cos = Math.abs(Math.cos(radians))
const imgWidth = img.width
const imgHeight = img.height

// 计算旋转后需要的画布尺寸
canvas.width = imgWidth * cos + imgHeight * sin
canvas.height = imgWidth * sin + imgHeight * cos

// 将原点移到画布中心
ctx.translate(canvas.width / 2, canvas.height / 2)

// 旋转画布
ctx.rotate(radians)

// 将图片中心与画布中心对齐
ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight)

// 返回旋转后的base64
resolve(canvas.toDataURL(esignConfig.value.format, esignConfig.value.quality))
}
})
}

// 角度转弧度
const degToRad = (degrees: number): number => {
return degrees * (Math.PI / 180)
}

28. base64

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
/**
* img url to base64
* @param url
*/
export function urlToBase64(url: string, mineType?: string): Promise<string> {
return new Promise((resolve, reject) => {
let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>
const ctx = canvas!.getContext('2d')

const img = new Image()
img.crossOrigin = ''
img.onload = function () {
if (!canvas || !ctx) {
return reject()
}
canvas.height = img.height
canvas.width = img.width
ctx.drawImage(img, 0, 0)
const dataURL = canvas.toDataURL(mineType || 'image/png')
canvas = null
resolve(dataURL)
}
img.src = url
})
}

/**
* @description: base64 to blob
*/
export function dataURLtoBlob(base64Buf: string): Blob {
const arr = base64Buf.split(',')
const typeItem = arr[0]
const mime = typeItem.match(/:(.*?);/)![1]
const bstr = window.atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}