1
1
<template >
2
2
<Dialog v-model =" dialogVisible" :appendToBody =" true" title =" 选择商品" width =" 70%" >
3
3
<ContentWrap >
4
- <el-row :gutter =" 20" class =" mb-10px" >
5
- <el-col :span =" 6" >
4
+ <el-form
5
+ ref =" queryFormRef"
6
+ :inline =" true"
7
+ :model =" queryParams"
8
+ class =" -mb-15px"
9
+ label-width =" 68px"
10
+ >
11
+ <el-form-item label =" 商品名称" prop =" name" >
6
12
<el-input
7
13
v-model =" queryParams.name"
8
14
class =" !w-240px"
9
15
clearable
10
16
placeholder =" 请输入商品名称"
11
17
@keyup.enter =" handleQuery"
12
18
/>
13
- </el-col >
14
- <el-col :span = " 6 " >
19
+ </el-form-item >
20
+ <el-form-item label = " 商品分类 " prop = " categoryId " >
15
21
<el-tree-select
16
22
v-model =" queryParams.categoryId"
17
23
:data =" categoryTreeList"
18
24
:props =" defaultProps"
19
25
check-strictly
20
- class =" w-1/1 "
26
+ class =" !w-240px "
21
27
node-key =" id"
22
28
placeholder =" 请选择商品分类"
23
29
/>
24
- </el-col >
25
- <el-col :span = " 6 " >
30
+ </el-form-item >
31
+ <el-form-item label = " 创建时间 " prop = " createTime " >
26
32
<el-date-picker
27
33
v-model =" queryParams.createTime"
28
34
:default-time =" [new Date('1 00:00:00'), new Date('1 23:59:59')]"
32
38
type =" daterange"
33
39
value-format =" YYYY-MM-DD HH:mm:ss"
34
40
/>
35
- </el-col >
36
- <el-col :span = " 6 " >
41
+ </el-form-item >
42
+ <el-form-item >
37
43
<el-button @click =" handleQuery" >
38
44
<Icon class =" mr-5px" icon =" ep:search" />
39
45
搜索
42
48
<Icon class =" mr-5px" icon =" ep:refresh" />
43
49
重置
44
50
</el-button >
45
- </el-col >
46
- </el-row >
51
+ </el-form-item >
52
+ </el-form >
47
53
<el-table v-loading =" loading" :data =" list" show-overflow-tooltip >
48
- <!-- 多选模式 -->
49
- <el-table-column key = " 2 " type = " selection " width =" 55" v-if =" multiple" >
54
+ <!-- 1. 多选模式(不能使用type="selection",Element会忽略Header插槽) -->
55
+ <el-table-column width =" 55" v-if =" multiple" >
50
56
<template #header >
51
57
<el-checkbox
52
- :value =" allChecked && checkedPageNos.indexOf(queryParams.pageNo) > -1"
58
+ v-model =" isCheckAll"
59
+ :indeterminate =" isIndeterminate"
53
60
@change =" handleCheckAll"
54
61
/>
55
62
</template >
56
63
<template #default =" { row } " >
57
64
<el-checkbox
58
- :value = " checkedSpuIds.indexOf( row.id) > -1 "
59
- @change =" (checked: boolean) => handleCheckOne(checked, row)"
65
+ v-model = " checkedStatus[ row.id] "
66
+ @change =" (checked: boolean) => handleCheckOne(checked, row, true )"
60
67
/>
61
68
</template >
62
69
</el-table-column >
63
- <!-- 单选模式 -->
70
+ <!-- 2. 单选模式 -->
64
71
<el-table-column label =" #" width =" 55" v-else >
65
72
<template #default =" { row } " >
66
- <el-radio :label =" row.id" v-model =" selectedSpuId" @change =" handleSingleSelected(row)"
67
- >  ; </el-radio
68
- >
73
+ <el-radio :label =" row.id" v-model =" selectedSpuId" @change =" handleSingleSelected(row)" >
74
+ <!-- 空格不能省略,是为了让单选框不显示label,如果不指定label不会有选中的效果 -->
75
+   ;
76
+ </el-radio >
69
77
</template >
70
78
</el-table-column >
71
79
<el-table-column key =" id" align =" center" label =" 商品编号" prop =" id" min-width =" 60" />
102
110
</template >
103
111
104
112
<script lang="ts" setup>
105
- import { ElTable } from ' element-plus'
106
113
import { defaultProps , handleTree } from ' @/utils/tree'
107
114
108
115
import * as ProductCategoryApi from ' @/api/mall/product/category'
109
116
import * as ProductSpuApi from ' @/api/mall/product/spu'
110
117
import { propTypes } from ' @/utils/propTypes'
118
+ import { CHANGE_EVENT } from ' element-plus'
111
119
112
120
type Spu = Required <ProductSpuApi .Spu >
113
121
122
+ /**
123
+ * 商品表格选择对话框
124
+ * 1. 单选模式:
125
+ * 1.1 点击表格左侧的单选框时,结束选择,并关闭对话框
126
+ * 1.2 再次打开时,保持选中状态
127
+ * 2. 多选模式:
128
+ * 2.1 点击表格左侧的多选框时,记录选中的商品
129
+ * 2.2 切换分页时,保持商品的选中的状态
130
+ * 2.3 点击右下角的确定按钮时,结束选择,关闭对话框
131
+ * 2.4 再次打开时,保持选中状态
132
+ */
114
133
defineOptions ({ name: ' SpuTableSelect' })
115
134
116
- const props = defineProps ({
117
- // 多选
135
+ defineProps ({
136
+ // 多选模式
118
137
multiple: propTypes .bool .def (false )
119
138
})
120
139
121
- const total = ref (0 ) // 列表的总页数
122
- const list = ref <Spu []>([]) // 列表的数据
123
- const loading = ref (false ) // 列表的加载中
124
- const dialogVisible = ref (false ) // 弹窗的是否展示
140
+ // 列表的总页数
141
+ const total = ref (0 )
142
+ // 列表的数据
143
+ const list = ref <Spu []>([])
144
+ // 列表的加载中
145
+ const loading = ref (false )
146
+ // 弹窗的是否展示
147
+ const dialogVisible = ref (false )
148
+ // 查询参数
125
149
const queryParams = ref ({
126
150
pageNo: 1 ,
127
151
pageSize: 10 ,
128
- tabType: 0 , // 默认获取上架的商品
152
+ // 默认获取上架的商品
153
+ tabType: 0 ,
129
154
name: ' ' ,
130
155
categoryId: null ,
131
156
createTime: []
132
- }) // 查询参数
133
-
134
- const selectedSpuId = ref () // 选中的商品 spuId
157
+ })
135
158
136
159
/** 打开弹窗 */
137
- const open = (spus ? : Spu []) => {
138
- if (spus && spus .length > 0 ) {
139
- // todo check-box不显示选中?
140
- checkedSpus .value = [... spus ]
141
- checkedSpuIds .value = spus .map ((spu ) => spu .id )
142
- } else {
143
- checkedSpus .value = []
144
- checkedSpuIds .value = []
160
+ const open = (spuList ? : Spu []) => {
161
+ // 重置
162
+ checkedSpus .value = []
163
+ checkedStatus .value = {}
164
+ isCheckAll .value = false
165
+ isIndeterminate .value = false
166
+
167
+ // 处理已选中
168
+ if (spuList && spuList .length > 0 ) {
169
+ checkedSpus .value = [... spuList ]
170
+ checkedStatus .value = Object .fromEntries (spuList .map (spu => [spu .id , true ]))
145
171
}
146
- allChecked .value = false
147
- checkedPageNos .value = []
148
172
149
173
dialogVisible .value = true
150
174
resetQuery ()
151
175
}
152
- defineExpose ({ open }) // 提供 open 方法,用于打开弹窗
176
+ // 提供 open 方法,用于打开弹窗
177
+ defineExpose ({ open })
153
178
154
179
/** 查询列表 */
155
180
const getList = async () => {
@@ -158,6 +183,10 @@ const getList = async () => {
158
183
const data = await ProductSpuApi .getSpuPage (queryParams .value )
159
184
list .value = data .list
160
185
total .value = data .total
186
+ // checkbox绑定undefined会有问题,需要给一个bool值
187
+ list .value .forEach ( spu => checkedStatus .value [spu .id ] = checkedStatus .value [spu .id ] || false )
188
+ // 计算全选框状态
189
+ calculateIsCheckAll ()
161
190
} finally {
162
191
loading .value = false
163
192
}
@@ -174,73 +203,98 @@ const resetQuery = () => {
174
203
queryParams .value = {
175
204
pageNo: 1 ,
176
205
pageSize: 10 ,
177
- tabType: 0 , // 默认获取上架的商品
206
+ // 默认获取上架的商品
207
+ tabType: 0 ,
178
208
name: ' ' ,
179
209
categoryId: null ,
180
210
createTime: []
181
211
}
182
212
getList ()
183
213
}
184
214
185
- const allChecked = ref (false ) // 是否全选
186
- const checkedPageNos = ref <number []>([]) // 选中的页码
187
- const checkedSpuIds = ref <number []>([]) // 选中的商品ID
188
- const checkedSpus = ref <Spu []>([]) // 选中的商品
215
+ // 是否全选
216
+ const isCheckAll = ref (false )
217
+ // 全选框是否处于中间状态:不是全部选中 && 任意一个选中
218
+ const isIndeterminate = ref (false )
219
+ // 选中的商品
220
+ const checkedSpus = ref <Spu []>([])
221
+ // 选中状态:key为商品ID,value为是否选中
222
+ const checkedStatus = ref <Record <string , boolean >>({})
189
223
224
+ // 选中的商品 spuId
225
+ const selectedSpuId = ref ()
190
226
/** 单选中时触发 */
191
- const handleSingleSelected = (row : Spu ) => {
192
- emits (' change ' , row )
227
+ const handleSingleSelected = (spu : Spu ) => {
228
+ emits (CHANGE_EVENT , spu )
193
229
// 关闭弹窗
194
230
dialogVisible .value = false
195
231
// 记住上次选择的ID
196
- selectedSpuId .value = row .id
232
+ selectedSpuId .value = spu .id
197
233
}
198
234
199
235
/** 多选完成 */
200
236
const handleEmitChange = () => {
201
237
// 关闭弹窗
202
238
dialogVisible .value = false
203
- emits (' change ' , [... checkedSpus .value ])
239
+ emits (CHANGE_EVENT , [... checkedSpus .value ])
204
240
}
205
241
206
242
/** 确认选择时的触发事件 */
207
243
const emits = defineEmits <{
208
- ( e : ' change ' , spu : Spu | Spu [] | any ) : void
244
+ change : [ spu : Spu | Spu [] | any ]
209
245
}>()
210
246
211
- /** 全选 */
247
+ /** 全选/全不选 */
212
248
const handleCheckAll = (checked : boolean ) => {
213
- debugger
214
- console .log (' checkAll' , checked )
215
- allChecked .value = checked
216
- const index = checkedPageNos .value .indexOf (queryParams .value .pageNo )
217
- checkedPageNos .value .push (queryParams .value .pageNo )
218
- if (index > - 1 ) {
219
- checkedPageNos .value .splice (index , 1 )
220
- }
249
+ isCheckAll .value = checked
250
+ isIndeterminate .value = false
221
251
222
- list .value .forEach ((item ) => handleCheckOne (checked , item ))
252
+ list .value .forEach ((spu ) => handleCheckOne (checked , spu , false ))
223
253
}
224
254
225
- /** 选中一行 */
226
- const handleCheckOne = (checked : boolean , spu : Spu ) => {
255
+ /**
256
+ * 选中一行
257
+ * @param checked 是否选中
258
+ * @param spu 商品
259
+ * @param isCalcCheckAll 是否计算全选
260
+ */
261
+ const handleCheckOne = (checked : boolean , spu : Spu , isCalcCheckAll : boolean ) => {
227
262
if (checked ) {
228
- const index = checkedSpuIds .value .indexOf (spu .id )
229
- if (index === - 1 ) {
230
- checkedSpuIds .value .push (spu .id )
231
- checkedSpus .value .push (spu )
232
- }
263
+ checkedSpus .value .push (spu )
264
+ checkedStatus .value [spu .id ] = true
233
265
} else {
234
- const index = checkedSpuIds . value . indexOf (spu . id )
266
+ const index = findCheckedIndex (spu )
235
267
if (index > - 1 ) {
236
- checkedSpuIds .value .splice (index , 1 )
237
268
checkedSpus .value .splice (index , 1 )
269
+ checkedStatus .value [spu .id ] = false
270
+ isCheckAll .value = false
238
271
}
239
272
}
273
+
274
+ // 计算全选框状态
275
+ if (isCalcCheckAll ){
276
+ calculateIsCheckAll ()
277
+ }
278
+ }
279
+
280
+ // 查找商品在已选中商品列表中的索引
281
+ const findCheckedIndex = (spu : Spu ) => checkedSpus .value .findIndex (item => item .id === spu .id )
282
+
283
+ // 计算全选框状态
284
+ const calculateIsCheckAll = () => {
285
+ isCheckAll .value = list .value .every (spu => {
286
+ let valueElement = checkedStatus .value [spu .id ];
287
+ debugger
288
+ return valueElement ;
289
+ });
290
+ // 计算中间状态:不是全部选中 && 任意一个选中
291
+ isIndeterminate .value = ! isCheckAll .value && list .value .some (spu => checkedStatus .value [spu .id ])
240
292
}
241
293
242
- const categoryList = ref () // 分类列表
243
- const categoryTreeList = ref () // 分类树
294
+ // 分类列表
295
+ const categoryList = ref ()
296
+ // 分类树
297
+ const categoryTreeList = ref ()
244
298
/** 初始化 **/
245
299
onMounted (async () => {
246
300
await getList ()
0 commit comments