问题集

Object.keys() & for…in 顺序

Object.keys在内部会根据属性名key的类型进行不同的排序逻辑。分三种情况:

  • 如果属性名的类型是Number,那么Object.keys返回值是按照key从小到大排序 ‘1’也算是number
  • 如果属性名的类型是String,那么Object.keys返回值是按照属性被创建的时间升序排序。
  • 如果属性名的类型是Symbol,那么逻辑同String相同

解构赋值

可以取代 delete xxx

1
2
3
4
5
6
7

const { name,...rest } = { name:'z', age:10, sex:'female' }
// name = 'z' , rest = { age:10, sex:'female' }

const [ item,...rest ] = [ { first:'a' }, 23, 55 ]
// item = { first:'a' } , rest = [ 23, 55 ]

json简写

keyvalue名字一样的时候可以省略value

1
2
3
4
5
6
7

let name = 'zz'
let age = 10

let json = { name, age, say(){ console.log('') } }
// => let json = { name:name, age:age, say:function(){ console.log('') } }

JavaScript浮点数运算精度问题

描述

一开始看到百度的部分解决方案是通过扩大N倍换为整数,但是亲测有问题。直接乘10的N次方部分数据还是会出现精度问题。

解决

  1. 我是通过先转换成字符串、然后去掉小数点转为整数
  2. 两个数之间小数点位数进行比较、少的在乘10的n-m次方,运算后在除以10的Math.max(n,m)

目前没发现bug、不确定是正确的方法

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
/**
* 浮点数运算
* type: 1 加法,2 减法
*/
function floatAddOrMinus(type: number, arg1: string, arg2: string) {
let len1 = 0
let len2 = 0
let m = 0
let item1 = 0
let item2 = 0;
try {
len1 = arg1.split(".")[1].length;
} catch (e) {
len1 = 0;
}
try {
len2 = arg2.split(".")[1].length;
} catch (e) {
len2 = 0;
}

if (len1 > len2) {
m = len1;
item1 = 0;
item2 = len1 - len2;
} else {
m = len2;
item1 = len2 - len1;
item2 = 0;
}

if (type === 1) {
return (
(Number(arg1.replace(".", "")) * 10 ** item1 +
Number(arg2.replace(".", "")) * 10 ** item2) /
10 ** m
);
} else {
return (
(Number(arg1.replace(".", "")) * 10 ** item1 -
Number(arg2.replace(".", "")) * 10 ** item2) /
10 ** m
);
}
}

vue3中ref和其他属性绑定值重名导致绑定失效

群友遇到的问题、我一开始看着也没觉得哪里不对。感觉以后能用的上,先存着 。

描述

使用element-plus 中的 el-form 的时候,model绑定了formData,但是在页面上对表单进行操作的时候,数据绑定无效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<el-form 
ref="formData"
:model="formData"
:rules="rule">
<el-row justify="center">
<el-col :span="12">
<el-form-item label="入库类型" prop="inOutType">
<el-select v-model="formData.inOutType" clearable>
<el-option label="外购" value="外购"></el-option>
<el-option label="其他" value="其他"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ...
setup(){
let formData: IEmptyObject = reactive({
warningUseTime: 0,
typeName: '',
categoryCode: '',
categoryName: '',
maintenanceCode: '',
maintenanceName: '',
model: '',
attribute: '',
unit: '',
warningProductionQuantity: 0
});

return { formData }
}
// ...

错误原因

ref绑定的内容和model绑定的内容重复了,vue3中ref绑定的内容也是通过在setup中定义变量进行获取。具体原因还没仔细看,估计是ref的优先级更高?

proxy多个路由代理

重写失效、请求路径包含某个 代理名 代理失效。

记下 还没解决

哈希路由引起样式问题

在一个页面引入了样式,并且添加了 scoped ,但是在进行页面跳转的时候,这些引入的样式会影响另一页面。刷新之后就好了

初步猜测是因为路由用的哈希模式的原因

hash 值变化不会导致浏览器向服务器发出请求

所以在第一个页面加载的样式,在跳转之后没有刷新,样式就一直会存在,因此影响到了页面的样式

解决方法

  • emit父子组件通讯,点击事件触发后、强制刷新
  • 监听hash值的变化、变化之后、强制刷新
1
2
3
4
5
6
7
8
9
10
11
12
setup: () => {
const route = useRoute();
watch(
() => route.path,
(to, from) => {
if (from.includes("papercss") && from !== to) {
location.reload();
}
}
);
}

el-date-picker限制时间选择范围

两个时间选择器、开始时间与结束时间的时间选择范围为24h,并且精确到 (format="yyyy-MM-dd HH:mm")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 基本使用 -->
<el-form-item prop="beginDateStr" label="开始时间">
<el-date-picker
v-model="formData.beginDateStr"
type="datetime"
placeholder="选择日期时间"
format="yyyy-MM-dd HH:mm"
>
</el-date-picker>
</el-form-item>
<el-form-item prop="endDateStr" label="结束时间">
<el-date-picker
v-model="formData.endDateStr"
type="datetime"
format="yyyy-MM-dd HH:mm"
placeholder="选择日期时间"
>
</el-date-picker>
</el-form-item>

Picker Options 配置

开始时间限制

首先对开始时间进行限制 限制在当前日期之前;并且当开始日期改变的时候,结束日期清空

disabledDate 日期禁用范围

1
2
3
4
5
6
7
8
<el-date-picker
v-model="formData.beginDateStr"
type="datetime"
placeholder="选择日期时间"
format="yyyy-MM-dd HH:mm"
:picker-options="pickerOptionsStart"
@change="handleChangeStart"
></el-date-picker>
1
2
3
4
5
6
7
8
9
private pickerOptionsStart = {
disabledDate: (time: Date) => {
return time.getTime() > Date.now();
},
}

private handleChangeStart() {
this.formData.endDateStr = '';
}

结束时间限制

因为是24h限制、所以 开始时间+24h 和 开始时间-24h 的时间都得禁用;精确到分钟,小时和分钟也得进行限制

selectableRange 时间选择范围
这个属性没有在el-date-picker里面,在el-time-picker里面,个人觉得文档写的不是很人性…
因为跨天的话、时间范围是不一样的 比如开始时间是2021-05-31 02:00
那么结束时间的范围是2021-05-31 02:00 - 2021-06-01 02:00
如果选的是31号 则 selectableRange = '02:00-23:59'
如果选的是1号 则 selectableRange = '00:00-02:00'
所以还得对日期选择进行监听,本来是想通过点击/change事件进行监听的
但是无效,然后通过监听input框内数据的变化 来进行监听

1
2
3
4
5
6
7
8
9
10
11
<el-date-picker
v-model="formData.endDateStr"
type="datetime"
format="yyyy-MM-dd HH:mm"
placeholder="选择日期时间"
@input="handleChangeInputDate($event)"
:picker-options="{
disabledDate: (time) => hanldeDisabledDate(time),
selectableRange: dafaulttime,
}"
></el-date-picker>
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
private dafaulttime: string = '00:00:00-23:59:59';

private handleChangeInputDate(e: Date) {
// 区别 同一天和下一天的时间限制
if (this.formData.beginDateStr) {
const begin = new EasyDate(this.formData.beginDateStr).format('dd');
const end = new EasyDate(e).format('dd');
if (begin === end) {
this.dafaulttime =
new EasyDate(this.formData.beginDateStr).format('HH:mm:ss') +
'-23:59:59';
} else {
this.dafaulttime =
'00:00:00-' +
new EasyDate(this.formData.beginDateStr).format('HH:mm:ss');
}
}
}

private hanldeDisabledDate(time: Date) {
// 24 * 3600 * 1000 == 8.64e7
if (this.formData.beginDateStr) {
const pickerMinDate = new Date(this.formData.beginDateStr).getTime();
const maxTime = pickerMinDate + 8.64e7;
const minTime = pickerMinDate - 8.64e7;
return time.getTime() >= maxTime || time.getTime() <= minTime;
} else {
return time.getTime() > Date.now();
}
}

针对daterangedatetimerange类型进行时间限制

不仅仅需要配置disabledDate,还需要配置onPick获取当前点击的日期
onPick Function({ maxDate, minDate }) 选中日期执行的回调,只选择一个的时候返回的minDate

实例:只能选择31天的范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<span>起止时间:</span>
<el-date-picker
style="width: 240px; margin-right: 10px"
v-model="dateRange"
type="datetimerange"
range-separator="至"
value-format="yyyy-MM-dd HH:mm:ss"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="dateChange"
:picker-options="{
disabledDate: (time) => hanldeDisabledDate(time),
onPick: (time) => hanldeOnPick(time),
}"
:clearable="false"
></el-date-picker>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

private hanldeOnPick(e: IEmptyObject) {
if (!e.maxDate) {
const timeRange = 8.64e7 * 31;
this.pickDateRange.maxTime = e.minDate.getTime() + timeRange;
this.pickDateRange.minTime = e.minDate.getTime() - timeRange;
} else {
this.pickDateRange.minTime = null;
this.pickDateRange.maxTime = null;
}
}

private hanldeDisabledDate(e: Date) {
const pickerDate = e.getTime();
if (this.pickDateRange.minTime && this.pickDateRange.maxTime) {
return (
e.getTime() <= this.pickDateRange.minTime ||
e.getTime() >= Date.now() ||
e.getTime() >= this.pickDateRange.maxTime
);
} else {
return e.getTime() >= Date.now();
}
}

主要问题

结束时间的 picker-options 属性,如果直接通过一个对象传递的话 ,会失效..,不清楚原因。我猜测是双向数据绑定相关的东西的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
// √ 生效
:picker-options="{
disabledDate: (time) => hanldeDisabledDate(time),
selectableRange: dafaulttime,
}"

// × 不生效
:picker-options="pickerOption"

private pickerOption = {
disabledDate: (time) => hanldeDisabledDate(time),
selectableRange: dafaulttime,
}

multipart/form-data 文件流请求和参数相关

一般的接口请求都是以json的形式进行传输、这次的表单会有文件上传的情况 所以用formdata的形式进行传输。

表单数据存在body内 且content-type需要设置为application/json,文件数据存在file内

  1. formData请求,参数必须为FromData类型的
1
2
3
4
5
6
let params = new FormData()

params.append('file',value,name)

// params.get('file') 获取file值
// params.set('file',newValue) 设置file值
  1. 对于不是文本/二进制类型的数据,还需要使用Blob对象进行转换

如上图所示,body内传递的是Object对象,而且,bodycontent-type需要设置为application/json类型

1
2
3
4
5
let obj = {...}
let blob = new Blob([JSON.stringify(obj)],{type:'application/json'})

params.append('body',blob)

  1. axios主要配置
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
// http.ts
const instance = axios.create({
timeout: 600000, // by huangrong
headers: { 'Content-Type': 'application/json' },
baseURL: '/',
// transformResponse: (data: any) => {
// //
// }
});

instance.upload = (url, data, config = {}, params) => {
const formData = new FormData();
data.forEach(item => {
formData.append('file', item.value, item.name || item.filename);
});
if (params && Object.keys(params).length > 0) {
for (const m in params) {
if (params.hasOwnProperty(m)) {
const key = m;
const value = params[m];
formData.append(key, value);
}
}
}
return instance.request({
method: 'post',
...config,
url,
data: formData,
});
};


// 请求拦截器
instance.interceptors.request.use(config => {
const { params = {} } = config;
config.params = { ...params, time: new Date().getTime() };
// add token if need
config.headers = {
...(config.headers || {}),
'X-Auth-Token': CookieStorage.token,
'X-Stargate-AppId': 25,
};
return config;
}, err => {
// show err
}
);

// common-api.ts
export function uploadFile(path: string, file: IUploadParam[], config?: AxiosRequestConfig, params?: IEmptyObject) {
return http.upload(path, file, config, params).then(res => {
return Promise.resolve(res.data);
});
}

// index.vue
uploadFile(
this.path,
(this.$refs.imageUpload as any).fileList,
{},
{
body: new Blob(
[
JSON.stringify({
...this.formData,
factory: this.factory,
workshop: this.workshop,
}),
],
{ type: 'application/json' }
),
}
)

axios实现文件导出功能,无法获取response的异常信息

如图所示: 上方是调文件导出接口,返回的异常信息。下方是普通请求获取的异常信息。上方的data内没有errorMsg

错误原因

可以看到上方红圈内的dataBlob类型的(文件存储类型),所以不能直接获取,需要FileReader对象进行处理,转换获取blob、file文件的数据。

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
// 响应拦截器
instance.interceptors.response.use(response => {
return Promise.resolve(response);
}, error => {
// show error_msg and reject
// console.log('响应失败信息', error.response);
if (error && error.response && error.response.status) {
let errCode = 0;
let errMsg = '';
if (!!error.response.data) {
// 文件流错误处理
if (error.response.config.responseType === 'blob') {
const reader = new FileReader();
reader.readAsText(error.response.data);
reader.onload = () => {
try {
const { errorMsg } = JSON.parse(reader.result as string);
errMsg = errorMsg;
handleError(error.response.status, errMsg);
} catch (error) {
console.log('解析错误');
}
};
} else {
// 普通错误处理
const { status, errorMsg } = error.response.data;
errCode = status;
errMsg = errorMsg;
handleError(error.response.status, errMsg);
}
}
}
return Promise.reject(error);
});

uni-app

文件目录结构

生命周期

应用生命周期 - App.vue

函数名 说明
onLaunch uni-app 初始化完成时触发(全局只触发一次)
onShow uni-app 启动,或从后台进入前台显示
onHide uni-app 从前台进入后台
onError uni-app 报错时触发
onUniNViewMessage nvue 页面发送的数据进行监听,可参考 nvue 向 vue 通讯
onUnhandledRejection 对未处理的 Promise 拒绝事件监听函数(2.8.1+)
onPageNotFound 页面不存在监听函数
onThemeChange 监听系统主题变化

页面生命周期

函数名 说明 平台差异说明 最低版本
onInit 监听页面初始化,其参数同 onLoad 参数,为上个页面传递的数据,参数类型为 Object(用于页面传参),触发时机早于 onLoad 百度小程序 3.1.0+
onLoad 监听页面加载,其参数为上个页面传递的数据,参数类型为 Object(用于页面传参),参考示例
onShow 监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面
onReady 监听页面初次渲染完成。注意如果渲染速度快,会在页面进入动画完成前触发
onHide 监听页面隐藏
onUnload 监听页面卸载
onResize 监听窗口尺寸变化 App、微信小程序
onPullDownRefresh 监听用户下拉动作,一般用于下拉刷新
onReachBottom 页面滚动到底部的事件(不是scroll-view滚到底),常用于下拉下一页数据。具体见下方注意事项
onTabItemTap 点击 tab 时触发,参数为Object,具体见下方注意事项 微信小程序、QQ小程序、支付宝小程序、百度小程序、H5、App
onShareAppMessage 用户点击右上角分享 微信小程序、QQ小程序、支付宝小程序、字节小程序、飞书小程序、快手小程序
onPageScroll 监听页面滚动,参数为Object nvue暂不支持
onNavigationBarButtonTap 监听原生标题栏按钮点击事件,参数为Object App、H5
onBackPress 监听页面返回,返回 event = {from:backbutton、 navigateBack} ,backbutton 表示来源是左上角返回按钮或 android 返回键;navigateBack表示来源是 uni.navigateBack ;详细说明及使用:onBackPress 详解。支付宝小程序只有真机能触发,只能监听非navigateBack引起的返回,不可阻止默认行为。 app、H5、支付宝小程序
onNavigationBarSearchInputChanged 监听原生标题栏搜索输入框输入内容变化事件 App、H5 1.6.0
onNavigationBarSearchInputConfirmed 监听原生标题栏搜索输入框搜索事件,用户点击软键盘上的“搜索”按钮时触发。 App、H5 1.6.0
onNavigationBarSearchInputClicked 监听原生标题栏搜索输入框点击事件(pages.json 中的 searchInput 配置 disabled 为 true 时才会触发) App、H5 1.6.0
onShareTimeline 监听用户点击右上角转发到朋友圈 微信小程序 2.8.1+
onAddToFavorites 监听用户点击右上角收藏 微信小程序 2.8.1+

路由

跳转方式:navigator组件跳转(声明式)、调用API跳转(编程式)

1
<navigator url="/pages/404/index">404</navigator>
1
2
3
4
5
6
7
8
9
10
11
uni.navigateTo({ url: "/pages/index/index" });

/**
* open-type属性
* 值:
* navigate - 打卡新页面
* redirectTo - 页面重定向
* navigateBack - 页面返回
* switchTab - tab切换
* reLaunch - 重加载
**/

tab切换 - 调用APIuni.switchTab、使用组件<navigator open-type="switchTab"/>

路由传参与接收

传参

1
uni.navigateTo(url:'page?name=doreen&age=18')

接收

1
2
3
onLoad:function(option){
console.log(option.name,option.age)
}

页面配置

pages.json内配置页面路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"pages": [ 
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
},
{
"path": "pages/404/index",
"style": {
"navigationBarTitleText": "页面不存在",
"enablePullDownRefresh": false
}
}
],

pages配置项

属性 类型 子属性 子类型 默认值 描述
path string
style Object
navigationBarBackgroundColor HexColor #000000 导航栏背景颜色
navigationBarTextStyle String white 导航栏标题颜色及状态栏前景颜色,仅支持 black/white
navigationBarTitleText String 导航栏标题文字内容
navigationBarBackgroundColor HexColor #000000 导航栏背景颜色
……

tabBar配置项

属性 类型 必填 默认值 描述
color HexColor tab上文字默认颜色
selectedColor HexColor tab上文字选中时的默认颜色
backgroundColor HexColor tab的背景色
list Array tab的列表,2-5个

list对象属性如下:

属性 类型 必填 说明
pagePath String 页面路径,必须在pages中先定义
text String tab上按钮文字
iconPath String 图片路径
selectedIconPath String 选中时的图片路径
visible Boolean 该项是否展示,默认展示

配置了tabBar之后list内的页面不能使用uni.navigateTo进行跳转,需要改成uni.switchTab
对于在list内的页面,使用<navigator url=''>进行跳转时要加上 open-type="switchTab" 属性

subPackages分包配置

主包: 即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本 (公共资源)
分包: 则是根据pages.json的配置进行划分。(按需加载的资源)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"pages": [{
"path": "pages/index/index",
"style": { ... }
}],
// .....
"subPackages": [{
"root": "pagesA", // 分包文件夹名称(与pages文件夹同级)
"pages": [{
"path": "list/list", // 路径
"style": { ... }
}]
}, {
"root": "pagesB",
"pages": [{
"path": "detail/detail",
"style": { ... }
}]
}],

}

常用API

组件通信

父子组件通信

和Vue一样

  1. 父组件通过绑定属性将数据传递给子组件,子组件通过props进行接收
  2. 子组件通过$emit进行广播,父组件通过@xx进行接收
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
<!--  child.vue -->
<template>
<view>
<text>子组件</text>
{{msg}}
<button type="default" @click="getChild">子传父</button>
</view>
</template>
<script>
export default {
props:['msg'], // 接收父组件传递的数据,只读
methods: {
getChild(){
this.$emit('getChild','aaaa') // 传递给父组件的数据
}
}
}
</script>

<!-- parent.vue -->
<template>
<view>
<!-- 传递&获取数据 -->
<child :msg='title' @getChild='say'></child>
</view>
</template>

<script>
import Child from '@/componets/child.vue' // 引入子组件
export default {
data() {
return {
title: "Hello",
};
},
components:{
Child // 声明
},
methods: {
say(info){
this.title = info // 获取子组件的数据
}
},
};
</script>


全局通信

uniapp提供了 uni.$emituni.$on 两个api进行全局的通信 (和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
32
33
34
35
36
37
38
39
40
41
<!-- pages1 -->
<template>
<view>
<text>{{name}}</text>
</view>
</template>
<script>
export default {
data() {
return {
name:'me'
};
},
onLoad() {
// 发布广播
uni.$on('getMeFun',(str)=>{
this.name = str
console.log('me 页面的全局事件被触发')
})
},
};
</script>

<!-- pages2 -->
<!-- 点击事件触发后打印文字、并将pages1的数据修改为 a-me -->
<template>
<view>
<button type="default" @click="hanldeClick">点击</button>
</view>
</template>

<script>
export default {
methods: {
hanldeClick(){
uni.$emit('getMeFun','a-me')
}
}
}
</script>

Vuex

axios封装&proxyTable代理配置

只在开发环境有效、打包之后代理配置就不生效了

vite.config.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
server: {
open: true,
proxy: {
'/api': {
target: 'http://rap2api.taobao.org/app/mock/data',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
'/music-api': {
target: 'https://api.uomg.com/api/rand.music?format=json&sort=%E7%83%AD%E6%AD%8C%E6%A6%9C',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/music-api/, '')
},
'/music-lyric': {
// target:'https://api.imjad.cn/cloudmusic/?type=lyric&id=1479526505'
target: 'https://api.imjad.cn/cloudmusic/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/music-lyric/, '')
},
'/one-api': {
target: 'https://api.tianapi.com/txapi/one/index?key=7764228bedff0b2310879d47173c4603&rand=1',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/one-api/, '')
},
'/diary-api': {
target: 'http://api.tianapi.com/txapi/tiangou/index?key=7764228bedff0b2310879d47173c4603',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/diary-api/, '')
},

},
}

新建一个http.ts文件对axios封装

最基础的配置只需要实例化axios对象,配置baseUrl就行了

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
128
129
import { CookieStorage } from '@/utils/cookies';
import axios, { AxiosRequestConfig } from 'axios';
import qs from 'qs';

const instance = axios.create({
timeout: 600000,
headers: { 'Content-Type': 'application/json' },
baseURL: '/',
});

// 保存下载文件
function saveFile(binaryData: string, fileName: string, mime?: string) {
const fileDownload = require('js-file-download');
fileDownload(binaryData, fileName, mime);
}

instance.download = {
get(url, config) {
const { filename = 'temp', mime = '' } = config || {};
return instance.get(url, { ...config, responseType: 'blob' }).then(res => {
saveFile(res.data, filename, mime);
return Promise.resolve(res);
});
},
post: (url, data, config) => {
const { filename = 'temp', mime = '' } = config || {};
return instance.post(url, data, { ...config, responseType: 'blob' }).then(res => {
saveFile(res.data, filename, mime);
return Promise.resolve(res);
});
},
};

// ne-upload
instance.upload = (url, data, config = {}, params) => {
const formData = new FormData();
data.forEach(item => {
formData.append('file', item.value, item.name || item.filename);
});
if (params && Object.keys(params).length > 0) {
for (const m in params) {
if (params.hasOwnProperty(m)) {
const key = m;
const value = params[m];
formData.append(key, value);
}
}
}
return instance.request({
method: 'post',
...config,
url,
data: formData,
});
};

instance.formdata = (url, data, config = {}) => {
const formData = qs.stringify(data); // 会将汉字encode
return instance.request({
...config,
url,
data: formData,
method: config.method || 'post',
headers: { ...config.headers, 'Content-Type': 'application/x-www-form-urlencoded' },
});
};

// 请求拦截器
instance.interceptors.request.use(config => {
const { params = {} } = config;
config.params = { ...params, time: new Date().getTime() };
// add token if need
config.headers = {
...(config.headers || {}),
'X-Auth-Token': CookieStorage.token,
'X-Stargate-AppId': 25,
};
return config;
}, err => {
// show err
}
);

// 响应拦截器
instance.interceptors.response.use(response => {
return Promise.resolve(response);
}, error => {
// show error_msg and reject
console.log('响应失败信息', error.response);
if (error && error.response && error.response.status) {
let errCode = 0;
let errMsg = '';
if (!!error.response.data) {
const { status, errorMsg } = error.response.data;
errCode = status;
errMsg = errorMsg;
}
switch (error.response.status) {
case 400:
window._vm.$messageSelf.error(errMsg);
break;
case 401:
// to login or authorize
window._vm.$messageSelf.error(errMsg);
CookieStorage.clear();
// window.location.href = '';
break;
case 403:
// to login or authorize
window._vm.$messageSelf.error(errMsg);
CookieStorage.clear();
window.location.href = '/';
break;
case 404:
// request failed, no res on server
window._vm.$messageSelf.error('系统错误,请稍候再试');
break;
case 500:
// server error
window._vm.$messageSelf.error(errMsg);
break;
}
}
return Promise.reject(error);
}
);

export default instance;

新建一个common-api.ts对公共请求进行封装

引入http.ts文件,并根据业务类型进程处理

1
2
3
4
5
6
7
8
9
10
11
12
13
import http from '@/utils/http';

export function commonRequest(path: string, params: any = {}, method: string = 'get') {
if (method === 'post') {
return http.post(path, params).then(res => {
return Promise.resolve(res.data);
});
} else {
return http.get(path, { params }).then(res => {
return Promise.resolve(res.data);
});
}
}

具体使用

按照commonRequest方法要求进行传参,并使用。根据不同的接口名,匹配不同的接口地址。

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
<template>
<div>
<button @click="handleRequest('/music-api')">/music-api</button>
<button @click="handleRequest('/music-lyric')">/music-lyric</button>
<button @click="handleRequest('/one-api')">/one-api</button>
<button @click="handleRequest('/diary-api')">/diary-api</button>
<h3>{{ showData }}</h3>
</div>
</template>
<script lang='ts'>
import { defineComponent } from "@vue/runtime-core";
import { ref } from "vue";
import { commonRequest } from "../api/http";

export default defineComponent({
setup: () => {
let showData = ref("e");

function handleRequest(api: string) {
commonRequest(api, {}).then((res) => (showData.value = res));
}
return { showData, handleRequest };
},
});
</script>

HarmonyOS初尝试

抱着凑热闹的心态4月30号赶上了harmony开发者一轮公测报名的末班车,经过漫长的等待终于5月12号晚上收到了短信,十几分钟后系统就给推送了。当时收到推送还是很激动的,马上就更新了。总的来说挺好、没发现什么bug太严重的bug。

官方文档

UI框架主要分为javajs,js应用所用到的东西和原生js、html、css没有太大差别,有的语法和vue有些相似,上手应该是比较容易的。主要是这个开发工具用的不太熟悉。
主要问题是 previewer 的时候可以进行热更新但是不能进行网络请求。我不知道是我操作的问题还是本来就不允许。看了官方文档是说的部分接口不能请求。

主要目录结构

主要是在 entry/main/js 下进行编写

各个文件夹的作用:

  • app.js文件用于全局JavaScript逻辑和应用生命周期管理。
  • pages目录用于存放所有组件页面。
  • common目录用于存放公共资源文件,比如:媒体资源,自定义组件和JS文件。
  • resources目录用于存放资源配置文件,比如:全局样式、多分辨率加载等配置文件。
  • i18n目录用于配置不同语言场景资源内容,比如:应用文本词条,图片路径等资源。
  • share目录用于配置多个实例共享的资源内容,比如:share中的图片和JSON文件可被default1和default2实例共享。

pages

一个文件夹对应一个页面。并且要在config.json文件下对路由进行配置

1
2
3
4
5
6
7
8
9
10
// pages数组内排在首位的 代表展示的首页
"js": [
{
"pages": [
"pages/demo/demo", // 对应demo.hml
"pages/index/index"
],
"name": "default",
}
]

i18n

内部存放的是.json文件,在页面中使用的时候直接通过 $t('...') 来获取

1
2
3
4
5
6
7
// zh-CN.json
{
"strings": {
"app_bar": {
"title": "标题"
},
}
1
2
3
4
5
// index.hml
<text>
{{ $t('strings.app_bar.title') }}
<!-- 标题 -->
</text>

组件相关

例如在 common 中创建 component 文件夹,存放公共组件。以tabbar组件为例

组件使用

通过element标签进行获取、使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- tabbar.hml -->
<toolbar>....</toolbar>


<!-- home.hml -->
<!-- 在页面的顶部通过element引入组件所在的地址 并且通过name属性取名 -->
<element name='tab-bar-com' src='../../common/component/tabbar/tabbar.hml'>
</element>

...

<div>
...
<!-- 把组件放在页面需要的位置 -->
<tab-bar-com></tab-bar-com>
</div>

父子组件传值

与vue类似,子组件通过props接收父组件的数据,父组件通过@获取子组件传递的数据

子组件tabbar

1
2
3
4
5
<!-- tabbar.hml -->
<toolbar style="position : fixed; bottom : 0px;" data='hello'>
<toolbar-item icon='{{ iconurl }}' value='我的' on:click="handleClick"></toolbar-item>
</toolbar>

1
2
3
4
5
6
7
8
9
10
11
12
// tabbar.js
export default {
props: ['iconurl'], // 接收父组件传来的数据

handleClick(e){
// emit来广播给父组件 传递给父组件数据
this.$emit('getItem', {
title: e.target.attr.value
})
}

}

父组件

1
2
3
4
5
6
7
8
<!-- home.hml -->

<div>
<!-- 通过绑定属性的方式把数据传递给子组件 -->
<!-- 通过 @..='fn' 的方式获取子组件传递的数据 -->
<tab-bar-com iconurl='https://....' @get-item='getTabbarItem'></tab-bar-com>
</div>

1
2
3
4
5
6
7
8
9
// home.js
export.default{
// 获取子组件传来的数据
getTabbarItem(item) {
// 还有一层 detail
console.log(item.detail.title) // 我的
},
}

Vue3-音乐播放器

在线展示

API文档

接口说明

地址 https://api.uomg.com/api/rand.music
返回格式 json / mp3
请求方式 get / post

请求参数

名称 类型 必填 描述
sort string 选填 默认为热歌榜 [热歌榜,新歌榜,飙升榜,抖音榜,电音榜]
mid int 选填 网易云歌单ID
format string 选填 选择输出的格式

返回参数

名称 类型 描述
code string 状态码
name string 歌曲名称
url string 歌曲地址
picurl string 歌曲封面地址

返回示例

1
2
3
4
5
6
7
8
9
{
"code": 1,
"data": {
"name": "江南",
"url": "http://music.163.com/song/media/outer/url?id=26305527",
"picurl": "http://p4.music.126.net/CcthX_ZCexIdriZADoNn3g==/109951165628166191.jpg",
"artistsname": "林俊杰"
}
}
  • 歌词接口

接口说明

地址 https://api.imjad.cn/cloudmusic
返回格式 json
请求方式 get / post

请求参数

名称 类型 必填 描述
type string 必填 值为lyric时,返回歌词
id int 必填 网易云歌区ID

返回参数

名称 类型 描述
code string 状态码
lrc object 歌词数据
version int 版本
lyric string 歌词

返回示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"sgc": false,
"sfy": false,
"qfy": false,
"lrc": {
"version": 8,
"lyric": "[00:00.000] 作曲 : 祝何\n[00:00.693] 作词 : 祝何\n[00:02.80]编曲 Arranger:祝何\n[00:06.84]楚河流沙几聚散 日月沧桑尽变换\n[00:35.08]乱世多少红颜换一声长叹\n[00:40.38]谁曾巨鹿踏破了秦关 千里兵戈血染\n[00:46.45]终究也不过是风轻云淡\n[00:51.51]长枪策马平天下 此番诀别却为难\n[00:57.72]一声虞兮虞兮泪眼已潸然\n[01:03.07]与君共饮这杯中冷暖 西风彻夜回忆吹不断\n[01:09.38]醉里挑灯看剑 妾舞阑珊\n[01:14.91]垓下一曲离乱 楚歌声四方\n[01:20.31]含悲 辞君 饮剑 血落凝寒霜\n[01:25.87]难舍一段过往 缘尽又何妨\n[01:31.77]与你魂归之处便是苍茫\n[02:00.25]长枪策马平天下 此番诀别却为难\n[02:06.17]一声虞兮虞兮泪眼已潸然\n[02:11.70]与君共饮这杯中冷暖 西风彻夜回忆吹不断\n[02:18.00]醉里挑灯看剑 妾舞阑珊\n[02:23.25]垓下一曲离乱 楚歌声四方\n[02:28.74]含悲 辞君 饮剑 血落凝寒霜\n[02:34.73]难舍一段过往 缘尽又何妨\n[02:40.11]与你魂归之处便是苍茫\n[02:46.39]汉兵刀剑纷乱 折断了月光\n[02:51.55]江畔 只身 孤舟 余生不思量\n[02:57.42]难舍一段过往 缘尽又何妨\n[03:03.01]与你来生共寄山高水长\n[03:13.11]混音师 Mixing Engineer:唐瑜\n[03:13.78]和声 Backing vocals:田跃君\n[03:14.24]制作人 Produced by :蒋雪儿 Snow.J\n[03:14.67]监制 Executive producer: 蒋雪儿 Snow.J\n[03:15.18]OP/SP :乐无限 ETERNAL MUSIC\n[03:15.90]【未经授权不得翻唱或使用】\n"
},
"klyric": {
"version": 0,
"lyric": null
},
"tlyric": {
"version": 0,
"lyric": null
},
"code": 200
}

基本思路

音乐播放 —— audio

html内添加audio标签进行相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<audio
ref="audio"
autoplay
@pause="handlePause"
@play="handlePlay"
@timeupdate="handleMusicTimeChange"
:src="music.value.url"
></audio>

<!--

绑定ref方便用js对Audio对象进行操作

用到的属性:
autoplay 是否自动播放
src 音频地址

用到的方法:
pause 暂停当前播放的音频时触发
play 开始播放音频时触发
timeupdate 播放位置发生改变时触发
-->

歌词获取和展示
  1. 首先将获取随机歌曲和歌词数据的方法封装成一个方法
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

import { ref, reactive } from 'vue'
import { getData } from "../api/api";
import { toSeconds } from '../utils/time'
export async function useMusic() {

// 随机歌曲相关信息获取
let datas: any = await getData(2, "", {});
let music = reactive({ value: datas.data.data });
let music_id = ref(music.value.url.split("?id=")[1]);

// 歌词通过歌曲id获取
let lyric: any = await getData(3, "", {
type: "lyric",
id: music_id.value,
});
// 对歌词数据进行处理 [{text:'歌词',time:2.333 (歌词开始秒数)},...]
let arr = lyric.data.lrc.lyric.split('\n')
let obj: any = reactive({ data: [] })
arr.forEach((element: string) => {
let key = toSeconds(element.split(']')[0].slice(1))
let value = element.split(']')[1]
obj.data.push({ text: value, time: key })
});

// 设置一个index作为歌词数据的索引
let index = ref(0)

return { index, obj, music }
}

  1. 对audio标签绑定ref,对audio对象进行操作。
  • timeupdate事件进行监听
    • 通过audio.value.currentTime获取到当前播放的秒数
    • 从前向后与歌词数据 (obj.data[index.value + 1].time) 的time进行对比
    • 如果当前秒数小于当前索引的歌词数据的秒数、就展示该条歌词数据;否则index++与下一条数据进行比较
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
export default defineComponent({
setup: async () => {
// 在异步组合api中,生命周期函数必须写在 数据/方法前面
onMounted(() => {});

let { index,obj,music } = await useMusic();

// 绑定audio对象
let audio = ref();

// 每条歌词数据
let msg = obj.data && obj.data[0].text ? ref(obj.data[0].text) : ref(" ");

// 进度条改变触发事件
function handleMusicTimeChange() {
if (audio.value.currentTime >= obj.data[index.value + 1].time) {
index.value++;
}
msg.value = obj.data[index.value].text;

}
return {
msg,
index,
obj,
audio,
music,
handleMusicTimeChange,
};
},
});

未解决的问题
1
2
3
4
5
6
7
8
9
10
/**
* 目前的播放器进度条不能进行控制 控制之后 不知道怎么获取对应索引的歌词
* (
* 进度条长度和音频总时长是按比例的
* 所以进度条播放位置发生改变时 timeupdate对于不同长度的歌曲触发秒数间隔都是不一样的
* currentTime也不一定能和time对应上 所以不知道怎么获取到歌词
*
* 不知道怎么实现QAQ , 所以就没展示audio的控件(controls)
* )
*/

Vue3+Vite+ElementPlus+VueRouter相关配置

demo在线展示 |
仓库地址

element-plus源码有点问题 打包的时候会报错,需要手动修改…

vite中文文档

前端构建工具,能够显著提升前端开发体验

初始化项目

1
npm init @vitejs/app

element-plus UI

element-ui没有兼容vue3,但是新推出了element-plus来兼容vue3

安装element-plus

1
npm i element-plus -S

修改配置文件引入库

修改main.ts

1
2
3
4
5
6
7
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus' // +
import 'element-plus/lib/theme-chalk/index.css' // +

createApp(App).use(ElementPlus).mount('#app') // .use(ElementPlus)

ElMessageBox消息弹框

一个我自己推出来的功能.. 文档没找着
原因是我要在setup里面使用消息弹框组件 但按文档的配置需要用到this

1
2
3
4
5
6
7
8
9
10
11
// 但是文档上是写的 this.$alert('...')
// 众所周知 vue3 的setup里面没有 this
// 根据 消息提示 组件的配置方法: ElMessage('...')
// 我大胆猜想了下消息弹框的使用 然后 成功了..

setup:()=>{
// ...
ElMessageBox.alert('...')
}


vue-router

安装插件

1
npm i vue-router@next -S

编写路由配置文件

新建router.ts文件
没有在vite.config.ts配置的话 不能使用@代替src

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createRouter, createWebHistory } from 'vue-router'

const Router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import('../components/HelloWorld.vue')
},
{
path: '/home',
name: 'home',
component: () => import('../components/Home.vue')
},
]
})

export default Router

修改配置文件引入库

main.ts文件内添加内容

1
2
3
import router from './router/router'

createApp(App).use(router).mount('#app') // .use(router)

修改App.vue

将文件修改成

1
2
3
4
<div id="app">
<!-- 展示路由内容 -->
<router-view></router-view>
</div>

axios

安装插件

1
npm i axios -S

新建 http.ts 文件对axios进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import axios from 'axios'

axios.defaults.baseURL = '/api'
//'http://rap2api.taobao.org/app/mock/data' 不代理
axios.defaults.timeout = 10000

export function get(url: string, params: any) {
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
}).then((res) => {
resolve(res)
}).catch((err) => {
reject(err)
})
})
}

新建 api.ts 文件对接口请求进行封装

1
2
import { get } from './http'
export const getData = (url: string, params: any) => get(url, params)

在 vite.config.ts 中配置proxy代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ...
server: {
host: '10.9.37.4',
port: 8080,
open: true,
proxy: {
'/api': {
target: 'http://rap2api.taobao.org/app/mock/data', // 目标接口域名
changeOrigin: true,
secure: false, // 是否是https
ws: true,
rewrite: (path) => path.replace(/^\/api/, '') // 重新接口地址
}
}

}

使用

  • 引入方法
    1
    import { getData } from '../api/api'
  • 使用

在组合api中使用异步获取的话 父组件使用的时候要在外层添加 <suspense>进行包裹 不然数据不会展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// child component
setup: async () => {
let datas: any = await getData('/1531382', { scope: 'response' })

let color = reactive(JSON.parse(JSON.stringify(datas.data.data.color)))
return { color }
}

// App.vue
<template>
<suspense>
<router-view></router-view>
</suspense>
</template>

vite.config.ts配置

详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

const path = require('path')

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
alias: {
'@': path.join(__dirname, 'src') // 配置用@来代表src目录
},
// 服务器相关配置
server: {
port: 8080,
host: '0.0.0.0',
open: true, // 是否自动开启浏览器
},
})

Flex相关

父容器的属性

flex-direction (主轴/项目排列方向)

row(默认,水平,起点在左端) | row-reverse | column | column-reverse

flex-wrap (是否换行)

nowrap(默认不换行) | wrap(换行,第一行在上方) | wrap-reverse(换行,第一行在下方)

flex-flow (flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap)

<flex-deirection> || <flex-wrap>

justify-content (主轴的对齐方式)

flex-start(默认 左对齐) | flex-end | center | space-between (两端对齐,项目之间的间隔都相等。) | space-around (每个项目两侧的间隔相等)

align-items (定义项目在交叉轴上如何对齐)

flex-start (交叉轴的起点对齐) | flex-end(交叉轴的终点对齐) | center(交叉轴的中点对齐) | baseline(项目的第一行文字的基线对齐) | stretch(默认值 如果项目未设置高度或设为auto,将占满整个容器的高度)

子项目的属性

order (属性定义项目的排列序列。数值越小排列越靠前,默认为0)

flex-grow (项目的放大比例,默认为0)

如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。
如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

flex-shrink (项目的缩小比列,默认为1)

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小

  • 计算方式:
    (项目n宽度*n的缩小比例/(项目1宽度*1的缩小比例)+...+(项目n宽度*n的缩小比例))*超出的宽度

    100+400且flex-shrink都为1的子项目 父级为400 ,子项目超出100
    100的子项目应该缩小的宽度:100*1/(100*1+400*1)=0.2 0.2*100 = 20
    400的子项目应该缩小的宽度:400*1/(100*1+400*1)=0.8 0.8*100 = 80

flex-basis (项目的固定空间 与width类似)

length | auto(默认) | 百分比(主轴长度)

  • flex-growflex-shrink一起使用的时候的计算方式
父容器主轴的长/宽 设置为`900`

所有元素都设置为` flex:1 `
其中`元素1`设置` flex-basis: 30% ` 
`元素2`设置`flex-grow: 2`

1. 先看`flex-basis`,获取到应有的长度
1
2
3
// 只有元素1设置了宽度
width1 = 900*0.3 = 270

2. 因为设置了`flex-grow`,所有元素平分剩余的宽度
1
2
3
4
5
6
7
8
9
// 剩余 630
// 总份数: 1+2+1=4

// 元素1:占1份
width1 = 630*(1/4) + 270 = 427.5
// 元素2:占2份
width2 = 630*(2/4) = 315
// 元素3:占1份
width3 = 630*(1/4) = 157.5

flex (属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选 )

none | [ <flex-grow> <flex-shrink>?|| <flex-basis> ]

align-self (属性允许单个项目有与其他项目不一样的对齐方式 默认为auto)

auto | flex-start | flex-end | center | baseline | stretch

可视化

原型链

原型链有点懵 画了图好像理解了点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// person的构造函数
function Person(name){
this.name = name
}
// person原型对象方法
Person.prototype.eat = function(){
console.log('person eat')
}

// ...
function Student(){}
Student.prototype.sayhi = function(){
console.log('student sayhi')
}


Student.prototype = new Person('doreen')
var stu = new Student()
stu.eat() // person eat
stu.sayhi() // 报错 stu.sayhi is not a function

// stu.__proto__ => new Person()
// stu.__proto__.__proto__ === Person.prototype

总结

  • 构造函数可以实例化对象
  • 构造函数中有一个属性叫prototype,是构造函数的原型对象
  • 原型对象中有一个叫constructor构造器,指向自己所在的构造函数
  • 实例对象的原型对象 (__proto__) 指向的是构造函数的原型对象
  • 构造函数中的原型对象 (prototype) 中的方法是可以被实例对象直接访问

vue-router导航守卫

官方文档

  • 导航 表示路由正在发生改变

  • 守卫方法的三个参数: to: Route(目标的路由) ; from: Route(离开的路由) ; next: Function() (是否执行目标路由)

    • next(): 执行下一个钩子
    • next(false): 中断当前导航。如果url改变重置到from
    • next('/') / next({path:'/'}): 跳转到不同地址
    • next(error): 导航终止&将错误传递给router.onError()

      全局前置守卫

      router.beforeEach

1
2
3
4
5
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
// ...
})

路由独享的守卫

在路由配置的守卫 beforeEnter , (对于进入该路由进行控制)

1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})

组件内部守卫

对出入该组件路由进行控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},

beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}

完整生命周期

  1. 导航被触发。
  2. 失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局beforeResolve 守卫。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

纽约时间比加州时间早三个小时

纽约时间比加州时间早三个小时,
New York is 3 hours ahead of California,

但加州时间并没有变慢。
but it does not make California slow.

有人22岁就毕业了,
Someone graduated at the age of 22,

但等了五年才找到稳定的工作!
but waited 5 years before securing a good job!

有人25岁就当上CEO,
Someone became a CEO at 25,

却在50岁去世。
and died at 50.

也有人迟到50岁才当上CEO,
While another became a CEO at 50,

然后活到90岁。
and lived to 90 years.

有人单身,
Someone is still single,

同时也有人已婚。
while someone else got married.

欧巴马55岁就退休,
Obama retires at 55,

川普70岁才开始当总统。
but Trump starts at 70.

世上每个人本来就有自己的发展时区。
Absolutely everyone in this world works based on their Time Zone.

身边有些人看似走在你前面,
People around you might seem to go ahead of you,

也有人看似走在你后面。
some might seem to be behind you.

但其实每个人在自己的时区有自己的步程。
But everyone is running their own RACE, in their own TIME.

不用嫉妒或嘲笑他们。
Don’t envy them or mock them.

他们都在自己的时区里,你也是!
They are in their TIME ZONE, and you are in yours!

生命就是等待正确的行动时机。
Life is about waiting for the right moment to act.

所以,放轻松。
So, RELAX.

你没有落后。
You’re not LATE.

你没有领先。
You’re not EARLY.

在你自己的时区里,一切安排都准时。
You are very much ON TIME, and in your TIME ZONE.

  • Copyrights © 2019-2026 John Doe
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信