Vue3 v3.4之前如何实现组件中多个值的双向绑定?

Vue3 v3.4之前如何实现组件中多个值的双向绑定?

官方给的例子是关于el-input的,如下。但是@input不是所有组件标签都有的属性啊,有没有一种通用的办法呢?

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
defineProps({

firstName: String,
lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>

基础代码

以一个Dialog组件为例。我们自己写一个course-buy.vue

language-html
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
<template>
<el-dialog
v-model="localValue.dialogVisible"
title="Warning"
width="500"
align-center
>
<span>Open the dialog from the center from the screen</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="localValue.dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="localValue.dialogVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: props.dialogVisible,
courseId: props.courseId
})

</script>

外部在使用时(假设为base.vue),如下使用

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<CourseBuy
v-model:dialog-visible="orderPayParams.dialogVisible"
v-model:course-id="orderPayParams.courseId"
/>
</template>
<script setup lang="ts">
const orderPayParams = reactive({

dialogVisible: false,
courseId: 1
});
</script>

上述代码,course-buy.vue中真正使用的变量是localValue本地变量,localValue的值来自base.vue
但是上述的基础代码,dialogVisiblecourseId的值只能从base.vue流向course-buy.vue
如何实现course-buy.vue本身修改localValue的值后,修改变化同步到base.vue呢?

1. watch

如果要让dialogVisible双向绑定,可以写两个watch互相监听并更新。要实现courseId双向绑定也是同理。

language-html
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
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: props.dialogVisible,
courseId: props.courseId
})
//值双向绑定
watch(() => props.dialogVisible, (newValue) => {

localValue.dialogVisible = newValue;
});
watch(() => localValue.dialogVisible, (newValue) => {

emit('update:dialogVisible', newValue);
});
</script>

2. computed(推荐)

不过使用computed可以更简洁,性能也更好。

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: computed({

get: () => props.dialogVisible,
set: (value) => emit('update:dialogVisible', value)
}),
courseId: props.courseId
})
</script>
Vue3 setup路由进入以及变化问题

Vue3 setup路由进入以及变化问题

1. 起因

在Vue 3中,<script setup>语法糖提供了一种更加简洁和组合式的方式来定义组件。然而,由于<script setup>的特性,它不能直接使用beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave这些导航守卫。

但是vue-router中有两个的类似函数可以触发路由离开和变化,只需要import一下就可以。

language-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import {
onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";

onBeforeRouteUpdate((to, from, next) => {

console.log("onBeforeRouteUpdate",to,from, typeof next)
next();
})
onBeforeRouteLeave((to, from, next)=>{

console.log("beforeRouteLeave",to,from, typeof next)
next()
})
</script>

但是却没有beforeRouteEnter的替代品。

2. 浏览过的代替方法

https://github.com/vuejs/rfcs/discussions/302#discussioncomment-2794537
https://blog.richex.cn/vue3-how-to-use-beforerouteenter-in-script-setup-syntactic-sugar.html
https://blog.csdn.net/oafzzl/article/details/125045087

但是都是在<script setup>之上新加了第二个<script>,用第二个<script>来使用无setupbeforeRouteEnter。限制很多,尤其是两个script之间互动很麻烦,甚至无法实现。

3. 还是Watch

https://juejin.cn/post/7171489778230100004
https://blog.csdn.net/m0_55986526/article/details/122827829

下面是一个当路由从"/stock/move/moveDetail""/stock/move/moveSearch"触发函数的例子。同时第一次进入时也会触发watch,就相当于beforeRouteEnter了。

language-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import {
watch} from "vue";
import {
useRouter} from "vue-router";
let router = useRouter()
// 监听当前路由变化
watch(() => router.currentRoute.value,(newPath, oldPath) => {

if (newPath != null && oldPath != null && "fullPath" in newPath && "fullPath" in oldPath && newPath["fullPath"] === "/stock/move/moveSearch" && oldPath["fullPath"] === "/stock/move/moveDetail"){

onSubmitQuiet()
}
}, {
immediate: true});
</script>
ElementPlus表单验证v-for循环问题

ElementPlus表单验证v-for循环问题

提供两个样例,主要注意<el-form-item>中的prop

样例一

language-html
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
<template>
<el-form
ref="dynamicValidateForm"
:model="dynamicValidateForm"
label-width="120px"
class="demo-dynamic"
>
//重点关注el-form-item标签中的prop内容
<el-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: 'domain can not be null',
trigger: 'blur',
}"
>
<el-input v-model="domain.value"></el-input>
</el-form-item>
</el-form>
</template>

<script lang="ts">
export default {

data() {

return {

dynamicValidateForm: {

domains: [
{

key: 1,
value: '',
},
],
email: '',
},
}
},
}
</script>


样例二

language-html
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
<template>
<el-form :model="queryParams" style="width: 100%">
<el-table :data="queryParams.items">
<el-table-column label="移动数量" width="100">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-form-item style="width: 100px;padding: 0px;margin: 0px" :prop="'items.'+scope.$index+'.moveAmount'" :rules="{ required: true, message: '不能为空', trigger: 'blur',}">
<el-input-number class="number" :controls="false" v-model="scope.row.moveAmount"></el-input-number>
</el-form-item>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
</template>

<script setup>
const queryParams = reactive({

factory1: undefined,
factory2: undefined,
commentText: undefined,
items:[]
})
</script>
ElementPlus隐藏Scrollbar的横向滚动条
ElementPlus-穿梭框长宽设置

ElementPlus-穿梭框长宽设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style scoped>
:deep(.el-transfer-panel) {
height: 400px; /* 穿梭框高度 */
width: 300px;
}

:deep(.el-transfer-panel__list.is-filterable) {
height: 400px; /* 穿梭框列表高度 */
}

:deep(.el-transfer-panel__body) {
height: 400px; /* 穿梭框视图层高度 */
}
:deep(.el-transfer-panel__filter) {
width: 300px; /* 修改搜索栏的宽度 */
}
</style>
Vue3 setup组合式语法优雅地使用Echarts库

Vue3 setup组合式语法优雅地使用Echarts库

1. 安装Echarts

npm或者yarn安装

npm install echarts
yarn add echarts

2.main.js全局挂载Echarts

language-javascript
1
2
3
4
5
6
7
8
9
import {
createApp } from 'vue'
import App from './App.vue'

import * as echarts from 'echarts'

const app = createApp(App)
app.config.globalProperties.$echarts = echarts // 全局挂载echarts
app.mount('#app')

3.Vue setup组合式语法使用案例

language-html
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
<template>
<!-- 通过ref获取html元素 宽高必须设置 -->
<el-row>
<el-col :offset="8" :span="8">
<div ref="info" style="width: 100%; height: 600px"></div>
</el-col>
</el-row>
</template>
<script setup>
import {
onMounted, ref, inject } from "vue";
const {
proxy } = getCurrentInstance()//获取Vue3全局配置
const info = ref();//用来获取对应标签组件

onMounted(() => {

var infoEl = info.value;//获取ref="info"的标签组件
var userEc = proxy.$echarts.init(infoEl, "light");//proxy.$echarts是在获取全局配置中的echarts,这样就不需要在每个vue中import echarts了

//此处为图表配置区,可参考Echarts官网替换各种图表
var option = {

tooltip: {

trigger: 'item'
},
legend: {

top: '5%',
left: 'center'
},
series: [
{

name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {

borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {

show: false,
position: 'center'
},
emphasis: {

label: {

show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {

show: false
},
data: [
{
value: 1048, name: 'Search Engine' },
{
value: 735, name: 'Direct' },
{
value: 580, name: 'Email' },
{
value: 484, name: 'Union Ads' },
{
value: 300, name: 'Video Ads' }
]
}
]
};
userEc.setOption(option);//将图标挂载到标签组件
});
</script>

(完)

Vue-Router-编程式路由跳转

Vue-Router-编程式路由跳转

历届办法

  1. path+query
    参数会暴露在url
  2. name+params
    官方在2022–8-22已禁用params传参,具体看这This
  3. state
    参数不会暴露在url,但刷新页面会失效,和以前的params一样
  4. store
    用额外的插件来store,顾名思义存储数据,此本章不做讲解。

起手式:配置路由

无论是path还是name,都需要在路由配置中指定每个路径对应的组件。将配置都写到一个配置文件中,然后在vue的main.js中挂载配置它,具体流程是这样的:

  1. 首先,您需要在项目的src目录下创建一个router文件夹,用来存放路由相关的文件。
  2. 然后,在router文件夹中创建一个index.js文件,用来编写路由配置。例如:
language-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
29
30
// router/index.js
import {
createRouter, createWebHashHistory } from 'vue-router'

// 导入路由组件
import Home from '.../components/Home.vue'
import About from '.../components/About.vue'

// 定义路由
//path
const routes = [
{
path: '/', component: Home },
{
path: '/about', component: About },
]
/*
//name
const routes = [
{ name: "Home", path: "/", component: Home }
{ name: "About ", path: "/about", component: About }
]

*/
// 创建路由实例
const router = createRouter({
history: createWebHashHistory(), routes, })

// 导出路由实例
export default router
  1. 最后,在main.js文件中导入路由器对象,并将其挂载到Vue实例上。例如:
language-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// main.js
// 在main.js中
import {
createApp } from 'vue'
import App from './App.vue'

// 导入路由实例
import router from './router'

// 创建并挂载根实例
const app = createApp(App)
// 使用路由实例
app.use(router)
app.mount('#app')

1. path+query

发送方

language-typescript
1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
import {
useRouter } from "vue-router";
const router = useRouter();
const goPath = (id: number) => {

router.push({
path: "/about", query: {
id: id } });
};
</script>

接收方

language-typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup lang="ts">
import {
useRoute } from "vue-router";
const route = useRoute();
const id = route.query.id;
</script>

<template>
<div>Id: {
{
id }}</div>
</template>

2. state

发送方

language-typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts">
import {
useRouter } from "vue-router";
const router = useRouter();
const goPath = (id: number) => {

router.push({
path: "/about", state: {
id: id } });
//或者
//router.push({ name: "About", state: { id: id } });
};
</script>

接收方

language-typescript
1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
const id = history.state.data;
</script>

<template>
<div>Id: {
{
id }}</div>
</template>

Vue-ts-优雅的响应式对象数组(reactive型数组)

Vue-ts-优雅的响应式对象数组(reactive型数组)

需求

绘制如下画面,每一行绑定的数据都是独立的,并且还需要完成”增加行”按钮。

Vue前端代码

language-html
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
<el-button @click="addLine">增加行</el-button>
<el-row>
<el-col :span="4" align="middle">产品</el-col>
<el-col :span="4" align="middle">仓库</el-col>
<el-col :span="4" align="middle">批次号</el-col>
<el-col :span="4" align="middle">库存数量</el-col>
<el-col :span="4" align="middle">隔离数量</el-col>
</el-row>
<el-form>
<el-row v-for="(item, i) in inItemNums">
<el-col :span="4">
<el-select v-model="inQueryParams[i].product" placeholder="请选择产品" @change="inSelectProductChange(i)">
<el-option
v-for="item in inSelectOption[i].product"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="inQueryParams[i].factory" placeholder="Select" @change="inSelectFactoryChange(i)">
<el-option
v-for="item in inSelectOption[i].factory"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="inQueryParams[i].batchNum" placeholder="Select" @change="inSelectBatchNumChange(i)">
<el-option
v-for="item in inSelectOption[i].batchNum"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
<el-col :span="4">
<el-input v-model="inQueryParams[i].stockQuantity" readonly></el-input>
</el-col>
<el-col :span="4">
<el-input v-model="inQueryParams[i].isolateQuantity"></el-input>
</el-col>
</el-row>
</el-form>

Vue逻辑实现

language-typescript
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
interface IselectOptionItem {

product: [],
factory: [],
batchNum: [],
isolateQuantity: []
}
const selectOptionItem = {

product: [],
factory: [],
batchNum: [],
isolateQuantity: []
}
interface IqueryParamsItem {

product: undefined,
factory: undefined,
batchNum: undefined,
stockQuantity: undefined,
isolateQuantity: undefined
}
const queryParamsItem = {

product: undefined,
factory: undefined,
batchNum: undefined,
stockQuantity: undefined,
isolateQuantity: undefined
}
const inItemNums = ref(5);
const inSelectOption = reactive<IselectOptionItem []>([])
for (let i = 0; i < inItemNums.value; i++) {

inSelectOption.push({
...selectOptionItem})
}
const inQueryParams = reactive<IqueryParamsItem[]>([]);
for (let i = 0; i < inItemNums.value; i++) {

inQueryParams.push({
...queryParamsItem})
}
const addLine=()=>{

inSelectOption.push({
...selectOptionItem})
inQueryParams.push({
...queryParamsItem})
inItemNums.value = inItemNums.value+1;
}

注意点

往数组添加元素时,不使用selectOptionItem而是{...selectOptionItem},区别是前者是引用,而后置是复制,如果写前者,那么数组里的元素都指向一个对象,那么就会出现下拉框的值一起联动。