form-picker.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // components/form-picker/form-picker.ts
  2. interface Option {
  3. label: string;
  4. value: any;
  5. checked?: boolean;
  6. disabled?: boolean;
  7. mutex?: boolean;
  8. };
  9. const filtration = (selected: string[] | Set<string>, options: Option[], option?: Option): string[] => {
  10. const _selected = new Set(selected);
  11. if (option) {
  12. if (_selected.has(option.value)) {
  13. _selected.delete(option.value);
  14. } else {
  15. if (option.mutex) {
  16. _selected.clear();
  17. } else {
  18. for (const option of options) {
  19. if (option.mutex) _selected.delete(option.value);
  20. }
  21. }
  22. _selected.add(option.value);
  23. }
  24. }
  25. return [..._selected];
  26. }
  27. const reset = (values: Option[] | Option | string, options: Option[]) => {
  28. const selected = new Set<string>();
  29. const _values = Array.isArray(values) ? values : values ? [values] : [];
  30. for (const item of _values) {
  31. const value = typeof item === 'object' ? item?.value : item;
  32. if (value && options.find(item => item.value === value)) selected.add(value);
  33. }
  34. for (const option of options) {
  35. if (option.checked) selected.add(option.value);
  36. }
  37. return filtration(selected, options);
  38. }
  39. Component({
  40. options: {
  41. multipleSlots: true,
  42. },
  43. lifetimes: {
  44. attached() {
  45. }
  46. },
  47. /**
  48. * 组件的属性列表
  49. */
  50. properties: {
  51. visible: { type: Boolean, value: false },
  52. title: { type: String, value: '' },
  53. value: { type: Array, value: [] },
  54. options: { type: Array, value: [] },
  55. optionsColumns: { type: Number, value: 1 },
  56. itemHeight: { type: Number, value: 64 },
  57. multiple: { type: Boolean, value: true },
  58. closeOnOverlayClick: { type: Boolean, value: true },
  59. },
  60. /**
  61. * 组件的初始数据
  62. */
  63. data: {
  64. containerHeight: 350,
  65. gap: 8,
  66. selected: [] as any[],
  67. replenish: {} as AnyObject,
  68. showReplenish: false,
  69. replenishValue: '',
  70. offset: 0,
  71. },
  72. observers: {
  73. 'options,optionsColumns,itemHeight'(options, columns, height) {
  74. const rows = Math.ceil(options.length / columns);
  75. this.setData({ containerHeight: Math.min(rows * height + (rows - 1) * this.data.gap, 350) })
  76. },
  77. 'value, options'(values: Option[] | Option | string, options: Option[]) {
  78. this.setData({ selected: reset(values, options) });
  79. }
  80. },
  81. /**
  82. * 组件的方法列表
  83. */
  84. methods: {
  85. handle(event: WechatMiniprogram.TouchEvent) {
  86. const index = event.currentTarget.dataset.index;
  87. const option = this.data.options[index];
  88. if (option.disabled) return;
  89. const handle = () => {
  90. if (this.data.multiple) {
  91. this.setData({ selected: filtration(this.data.selected, this.data.options, option) });
  92. } else {
  93. this.setData({ selected: this.data.selected.includes(option.value) ? [] : [option.value] });
  94. }
  95. }
  96. if (option.label === '其他') {
  97. if (this.data.selected.includes(option.value)) {
  98. handle();
  99. this.setData({ [`replenish.${option.value}`]: '' })
  100. } else {
  101. this.setData({ showReplenish: true, replenishValue: '' });
  102. this.onSubConfirm = () => {
  103. if (this.data.replenishValue) {
  104. handle();
  105. this.setData({ [`replenish.${option.value}`]: this.data.replenishValue })
  106. }
  107. this.onSubCancel();
  108. }
  109. }
  110. } else {
  111. handle()
  112. }
  113. },
  114. onConfirm() {
  115. const get = (option: Option) => {
  116. if (!option) return null;
  117. const replenish = this.data.replenish[option.value];
  118. return {
  119. label: replenish ? replenish : option.label,
  120. value: replenish ? `${option.value}:${replenish}` : option.value
  121. }
  122. };
  123. this.setData({ visible: false })
  124. this.triggerEvent('confirm', {
  125. selected: this.data.selected,
  126. options: this.data.selected
  127. .map(value => get(this.data.options.find(item => item.value === value)))
  128. .filter(Boolean),
  129. })
  130. this.triggerEvent('close', { trigger: 'confirm-btn' });
  131. },
  132. onCancel() {
  133. this.setData({ visible: false });
  134. this.triggerEvent('cancel');
  135. this.triggerEvent('close', { trigger: 'cancel-btn' });
  136. },
  137. onSubConfirm() { },
  138. onSubCancel() {
  139. this.setData({ showReplenish: false, offset: 0 });
  140. },
  141. onVisibleChange(event: any) {
  142. if (!event.detail.visible) {
  143. this.triggerEvent('close', { trigger: event.detail.trigger })
  144. setTimeout(() => {
  145. this.setData({
  146. selected: reset(this.data.value, this.data.options),
  147. offset: 0
  148. });
  149. }, 100)
  150. };
  151. },
  152. onBlur() {
  153. this.setData({ offset: 0 })
  154. },
  155. onKeyboardheightchange(event: any) {
  156. const _height = event?.detail?.height;
  157. if (_height !== this.data.offset) this.setData({ offset: _height });
  158. }
  159. }
  160. })