本文档旨在收集开发过程中遇到的问题及解决方法,方便日后查阅和学习。
# 问题记录
# 1. reactive 声明数组有时不会触发自动更新,建议使用 ref (PS: 不可以直接对 user = reactive 赋值,会失去响应式)
reactive
是 Vue 3 提供的一个函数,用于将一个普通的 JavaScript 对象转换为一个响应式对象。这意味着,当你修改这个对象的属性时,Vue 会自动追踪这些修改,并相应地更新视图
通常 reactive
可以不加 .value
,而 Ref
需要。
不可以直接对 reactive
声明对接赋值,会失去响应式
// 错误示例
let loginForm = reactive({
mobile: "",
password: "",
sms: ""
})
loginForm = {
mobile: remember_account_obj.mobile || "",
password: remember_account_obj.password || "",
sms: ""
}
// 正确的做法
onBeforeMount(() => {
loginForm.mobile = remember_account_obj.mobile || ""
loginForm.password = remember_account_obj.password || ""
loginForm.sms = ""
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
通过 store storeToRefs
引入的 数据也需要 .value
使用场景
- 复杂的嵌套对象(多层对象):当你的状态是一个复杂的嵌套对象时,使用 reactive 可以方便地将整个对象转换为响应式。
- 共享状态:reactive 通常用于在多个组件之间共享状态,因为它能保持对象的引用和结构不变。
复杂对象示例
<template>
<div>
<p>用户: {{ user.name }}</p>
<p>年龄: {{ user.details.age }}</p>
<button @click="incrementAge">增加年龄</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// 创建一个复杂的响应式对象
const user = reactive({
name: '张三',
details: {
age: 30,
address: '北京市'
}
})
const incrementAge = () => {
user.details.age++
}
</script>
ps:
import { useStore } from "@/store"
import { storeToRefs } from "pinia"
const { useActivityStore } = useStore()
const { baseInfo } = storeToRefs(useActivityStore)
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
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
# 2. 父子组件通信,Vue 3 更鼓励使用组件事件、props 和 Provide/Inject 来实现组件间通信,因为这些方式更符合组件解耦和单向数据流的设计理念。
# 使用 Provide/Inject
# 3. vue 项目切换页面之后 滚动条不在顶部
简单粗暴的方法 在router路由配置 scrollBehavior
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
/*
* 如果存在保存的滚动位置,使用保存的位置
* 支持滚动到元素位置
* 支持滚动到锚点
* behavior: "smooth"
*/
if (savedPosition) {
return savedPosition
}
// 否则滚动到顶部
return { top: 0 }
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 快速构建模版 (列表页)
<template>
<div class="pageStylePublic">
<div class="pageMainBoxPublic">
<!-- 头部 -->
<PageTitle :list="pagetitle"></PageTitle>
<div class="pageMainBoxPublicmain">
<!-- 搜索条件 -->
<div class="searchbox"></div>
<!-- table -->
<div class="tablebox"></div>
<PagePaging
:pageinfo="pageInfo"
@pagesizechage="pagesizechage"
@pagecurrentchage="pagecurrentchage"
></PagePaging>
</div>
</div>
</div>
</template>
<script setup>
// 引入接口
import { getInstitutionList } from "@/api/activity.js"
const router = useRouter() // router.push()
const route = useRoute() //携带参数的当前实例 // route.query.id
const loading = ref(false)
const tableData = ref([])
// 搜索条件内容
const formSearch = ref({
keyword: ""
})
// pageinfo
const pageInfo = ref({
total: 0,
currentpage: 1,
pageSize: 10
})
// 复杂数据
const pageData = reactive({
data: {}
})
// 打开编辑/添加弹窗
const addLeaderRef = ref(null)
const addLeaderOpen = (tit, row) => {
addLeaderRef.value.openDialog(tit, row)
}
// 获取列表
const getList = async() => {
loading.value = true
let params = {
page: pageInfo.value.currentpage,
limit: pageInfo.value.pageSize,
keyword: formSearch.value.keyword
}
let { data, code, msg } = await getInstitutionList(params)
if (code == 1) {
tableData.value = data?.data || []
pageInfo.value.total = data?.total
} else {
ElMessage({
message: msg,
type: code == 1 ? "success":"error"
})
}
loading.value = false
}
const searchBtn = () => {
pageInfo.value.currentpage = 1
getList()
}
const pagesizechage = (value) => {
pageInfo.value.pageSize = value
searchBtn()
}
const pagecurrentchage = (value) => {
pageInfo.value.currentpage = value
getList()
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
</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
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
<!-- 需要做 keepAlive 的页面需要添加 name -->
<script>
export default {
name: "Default",
}
</script>
// 引入store
import { useStore } from "@/store"
import { storeToRefs } from "pinia"
const { useActivityStore } = useStore()
// 转换 store 里面的属性为响应式数据,script 里面使用要加 .value // baseInfo.value
const { baseInfo } = storeToRefs(useActivityStore)
// 复杂数据建议使用 store
// 父组件提供刷新条件给子组件, 比如添加后刷新列表 等 Provide/Inject 可以跨越多级组件层级传递数据 需要注意接收
provide("leaderIndexRefresh", searchBtn)
// 子组件或后代组件注入
const leaderIndexRefresh = inject("leaderIndexRefresh", null)
// 也可以使用 props 和 emit 来实现。props 用于从父组件向子组件传递数据,emit 用于从子组件向父组件发送事件
<ChildComponent message="Hello from Parent!" @update-message="handleUpdateMessage"></ChildComponent>
const handleUpdateMessage = (newMessage) => {
console.log('Received message from child:', newMessage)
}
// props 传递的参数要用 props.name 来获取
// 子组件
const props = defineProps({
message: {
type: String,
required: true
}
})
const emit = defineEmits(['update-message'])
const sendMessage = () => {
emit('update-message', 'Hello from Child!')
}
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
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
// 页面内容过多建议子组件动态引入
<Goodsfilter></Goodsfilter>
// 商品筛选
const Goodsfilter = defineAsyncComponent(() => import("../manage/components/goodsfilter.vue"))
1
2
3
4
5
6
2
3
4
5
6
# 快速构建模版 (弹框 dialog)
<template>
<div class="add-leader">
<el-dialog v-model="dialogShow" width="550" draggable>
<template #header>
<h5 class="title">{{ title }}</h5>
</template>
<!-- 表单 -->
<div v-if="dialogShow" class="con-box">
<el-form
ref="formRef"
label-width="120px"
label-position="right"
:model="formVal"
:rules="fromRules"
>
<el-form-item label="团长名称:" prop="institution_name">
<el-input v-model="formVal.institution_name" placeholder="请填写" maxlength="20" />
</el-form-item>
<el-form-item label="百应ID:" prop="buyin_id">
<el-input v-model="formVal.buyin_id" placeholder="请填写" maxlength="20" />
</el-form-item>
<el-form-item label="团长主页链接:" prop="institution_page">
<el-input v-model="formVal.institution_page" placeholder="请输入团长主页链接" />
</el-form-item>
<el-form-item label="跟进人:" prop="account_id">
<el-select v-model="formVal.account_id" placeholder="请选择跟进人">
<el-option
v-for="item in accountOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="主推类目:" prop="category_id">
<el-select v-model="formVal.category_id" placeholder="请选择主推类目">
<el-option
v-for="item in businessOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="出单商品数:" prop="order_product_num">
<el-input v-model="formVal.order_product_num" placeholder="请填写" />
</el-form-item>
<el-form-item label="出单达人数:" prop="order_talent_num">
<el-input v-model="formVal.order_talent_num" placeholder="请填写" />
</el-form-item>
<el-form-item label="总销售额:" prop="total_gmv">
<el-input v-model="formVal.total_gmv" placeholder="请填写">
<template #append>¥</template>
</el-input>
</el-form-item>
<el-form-item label="备注:" prop="msg">
<el-input
v-model="formVal.msg"
type="textarea"
rows="4"
resize="none"
maxlength="150"
show-word-limit
placeholder="最多150个字"
/>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button class="cancel-btn" @click="closeDrawer">取消</el-button>
<el-button type="primary" :loading="btnLoading" @click="submitForm"> 确定 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { setInstitution, getAccountList, businessCategory } from "@/api/activity"
const leaderIndexRefresh = inject("leaderIndexRefresh", null)
import { useStore } from "@/store"
const { useActivityStore } = useStore()
let btnLoading = ref(false)
let pageData = reactive({})
const dialogShow = ref(false)
// 表单
const formVal = ref({
institution_name: "",
buyin_id: "",
institution_page: "",
account_id: "",
category_id: "",
order_product_num: "",
order_talent_num: "",
total_gmv: ""
})
// 表单校验
const fromRules = {
institution_name: [{ required: true, message: " ", trigger: "blur" }],
buyin_id: [{ required: true, message: " ", trigger: "blur" }],
account_id: [{ required: true, message: " ", trigger: "blur" }]
}
// 下拉选项
const accountOptions = ref([])
const businessOptions = ref([])
// 提交
const formRef = ref(null)
const submitForm = () => {
formRef.value.validate(async(valid) => {
if (valid) {
btnLoading.value = true
let params = JSON.parse(JSON.stringify(formVal.value))
if (pageData && pageData.id ){
params.id = pageData.id
}
delete params.create_time
delete params.delete_time
delete params.category_name
delete params.ins_id
delete params.update_time
delete params.site_id
const { code, msg } = await setInstitution(params)
ElMessage({
message: msg,
type: code == 1 ? "success" : "error"
})
btnLoading.value = false
if (code == 1) {
useActivityStore.$patch((state) => {
state.baseInfo = JSON.parse(JSON.stringify(Object.assign(state.baseInfo, params)))
})
dialogShow.value = false
}
leaderIndexRefresh && leaderIndexRefresh()
} else {
return false
}
})
}
let title = ref("添加团长信息")
// 打开
const openDialog = (tit, data) => {
title.value = tit ? tit : "添加团长信息"
console.log(tit, data)
if (data) {
pageData = JSON.parse(JSON.stringify(data))
formVal.value = Object.assign(formVal.value, pageData)
} else {
pageData = {}
formVal.value = {
institution_name: "",
buyin_id: "",
institution_page: "",
account_id: "",
category_id: "",
order_product_num: "",
order_talent_num: "",
total_gmv: ""
}
}
// eslint-disable-next-line no-use-before-define
getCategory()
dialogShow.value = true
formRef.value && formRef.value.resetFields()
}
// 关闭
const closeDrawer = () => {
dialogShow.value = false
}
// 获取招商类目
const getCategory = () => {
businessCategory().then( res => {
if ( res.code==1 ) {
let arr = []
let key = Object.keys(res.data)
let value = Object.values(res.data)
key.map((ele, index) => {
arr.push({
name: value[index],
id: key[index]
})
})
businessOptions.value = arr
}
})
getAccountList({
page: 1,
limit: 9999
}).then( res => {
if (res.code==1) {
accountOptions.value = res?.data?.data
}
})
}
defineExpose({ openDialog })
</script>
<style lang="scss" scoped>
.add-leader {
.title {
font-size: 16px;
color: var(--heTxtColor-6);
}
.cancel-btn {
color: var(--maincolor);
border-color: var(--maincolor);
}
.con-box {
padding-right: 20px;
border-top: 1px solid var(--heBorderColor-3);
border-bottom: 1px solid var(--heBorderColor-3);
padding-top: 20px;
padding-bottom: 29px;
}
}
</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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# 快速构建模块 (弹框 dialog) (PC)
点击展开代码
<template>
<div class="dialogStyleOne">
<el-dialog v-model="dialogShow" width="550" draggable>
<template #header>
<div></div>
</template>
<!-- 表单 -->
<div class="dialog-content">
<el-form
ref="formRef"
label-width="120px"
label-position="right"
:model="formData"
:rules="fromRules"
>
<el-form-item label="联系人姓名:" prop="name">
<el-input v-model="formData.name" placeholder="请填写联系人姓名" maxlength="20" />
</el-form-item>
<el-form-item label="微信号:" prop="wechat_id">
<el-input v-model="formData.wechat_id" placeholder="请填写微信号" maxlength="20" />
</el-form-item>
<el-form-item label="手机号:" prop="phone_number">
<el-input v-model="formData.phone_number" placeholder="请填写手机号" />
</el-form-item>
<el-form-item label="备注:" prop="msg">
<el-input
v-model="formData.msg"
type="textarea"
rows="4"
resize="none"
maxlength="150"
show-word-limit
placeholder="最多150个字"
/>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="submitForm"> 确定 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
// 表单
const formData = ref({
name: "",
wechat_id: "",
phone_number: "",
msg: ""
})
// 校验手机号
const validatePhone = (rule, value, callback) => {
const regPhone = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/
if (value == "") {
callback()
} else if (!regPhone.test(value)) {
callback(new Error("请输入正确的手机号"))
} else {
callback()
}
}
// 表单校验
const fromRules = {
name: [{ required: true, message: " ", trigger: "blur" }],
phone_number: [
{ required: true, message: " ", trigger: "blur" },
{ validator: validatePhone, trigger: "blur" }
],
wechat_id: [{ required: true, message: " ", trigger: "blur" }],
msg: [{ required: true, message: " ", trigger: "blur" }]
}
// 提交
const formRef = ref(null)
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
console.log("校验通过")
// contactsMainRefresh && contactsMainRefresh()
// 刷新父页面或 emit("getList") 记得注入
} else {
return false
}
})
}
// 是否显示
const dialogShow = ref(false)
// 打开
const openDialog = (data = {}) => {
console.log(data)
dialogShow.value = true
formRef.value && formRef.value.resetFields()
}
// 关闭
const closeDialog = () => {
dialogShow.value = false
}
defineExpose({ openDialog })
</script>
<style lang="scss" scoped>
</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
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