@ecolovo/uniapp-components 1.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 (97) hide show
  1. package/components/aspect/aspect.vue +43 -0
  2. package/components/badge/badge.vue +88 -0
  3. package/components/button/button.vue +409 -0
  4. package/components/checkbox/checkbox.vue +183 -0
  5. package/components/checkbox-group/checkbox-group.vue +101 -0
  6. package/components/checkbox-group2/checkbox-group2.vue +138 -0
  7. package/components/checkbox-group2/checkbox.vue +103 -0
  8. package/components/data-list/data-list.vue +130 -0
  9. package/components/date-picker/date-picker-view.vue +181 -0
  10. package/components/date-picker/date-picker.vue +237 -0
  11. package/components/datetime-picker/datetime-picker-view.vue +209 -0
  12. package/components/datetime-picker/datetime-picker.vue +235 -0
  13. package/components/dialog/dialog-content.vue +162 -0
  14. package/components/dialog/dialog.vue +166 -0
  15. package/components/dropdown/dropdown.vue +145 -0
  16. package/components/empty/empty.vue +102 -0
  17. package/components/flex/flex.vue +76 -0
  18. package/components/form/form.vue +43 -0
  19. package/components/form/mixin.js +13 -0
  20. package/components/form-item/form-item.vue +141 -0
  21. package/components/icon/icon.vue +114 -0
  22. package/components/image/image.vue +61 -0
  23. package/components/input/input.vue +154 -0
  24. package/components/input/password-input.vue +95 -0
  25. package/components/list-page/list-page.js +10 -0
  26. package/components/list-page/list-page.vue +150 -0
  27. package/components/load-more/load-more.vue +97 -0
  28. package/components/loading/animations/loading-animation1.vue +92 -0
  29. package/components/loading/animations/loading-animation2.vue +28 -0
  30. package/components/loading/animations/loading-animation3.vue +81 -0
  31. package/components/loading/animations/loading-animation4.vue +74 -0
  32. package/components/loading/loading.vue +91 -0
  33. package/components/message-box/message-box.vue +60 -0
  34. package/components/number-dialog/number-dialog.vue +360 -0
  35. package/components/number-input/number-input.vue +352 -0
  36. package/components/numpad/numpad.vue +224 -0
  37. package/components/page/page-body.vue +227 -0
  38. package/components/page/page-footer.vue +75 -0
  39. package/components/page/page-header.vue +222 -0
  40. package/components/page/page.vue +128 -0
  41. package/components/picker/picker-popup.vue +295 -0
  42. package/components/picker/picker.vue +151 -0
  43. package/components/popover/popover-content.vue +255 -0
  44. package/components/popover/popover-mask.vue +113 -0
  45. package/components/popover/popover.vue +84 -0
  46. package/components/popup/popup-content.vue +162 -0
  47. package/components/popup/popup.vue +168 -0
  48. package/components/price/price.vue +104 -0
  49. package/components/search/search.vue +220 -0
  50. package/components/searching/searching.vue +86 -0
  51. package/components/selector/selector.vue +86 -0
  52. package/components/switch/switch.vue +180 -0
  53. package/components/tab/tab.vue +235 -0
  54. package/components/table/table.vue +112 -0
  55. package/components/textarea/textarea.vue +192 -0
  56. package/components/time-picker/time-picker-view.vue +127 -0
  57. package/components/time-picker/time-picker.vue +140 -0
  58. package/components/uploader/download.js +81 -0
  59. package/components/uploader/file-uploader.vue +52 -0
  60. package/components/uploader/files.js +225 -0
  61. package/components/uploader/media-uploader.vue +133 -0
  62. package/components/uploader/mixin.js +106 -0
  63. package/components/uploader/uni.js +38 -0
  64. package/components/uploader/upload-button.vue +29 -0
  65. package/components/uploader/uploader copy.vue +547 -0
  66. package/components/uploader/uploader.vue +117 -0
  67. package/configs/index.js +41 -0
  68. package/easyCom.js +25 -0
  69. package/init.js +17 -0
  70. package/mixins/componentName.js +32 -0
  71. package/mixins/listPage.js +1 -0
  72. package/mixins/windowInfo.js +1 -0
  73. package/package.json +12 -0
  74. package/resources/iconfont/uniapp_ui_icon/demo.css +539 -0
  75. package/resources/iconfont/uniapp_ui_icon/demo_index.html +555 -0
  76. package/resources/iconfont/uniapp_ui_icon/iconfont.css +78 -0
  77. package/resources/iconfont/uniapp_ui_icon/iconfont.js +1 -0
  78. package/resources/iconfont/uniapp_ui_icon/iconfont.json +121 -0
  79. package/resources/iconfont/uniapp_ui_icon/iconfont.woff2 +0 -0
  80. package/resources/image/empty.svg +13 -0
  81. package/styles/global.scss +24 -0
  82. package/styles/index.scss +2 -0
  83. package/styles/utils/css-var.scss +87 -0
  84. package/styles/utils/ellipsis.scss +26 -0
  85. package/styles/utils/global-var.scss +11 -0
  86. package/styles/utils/index.scss +5 -0
  87. package/styles/utils/selector.scss +5 -0
  88. package/styles/utils/z-index.scss +10 -0
  89. package/styles/var/component.scss +242 -0
  90. package/styles/var/index.scss +2 -0
  91. package/styles/var/theme.scss +40 -0
  92. package/utils/date.js +151 -0
  93. package/utils/floor.js +20 -0
  94. package/utils/freezeDeep.js +19 -0
  95. package/utils/price.js +32 -0
  96. package/utils/round.js +22 -0
  97. package/utils/windowInfo.js +77 -0
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <view :class="componentClassNames">
3
+ <view :class="$style['Aspect--content-wrapper']" :style="{ paddingTop: 100 / ratio + '%' }">
4
+ <view :class="$style['Aspect--content']">
5
+ <slot></slot>
6
+ </view>
7
+ </view>
8
+ </view>
9
+ </template>
10
+
11
+ <script>
12
+ import componentName from '../../mixins/componentName';
13
+
14
+ export default {
15
+ name: 'AppAspect',
16
+ options: {
17
+ virtualHost: true,
18
+ styleIsolation: 'shared',
19
+ },
20
+ mixins: [componentName],
21
+ props: {
22
+ /** 纵横比,默认为 1,即宽高相等 */
23
+ ratio: {
24
+ type: [String, Number],
25
+ default: 1,
26
+ },
27
+ },
28
+ };
29
+ </script>
30
+
31
+ <style lang="css" module>
32
+ .Aspect--content-wrapper {
33
+ position: relative;
34
+ width: 100%;
35
+ }
36
+ .Aspect--content {
37
+ position: absolute;
38
+ top: 0;
39
+ left: 0;
40
+ width: 100%;
41
+ height: 100%;
42
+ }
43
+ </style>
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <view :class="componentClassNames">
3
+ <slot></slot>
4
+ <template v-if="$slots['badge'] || $slots['badge-value']">
5
+ <slot name="badge">
6
+ <view :class="$style['Badge--box']">
7
+ <slot name="badge-value"></slot>
8
+ </view>
9
+ </slot>
10
+ </template>
11
+ <view v-else-if="value" :class="$style['Badge--box']">
12
+ <slot name="badge"></slot>
13
+ {{ showValue }}
14
+ </view>
15
+ </view>
16
+ </template>
17
+
18
+ <script>
19
+ import componentName from '../../mixins/componentName';
20
+
21
+ export default {
22
+ name: 'AppBadge',
23
+ options: {
24
+ virtualHost: true,
25
+ styleIsolation: 'shared',
26
+ },
27
+ mixins: [componentName],
28
+ props: {
29
+ /** 显示在右上角的内容 */
30
+ value: {
31
+ type: [String, Number],
32
+ default: 0,
33
+ },
34
+ /** 最大显示值,超出时会在后面添加一个“+” */
35
+ max: {
36
+ type: Number,
37
+ default: 99,
38
+ },
39
+ },
40
+ computed: {
41
+ /** 显示值 */
42
+ showValue() {
43
+ const numberValue = Number(this.value);
44
+ return numberValue && numberValue > 0 ? (numberValue > this.max ? `${this.max}+` : numberValue) : '';
45
+ },
46
+ },
47
+ };
48
+ </script>
49
+
50
+ <style lang="scss" module>
51
+ @use '../../styles/index' as *;
52
+
53
+ // 定义外部变量
54
+ $component-name: 'badge';
55
+ $component-vars: $badge;
56
+ %external-vars {
57
+ @include UTILS__DEFINE_COMPONENT_VARS();
58
+ }
59
+
60
+ .Badge {
61
+ @extend %external-vars;
62
+
63
+ display: inline-block;
64
+ position: relative;
65
+ width: fit-content;
66
+
67
+ .Badge--box {
68
+ --padding-x: calc(#{UTILS__LOCAL_VAR(font-size)} / 2);
69
+
70
+ position: absolute;
71
+ top: 0;
72
+ right: 0;
73
+ z-index: 1;
74
+ padding: 0 var(--padding-x);
75
+ box-sizing: content-box;
76
+ text-align: center;
77
+ min-width: calc(#{UTILS__LOCAL_VAR(height)} - 2 * var(--padding-x));
78
+ height: UTILS__LOCAL_VAR(height);
79
+ line-height: UTILS__LOCAL_VAR(height);
80
+ font-size: UTILS__LOCAL_VAR(font-size);
81
+ border: UTILS__LOCAL_VAR(border);
82
+ border-radius: UTILS__LOCAL_VAR(height);
83
+ translate: UTILS__LOCAL_VAR(translate);
84
+ color: UTILS__LOCAL_VAR(color);
85
+ background: UTILS__LOCAL_VAR(background);
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,409 @@
1
+ <template>
2
+ <button
3
+ :open-type="openType"
4
+ :class="[
5
+ componentClassNames,
6
+ {
7
+ // 不能点击,但不是禁用态,显示与激活态一致
8
+ [$style['Button--not-clickable']]: !disabled && isNotClickable,
9
+ // 禁用态
10
+ [$style['Button--disabled']]: disabled,
11
+ // 面性按钮
12
+ [$style['Button--block']]: !text,
13
+ // 线性按钮
14
+ [$style['Button--plain']]: !text && plain,
15
+ // 文本按钮
16
+ [$style['Button--text']]: text,
17
+ // 半圆圆角按钮
18
+ [$style['Button--round']]: !text && round,
19
+ },
20
+ buttonSize,
21
+ ]"
22
+ :hover-class="disabled ? 'none' : $style['Button--active']"
23
+ :style="buttonTheme"
24
+ @click.stop="onClick"
25
+ @getphonenumber="onOpenType('getphonenumber', $event)"
26
+ @agreeprivacyauthorization="onOpenType('agreeprivacyauthorization', $event)"
27
+ >
28
+ <!-- 仅在线性按钮且不是禁用态时显示 -->
29
+ <view v-if="!disabled && !text && plain" :class="$style['Button--plain-border']"></view>
30
+ <!-- 仅在配置了图标或文本按钮处于加载态(不能处于禁用态)时显示 -->
31
+ <template v-if="text">
32
+ <icon-cpn
33
+ v-if="icon || (text && isLoading && !disabled)"
34
+ :class="$style['Button--icon']"
35
+ :icon="text && isLoading ? 'loading-dot-spinner' : icon"
36
+ />
37
+ <slot></slot>
38
+ </template>
39
+ <view v-else :class="$style['Button--title']">
40
+ <icon-cpn
41
+ v-if="icon || (text && isLoading && !disabled)"
42
+ :class="$style['Button--icon']"
43
+ :icon="text && isLoading ? 'loading-dot-spinner' : icon"
44
+ />
45
+ <slot></slot>
46
+ </view>
47
+ <!-- 仅在非文本按钮且不是禁用态时显示 -->
48
+ <view v-if="!disabled && !text" :class="$style['Button--active-mask']"></view>
49
+ <!-- 仅在非文本按钮处于加载态(不能处于禁用态)时显示 -->
50
+ <view v-if="!disabled && !text && isLoading" :class="$style['Button--loading']">
51
+ <icon-cpn icon="loading-dot-spinner" />
52
+ </view>
53
+ </button>
54
+ </template>
55
+
56
+ <script>
57
+ import componentName from '../../mixins/componentName';
58
+ import IconCpn from '../icon/icon.vue';
59
+
60
+ /**
61
+ * **组件使用说明:**
62
+ *
63
+ * 当 async 为 false 时,组件会按传入的 coolDown 控制两次点击之间的间隔
64
+ * 当 async 为 true 时,需要在父组件监听 click 事件的回调函数中调用传入的 resolve 或 reject 方法,否则按钮将持续处于 loading 状态
65
+ */
66
+ export default {
67
+ name: 'AppButton',
68
+ components: { IconCpn },
69
+ mixins: [componentName],
70
+ options: {
71
+ virtualHost: true,
72
+ styleIsolation: 'shared',
73
+ },
74
+ props: {
75
+ /** 是否禁用,禁用时按钮会显示禁用态(置灰) */
76
+ disabled: {
77
+ type: Boolean,
78
+ default: false,
79
+ },
80
+ /** 是否禁止点击,为 `true` 时按钮会显白 */
81
+ forbidClick: {
82
+ type: Boolean,
83
+ default: false,
84
+ },
85
+ /** 按钮主题 */
86
+ type: {
87
+ type: String,
88
+ default: 'primary',
89
+ },
90
+ /** 对应小程序的 openType */
91
+ openType: {
92
+ type: String,
93
+ default: '',
94
+ },
95
+ /** 按钮冷却时间,单位毫秒,两次点击之间的最小间隔,为 `0` 时可连续点击 */
96
+ coolDown: {
97
+ type: Number,
98
+ default: 500,
99
+ },
100
+ /** 是否异步事件 */
101
+ async: {
102
+ type: Boolean,
103
+ default: false,
104
+ },
105
+ /** 是否圆角 */
106
+ round: {
107
+ type: Boolean,
108
+ default: false,
109
+ },
110
+ /** 是否线性 */
111
+ plain: {
112
+ type: Boolean,
113
+ default: false,
114
+ },
115
+ /** 是否文本按钮 */
116
+ text: {
117
+ type: Boolean,
118
+ default: false,
119
+ },
120
+ /** 按钮图标 */
121
+ icon: {
122
+ type: String,
123
+ default: '',
124
+ },
125
+ /** 加载中 */
126
+ loading: {
127
+ type: Boolean,
128
+ default: false,
129
+ },
130
+ /** 小尺寸 */
131
+ small: {
132
+ type: Boolean,
133
+ default: false,
134
+ },
135
+ /** 大尺寸 */
136
+ large: {
137
+ type: Boolean,
138
+ default: false,
139
+ },
140
+ },
141
+ data() {
142
+ return {
143
+ /** 按钮是否冷却中 */
144
+ isCoolingDown: false,
145
+ /** 按钮是否处于请求状态 */
146
+ isAsyncLoading: false,
147
+ };
148
+ },
149
+ computed: {
150
+ buttonTheme() {
151
+ return `--theme-from-prop: var(--button-theme-${this.type});`;
152
+ },
153
+ /** 按钮尺寸样式 */
154
+ buttonSize() {
155
+ if (this.text) {
156
+ return this.large
157
+ ? this.$style['Button--text-large']
158
+ : this.small
159
+ ? this.$style['Button--text-small']
160
+ : '';
161
+ }
162
+ return this.large
163
+ ? this.$style['Button--block-large']
164
+ : this.small
165
+ ? this.$style['Button--block-small']
166
+ : '';
167
+ },
168
+ /** 按钮 loading */
169
+ isLoading() {
170
+ return this.loading || this.isAsyncLoading;
171
+ },
172
+ /** 按钮是否可点击 */
173
+ isNotClickable() {
174
+ return this.disabled || this.forbidClick || this.isLoading || this.isCoolingDown;
175
+ },
176
+ },
177
+ // emits: ['click', 'getphonenumber', 'agreeprivacyauthorization'],
178
+ methods: {
179
+ /** 进入冷却 */
180
+ $_coolDown() {
181
+ if (this.coolDown > 0) {
182
+ this.isCoolingDown = true;
183
+
184
+ setTimeout(() => {
185
+ this.isCoolingDown = false;
186
+ }, this.coolDown);
187
+ }
188
+ },
189
+ /** 点击按钮响应 */
190
+ onClick() {
191
+ if (this.isNotClickable) {
192
+ return;
193
+ }
194
+
195
+ this.$_coolDown();
196
+
197
+ if (!this.async) {
198
+ this.$emit('click');
199
+ return;
200
+ }
201
+
202
+ this.isAsyncLoading = true;
203
+ const { promise, resolve, reject } = Promise.withResolvers();
204
+ promise
205
+ .catch((err) => {
206
+ if (process.env.NODE_ENV === 'development') {
207
+ // eslint-disable-next-line no-console
208
+ console.warn(`${this.componentClass} unresolved error:`, err);
209
+ }
210
+ })
211
+ .finally(() => {
212
+ this.isAsyncLoading = false;
213
+ });
214
+ this.$emit('click', { resolve, reject });
215
+ },
216
+ /** 开放能力 */
217
+ onOpenType(eventType, event) {
218
+ if (this.isNotClickable) {
219
+ return;
220
+ }
221
+
222
+ this.$emit(eventType, event);
223
+ this.$_coolDown();
224
+ },
225
+ },
226
+ };
227
+ </script>
228
+
229
+ <style lang="scss" module>
230
+ @use 'sass:color';
231
+ @use 'sass:math';
232
+ @use 'sass:map';
233
+ @use '../../styles/index' as *;
234
+
235
+ // 定义外部变量
236
+ $component-name: 'button';
237
+ $component-vars: $button;
238
+ %external-vars {
239
+ @include UTILS__DEFINE_COMPONENT_VARS();
240
+ }
241
+
242
+ %button-themes {
243
+ @each $name, $color in $component-themes {
244
+ --button-theme-#{$name}: #{$color};
245
+ }
246
+ }
247
+
248
+ $button-sizes: (
249
+ normal: (
250
+ height: 80rpx,
251
+ font-size: 28rpx,
252
+ border-radius: 20rpx,
253
+ ),
254
+ small: (
255
+ height: 72rpx,
256
+ font-size: 24rpx,
257
+ border-radius: 16rpx,
258
+ ),
259
+ large: (
260
+ height: 96rpx,
261
+ font-size: 32rpx,
262
+ border-radius: 24rpx,
263
+ ),
264
+ );
265
+
266
+ @mixin button-size($type: 'block', $size: 'normal') {
267
+ $button-size: map.get($button-sizes, $size);
268
+ font-size: map.get($button-size, font-size);
269
+ @if $type == 'block' {
270
+ height: map.get($button-size, height);
271
+ border-radius: map.get($button-size, border-radius);
272
+ }
273
+ }
274
+
275
+ // 去除 button 默认样式
276
+ %button-reset {
277
+ &::before, // loading 动画
278
+ &::after // 边框
279
+ {
280
+ all: initial;
281
+ }
282
+ }
283
+
284
+ .Button {
285
+ @extend %external-vars;
286
+ @extend %button-themes;
287
+ }
288
+
289
+ .Button--block {
290
+ @extend %button-reset;
291
+ display: block;
292
+ position: relative;
293
+ font-weight: 500;
294
+ width: 100%;
295
+ padding: 0 20rpx;
296
+ box-sizing: border-box;
297
+ text-decoration: none;
298
+ text-align: center;
299
+ overflow: hidden;
300
+ color: UTILS__LOCAL_VAR(text-color);
301
+ background: UTILS__LOCAL_VAR(theme);
302
+ border: none;
303
+
304
+ @include button-size('block', 'normal');
305
+ @at-root .Button--block-small {
306
+ @include button-size('block', 'small');
307
+ }
308
+ @at-root .Button--block-large {
309
+ @include button-size('block', 'large');
310
+ }
311
+
312
+ .Button--active-mask {
313
+ position: absolute;
314
+ top: 0;
315
+ left: 0;
316
+ width: 100%;
317
+ height: 100%;
318
+ border-radius: inherit;
319
+ background: UTILS__LOCAL_VAR(active-mask);
320
+ pointer-events: none;
321
+ opacity: 0;
322
+ will-change: opacity;
323
+
324
+ transition: opacity 0.3s;
325
+ }
326
+
327
+ &.Button--active,
328
+ &.Button--not-clickable {
329
+ .Button--active-mask {
330
+ opacity: 0.5;
331
+ }
332
+ }
333
+
334
+ .Button--loading {
335
+ position: absolute;
336
+ top: 50%;
337
+ left: 50%;
338
+ transform: translate(-50%, -50%);
339
+ color: #666;
340
+ font-size: 36rpx;
341
+ }
342
+
343
+ &.Button--disabled {
344
+ color: #fff;
345
+ background: #ccc;
346
+ }
347
+ }
348
+
349
+ .Button--icon {
350
+ position: relative;
351
+ margin-right: 10rpx;
352
+ }
353
+
354
+ .Button--title {
355
+ position: absolute;
356
+ inset: 0;
357
+ overflow: hidden;
358
+ display: flex;
359
+ justify-content: center;
360
+ align-items: center;
361
+ }
362
+
363
+ .Button--plain {
364
+ color: UTILS__LOCAL_VAR(theme);
365
+ background: transparent;
366
+
367
+ .Button--plain-border {
368
+ position: absolute;
369
+ top: 0;
370
+ left: 0;
371
+ width: 100%;
372
+ height: 100%;
373
+ box-sizing: border-box;
374
+ border-radius: inherit;
375
+ border: UTILS__LOCAL_VAR(border-width) solid UTILS__LOCAL_VAR(theme);
376
+ pointer-events: none;
377
+ }
378
+ }
379
+
380
+ .Button--text {
381
+ @extend %button-reset;
382
+ display: inline-block;
383
+ padding: 0;
384
+ color: UTILS__LOCAL_VAR(theme);
385
+ line-height: inherit;
386
+ background: transparent;
387
+ font-weight: inherit;
388
+ will-change: color;
389
+
390
+ transition: color 0.3s;
391
+
392
+ @include button-size('inline', 'normal');
393
+ @at-root .Button--text-small {
394
+ @include button-size('inline', 'small');
395
+ }
396
+ @at-root .Button--text-large {
397
+ @include button-size('inline', 'large');
398
+ }
399
+
400
+ &.Button--active,
401
+ &.Button--not-clickable {
402
+ color: #999;
403
+ }
404
+
405
+ &.Button--disabled {
406
+ color: #ccc;
407
+ }
408
+ }
409
+ </style>
@@ -0,0 +1,183 @@
1
+ <template>
2
+ <view :class="componentClassNames" @click="toggle()">
3
+ <icon-cpn
4
+ :icon="usingIcon || ''"
5
+ tap-size="50rpx"
6
+ :class="[$style['Checkbox--icon'], { [$style['Checkbox--checked']]: isChecked }]"
7
+ />
8
+ <view :class="$style['Checkbox--text']">
9
+ <slot></slot>
10
+ </view>
11
+ </view>
12
+ </template>
13
+
14
+ <script>
15
+ import componentName from '../../mixins/componentName';
16
+ import { isFormReadonly } from '../form/mixin';
17
+ import IconCpn from '../icon/icon.vue';
18
+
19
+ export default {
20
+ name: 'AppCheckbox',
21
+ options: {
22
+ virtualHost: true,
23
+ styleIsolation: 'shared',
24
+ },
25
+ components: { IconCpn },
26
+ mixins: [componentName, isFormReadonly],
27
+ inject: {
28
+ /** checkbox-group 传入的 check 方法 */
29
+ checkGroup: {
30
+ from: 'checkboxGroupCheck',
31
+ default: null,
32
+ },
33
+ /** checkbox-group 组件的 data 对象,用于读取当前值 */
34
+ checkboxGroupData: {
35
+ from: 'checkboxGroupData',
36
+ default: null,
37
+ },
38
+ /** checkbox-group 组件的 props 对象,用于读取 radio 和图标 */
39
+ checkboxGroupProps: {
40
+ from: 'checkboxGroupProps',
41
+ default: {},
42
+ },
43
+ },
44
+ props: {
45
+ /** 值 */
46
+ value: {
47
+ type: [Number, String, Boolean, null],
48
+ default: null,
49
+ },
50
+ /** 是否只读 */
51
+ readonly: {
52
+ type: Boolean,
53
+ default: false,
54
+ },
55
+ /** 选中时的图标 */
56
+ checkedIcon: {
57
+ type: String,
58
+ default: '',
59
+ },
60
+ /** 未选中时的图标 */
61
+ uncheckedIcon: {
62
+ type: String,
63
+ default: '',
64
+ },
65
+ },
66
+ data() {
67
+ return {
68
+ /** 组件内部记录选中状态,仅在非 checkbox-group 中有效 */
69
+ innerChecked: false,
70
+ };
71
+ },
72
+ computed: {
73
+ isReadonly() {
74
+ return this.readonly || this.isFormReadonly;
75
+ },
76
+ /** 是否在 checkbox-group 中 */
77
+ isInGroup() {
78
+ return !!this.checkboxGroupData;
79
+ },
80
+ /** 是否为单选 */
81
+ isRadio() {
82
+ return !!this.checkboxGroupProps.radio;
83
+ },
84
+ /** checkbox-group 的值 */
85
+ groupCheckedValue() {
86
+ return this.checkboxGroupData?.checkedValue;
87
+ },
88
+ /** 最终使用的选中态的图标 */
89
+ usingCheckedIcon() {
90
+ return (
91
+ this.checkboxGroupProps.checkedIcon ||
92
+ this.checkedIcon ||
93
+ (this.isRadio ? 'radio-checked' : 'checkbox-checked')
94
+ );
95
+ },
96
+ /** 最终使用的未选中态的图标 */
97
+ usingUncheckedIcon() {
98
+ return (
99
+ this.checkboxGroupProps.uncheckedIcon ||
100
+ this.uncheckedIcon ||
101
+ (this.isRadio ? 'radio-unchecked' : 'checkbox-unchecked')
102
+ );
103
+ },
104
+ /** 最终使用的图标 */
105
+ usingIcon() {
106
+ return this.isChecked ? this.usingCheckedIcon : this.usingUncheckedIcon;
107
+ },
108
+ isChecked: {
109
+ get() {
110
+ if (this.isInGroup) {
111
+ return this.isRadio
112
+ ? this.value === this.groupCheckedValue
113
+ : this.groupCheckedValue?.includes(this.value) || false;
114
+ }
115
+ return this.value === null ? this.innerChecked : this.value;
116
+ },
117
+ set(value) {
118
+ this.$emit('input', value);
119
+ this.innerChecked = value;
120
+ this.$emit('change', this.innerChecked);
121
+ },
122
+ },
123
+ },
124
+ methods: {
125
+ /** 切换选择 */
126
+ toggle(isChecked) {
127
+ if (this.isReadonly) return;
128
+
129
+ if (this.isInGroup) {
130
+ this.checkGroup(this.value);
131
+ return;
132
+ }
133
+
134
+ if (isChecked === undefined || isChecked === null) {
135
+ this.isChecked = !this.isChecked;
136
+ return;
137
+ }
138
+
139
+ if (this.isChecked !== !!isChecked) {
140
+ this.isChecked = !!isChecked;
141
+ }
142
+ },
143
+ },
144
+ };
145
+ </script>
146
+
147
+ <style lang="scss" module>
148
+ @use '../../styles/index' as *;
149
+
150
+ // 定义外部变量
151
+ $component-name: 'checkbox';
152
+ $component-vars: $checkbox;
153
+ %external-vars {
154
+ @include UTILS__DEFINE_COMPONENT_VARS();
155
+ }
156
+
157
+ .Checkbox {
158
+ @extend %external-vars;
159
+ display: flex;
160
+ align-items: center;
161
+
162
+ .Checkbox--image {
163
+ flex: none;
164
+ width: 1em;
165
+ height: 1em;
166
+ color: UTILS__LOCAL_VAR(unchecked-color);
167
+ }
168
+ .Checkbox--icon {
169
+ flex: none;
170
+ color: UTILS__LOCAL_VAR(unchecked-color);
171
+ }
172
+ .Checkbox--checked {
173
+ color: UTILS__LOCAL_VAR(checked-color);
174
+ :global(.radio-checked) {
175
+ fill: UTILS__LOCAL_VAR(checked-color);
176
+ }
177
+ }
178
+ .Checkbox--text {
179
+ flex: 0 1 auto;
180
+ margin-left: 12rpx;
181
+ }
182
+ }
183
+ </style>