@gm-pc/react 1.27.5-beta.0 → 1.28.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gm-pc/react",
3
- "version": "1.27.5-beta.0",
3
+ "version": "1.28.0-alpha.0",
4
4
  "description": "观麦前端基础组件库",
5
5
  "author": "liyatang <liyatang@qq.com>",
6
6
  "homepage": "https://github.com/gmfe/gm-pc#readme",
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@gm-common/hooks": "^2.10.0",
26
26
  "@gm-common/tool": "^2.10.0",
27
- "@gm-pc/locales": "^1.27.5-beta.0",
27
+ "@gm-pc/locales": "^1.28.0-alpha.0",
28
28
  "big.js": "^6.0.1",
29
29
  "classnames": "^2.2.5",
30
30
  "lodash": "^4.17.19",
@@ -48,5 +48,5 @@
48
48
  "react-router-dom": "^5.2.0",
49
49
  "react-window": "^1.8.5"
50
50
  },
51
- "gitHead": "62f3bf16794e2d1778f97dc45c342746b9ea53f9"
51
+ "gitHead": "5349cb6157a22e000f906b3e19fa944ecd48d8d8"
52
52
  }
@@ -2,7 +2,7 @@ import { HTMLAttributes, ReactNode } from 'react'
2
2
 
3
3
  interface LoadingProps {
4
4
  size?: string
5
- type: any
5
+ type?: any
6
6
  }
7
7
 
8
8
  interface LoadingChunkProps extends HTMLAttributes<HTMLDivElement> {
@@ -20,13 +20,17 @@ import { getLocale } from '@gm-pc/locales'
20
20
  import { ListBase } from '../list'
21
21
  import { findDOMNode } from 'react-dom'
22
22
  import { ConfigConsumer, ConfigProvider, ConfigProviderProps } from '../config_provider'
23
-
23
+ import { Checkbox } from '../checkbox'
24
+ import { Switch } from '../switch'
24
25
  interface MoreSelectBaseState {
25
- searchValue: string | undefined
26
+ canClear?: boolean
27
+ searchValue: string
26
28
  loading: boolean
27
29
  /* keyboard 默认第一个位置 */
28
30
  willActiveIndex: number | null
29
- isOpen: boolean
31
+ isCheckedAll: boolean
32
+ isFilterDelete: boolean
33
+ displayCount: number
30
34
  }
31
35
 
32
36
  // @todo keydown item disabled
@@ -39,10 +43,12 @@ class MoreSelectBase<V extends string | number = string> extends Component<
39
43
  static renderListFilterPinYin = renderListFilterPinYin
40
44
 
41
45
  readonly state: MoreSelectBaseState = {
42
- searchValue: (undefined as unknown) as string,
46
+ searchValue: '',
43
47
  loading: false,
44
48
  willActiveIndex: this.props.isKeyboard ? 0 : null,
45
- isOpen: false,
49
+ isCheckedAll: false,
50
+ isFilterDelete: true,
51
+ displayCount: 0,
46
52
  }
47
53
 
48
54
  private _isUnmounted = false
@@ -50,6 +56,7 @@ class MoreSelectBase<V extends string | number = string> extends Component<
50
56
  private _selectionRef = createRef<HTMLDivElement>()
51
57
  private _popoverRef = createRef<Popover>()
52
58
  private _inputRef = createRef<HTMLInputElement>()
59
+ private _resizeObserver: ResizeObserver | null = null
53
60
 
54
61
  private _filterData: MoreSelectGroupDataItem<V>[] | undefined
55
62
 
@@ -64,8 +71,42 @@ class MoreSelectBase<V extends string | number = string> extends Component<
64
71
  }
65
72
  }
66
73
 
74
+ componentDidMount() {
75
+ const { maxTagCount, tagItemWidth = 80, omittedTagWidth = 45 } = this.props
76
+ if (maxTagCount === 'responsive' && this._selectionRef.current) {
77
+ // HACK: 首次计算
78
+ setTimeout(() => {
79
+ if (this._selectionRef.current) {
80
+ const { width } = this._selectionRef.current.getBoundingClientRect()
81
+ const availableWidth = width - omittedTagWidth
82
+ const newDisplayCount = Math.floor(availableWidth / tagItemWidth)
83
+ if (this.state.displayCount !== newDisplayCount) {
84
+ this.setState({ displayCount: newDisplayCount > 0 ? newDisplayCount : 0 })
85
+ }
86
+ }
87
+ }, 0)
88
+
89
+ this._resizeObserver = new ResizeObserver((entries) => {
90
+ for (const entry of entries) {
91
+ const { width } = entry.contentRect
92
+ // Estimate item width, let's say 80px.
93
+ const availableWidth = width - omittedTagWidth
94
+ const newDisplayCount = Math.floor(availableWidth / tagItemWidth)
95
+
96
+ if (this.state.displayCount !== newDisplayCount) {
97
+ this.setState({ displayCount: newDisplayCount })
98
+ }
99
+ }
100
+ })
101
+ this._resizeObserver.observe(this._selectionRef.current)
102
+ }
103
+ }
104
+
67
105
  componentWillUnmount() {
68
- this._isUnmounted = false
106
+ this._isUnmounted = true
107
+ if (this._resizeObserver) {
108
+ this._resizeObserver.disconnect()
109
+ }
69
110
  }
70
111
 
71
112
  public apiDoFocus = (): void => {
@@ -132,13 +173,15 @@ class MoreSelectBase<V extends string | number = string> extends Component<
132
173
  event: ChangeEvent<HTMLInputElement>,
133
174
  isInitSearch?: boolean
134
175
  ): void => {
176
+ const { onSearch } = this.props
135
177
  const searchValue = event.target.value
136
178
  this.setState({ searchValue })
137
- this._debounceDoSearch(searchValue)
138
- if (this.props.isSameAntd) {
139
- // eslint-disable-next-line no-unused-expressions
140
- this._popoverRef.current?.apiDoSetActive(true)
179
+ if (onSearch && !this._isUnmounted) {
180
+ this.setState({
181
+ loading: true,
182
+ })
141
183
  }
184
+ this._debounceDoSearch(searchValue)
142
185
  setTimeout(() => {
143
186
  // eslint-disable-next-line no-unused-expressions
144
187
  isInitSearch && this._inputRef.current?.select()
@@ -151,14 +194,12 @@ class MoreSelectBase<V extends string | number = string> extends Component<
151
194
  private _doSearch = (query: string): void => {
152
195
  const { onSearch, data = [] } = this.props
153
196
  if (!this._isUnmounted && onSearch) {
154
- const result = onSearch(query, data)
155
- if (!result) {
156
- return
157
- }
158
197
  this.setState({ loading: true })
159
-
198
+ const result = onSearch(query, data)
160
199
  Promise.resolve(result).finally(() => {
161
- this.setState({ loading: false })
200
+ setTimeout(() => {
201
+ this.setState({ loading: false })
202
+ }, 50)
162
203
  })
163
204
  }
164
205
  }
@@ -172,7 +213,13 @@ class MoreSelectBase<V extends string | number = string> extends Component<
172
213
  onSelect(willSelected)
173
214
  }
174
215
 
175
- private _handlePopupKeyDown = (event: KeyboardEvent): void => {
216
+ private _handleClearAll = (event: MouseEvent): void => {
217
+ event.stopPropagation()
218
+ const { onSelect = _.noop } = this.props
219
+ onSelect([])
220
+ }
221
+
222
+ private _handlePopupKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
176
223
  const { onKeyDown } = this.props
177
224
  let willActiveIndex = this.state.willActiveIndex as number
178
225
  if (!onKeyDown) {
@@ -207,9 +254,9 @@ class MoreSelectBase<V extends string | number = string> extends Component<
207
254
  }
208
255
 
209
256
  private _getFilterData = () => {
210
- const { data = [], renderListFilter, renderListFilterType } = this.props
257
+ const { data = [], renderListFilter, renderListFilterType, onSearch } = this.props
211
258
  const { searchValue } = this.state
212
- let filterData: MoreSelectGroupDataItem<V>[]
259
+ let filterData: MoreSelectGroupDataItem<V>[] = []
213
260
  if (renderListFilter) {
214
261
  filterData = renderListFilter(data, searchValue)
215
262
  } else if (renderListFilterType === 'pinyin') {
@@ -238,68 +285,246 @@ class MoreSelectBase<V extends string | number = string> extends Component<
238
285
  )
239
286
  }
240
287
 
241
- private _renderSearchInput = () => {
242
- const { searchPlaceholder } = this.props
243
- const { searchValue } = this.state
288
+ renderBottom = () => {
289
+ const {
290
+ selected = [],
291
+ isShowDeletedSwitch = true,
292
+ isShowCheckedAll = true,
293
+ } = this.props
294
+ const { isCheckedAll, isFilterDelete } = this.state
295
+ const flatFilterData = this._getFlatFilterData()
296
+
297
+ // 根据过滤状态决定是否过滤已删除商品
298
+ const availableData = isFilterDelete
299
+ ? flatFilterData.filter((item) => !item.deleted)
300
+ : flatFilterData
301
+
302
+ // 检查是否所有可用数据都被选中
303
+ const allSelected =
304
+ availableData.length > 0 &&
305
+ availableData.every((item) =>
306
+ selected.some((selectedItem) => selectedItem.value === item.value)
307
+ )
308
+
244
309
  return (
245
- <div className='gm-more-select-popup-input'>
246
- <Input
247
- ref={this._inputRef}
248
- autoFocus
249
- value={searchValue}
250
- onChange={this._handleChange}
251
- placeholder={searchPlaceholder}
252
- />
253
- </div>
310
+ <Flex
311
+ justifyBetween
312
+ className='tw-p-[8px] gm-more-select-default-bottom'
313
+ alignCenter
314
+ >
315
+ {isShowCheckedAll && (
316
+ <Checkbox
317
+ checked={allSelected}
318
+ onChange={(e) => {
319
+ const isChecked = e.target.checked
320
+ this.setState({
321
+ isCheckedAll: isChecked,
322
+ })
323
+
324
+ if (isChecked) {
325
+ // 全选当前过滤后的可用数据
326
+ const valuesToSelect = availableData.map((item) => item.value)
327
+ const prevSelectedValue = selected.map((item) => item.value)
328
+ // console.log(valuesToSelect, selected)
329
+ const newSelected = Array.from(
330
+ new Set([...prevSelectedValue, ...valuesToSelect])
331
+ )
332
+ this._handleSelect(newSelected)
333
+ } else {
334
+ // 取消全选 - 只反勾选availableData的数据
335
+ const { selected = [] } = this.props
336
+ const availableValues = availableData.map((item) => item.value)
337
+ const newSelected = selected.filter(
338
+ (item) => !availableValues.includes(item.value)
339
+ )
340
+ this._handleSelect(newSelected.map((item) => item.value))
341
+ }
342
+ }}
343
+ >
344
+ 全选({availableData.length})
345
+ </Checkbox>
346
+ )}
347
+ {isShowDeletedSwitch && (
348
+ <Flex alignCenter>
349
+ <Flex row>
350
+ <Switch
351
+ size='small'
352
+ style={{ width: 32 }}
353
+ checked={isFilterDelete}
354
+ onChange={(open) => {
355
+ this.setState({
356
+ isFilterDelete: open,
357
+ })
358
+ // if (isCheckedAll) {
359
+ // const newAvailableData = open
360
+ // ? flatFilterData.filter((item) => !item.deleted)
361
+ // : flatFilterData
362
+
363
+ // const valuesToSelect = newAvailableData.map((item) => item.value)
364
+ // this._handleSelect(valuesToSelect)
365
+ // }
366
+ }}
367
+ />
368
+ </Flex>
369
+ <span className='gm-margin-left-5'>过滤已删除数据</span>
370
+ </Flex>
371
+ )}
372
+ </Flex>
254
373
  )
255
374
  }
256
375
 
376
+ renderContent = () => {
377
+ const { loading, willActiveIndex, isFilterDelete } = this.state
378
+
379
+ if (loading) {
380
+ return (
381
+ <Flex alignCenter justifyCenter className='gm-bg gm-padding-5'>
382
+ <Loading size='20px' />
383
+ </Flex>
384
+ )
385
+ }
386
+
387
+ let filterData = this._getFilterData()
388
+
389
+ // 如果开启了过滤已删除商品功能,需要过滤掉已删除的商品
390
+ if (isFilterDelete) {
391
+ filterData = filterData
392
+ .map((group) => ({
393
+ ...group,
394
+ children: group.children.filter((item) => !item.deleted),
395
+ }))
396
+ .filter((group) => group.children.length > 0)
397
+ }
398
+
399
+ if (!loading && filterData.length === 0) {
400
+ return this._renderEmpty()
401
+ }
402
+
403
+ if (!loading && filterData.length > 0) {
404
+ const {
405
+ selected = [],
406
+ multiple,
407
+ isGroupList,
408
+ renderListItem,
409
+ listHeight,
410
+ } = this.props
411
+
412
+ const selectedValues = new Set(selected?.map((v) => v.value))
413
+
414
+ // 分离已勾选和未勾选的数据
415
+ const availableGroups: MoreSelectGroupDataItem<V>[] = []
416
+ // 已选中区域直接使用 selected 构建,不受筛选影响
417
+ const selectedGroups: MoreSelectGroupDataItem<V>[] = []
418
+
419
+ if (multiple) {
420
+ filterData.forEach((group) => {
421
+ const availableChildren = group.children.filter(
422
+ (item) => !selectedValues.has(item.value)
423
+ )
424
+
425
+ if (availableChildren.length > 0) {
426
+ availableGroups.push({
427
+ ...group,
428
+ children: availableChildren,
429
+ })
430
+ }
431
+ })
432
+
433
+ if (selected.length > 0) {
434
+ selectedGroups.push({
435
+ label: '',
436
+ children: selected,
437
+ })
438
+ }
439
+ }
440
+
441
+ return (
442
+ <div style={{ height: listHeight, overflow: 'auto' }}>
443
+ {selected.length > 0 && multiple && (
444
+ <>
445
+ <div className='gm-more-select-section-title gm-padding-5 gm-text-desc gm-text-12'>
446
+ 已选中
447
+ </div>
448
+ <ListBase
449
+ selected={selected.map((v) => v.value)}
450
+ data={selectedGroups}
451
+ multiple={multiple}
452
+ isGroupList={false}
453
+ className='gm-border-0'
454
+ renderItem={renderListItem}
455
+ onSelect={this._handleSelect}
456
+ isScrollTo={false}
457
+ />
458
+ </>
459
+ )}
460
+ <>
461
+ {multiple && (
462
+ <div className='gm-more-select-section-title gm-padding-5 gm-text-desc gm-text-12'>
463
+ 未选中
464
+ </div>
465
+ )}
466
+
467
+ <ListBase
468
+ selected={selected?.map((v) => v.value)}
469
+ data={multiple ? availableGroups : filterData}
470
+ multiple={multiple}
471
+ isGroupList={isGroupList}
472
+ className='gm-border-0'
473
+ renderItem={renderListItem}
474
+ onSelect={this._handleSelect}
475
+ isScrollTo
476
+ willActiveIndex={willActiveIndex!}
477
+ />
478
+ </>
479
+ </div>
480
+ )
481
+ }
482
+ }
483
+
257
484
  private _renderList = (config: ConfigProviderProps): ReactNode => {
258
485
  const {
259
- selected = [],
260
- multiple,
261
- isGroupList,
262
- renderListItem,
486
+ searchPlaceholder,
263
487
  listHeight,
264
488
  popupClassName,
265
489
  renderCustomizedBottom,
266
- isSameAntd = false,
490
+ isRenderDefaultBottom = false,
267
491
  } = this.props
268
- const { loading, willActiveIndex } = this.state
269
- const filterData = this._getFilterData()
492
+ const { loading, searchValue, willActiveIndex, isFilterDelete } = this.state
493
+ let filterData = this._getFilterData()
494
+
495
+ // 如果开启了过滤已删除商品功能,需要过滤掉已删除的商品
496
+ if (isFilterDelete) {
497
+ filterData = filterData
498
+ .map((group) => ({
499
+ ...group,
500
+ children: group.children.filter((item) => !item.deleted),
501
+ }))
502
+ .filter((group) => group.children.length > 0)
503
+ }
504
+
270
505
  return (
271
506
  <ConfigProvider {...config}>
272
507
  <div
273
508
  className={classNames('gm-more-select-popup', popupClassName)}
274
509
  onKeyDown={this._handlePopupKeyDown}
275
510
  >
276
- {isSameAntd ? null : this._renderSearchInput()}
277
- <div style={{ height: listHeight }}>
278
- {loading && (
279
- <Flex alignCenter justifyCenter className='gm-bg gm-padding-5'>
280
- <Loading size='20px' />
281
- </Flex>
282
- )}
283
- {!loading && !filterData.length && this._renderEmpty()}
284
- {!loading && !!filterData.length && (
285
- <ListBase
286
- selected={selected.map((v) => v.value)}
287
- data={filterData}
288
- multiple={multiple}
289
- isGroupList={isGroupList}
290
- className='gm-border-0'
291
- renderItem={renderListItem}
292
- onSelect={this._handleSelect}
293
- isScrollTo
294
- willActiveIndex={willActiveIndex!}
295
- style={{ height: listHeight }}
296
- />
297
- )}
511
+ <div className='gm-more-select-popup-input'>
512
+ <Input
513
+ ref={this._inputRef}
514
+ autoFocus
515
+ value={searchValue}
516
+ onChange={this._handleChange}
517
+ placeholder={searchPlaceholder}
518
+ />
298
519
  </div>
520
+ <div style={{ height: listHeight }}>{this.renderContent()}</div>
299
521
  {!loading &&
300
522
  !!filterData.length &&
301
- renderCustomizedBottom &&
302
- renderCustomizedBottom(this._popoverRef)}
523
+ (renderCustomizedBottom
524
+ ? renderCustomizedBottom(this._popoverRef, this.renderBottom)
525
+ : isRenderDefaultBottom
526
+ ? this.renderBottom()
527
+ : null)}
303
528
  </div>
304
529
  </ConfigProvider>
305
530
  )
@@ -313,16 +538,7 @@ class MoreSelectBase<V extends string | number = string> extends Component<
313
538
  }
314
539
 
315
540
  private _handlePopoverVisibleChange = (active: boolean) => {
316
- if (this.props.isSameAntd) {
317
- this.setState({
318
- isOpen: active,
319
- })
320
- if (!this.props.multiple && !active) {
321
- this.setState({ searchValue: undefined })
322
- }
323
- }
324
-
325
- if (active && this.props.searchOnActive && !this.props.isSameAntd) {
541
+ if (active && this.props.searchOnActive) {
326
542
  const searchValue = localStorage.getItem('_GM-PC_MORESELECT_SEARCHVALUE')
327
543
  if (searchValue) {
328
544
  this.setState({ searchValue })
@@ -348,10 +564,93 @@ class MoreSelectBase<V extends string | number = string> extends Component<
348
564
  style,
349
565
  popoverType,
350
566
  children,
351
- searchPlaceholder,
352
- isSameAntd = false,
567
+ maxTagCount,
568
+ maxTagPlaceholder,
353
569
  } = this.props
354
- const { searchValue } = this.state
570
+
571
+ // 处理 maxTagCount 逻辑
572
+ const renderSelectedItems = () => {
573
+ if (!multiple || !maxTagCount || selected.length === 0) {
574
+ return selected.map((item) => (
575
+ <Flex key={item.value as any} className='gm-more-select-selected-item'>
576
+ <Popover
577
+ disabled={!this.props.isKeyboard}
578
+ type='hover'
579
+ popup={<div className='gm-padding-10'>{item.text}</div>}
580
+ >
581
+ <Flex flex column>
582
+ {renderSelected!(item)}
583
+ </Flex>
584
+ </Popover>
585
+ {multiple ? (
586
+ <SVGRemove
587
+ className='gm-cursor gm-more-select-clear-btn'
588
+ onClick={disabled ? _.noop : this._handleClear.bind(this, item)}
589
+ />
590
+ ) : (
591
+ !disabledClose && ( // 是否不限时清除按钮,仅单选可用
592
+ <SVGCloseCircle
593
+ onClick={disabled ? _.noop : this._handleClear.bind(this, item)}
594
+ className='gm-cursor gm-more-select-clear-btn'
595
+ />
596
+ )
597
+ )}
598
+ </Flex>
599
+ ))
600
+ }
601
+
602
+ // 处理 maxTagCount 逻辑
603
+ const isResponsive = maxTagCount === 'responsive'
604
+ let displayCount: number
605
+
606
+ if (isResponsive) {
607
+ displayCount = this.state.displayCount
608
+ } else {
609
+ displayCount = maxTagCount as number
610
+ }
611
+
612
+ const itemsToShow = selected.slice(0, displayCount)
613
+ const omittedItems = selected.slice(displayCount)
614
+ const omittedCount = selected.length - displayCount
615
+
616
+ return (
617
+ <>
618
+ {itemsToShow.map((item) => (
619
+ <Flex key={item.value as any} className='gm-more-select-selected-item'>
620
+ <Popover
621
+ disabled={!this.props.isKeyboard}
622
+ type='hover'
623
+ popup={<div className='gm-padding-10'>{item.text}</div>}
624
+ >
625
+ <Flex flex column>
626
+ {renderSelected!(item)}
627
+ </Flex>
628
+ </Popover>
629
+ <SVGRemove
630
+ className='gm-cursor gm-more-select-clear-btn'
631
+ onClick={disabled ? _.noop : this._handleClear.bind(this, item)}
632
+ />
633
+ </Flex>
634
+ ))}
635
+ {omittedCount > 0 && (
636
+ <Flex
637
+ key='omitted'
638
+ className='gm-more-select-selected-item gm-more-select-omitted-item'
639
+ >
640
+ {maxTagPlaceholder ? (
641
+ maxTagPlaceholder(omittedItems, omittedCount)
642
+ ) : (
643
+ <span className='gm-more-select-omitted-count'>+{omittedCount}...</span>
644
+ )}
645
+ </Flex>
646
+ )}
647
+ <SVGRemove
648
+ className='gm-cursor gm-more-select-clear-btn'
649
+ onClick={disabled ? _.noop : this._handleClearAll}
650
+ />
651
+ </>
652
+ )
653
+ }
355
654
 
356
655
  return (
357
656
  <ConfigConsumer>
@@ -367,15 +666,13 @@ class MoreSelectBase<V extends string | number = string> extends Component<
367
666
  },
368
667
  className
369
668
  )}
370
- style={style}
669
+ style={style as any}
371
670
  >
372
671
  <Popover
373
672
  ref={this._popoverRef}
374
673
  type={popoverType}
375
674
  popup={() => this._renderList(config)}
376
- disabled={
377
- disabled || (!isSameAntd ? false : this.state.searchValue === undefined)
378
- }
675
+ disabled={disabled}
379
676
  isInPopup={isInPopup}
380
677
  onVisibleChange={this._handlePopoverVisibleChange}
381
678
  >
@@ -384,78 +681,13 @@ class MoreSelectBase<V extends string | number = string> extends Component<
384
681
  ref={this._selectionRef}
385
682
  tabIndex={0}
386
683
  wrap
387
- className={classNames({
388
- 'gm-more-select-selected': true,
389
- 'gm-more-select-selected-antd': isSameAntd,
390
- })}
684
+ className='gm-more-select-selected'
391
685
  >
392
686
  {selected.length !== 0 ? (
393
- selected.map((item) => (
394
- <Flex
395
- key={item.value as any}
396
- className='gm-more-select-selected-item'
397
- alignCenter
398
- >
399
- <Popover
400
- disabled={!this.props.isKeyboard}
401
- type='hover'
402
- popup={<div className='gm-padding-10'>{item.text}</div>}
403
- >
404
- <Flex flex column>
405
- {isSameAntd ? (
406
- <div className='gm-more-select-popup-input'>
407
- <Input
408
- ref={this._inputRef}
409
- value={
410
- !this.state.isOpen
411
- ? renderSelected!(item)
412
- : searchValue
413
- }
414
- onChange={this._handleChange}
415
- placeholder={searchPlaceholder}
416
- className='gm-more-select-popup-input-no-border'
417
- />
418
- </div>
419
- ) : (
420
- renderSelected!(item)
421
- )}
422
- </Flex>
423
- </Popover>
424
- {multiple ? (
425
- <SVGRemove
426
- className='gm-cursor gm-more-select-clear-btn'
427
- onClick={
428
- disabled ? _.noop : this._handleClear.bind(this, item)
429
- }
430
- />
431
- ) : (
432
- !disabledClose && ( // 是否不限时清除按钮,仅单选可用
433
- <SVGCloseCircle
434
- onClick={
435
- disabled ? _.noop : this._handleClear.bind(this, item)
436
- }
437
- className='gm-cursor gm-more-select-clear-btn'
438
- />
439
- )
440
- )}
441
- </Flex>
442
- ))
687
+ renderSelectedItems()
443
688
  ) : (
444
689
  // 加多个 &nbsp; 避免对齐问题,有文本才有对齐
445
- <>
446
- {isSameAntd ? (
447
- <Input
448
- ref={this._inputRef}
449
- value={searchValue}
450
- onChange={this._handleChange}
451
- placeholder={searchPlaceholder}
452
- className='gm-more-select-popup-input-no-border'
453
- />
454
- ) : (
455
- <div className='gm-text-placeholder'>{placeholder}&nbsp; </div>
456
- )}
457
- </>
458
- //
690
+ <div className='gm-text-placeholder'>{placeholder}&nbsp; </div>
459
691
  )}
460
692
  </Flex>
461
693
  )}
@@ -8,10 +8,16 @@ class MoreSelect<V = any> extends Component<MoreSelectProps<V>> {
8
8
  renderSelected: (item: MoreSelectDataItem<any>) => item.text,
9
9
  renderListItem: (item: MoreSelectDataItem<any>) => item.text,
10
10
  delay: 500,
11
- listHeight: '180px',
11
+ listHeight: '280px',
12
12
  renderListFilterType: 'default',
13
13
  popoverType: 'focus',
14
14
  onKeyDown: _.noop,
15
+ /** 是否展示全选以及过滤已删除商品 */
16
+ isRenderDefaultBottom: false,
17
+ /** 是否展示已删除商品 */
18
+ isShowDeletedSwitch: true,
19
+ /** 是否展示全选 */
20
+ isShowCheckedAll: true,
15
21
  }
16
22
 
17
23
  private _moreSelectBaseRef = createRef<MoreSelectBase>()
@@ -85,6 +91,8 @@ class MoreSelect<V = any> extends Component<MoreSelectProps<V>> {
85
91
  onSearch,
86
92
  onClick,
87
93
  renderListFilter,
94
+ maxTagCount,
95
+ maxTagPlaceholder,
88
96
  ...rest
89
97
  } = this.props
90
98
  let tempSelect = selected as MoreSelectDataItem<V>[]
@@ -129,6 +137,8 @@ class MoreSelect<V = any> extends Component<MoreSelectProps<V>> {
129
137
  isGroupList={isGroupList}
130
138
  onSearch={onSearch && this._handleSearch}
131
139
  renderListFilter={renderListFilter && this._renderListFilter}
140
+ maxTagCount={maxTagCount}
141
+ maxTagPlaceholder={maxTagPlaceholder}
132
142
  />
133
143
  )
134
144
  }
@@ -318,6 +318,98 @@ export const ComMoreSelectWithIsGroupListMultiple = () => (
318
318
  />
319
319
  )
320
320
 
321
+ export const ComMoreSelectWithMaxTagCount = () => {
322
+ // 创建一个包含多个选项的数据集
323
+ const manyOptions = [
324
+ { value: 1, text: '选项1' },
325
+ { value: 2, text: '选项2' },
326
+ { value: 3, text: '选项3' },
327
+ { value: 4, text: '选项4' },
328
+ { value: 5, text: '选项5' },
329
+ { value: 6, text: '选项6' },
330
+ { value: 7, text: '选项7' },
331
+ { value: 8, text: '选项8' },
332
+ ]
333
+
334
+ // 预选多个选项
335
+ const preSelected = manyOptions.slice(0, 6)
336
+
337
+ return (
338
+ <div style={{ width: '300px' }}>
339
+ <h3>maxTagCount 示例</h3>
340
+
341
+ <div style={{ marginBottom: '20px' }}>
342
+ <h4>不使用 maxTagCount(显示所有选项)</h4>
343
+ <MoreSelect<number>
344
+ multiple
345
+ data={manyOptions}
346
+ selected={preSelected}
347
+ onSelect={(selected) => {
348
+ console.log('不限制显示数量:', selected)
349
+ }}
350
+ />
351
+ </div>
352
+
353
+ <div style={{ marginBottom: '20px' }}>
354
+ <h4>maxTagCount={2}(最多显示2个选项)</h4>
355
+ <MoreSelect<number>
356
+ multiple
357
+ maxTagCount={2}
358
+ data={manyOptions}
359
+ selected={preSelected}
360
+ onSelect={(selected) => {
361
+ console.log('最多显示2个:', selected)
362
+ }}
363
+ />
364
+ </div>
365
+
366
+ <div style={{ marginBottom: '20px' }}>
367
+ <h4>maxTagCount={3}(最多显示3个选项)</h4>
368
+ <MoreSelect<number>
369
+ multiple
370
+ maxTagCount={3}
371
+ data={manyOptions}
372
+ selected={preSelected}
373
+ onSelect={(selected) => {
374
+ console.log('最多显示3个:', selected)
375
+ }}
376
+ />
377
+ </div>
378
+
379
+ <div style={{ marginBottom: '20px' }}>
380
+ <h4>maxTagCount=responsive(响应式模式)</h4>
381
+ <MoreSelect<number>
382
+ multiple
383
+ maxTagCount='responsive'
384
+ data={manyOptions}
385
+ selected={preSelected}
386
+ onSelect={(selected) => {
387
+ console.log('响应式模式:', selected)
388
+ }}
389
+ />
390
+ </div>
391
+
392
+ <div style={{ marginBottom: '20px' }}>
393
+ <h4>自定义 maxTagPlaceholder</h4>
394
+ <MoreSelect<number>
395
+ multiple
396
+ maxTagCount={2}
397
+ maxTagPlaceholder={(omittedValues, omittedCount) => (
398
+ <span style={{ color: '#1890ff', fontWeight: 'bold' }}>
399
+ 还有{omittedCount}项未显示
400
+ </span>
401
+ )}
402
+ data={manyOptions}
403
+ selected={preSelected}
404
+ onSelect={(selected) => {
405
+ console.log('自定义占位符:', selected)
406
+ }}
407
+ />
408
+ </div>
409
+ </div>
410
+ )
411
+ }
412
+
321
413
  export default {
322
414
  title: '表单/MoreSelect',
323
415
  }
@@ -14,15 +14,10 @@
14
14
  }
15
15
  }
16
16
 
17
- .gm-more-select-selected-antd {
18
- padding: 0 12px;
19
- }
20
-
21
17
  .gm-more-select-clear-btn {
22
18
  position: absolute;
23
- right: 4px;
24
- top: 50%;
25
- transform: translateY(-50%);
19
+ right: 5px;
20
+ top: 7px;
26
21
  cursor: pointer;
27
22
  color: var(--gm-color-desc);
28
23
  }
@@ -76,9 +71,6 @@
76
71
  }
77
72
  }
78
73
 
79
- .gm-more-select-popup-input-no-border {
80
- border: none !important;
81
- border-radius: 0 !important;
82
- padding: 0 !important;
83
- box-shadow: none !important;
74
+ .gm-more-select-default-bottom {
75
+ border-top: 1px solid var(--gm-color-border);
84
76
  }
@@ -1,11 +1,13 @@
1
- import { Popover } from '@gm-pc/react'
2
1
  import { CSSProperties, ReactNode, KeyboardEvent } from 'react'
2
+ import { Popover } from '../popover'
3
3
 
4
4
  /** 普通的数据格式 */
5
5
  interface MoreSelectDataItem<V extends string | number = string> {
6
6
  value: V
7
7
  text: string
8
8
  disabled?: boolean
9
+ /** 是否已删除 */
10
+ deleted?: boolean
9
11
  [key: string]: any
10
12
  }
11
13
 
@@ -36,10 +38,13 @@ interface MoreSelectCommonProps<V extends string | number = string> {
36
38
  renderListItem?(value: MoreSelectDataItem<V>, index: number): ReactNode
37
39
 
38
40
  /** 自定义popup底部渲染 */
39
- renderCustomizedBottom?(ref: React.RefObject<Popover>): ReactNode
41
+ renderCustomizedBottom?(
42
+ ref: React.RefObject<Popover>,
43
+ defaultBottom: () => ReactNode
44
+ ): ReactNode
40
45
 
41
46
  /**
42
- * 自定义“空状态”渲染
47
+ * 自定义"空状态"渲染
43
48
  *
44
49
  * 若函数返回 undefined 则使用默认的空状态
45
50
  */
@@ -60,6 +65,25 @@ interface MoreSelectCommonProps<V extends string | number = string> {
60
65
  /** 目前为了 keyboard */
61
66
  isKeyboard?: boolean
62
67
  onKeyDown?(event: KeyboardEvent): void
68
+
69
+ /** 最多显示的选中项数量,超出部分会折叠 */
70
+ maxTagCount?: number | 'responsive'
71
+ /** 自定义超出 maxTagCount 时显示的内容 */
72
+ maxTagPlaceholder?: (
73
+ omittedValues: MoreSelectDataItem<V>[],
74
+ omittedCount: number
75
+ ) => ReactNode
76
+
77
+ /** 是否展示全选以及过滤已删除商品 */
78
+ isRenderDefaultBottom?: boolean
79
+ /** 是否展示已删除商品 */
80
+ isShowDeletedSwitch?: boolean
81
+ /** 是否展示全选 */
82
+ isShowCheckedAll?: boolean
83
+ /** 当设置 maxTagCount 的时候的宽度, 根据这个去计算显示内容 */
84
+ tagItemWidth?: number
85
+ /** +N 显示的宽度 */
86
+ omittedTagWidth?: number
63
87
  }
64
88
 
65
89
  interface MoreSelectBaseProps<V extends string | number = string>
@@ -69,7 +93,7 @@ interface MoreSelectBaseProps<V extends string | number = string>
69
93
  onSelect(selected: MoreSelectDataItem<V>[]): void
70
94
 
71
95
  /** 搜索回调 */
72
- onSearch?(searchWord: string, data: MoreSelectGroupDataItem<V>[]): Promise<void> | void
96
+ onSearch?(searchWord: string, data: MoreSelectGroupDataItem<V>[]): Promise<any> | any
73
97
  /** 点击回调 */
74
98
  onClick?(selected: MoreSelectSelected<V>[]): void
75
99
 
@@ -80,8 +104,12 @@ interface MoreSelectBaseProps<V extends string | number = string>
80
104
  ): MoreSelectGroupDataItem<V>[]
81
105
  /** 是否在active的时候搜索,订单业务相关,searchValue放在localstorage */
82
106
  searchOnActive?: boolean
83
- /** 是否用select */
84
- isSameAntd?: boolean
107
+ /** 是否展示全选以及过滤已删除商品 */
108
+ isRenderDefaultBottom?: boolean
109
+ /** 是否展示已删除商品 */
110
+ isShowDeletedSwitch?: boolean
111
+ /** 是否展示全选 */
112
+ isShowCheckedAll?: boolean
85
113
  }
86
114
 
87
115
  type MoreSelectData<V extends string | number = string> =
@@ -100,7 +128,7 @@ interface MoreSelectProps<V extends string | number = string>
100
128
  onSelect?(selected?: MoreSelectSelected<V>): void
101
129
  onChange?(value: V | V[]): void
102
130
  /** 搜索回调 */
103
- onSearch?(searchWord: string, data: MoreSelectData<V>): Promise<void> | void
131
+ onSearch?(searchWord: string, data: MoreSelectData<V>): Promise<void | any> | any
104
132
  /** 点击回调 */
105
133
  onClick?(selected: MoreSelectSelected<V>[]): void
106
134
 
@@ -6,13 +6,17 @@ import _ from 'lodash'
6
6
  import { getLocale } from '@gm-pc/locales'
7
7
 
8
8
  function getLimitData(limit: number, pageSizeOptions?: string[]) {
9
- const limitData = [
10
- { value: limit, text: limit + '' },
11
- { value: 10, text: '10' },
12
- { value: 20, text: '20' },
13
- { value: 50, text: '50' },
14
- ...(pageSizeOptions?.map((v) => ({ value: Number(v), text: v })) || []),
15
- ]
9
+ let limitData = []
10
+ if (pageSizeOptions) {
11
+ limitData = pageSizeOptions.map((v) => ({ value: Number(v), text: v }))
12
+ } else {
13
+ limitData = [
14
+ { value: limit, text: limit + '' },
15
+ { value: 10, text: '10' },
16
+ { value: 20, text: '20' },
17
+ { value: 50, text: '50' },
18
+ ]
19
+ }
16
20
 
17
21
  return _.orderBy(
18
22
  _.uniqBy(limitData, (v) => v.value),
@@ -6,6 +6,10 @@
6
6
  border-radius: calc(var(--gm-switch-size-height) / 2);
7
7
  background-color: var(--gm-color-bg-switch);
8
8
 
9
+ &.gm-switch-small {
10
+ --gm-switch-size-height: 16px;
11
+ }
12
+
9
13
  &::before {
10
14
  position: absolute;
11
15
  content: '';
@@ -11,6 +11,7 @@ interface SwitchProps {
11
11
  onChange?(checked: boolean): void
12
12
  className?: string
13
13
  style?: CSSProperties
14
+ size?: 'small' | 'middle'
14
15
  }
15
16
 
16
17
  interface SwitchState {
@@ -26,10 +27,11 @@ class Switch extends Component<SwitchProps, SwitchState> {
26
27
  on: '',
27
28
  off: '',
28
29
  onChange: _.noop,
30
+ size: 'middle',
29
31
  }
30
32
 
31
33
  readonly state: SwitchState = {
32
- checked: this.props.checked,
34
+ checked: !!this.props.checked,
33
35
  labelWidth: null,
34
36
  isReady: false,
35
37
  }
@@ -53,7 +55,7 @@ class Switch extends Component<SwitchProps, SwitchState> {
53
55
  UNSAFE_componentWillReceiveProps(nextProps: Readonly<SwitchProps>) {
54
56
  if ('checked' in nextProps) {
55
57
  this.setState({
56
- checked: nextProps.checked,
58
+ checked: !!nextProps.checked,
57
59
  })
58
60
  }
59
61
  }
@@ -82,6 +84,7 @@ class Switch extends Component<SwitchProps, SwitchState> {
82
84
  disabled,
83
85
  on,
84
86
  off,
87
+ size,
85
88
  ...rest
86
89
  } = this.props
87
90
 
@@ -101,8 +104,9 @@ class Switch extends Component<SwitchProps, SwitchState> {
101
104
  ref={this._inputOffRef}
102
105
  className={classNames('gm-switch gm-switch-' + type, className, {
103
106
  disabled,
107
+ [`gm-switch-${size}`]: size,
104
108
  })}
105
- style={style}
109
+ style={style as any}
106
110
  data-attr={this.state.labelWidth}
107
111
  disabled={disabled}
108
112
  type='checkbox'