@ecolovo/uniapp-components 1.0.9 → 2.0.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.
Files changed (117) hide show
  1. package/README.md +83 -51
  2. package/components/aspect/aspect.vue +24 -15
  3. package/components/badge/badge.scss +20 -0
  4. package/components/badge/badge.vue +49 -40
  5. package/components/button/button.scss +15 -0
  6. package/components/button/button.vue +125 -181
  7. package/components/checkbox/checkbox.scss +10 -0
  8. package/components/checkbox/checkbox.vue +118 -146
  9. package/components/checkbox-group/checkbox-group.vue +79 -81
  10. package/components/checkbox-group2/checkbox-group2.vue +111 -107
  11. package/components/checkbox-group2/checkbox2.vue +104 -0
  12. package/components/data-list/data-list.scss +9 -0
  13. package/components/data-list/data-list.vue +70 -79
  14. package/components/date-picker/date-picker-view.vue +125 -124
  15. package/components/date-picker/date-picker.vue +145 -146
  16. package/components/dialog/dialog-content.vue +39 -47
  17. package/components/dialog/dialog.scss +11 -0
  18. package/components/dialog/dialog.vue +125 -138
  19. package/components/dropdown/dropdown.scss +11 -0
  20. package/components/dropdown/dropdown.vue +55 -97
  21. package/components/empty/empty.scss +14 -0
  22. package/components/empty/empty.vue +42 -41
  23. package/components/flex/flex.vue +45 -54
  24. package/components/form/form.vue +29 -24
  25. package/components/form-item/form-item.scss +11 -0
  26. package/components/form-item/form-item.vue +43 -40
  27. package/components/icon/icon.vue +70 -85
  28. package/components/image/image.vue +35 -38
  29. package/components/input/input.vue +65 -79
  30. package/components/input/password-input.vue +34 -42
  31. package/components/list-page/list-page.vue +73 -99
  32. package/components/load-more/load-more.scss +12 -0
  33. package/components/load-more/load-more.vue +29 -29
  34. package/components/loading/animations/loading-animation1.vue +8 -8
  35. package/components/loading/animations/loading-animation2.vue +8 -9
  36. package/components/loading/animations/loading-animation3.vue +11 -11
  37. package/components/loading/animations/loading-animation4.vue +8 -8
  38. package/components/loading/loading.vue +19 -20
  39. package/components/message-box/message-box.vue +59 -41
  40. package/components/number-dialog/number-dialog.scss +9 -0
  41. package/components/number-dialog/number-dialog.vue +209 -204
  42. package/components/number-input/number-input.scss +9 -0
  43. package/components/number-input/number-input.vue +233 -232
  44. package/components/numpad/numpad.scss +9 -0
  45. package/components/numpad/numpad.vue +133 -150
  46. package/components/page/page-body.scss +15 -0
  47. package/components/page/page-body.vue +137 -119
  48. package/components/page/page-footer.scss +13 -0
  49. package/components/page/page-footer.vue +31 -33
  50. package/components/page/page-header.scss +17 -0
  51. package/components/page/page-header.vue +89 -94
  52. package/components/page/page.scss +9 -0
  53. package/components/page/page.vue +105 -81
  54. package/components/picker/picker-popup.vue +218 -209
  55. package/components/picker/picker.vue +99 -104
  56. package/components/popover/popover-content.vue +54 -63
  57. package/components/popover/popover-mask.vue +63 -63
  58. package/components/popover/popover.scss +28 -0
  59. package/components/popover/popover.vue +49 -47
  60. package/components/popup/popup-content.vue +49 -53
  61. package/components/popup/popup.scss +11 -0
  62. package/components/popup/popup.vue +117 -132
  63. package/components/price/price.scss +20 -0
  64. package/components/price/price.vue +31 -42
  65. package/components/search/search.scss +12 -0
  66. package/components/search/search.vue +93 -117
  67. package/components/searching/searching.vue +33 -40
  68. package/components/selector/selector.vue +43 -48
  69. package/components/switch/switch.scss +12 -0
  70. package/components/switch/switch.vue +99 -96
  71. package/components/tab/tab.scss +19 -0
  72. package/components/tab/tab.vue +151 -145
  73. package/components/textarea/textarea.vue +104 -101
  74. package/composable/component-name.ts +10 -0
  75. package/configs/index.ts +82 -0
  76. package/{easyCom.js → easyCom.ts} +1 -1
  77. package/index.ts +1 -0
  78. package/init.ts +34 -0
  79. package/package.json +12 -4
  80. package/styles/utils/css-var.scss +12 -15
  81. package/styles/utils/ellipsis.scss +0 -7
  82. package/styles/utils/z-index.scss +1 -1
  83. package/styles/var/component.scss +21 -245
  84. package/utils/{date.js → date.ts} +44 -42
  85. package/utils/freezeDeep.ts +9 -0
  86. package/utils/price.ts +57 -0
  87. package/utils/round.ts +11 -0
  88. package/utils/{windowInfo.js → windowInfo.ts} +12 -35
  89. package/components/checkbox-group2/checkbox.vue +0 -109
  90. package/components/datetime-picker/datetime-picker-view.vue +0 -209
  91. package/components/datetime-picker/datetime-picker.vue +0 -235
  92. package/components/form/mixin.js +0 -13
  93. package/components/list-page/list-page.js +0 -10
  94. package/components/table/table.vue +0 -112
  95. package/components/time-picker/time-picker-view.vue +0 -127
  96. package/components/time-picker/time-picker.vue +0 -140
  97. package/components/uploader/download.js +0 -81
  98. package/components/uploader/file-uploader.vue +0 -52
  99. package/components/uploader/files.js +0 -225
  100. package/components/uploader/media-uploader.vue +0 -179
  101. package/components/uploader/mixin.js +0 -171
  102. package/components/uploader/uni.js +0 -38
  103. package/components/uploader/upload-button.vue +0 -29
  104. package/components/uploader/uploader copy.vue +0 -547
  105. package/components/uploader/uploader.vue +0 -117
  106. package/configs/index.d.ts +0 -22
  107. package/configs/index.js +0 -68
  108. package/init.d.ts +0 -19
  109. package/init.js +0 -20
  110. package/mixins/componentName.js +0 -32
  111. package/mixins/listPage.js +0 -1
  112. package/mixins/windowInfo.js +0 -1
  113. package/utils/fileSize.js +0 -39
  114. package/utils/floor.js +0 -20
  115. package/utils/freezeDeep.js +0 -19
  116. package/utils/price.js +0 -32
  117. package/utils/round.js +0 -22
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <popup-cpn ref="popup" :value="value" :class="componentClassNames" @input="$emit('input', $event)">
2
+ <popup-cpn
3
+ ref="popupRef"
4
+ :model-value="modelValue"
5
+ :class="[...classNames, $style['PickerPopup']]"
6
+ @update:model-value="emit('update:modelValue', $event)"
7
+ >
3
8
  <template #content>
4
9
  <popup-content-cpn
5
10
  :loading="isLoading"
@@ -50,10 +55,10 @@
50
55
  :class="$style['PickerPopup--option-single']"
51
56
  />
52
57
  </view>
53
- <view v-if="pager && pager.data.length > 0" :class="$style['PickerPopup--load-more-wrapper']">
58
+ <view v-if="pagingData && pagingData.data.length > 0" :class="$style['PickerPopup--load-more-wrapper']">
54
59
  <load-more-cpn
55
- :loading="pager.loadMoreTime !== 0"
56
- :end="!pager.hasMore"
60
+ :loading="loadingMore"
61
+ :end="!pagingData.hasMore"
57
62
  end-title="到底了"
58
63
  @load-more="loadMore()"
59
64
  />
@@ -64,8 +69,10 @@
64
69
  </popup-cpn>
65
70
  </template>
66
71
 
67
- <script>
68
- import componentName from '../../mixins/componentName';
72
+ <script lang="ts" setup>
73
+ import { computed, ref, watch } from 'vue';
74
+ import { PagingData } from '@ecolovo/utils/uniapp/paging';
75
+ import useClassNames from '../../composable/component-name';
69
76
  import EmptyCpn from '../empty/empty.vue';
70
77
  import IconCpn from '../icon/icon.vue';
71
78
  import LoadMoreCpn from '../load-more/load-more.vue';
@@ -73,223 +80,225 @@ import PopupCpn from '../popup/popup.vue';
73
80
  import PopupContentCpn from '../popup/popup-content.vue';
74
81
  import SearchCpn from '../search/search.vue';
75
82
 
76
- export default {
77
- name: 'PickerPopup',
83
+ defineOptions({
78
84
  options: {
79
85
  virtualHost: true,
80
86
  styleIsolation: 'shared',
81
87
  },
82
- components: { EmptyCpn, IconCpn, LoadMoreCpn, PopupCpn, PopupContentCpn, SearchCpn },
83
- mixins: [componentName],
84
- props: {
85
- /** 是否可见 */
86
- value: {
87
- type: Boolean,
88
- default: false,
89
- },
90
- /** 标题 */
91
- title: {
92
- type: String,
93
- default: '',
94
- },
95
- /** 副标题 */
96
- subTitle: {
97
- type: String,
98
- default: '',
99
- },
100
- /** 是否有筛选框 */
101
- searchable: {
102
- type: Boolean,
103
- default: false,
104
- },
105
- /** 是否多选 */
106
- multiple: {
107
- type: Boolean,
108
- default: false,
109
- },
110
- /** 搜索框占位文本 */
111
- searchPlaceholder: {
112
- type: String,
113
- default: '请输入',
114
- },
115
- /** 选项 */
116
- options: {
117
- type: Array,
118
- default: () => [],
119
- },
120
- /** 分页 */
121
- pager: {
122
- type: Object,
123
- default: null,
124
- },
125
- /** 已选选项 */
126
- checked: {
127
- type: Array,
128
- default: () => [],
129
- },
130
- /** 选项唯一标识 */
131
- valueKey: {
132
- type: String,
133
- default: 'id',
134
- },
135
- /** 选项标签字段 */
136
- labelKey: {
137
- type: String,
138
- default: 'label',
139
- },
140
- },
141
- data() {
142
- return {
143
- /** 用于回到顶部 */
144
- scrollTopValue: 0,
145
- optionList: [],
146
- tempCheckedMap: new Map(),
147
- tempCheckedCount: 0,
148
- filterText: '',
149
- };
150
- },
151
- computed: {
152
- filteredList() {
153
- const text = this.filterText.trim();
154
- return text ? this.optionList.filter((item) => item.option[this.labelKey].includes(text)) : this.optionList;
155
- },
156
- },
157
- watch: {
158
- options() {
159
- this.setOptionList();
160
- },
161
- },
162
- created() {
163
- this.initPopup();
164
- this.setOptionList();
165
- },
166
- methods: {
167
- /** 初始化 */
168
- initPopup() {
169
- this.tempCheckedCount = this.checked.length;
170
- this.checked.forEach((option) => {
171
- this.tempCheckedMap.set(option[this.valueKey], option);
172
- });
173
- },
174
- /** 格式化选项数据列表 */
175
- setOptionList() {
176
- this.optionList = this.options.map((option) => {
177
- const checked = this.tempCheckedMap.has(option[this.valueKey]);
178
- return { option, checked };
179
- });
180
- },
181
- /** 勾选选项 */
182
- checkPickerItem(option) {
183
- const index = this.optionList.findIndex((item) => item.option[this.valueKey] === option[this.valueKey]);
184
- if (index < 0) return;
88
+ });
185
89
 
186
- if (!this.multiple) {
187
- this.tempCheckedMap.clear();
188
- this.tempCheckedMap.set(option[this.valueKey], option);
189
- this.setOptionList();
190
- this.$emit('confirm', option);
191
- this.$refs.popup.close();
192
- return;
193
- }
90
+ const classNames = useClassNames('picker-popup');
194
91
 
195
- this.optionList[index].checked = !this.optionList[index].checked;
196
- if (this.optionList[index].checked) {
197
- this.tempCheckedMap.set(option[this.valueKey], option);
198
- } else {
199
- this.tempCheckedMap.delete(option[this.valueKey]);
200
- }
201
- this.tempCheckedCount = this.tempCheckedMap.size;
202
- },
203
- /** 回到顶部 */
204
- scrollTop() {
205
- this.scrollTopValue = typeof this.scrollTopValue === 'number' ? '0px' : 0;
206
- },
207
- /** 过滤或搜索 */
208
- onFilter(text) {
209
- this.scrollTop();
210
- if (this.pager) {
211
- this.$emit('search', text);
212
- return;
213
- }
214
- this.filterText = text;
215
- },
216
- /** 加载更多 */
217
- loadMore() {
218
- if (!this.pager) return;
92
+ type _Option = Record<string, string | number>;
219
93
 
220
- this.$emit('load-more');
221
- },
222
- /** 点击确认按钮 */
223
- onConfirm() {
224
- this.$emit('confirm', this.tempCheckedMap.values().toArray());
225
- this.$refs.popup.close();
226
- },
227
- },
94
+ type _Props = {
95
+ /** 是否可见 */
96
+ modelValue?: boolean;
97
+ /** 标题 */
98
+ title?: string;
99
+ /** 副标题 */
100
+ subTitle?: string;
101
+ /** 是否有筛选框 */
102
+ searchable?: boolean;
103
+ /** 是否多选 */
104
+ multiple?: boolean;
105
+ /** 搜索框占位文本 */
106
+ searchPlaceholder?: string;
107
+ /** 选项 */
108
+ options?: _Option[];
109
+ /** 分页 */
110
+ pagingData?: PagingData | null;
111
+ /** 是否正在加载更多 */
112
+ loadingMore?: boolean;
113
+ /** 已选选项 */
114
+ checked?: _Option[];
115
+ /** 选项唯一标识 */
116
+ valueKey?: string;
117
+ /** 选项标签字段 */
118
+ labelKey?: string;
119
+ };
120
+
121
+ const props = withDefaults(defineProps<_Props>(), {
122
+ modelValue: false,
123
+ title: '',
124
+ subTitle: '',
125
+ searchable: false,
126
+ multiple: false,
127
+ searchPlaceholder: '请输入',
128
+ options: () => [],
129
+ pagingData: null,
130
+ checked: () => [],
131
+ valueKey: 'id',
132
+ labelKey: 'label',
133
+ });
134
+
135
+ const emit = defineEmits<{
136
+ (e: 'update:modelValue', val: boolean): void;
137
+ (e: 'confirm', val: _Option | _Option[]): void;
138
+ (e: 'search', text: string): void;
139
+ (e: 'load-more'): void;
140
+ }>();
141
+
142
+ const isLoading = ref(false);
143
+
144
+ /** 用于回到顶部 */
145
+ const scrollTopValue = ref<string | number>(0);
146
+
147
+ type _OptionItem = {
148
+ option: _Option;
149
+ checked: boolean;
150
+ };
151
+
152
+ const optionList = ref<_OptionItem[]>([]);
153
+ const tempCheckedMap = ref<Map<unknown, _Option>>(new Map());
154
+ const tempCheckedCount = ref(0);
155
+ const filterText = ref('');
156
+
157
+ const popupRef = ref<InstanceType<typeof PopupCpn>>();
158
+
159
+ /** 格式化选项数据列表 */
160
+ const setOptionList = () => {
161
+ optionList.value = props.options.map((option) => {
162
+ const checked = tempCheckedMap.value.has(option[props.valueKey]);
163
+ return { option, checked };
164
+ });
165
+ };
166
+
167
+ const filteredList = computed(() => {
168
+ const text = filterText.value.trim();
169
+ return text
170
+ ? optionList.value.filter((item) => (item.option[props.labelKey] as string).includes(text))
171
+ : optionList.value;
172
+ });
173
+
174
+ // 初始化
175
+ tempCheckedCount.value = props.checked.length;
176
+ props.checked.forEach((option) => {
177
+ tempCheckedMap.value.set(option[props.valueKey], option);
178
+ });
179
+ setOptionList();
180
+
181
+ watch(
182
+ () => props.options,
183
+ () => {
184
+ setOptionList();
185
+ }
186
+ );
187
+
188
+ /** 勾选选项 */
189
+ const checkPickerItem = (option: _Option) => {
190
+ const index = optionList.value.findIndex((item) => item.option[props.valueKey] === option[props.valueKey]);
191
+ if (index < 0) return;
192
+
193
+ if (!props.multiple) {
194
+ tempCheckedMap.value.clear();
195
+ tempCheckedMap.value.set(option[props.valueKey], option);
196
+ setOptionList();
197
+ emit('confirm', option);
198
+ popupRef.value?.close();
199
+ return;
200
+ }
201
+
202
+ optionList.value[index]!.checked = !optionList.value[index]!.checked;
203
+ if (optionList.value[index]!.checked) {
204
+ tempCheckedMap.value.set(option[props.valueKey], option);
205
+ } else {
206
+ tempCheckedMap.value.delete(option[props.valueKey]);
207
+ }
208
+ tempCheckedCount.value = tempCheckedMap.value.size;
209
+ };
210
+
211
+ /** 回到顶部 */
212
+ const scrollTop = () => {
213
+ scrollTopValue.value = typeof scrollTopValue.value === 'number' ? '0px' : 0;
214
+ };
215
+
216
+ /** 过滤或搜索 */
217
+ const onFilter = (text: string) => {
218
+ scrollTop();
219
+ if (props.pagingData) {
220
+ emit('search', text);
221
+ return;
222
+ }
223
+ filterText.value = text;
224
+ };
225
+
226
+ /** 加载更多 */
227
+ const loadMore = () => {
228
+ if (!props.pagingData) return;
229
+
230
+ emit('load-more');
231
+ };
232
+
233
+ /** 点击确认按钮 */
234
+ const onConfirm = () => {
235
+ emit('confirm', [...tempCheckedMap.value.values()]);
236
+ popupRef.value?.close();
228
237
  };
229
238
  </script>
230
239
 
231
240
  <style lang="scss" module>
232
- @use '../../styles/index' as *;
241
+ @use '../../styles/utils' as *;
233
242
 
234
- :host {
235
- display: block;
236
- }
243
+ .PickerPopup {
244
+ @extend %external-vars;
237
245
 
238
- .PickerPopup--header {
239
- position: relative;
240
- box-sizing: border-box;
241
- height: UTILS__LOCAL_VAR(header-height);
242
- padding: 20rpx 30rpx 0rpx;
243
- display: flex;
244
- align-items: center;
245
- }
246
- .PickerPopup--title {
247
- flex: 1;
248
- font-size: UTILS__GLOBAL_VAR(font-size, large);
249
- font-weight: 600;
250
- }
251
- .PickerPopup--sub-title {
252
- margin-left: 10rpx;
253
- color: UTILS__GLOBAL_VAR(theme, placeholder);
254
- font-size: UTILS__GLOBAL_VAR(font-size, normal);
255
- font-weight: 400;
256
- }
257
- .PickerPopup--search-wrapper {
258
- flex: none;
259
- height: 88rpx;
260
- display: flex;
261
- align-items: center;
262
- #{UTILS__VAR_NAME(height, search)}: 72rpx;
263
- }
264
- .PickerPopup--search {
265
- flex: 1;
266
- }
246
+ .PickerPopup--header {
247
+ position: relative;
248
+ box-sizing: border-box;
249
+ height: UTILS__LOCAL_VAR(header-height, popup);
250
+ padding: 20rpx 30rpx 0rpx;
251
+ display: flex;
252
+ align-items: center;
253
+ }
254
+ .PickerPopup--title {
255
+ flex: 1;
256
+ font-size: UTILS__GLOBAL_VAR(font-size, large);
257
+ font-weight: 600;
258
+ }
259
+ .PickerPopup--sub-title {
260
+ margin-left: 10rpx;
261
+ color: UTILS__GLOBAL_VAR(color, placeholder);
262
+ font-size: UTILS__GLOBAL_VAR(font-size, normal);
263
+ font-weight: 400;
264
+ }
265
+ .PickerPopup--search-wrapper {
266
+ flex: none;
267
+ height: 88rpx;
268
+ display: flex;
269
+ align-items: center;
270
+ #{UTILS__VAR_NAME(height, search)}: 72rpx;
271
+ }
272
+ .PickerPopup--search {
273
+ flex: 1;
274
+ }
267
275
 
268
- .PickerPopup--option {
269
- display: flex;
270
- justify-content: start;
271
- align-items: center;
272
- padding: 20rpx 0;
273
- &.PickerPopup--checked {
274
- color: UTILS__GLOBAL_VAR(theme, primary);
276
+ .PickerPopup--option {
277
+ display: flex;
278
+ justify-content: start;
279
+ align-items: center;
280
+ padding: 20rpx 0;
281
+ &.PickerPopup--checked {
282
+ color: UTILS__GLOBAL_VAR(theme, primary);
283
+ }
284
+ }
285
+ .PickerPopup--option-multi {
286
+ flex: none;
287
+ margin-right: 12rpx;
288
+ }
289
+ .PickerPopup--option-single {
290
+ flex: none;
291
+ margin-left: 12rpx;
292
+ }
293
+ .PickerPopup--option-text {
294
+ @include UTILS__ELLIPSIS();
295
+ }
296
+ .PickerPopup--empty {
297
+ margin: auto auto;
298
+ }
299
+ .PickerPopup--load-more-wrapper {
300
+ position: sticky;
301
+ top: 100%;
275
302
  }
276
- }
277
- .PickerPopup--option-multi {
278
- flex: none;
279
- margin-right: 12rpx;
280
- }
281
- .PickerPopup--option-single {
282
- flex: none;
283
- margin-left: 12rpx;
284
- }
285
- .PickerPopup--option-text {
286
- @include UTILS__ELLIPSIS();
287
- }
288
- .PickerPopup--empty {
289
- margin: auto auto;
290
- }
291
- .PickerPopup--load-more-wrapper {
292
- position: sticky;
293
- top: 100%;
294
303
  }
295
304
  </style>
@@ -1,7 +1,7 @@
1
1
  <template>
2
- <view :class="componentClassNames">
2
+ <view :class="[...classNames, $style['Picker']]">
3
3
  <selector-cpn
4
- :value="valueStr"
4
+ :model-value="valueStr"
5
5
  :readonly="isReadonly"
6
6
  :disabled="disabled"
7
7
  :placeholder="placeholder"
@@ -24,120 +24,115 @@
24
24
  </view>
25
25
  </template>
26
26
 
27
- <script>
27
+ <script lang="ts" setup>
28
28
  import isEqual from 'lodash-es/isEqual';
29
- import componentName from '../../mixins/componentName';
30
- import { isFormReadonly } from '../form/mixin';
29
+ import { computed, inject, ref } from 'vue';
30
+ import useClassNames from '../../composable/component-name';
31
31
  import SelectorCpn from '../selector/selector.vue';
32
32
  import PickerPopupCpn from './picker-popup.vue';
33
33
 
34
- export default {
35
- name: 'Picker',
34
+ defineOptions({
36
35
  options: {
37
36
  virtualHost: true,
38
37
  styleIsolation: 'shared',
39
38
  },
40
- components: { SelectorCpn, PickerPopupCpn },
41
- mixins: [componentName, isFormReadonly],
42
- props: {
43
- /** 绑定值 */
44
- value: {
45
- type: [String, Number, Boolean, Object, Array],
46
- default: null,
47
- },
48
- /** 绑定值文本 */
49
- valueText: {
50
- type: String,
51
- default: '',
52
- },
53
- /** 选项唯一标识 */
54
- valueKey: {
55
- type: String,
56
- default: 'id',
57
- },
58
- /** 选项标签字段 */
59
- labelKey: {
60
- type: String,
61
- default: 'label',
62
- },
63
- /** 是否禁用 */
64
- disabled: {
65
- type: Boolean,
66
- default: false,
67
- },
68
- /** 是否只读 */
69
- readonly: {
70
- type: Boolean,
71
- default: false,
72
- },
73
- /** 占位文本 */
74
- placeholder: {
75
- type: String,
76
- default: '请选择',
77
- },
78
- /** 弹出框标题 */
79
- popupTitle: {
80
- type: String,
81
- default: '请选择',
82
- },
83
- /** 选项 */
84
- options: {
85
- type: Array,
86
- default: () => [],
87
- },
88
- /** 是否多选 */
89
- multiple: {
90
- type: Boolean,
91
- default: false,
92
- },
93
- /** 多选时选项拼接的分隔符 */
94
- joinText: {
95
- type: String,
96
- default: '、',
97
- },
98
- },
99
- data() {
100
- return {
101
- /** 选择框是否可见 */
102
- isPickerVisible: false,
103
- };
104
- },
105
- computed: {
106
- isReadonly() {
107
- return this.readonly || this.isFormReadonly;
108
- },
109
- checkedOptions() {
110
- if (this.multiple) {
111
- return this.options.filter((item) => this.value.includes(item[this.valueKey]));
112
- }
113
- return this.options.filter((item) => item[this.valueKey] === this.value);
114
- },
115
- valueStr() {
116
- if (this.valueText) return this.valueText;
117
- if (this.value === null) return '';
118
- return this.checkedOptions.map((item) => item[this.labelKey]).join(this.joinText);
119
- },
120
- },
121
- methods: {
122
- showPicker() {
123
- if (this.disabled || this.isReadonly) {
124
- return;
125
- }
39
+ });
126
40
 
127
- this.isPickerVisible = true;
128
- this.$emit('show');
129
- },
41
+ const classNames = useClassNames('picker');
130
42
 
131
- onConfirm(checked) {
132
- const checkedValue = this.multiple ? checked.map((item) => item[this.valueKey]) : checked[this.valueKey];
43
+ type _Option = Record<string, string | number>;
44
+ type _Value = string | number | boolean | Record<string, unknown>;
133
45
 
134
- this.$emit('input', checkedValue);
135
- this.$emit('pick', checked);
46
+ type _Props = {
47
+ /** 绑定值 */
48
+ modelValue?: _Value | _Value[];
49
+ /** 绑定值文本 */
50
+ valueText?: string;
51
+ /** 选项唯一标识 */
52
+ valueKey?: string;
53
+ /** 选项标签字段 */
54
+ labelKey?: string;
55
+ /** 是否禁用 */
56
+ disabled?: boolean;
57
+ /** 是否只读 */
58
+ readonly?: boolean;
59
+ /** 占位文本 */
60
+ placeholder?: string;
61
+ /** 弹出框标题 */
62
+ popupTitle?: string;
63
+ /** 选项 */
64
+ options?: _Option[];
65
+ /** 是否多选 */
66
+ multiple?: boolean;
67
+ /** 多选时选项拼接的分隔符 */
68
+ joinText?: string;
69
+ };
136
70
 
137
- if (isEqual(checkedValue, this.value)) return;
138
- this.$emit('change', checkedValue, checked);
139
- },
140
- },
71
+ const props = withDefaults(defineProps<_Props>(), {
72
+ modelValue: undefined,
73
+ valueText: '',
74
+ valueKey: 'id',
75
+ labelKey: 'label',
76
+ disabled: false,
77
+ readonly: false,
78
+ placeholder: '请选择',
79
+ popupTitle: '请选择',
80
+ options: () => [],
81
+ multiple: false,
82
+ joinText: '、',
83
+ });
84
+
85
+ const emit = defineEmits<{
86
+ (e: 'update:modelValue', val: undefined | _Value | _Value[]): void;
87
+ (e: 'pick', val: _Option | _Option[]): void;
88
+ (e: 'change', val: unknown, checked: _Option | _Option[]): void;
89
+ (e: 'show'): void;
90
+ }>();
91
+
92
+ /** 表单是否只读 */
93
+ const isFormReadonly = inject(
94
+ 'ecolovo/form/readonly',
95
+ computed(() => false)
96
+ );
97
+
98
+ /** 选择框是否可见 */
99
+ const isPickerVisible = ref(false);
100
+
101
+ const isReadonly = computed(() => props.readonly || isFormReadonly.value);
102
+
103
+ const checkedOptions = computed(() => {
104
+ if (props.multiple) {
105
+ return props.options.filter((item) => (props.modelValue as unknown[])?.includes(item[props.valueKey]));
106
+ }
107
+ return props.options.filter((item) => item[props.valueKey] === props.modelValue);
108
+ });
109
+
110
+ const valueStr = computed(() => {
111
+ if (props.valueText) return props.valueText;
112
+ if (props.modelValue === null) return '';
113
+ return checkedOptions.value.map((item) => item[props.labelKey] as string).join(props.joinText);
114
+ });
115
+
116
+ const showPicker = () => {
117
+ if (props.disabled || isReadonly.value) {
118
+ return;
119
+ }
120
+
121
+ isPickerVisible.value = true;
122
+ emit('show');
123
+ };
124
+
125
+ const onConfirm = (checked: _Option | _Option[]) => {
126
+ const checkedVal = checked as _Option;
127
+ const checkedValue = props.multiple
128
+ ? (checked as _Option[]).map((item) => item[props.valueKey]!)
129
+ : checkedVal[props.valueKey];
130
+
131
+ emit('update:modelValue', checkedValue);
132
+ emit('pick', checked);
133
+
134
+ if (isEqual(checkedValue, props.modelValue)) return;
135
+ emit('change', checkedValue, checked);
141
136
  };
142
137
  </script>
143
138