im-ui-mobile 0.1.0 → 0.1.2

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 (85) hide show
  1. package/components/im-avatar/im-avatar.vue +7 -7
  2. package/components/im-badge/im-badge.vue +326 -0
  3. package/components/im-button/im-button.vue +71 -34
  4. package/components/im-card/im-card.vue +563 -0
  5. package/components/im-chat-item/im-chat-item.vue +5 -4
  6. package/components/im-col/im-col.vue +191 -0
  7. package/components/im-dialog/im-dialog.vue +543 -0
  8. package/components/im-double-tap-view/im-double-tap-view.vue +93 -0
  9. package/components/im-emoji-picker/im-emoji-picker.vue +1143 -0
  10. package/components/im-friend-item/im-friend-item.vue +1 -1
  11. package/components/im-group-item/im-group-item.vue +1 -1
  12. package/components/im-group-member-selector/im-group-member-selector.vue +5 -5
  13. package/components/im-group-rtc-join/im-group-rtc-join.vue +8 -8
  14. package/components/im-icon/im-icon.vue +593 -0
  15. package/components/im-image-upload/im-image-upload.vue +0 -2
  16. package/components/im-link/im-link.vue +628 -0
  17. package/components/im-loading/im-loading.vue +13 -4
  18. package/components/im-mention-picker/im-mention-picker.vue +8 -7
  19. package/components/im-message-action/im-message-action.vue +678 -0
  20. package/components/im-message-item/im-message-item.vue +28 -26
  21. package/components/im-message-list/im-message-list.vue +1108 -0
  22. package/components/im-modal/im-modal.vue +373 -0
  23. package/components/im-nav-bar/im-nav-bar.vue +689 -75
  24. package/components/im-parse/im-parse.vue +1054 -0
  25. package/components/im-popup/im-popup.vue +467 -0
  26. package/components/im-read-receipt/im-read-receipt.vue +10 -10
  27. package/components/im-row/im-row.vue +189 -0
  28. package/components/im-search/im-search.vue +762 -0
  29. package/components/im-sku/im-sku.vue +720 -0
  30. package/components/im-sku/utils/helper.ts +182 -0
  31. package/components/im-stepper/im-stepper.vue +585 -0
  32. package/components/im-stepper/utils/helper.ts +167 -0
  33. package/components/im-tabs/im-tabs.vue +1022 -0
  34. package/components/im-tabs/tabs-navigation.vue +489 -0
  35. package/components/im-tabs/utils/helper.ts +181 -0
  36. package/components/im-tabs-tab-pane/im-tabs-tab-pane.vue +145 -0
  37. package/components/im-upload/im-upload.vue +1236 -0
  38. package/components/im-voice-input/im-voice-input.vue +1 -1
  39. package/index.js +3 -5
  40. package/index.scss +19 -0
  41. package/libs/emoji-data.ts +229 -0
  42. package/libs/index.ts +16 -16
  43. package/package.json +1 -2
  44. package/styles/button.scss +33 -33
  45. package/theme.scss +2 -2
  46. package/types/components/badge.d.ts +42 -0
  47. package/types/components/button.d.ts +2 -1
  48. package/types/components/card.d.ts +122 -0
  49. package/types/components/col.d.ts +37 -0
  50. package/types/components/dialog.d.ts +125 -0
  51. package/types/components/double-tap-view.d.ts +31 -0
  52. package/types/components/emoji-picker.d.ts +121 -0
  53. package/types/components/group-rtc-join.d.ts +1 -1
  54. package/types/components/icon.d.ts +77 -0
  55. package/types/components/link.d.ts +55 -0
  56. package/types/components/loading.d.ts +1 -0
  57. package/types/components/message-action.d.ts +96 -0
  58. package/types/components/message-item.d.ts +2 -2
  59. package/types/components/message-list.d.ts +136 -0
  60. package/types/components/modal.d.ts +106 -0
  61. package/types/components/nav-bar.d.ts +125 -0
  62. package/types/components/parse.d.ts +90 -0
  63. package/types/components/popup.d.ts +58 -0
  64. package/types/components/row.d.ts +31 -0
  65. package/types/components/search.d.ts +54 -0
  66. package/types/components/sku.d.ts +195 -0
  67. package/types/components/stepper.d.ts +99 -0
  68. package/types/components/tabs-tab-pane.d.ts +27 -0
  69. package/types/components/tabs.d.ts +117 -0
  70. package/types/components/upload.d.ts +137 -0
  71. package/types/components.d.ts +19 -1
  72. package/types/index.d.ts +38 -1
  73. package/types/libs/index.d.ts +10 -10
  74. package/types/utils/base64.d.ts +5 -0
  75. package/types/utils/dom.d.ts +3 -0
  76. package/types/utils/enums.d.ts +4 -5
  77. package/types/utils/validator.d.ts +74 -0
  78. package/utils/base64.js +18 -0
  79. package/utils/dom.js +353 -1
  80. package/utils/enums.js +4 -5
  81. package/utils/validator.js +230 -0
  82. package/components/im-file-upload/im-file-upload.vue +0 -309
  83. package/plugins/uview-plus.js +0 -29
  84. package/types/components/arrow-bar.d.ts +0 -14
  85. package/types/components/file-upload.d.ts +0 -58
@@ -0,0 +1,720 @@
1
+ <template>
2
+ <im-popup v-model="innerVisible" position="bottom" :closeOnClickOverlay="true" @close="handleClose"
3
+ :show-close="showClose">
4
+ <view class="im-sku">
5
+ <!-- 商品信息 -->
6
+ <view class="sku-header">
7
+ <image class="sku-goods-image" :src="currentGoods.imgUrl" mode="aspectFill" />
8
+ <view class="sku-goods-info">
9
+ <view class="sku-goods-price">
10
+ <text class="sku-current-price">
11
+ {{ formatPrice(currentPrice) }}
12
+ </text>
13
+ <text v-if="currentOriginPrice" class="sku-origin-price">
14
+ {{ formatPrice(currentOriginPrice) }}
15
+ </text>
16
+ </view>
17
+ <view class="sku-goods-title">
18
+ {{ currentGoods.title }}
19
+ </view>
20
+ <view v-if="selectedSku.valid" class="sku-selected-info">
21
+ <text v-if="showStock" class="sku-stock">
22
+ 库存 {{ currentStock }} {{ currentGoods.unit || '件' }}
23
+ </text>
24
+ <text v-if="selectedSkuText" class="sku-selected-text">
25
+ 已选:{{ selectedSkuText }}
26
+ </text>
27
+ </view>
28
+ <view v-else class="sku-tip">
29
+ 请选择规格
30
+ </view>
31
+ </view>
32
+ </view>
33
+
34
+ <!-- 属性/规格选择 -->
35
+ <scroll-view class="sku-body" scroll-y :style="{ maxHeight: bodyMaxHeight + 'rpx' }">
36
+ <!-- 属性列表 -->
37
+ <view v-for="attr in attrs" :key="attr.id || attr.name" class="sku-spec-group">
38
+ <view class="sku-spec-title">
39
+ {{ attr.name }}
40
+ </view>
41
+ <view class="sku-spec-values">
42
+ <view v-for="value in attr.values" :key="value.id || value.name" class="sku-spec-value" :class="[
43
+ getSpecValueClass(attr.name, value.name),
44
+ { 'sku-spec-value-disabled': value.disabled }
45
+ ]" :style="value.style" @click="selectAttr(attr.name, value.name)">
46
+ <!-- 图片规格 -->
47
+ <template v-if="value.imgUrl">
48
+ <image class="sku-spec-image" :src="value.imgUrl" mode="aspectFill" />
49
+ <text class="sku-spec-image-text">{{ value.name }}</text>
50
+ </template>
51
+
52
+ <!-- 文字规格 -->
53
+ <template v-else>
54
+ {{ value.name }}
55
+ </template>
56
+
57
+ <!-- 选中标记 -->
58
+ <view v-if="isSpecSelected(attr.name, value.name)" class="sku-spec-selected">
59
+ <im-icon name="check" size="14" color="#ffffff" />
60
+ </view>
61
+ </view>
62
+ </view>
63
+ </view>
64
+
65
+ <!-- 规格列表 -->
66
+ <view v-for="spec in specs" :key="spec.id || spec.name" class="sku-spec-group">
67
+ <view class="sku-spec-title">
68
+ {{ spec.name }}
69
+ </view>
70
+ <view class="sku-spec-values">
71
+ <view v-for="value in spec.values" :key="value.id || value.name" class="sku-spec-value" :class="[
72
+ getSpecValueClass(spec.name, value.name),
73
+ { 'sku-spec-value-disabled': value.disabled }
74
+ ]" :style="value.style" @click="selectSpec(spec.name, value.name)">
75
+ <!-- 图片规格 -->
76
+ <template v-if="value.imgUrl">
77
+ <image class="sku-spec-image" :src="value.imgUrl" mode="aspectFill" />
78
+ <text class="sku-spec-image-text">{{ value.name }}</text>
79
+ </template>
80
+
81
+ <!-- 文字规格 -->
82
+ <template v-else>
83
+ {{ value.name }}
84
+ </template>
85
+
86
+ <!-- 选中标记 -->
87
+ <view v-if="isSpecSelected(spec.name, value.name)" class="sku-spec-selected">
88
+ <im-icon name="check" size="14" color="#ffffff" />
89
+ </view>
90
+ </view>
91
+ </view>
92
+ </view>
93
+
94
+ <!-- 数量选择 -->
95
+ <view class="sku-quantity">
96
+ <view class="sku-quantity-title">购买数量</view>
97
+ <im-stepper v-model="quantity" :min="quantityLimit.min || 1"
98
+ :max="Math.min(quantityLimit.max || 9999, currentStock)" :step="quantityLimit.step || 1"
99
+ :disabled="!selectedSku.valid" />
100
+ </view>
101
+
102
+ <!-- 商品描述 -->
103
+ <view v-if="currentGoods.description" class="sku-description">
104
+ <view class="sku-description-title">商品描述</view>
105
+ <view class="sku-description-content">
106
+ {{ currentGoods.description }}
107
+ </view>
108
+ </view>
109
+ </scroll-view>
110
+
111
+ <!-- 操作按钮 -->
112
+ <view class="sku-footer">
113
+ <!-- 自定义操作按钮 -->
114
+ <view v-for="(action, index) in customActions" :key="index" class="sku-custom-action"
115
+ :class="`sku-action-${action.type || 'default'}`" :style="action.style"
116
+ @click="handleCustomAction(index, action)">
117
+ {{ action.text }}
118
+ </view>
119
+
120
+ <!-- 加入购物车 -->
121
+ <view v-if="showAddToCart" class="sku-action sku-action-cart"
122
+ :class="{ 'sku-action-disabled': !selectedSku.valid }" @click="addToCart">
123
+ <im-icon name="cart-o" size="18" />
124
+ 加入购物车
125
+ </view>
126
+
127
+ <!-- 立即购买 -->
128
+ <view v-if="showQuickBuy" class="sku-action sku-action-buy"
129
+ :class="{ 'sku-action-disabled': !selectedSku.valid }" @click="buyNow">
130
+ 立即购买
131
+ </view>
132
+
133
+ <!-- 确认按钮(默认) -->
134
+ <view v-if="!showAddToCart && !showQuickBuy && customActions.length === 0"
135
+ class="sku-action sku-action-confirm" :class="{ 'sku-action-disabled': !selectedSku.valid }"
136
+ @click="confirm">
137
+ 确认
138
+ </view>
139
+ </view>
140
+ </view>
141
+ </im-popup>
142
+ </template>
143
+
144
+ <script lang="ts" setup>
145
+ import type {
146
+ SkuProps,
147
+ SkuEmits,
148
+ SelectedSku
149
+ } from '../../types/components/sku'
150
+
151
+ const props = withDefaults(defineProps<SkuProps>(), {
152
+ current: undefined,
153
+ visible: false,
154
+ attrs: () => [],
155
+ specs: () => [],
156
+ skus: () => [],
157
+ quantityLimit: () => ({
158
+ min: 1,
159
+ max: 9999,
160
+ step: 1
161
+ }),
162
+ customActions: () => [],
163
+ showQuickBuy: true,
164
+ showAddToCart: true,
165
+ showStock: true,
166
+ showPrice: true,
167
+ resetOnClose: true,
168
+ showClose: false
169
+ })
170
+
171
+ const emit = defineEmits<SkuEmits>()
172
+
173
+ // 响应式数据
174
+ const innerVisible = ref(false)
175
+ const quantity = ref(1)
176
+ const selectedAttrs = ref<Record<string, string>>({})
177
+ const selectedSpecs = ref<Record<string, string>>({})
178
+ const bodyMaxHeight = ref(800)
179
+
180
+ // 计算属性
181
+ const currentGoods = computed(() => props.goods)
182
+ const currentSku = computed(() => {
183
+ return findSkuBySpecs(selectedSpecs.value)
184
+ })
185
+
186
+ const currentPrice = computed(() => {
187
+ return currentSku.value?.price || props.goods.price
188
+ })
189
+
190
+ const currentOriginPrice = computed(() => {
191
+ return currentSku.value?.originPrice || props.goods.originPrice
192
+ })
193
+
194
+ const currentStock = computed(() => {
195
+ return currentSku.value?.stock || props.goods.stock || 0
196
+ })
197
+
198
+ const selectedSku = computed<SelectedSku>(() => {
199
+ let skuItem = currentSku.value
200
+ let valid = true
201
+
202
+ if (props.attrs.length > 0) {
203
+ valid = areAllRequiredAttrsSelected()
204
+ }
205
+
206
+ if (props.specs.length > 0 && props.skus.length > 0) {
207
+ valid = !!skuItem && skuItem.stock > 0 && !skuItem.disabled && areAllRequiredSpecsSelected()
208
+ }
209
+
210
+ return {
211
+ selectedAttrs: { ...selectedAttrs.value },
212
+ selectedSpecs: { ...selectedSpecs.value },
213
+ skuId: skuItem?.id,
214
+ quantity: quantity.value,
215
+ price: currentPrice.value,
216
+ originPrice: currentOriginPrice.value,
217
+ stock: currentStock.value,
218
+ valid,
219
+ goodsInfo: currentGoods.value
220
+ }
221
+ })
222
+
223
+ const selectedSkuText = computed(() => {
224
+ const specs = selectedSpecs.value
225
+ if (!specs || Object.keys(specs).length === 0) return ''
226
+
227
+ return Object.values(specs).join(',')
228
+ })
229
+
230
+ // 根据选中的规格查找SKU
231
+ const findSkuBySpecs = (specs: Record<string, string>) => {
232
+ if (Object.keys(specs).length === 0 || Object.keys(specs).length !== props.specs.length) {
233
+ return
234
+ }
235
+
236
+ return props.skus.find(sku => {
237
+ return Object.keys(specs).every(key => {
238
+ return sku.specs[key] === specs[key]
239
+ })
240
+ })
241
+ }
242
+
243
+ // 检查规格是否被选中
244
+ const isSpecSelected = (specName: string, valueName: string) => {
245
+ return selectedAttrs.value[specName] === valueName || selectedSpecs.value[specName] === valueName
246
+ }
247
+
248
+ // 获取规格值的类名
249
+ const getSpecValueClass = (specName: string, valueName: string) => {
250
+ const classes: string[] = []
251
+
252
+ if (isSpecSelected(specName, valueName)) {
253
+ classes.push('sku-spec-value-selected')
254
+ }
255
+
256
+ // 检查是否可用
257
+ const value = props.specs
258
+ .find(s => s.name === specName)
259
+ ?.values.find(v => v.name === valueName)
260
+
261
+ if (value?.disabled) {
262
+ classes.push('sku-spec-value-disabled')
263
+ }
264
+
265
+ return classes.join(' ')
266
+ }
267
+
268
+
269
+ // 选择属性
270
+ const selectAttr = (attrName: string, valueName: string) => {
271
+ const attr = props.attrs.find(s => s.name === attrName)
272
+ if (!attr) return
273
+
274
+ const value = attr.values.find(v => v.name === valueName)
275
+ if (!value || value.disabled) return
276
+
277
+ // 如果已经选中,则取消选中
278
+ if (selectedAttrs.value[attrName] === valueName) {
279
+ const newSpecs = { ...selectedAttrs.value }
280
+ delete newSpecs[attrName]
281
+ selectedAttrs.value = newSpecs
282
+ } else {
283
+
284
+ // 选中新的规格值
285
+ selectedAttrs.value = {
286
+ ...selectedAttrs.value,
287
+ [attrName]: valueName
288
+ }
289
+ }
290
+
291
+ // 触发规格变化事件
292
+ emit('spec-change', selectedSku.value)
293
+ }
294
+
295
+ // 选择规格
296
+ const selectSpec = (specName: string, valueName: string) => {
297
+ const spec = props.specs.find(s => s.name === specName)
298
+ if (!spec) return
299
+
300
+ const value = spec.values.find(v => v.name === valueName)
301
+ if (!value || value.disabled) return
302
+
303
+ // 如果已经选中,则取消选中
304
+ if (selectedSpecs.value[specName] === valueName) {
305
+ const newSpecs = { ...selectedSpecs.value }
306
+ delete newSpecs[specName]
307
+ selectedSpecs.value = newSpecs
308
+ } else {
309
+
310
+ // 选中新的规格值
311
+ selectedSpecs.value = {
312
+ ...selectedSpecs.value,
313
+ [specName]: valueName
314
+ }
315
+ }
316
+
317
+ // 触发规格变化事件
318
+ emit('spec-change', selectedSku.value)
319
+
320
+ // 如果规格组合发生变化,重置数量
321
+ const sku = findSkuBySpecs(selectedSpecs.value)
322
+ if (sku) {
323
+ quantity.value = Math.max(
324
+ props.quantityLimit.min || 1,
325
+ Math.min(quantity.value, sku.stock)
326
+ )
327
+ }
328
+ }
329
+
330
+ // 格式化价格
331
+ const formatPrice = (price: number) => {
332
+ const yuan = price / 100
333
+ return `¥${yuan.toFixed(2)}`
334
+ }
335
+
336
+ // 检查所有必选属性是否都已被选中
337
+ const areAllRequiredAttrsSelected = (): boolean => {
338
+ let selected = true
339
+
340
+ props.attrs.map(s => {
341
+ if (!s.required) { return }
342
+ selected = Object.keys(selectedAttrs.value).includes(s.name)
343
+ })
344
+
345
+ return selected
346
+ }
347
+
348
+
349
+ // 检查所有必选规格是否都已被选中
350
+ const areAllRequiredSpecsSelected = (): boolean => {
351
+ let selected = true
352
+
353
+ props.specs.map(s => {
354
+ if (!s.required) { return }
355
+ selected = Object.keys(selectedSpecs.value).includes(s.name)
356
+ })
357
+
358
+ return selected
359
+ }
360
+
361
+ // 加入购物车
362
+ const addToCart = () => {
363
+ if (!selectedSku.value.valid) return
364
+
365
+ emit('add-to-cart', selectedSku.value)
366
+ closeSku()
367
+ }
368
+
369
+ // 立即购买
370
+ const buyNow = () => {
371
+ if (!selectedSku.value.valid) return
372
+
373
+ emit('buy-now', selectedSku.value)
374
+ closeSku()
375
+ }
376
+
377
+ // 确认
378
+ const confirm = () => {
379
+ if (!selectedSku.value.valid) return
380
+
381
+ emit('confirm', selectedSku.value)
382
+ closeSku()
383
+ }
384
+
385
+ // 处理自定义操作
386
+ const handleCustomAction = (index: number, action: any) => {
387
+ if (action.onClick) {
388
+ action.onClick()
389
+ }
390
+ emit('custom-action', index)
391
+ }
392
+
393
+ // 关闭SKU选择器
394
+ const closeSku = () => {
395
+ innerVisible.value = false
396
+ }
397
+
398
+ // 处理关闭
399
+ const handleClose = () => {
400
+ emit('close')
401
+
402
+ if (props.resetOnClose) {
403
+ // 重置选中的规格和数量
404
+ selectedSpecs.value = {}
405
+ quantity.value = props.quantityLimit.min || 1
406
+ }
407
+ }
408
+
409
+ // 监听visible变化
410
+ watch(() => props.visible, (newVal) => {
411
+ innerVisible.value = newVal
412
+
413
+ if (newVal) {
414
+ // 打开时初始化数据
415
+ if (props.initialSku) {
416
+ selectedAttrs.value = { ...props.initialSku.selectedAttrs }
417
+ selectedSpecs.value = { ...props.initialSku.selectedSpecs }
418
+ quantity.value = props.initialSku.quantity
419
+ }
420
+
421
+ // 计算滚动区域高度
422
+ nextTick(() => {
423
+ const systemInfo = uni.getSystemInfoSync()
424
+ bodyMaxHeight.value = systemInfo.windowHeight * 0.8
425
+ })
426
+ }
427
+ })
428
+
429
+ // 监听innerVisible变化(用于v-model)
430
+ watch(innerVisible, (newVal) => {
431
+ emit('update:visible', newVal)
432
+ })
433
+
434
+ // 监听数量变化
435
+ watch(quantity, (newVal) => {
436
+ emit('quantity-change', newVal)
437
+ })
438
+
439
+ </script>
440
+
441
+ <style lang="scss" scoped>
442
+ // 文本溢出省略
443
+ @mixin text-ellipsis($lines: 1) {
444
+ overflow: hidden;
445
+ text-overflow: ellipsis;
446
+
447
+ @if $lines ==1 {
448
+ white-space: nowrap;
449
+ }
450
+
451
+ @else {
452
+ display: -webkit-box;
453
+ -webkit-line-clamp: $lines;
454
+ -webkit-box-orient: vertical;
455
+ }
456
+ }
457
+
458
+ .im-sku {
459
+ background: #ffffff;
460
+ border-radius: 30rpx 30rpx 0 0;
461
+ overflow: hidden;
462
+
463
+ .sku-header {
464
+ display: flex;
465
+ padding: 30rpx;
466
+ border-bottom: 1px solid #f5f5f5;
467
+ position: relative;
468
+
469
+ .sku-goods-image {
470
+ width: 80px;
471
+ height: 80px;
472
+ border-radius: 8px;
473
+ margin-right: 12px;
474
+ flex-shrink: 0;
475
+ }
476
+
477
+ .sku-goods-info {
478
+ flex: 1;
479
+ min-width: 0;
480
+
481
+ .sku-goods-price {
482
+ display: flex;
483
+ align-items: center;
484
+ margin-bottom: 8px;
485
+
486
+ .sku-current-price {
487
+ font-size: 20px;
488
+ font-weight: 600;
489
+ color: #ff4444;
490
+ margin-right: 8px;
491
+ }
492
+
493
+ .sku-origin-price {
494
+ font-size: 14px;
495
+ color: #999999;
496
+ text-decoration: line-through;
497
+ }
498
+ }
499
+
500
+ .sku-goods-title {
501
+ font-size: 14px;
502
+ color: #333333;
503
+ line-height: 1.4;
504
+ margin-bottom: 8px;
505
+ @include text-ellipsis(2);
506
+ }
507
+
508
+ .sku-selected-info {
509
+ font-size: 12px;
510
+ color: #666666;
511
+
512
+ .sku-stock {
513
+ margin-right: 12px;
514
+ }
515
+
516
+ .sku-selected-text {
517
+ color: #ff4444;
518
+ }
519
+ }
520
+
521
+ .sku-tip {
522
+ font-size: 12px;
523
+ color: #ff4444;
524
+ }
525
+ }
526
+ }
527
+
528
+ .sku-body {
529
+ padding: 0 30rpx;
530
+
531
+ .sku-spec-group {
532
+ padding: 30rpx 0;
533
+ border-bottom: 1px solid #f5f5f5;
534
+
535
+ &:last-child {
536
+ border-bottom: none;
537
+ }
538
+
539
+ .sku-spec-title {
540
+ font-size: 14px;
541
+ color: #333333;
542
+ font-weight: 500;
543
+ margin-bottom: 12px;
544
+ }
545
+
546
+ .sku-spec-values {
547
+ display: flex;
548
+ flex-wrap: wrap;
549
+ gap: 10px;
550
+
551
+ .sku-spec-value {
552
+ position: relative;
553
+ padding: 8px 30rpx;
554
+ border: 1px solid #e5e5e5;
555
+ border-radius: 20px;
556
+ font-size: 13px;
557
+ color: #333333;
558
+ background-color: #ffffff;
559
+ transition: all 0.2s;
560
+ display: flex;
561
+ align-items: center;
562
+ justify-content: center;
563
+ min-width: 60px;
564
+ box-sizing: border-box;
565
+
566
+ &.sku-spec-value-selected {
567
+ border-color: #ff4444;
568
+ color: #ff4444;
569
+ background-color: rgba(255, 68, 68, 0.05);
570
+ }
571
+
572
+ &.sku-spec-value-disabled {
573
+ color: #cccccc;
574
+ border-color: #f0f0f0;
575
+ background-color: #f9f9f9;
576
+ cursor: not-allowed;
577
+
578
+ &::after {
579
+ content: '';
580
+ position: absolute;
581
+ top: 50%;
582
+ left: 8px;
583
+ right: 8px;
584
+ height: 1px;
585
+ background-color: #cccccc;
586
+ transform: rotate(-45deg);
587
+ }
588
+ }
589
+
590
+ .sku-spec-image {
591
+ width: 40px;
592
+ height: 40px;
593
+ border-radius: 4px;
594
+ margin-right: 8px;
595
+ }
596
+
597
+ .sku-spec-image-text {
598
+ font-size: 12px;
599
+ }
600
+
601
+ .sku-spec-selected {
602
+ position: absolute;
603
+ right: -1px;
604
+ bottom: -1px;
605
+ width: 30rpx;
606
+ height: 30rpx;
607
+ background-color: #ff4444;
608
+ border-radius: 50%;
609
+ display: flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ }
613
+ }
614
+ }
615
+ }
616
+
617
+ .sku-quantity {
618
+ display: flex;
619
+ align-items: center;
620
+ justify-content: space-between;
621
+ padding: 30rpx 0;
622
+ border-bottom: 1px solid #f5f5f5;
623
+ width: calc(100% - 60rpx);
624
+
625
+ .sku-quantity-title {
626
+ font-size: 14px;
627
+ color: #333333;
628
+ font-weight: 500;
629
+ }
630
+ }
631
+
632
+ .sku-description {
633
+ padding: 30rpx 0;
634
+
635
+ .sku-description-title {
636
+ font-size: 14px;
637
+ color: #333333;
638
+ font-weight: 500;
639
+ margin-bottom: 8px;
640
+ }
641
+
642
+ .sku-description-content {
643
+ font-size: 13px;
644
+ color: #666666;
645
+ line-height: 1.5;
646
+ }
647
+ }
648
+ }
649
+
650
+ .sku-footer {
651
+ display: flex;
652
+ padding: 12px 30rpx;
653
+ background-color: #ffffff;
654
+ border-top: 1px solid #f5f5f5;
655
+ gap: 10px;
656
+
657
+ .sku-custom-action {
658
+ flex: 1;
659
+ padding: 12px 0;
660
+ border-radius: 20px;
661
+ font-size: 14px;
662
+ text-align: center;
663
+ font-weight: 500;
664
+
665
+ &.sku-action-default {
666
+ background-color: #f5f5f5;
667
+ color: #333333;
668
+ }
669
+
670
+ &.sku-action-primary {
671
+ background-color: #1989fa;
672
+ color: #ffffff;
673
+ }
674
+
675
+ &.sku-action-warning {
676
+ background-color: #ff976a;
677
+ color: #ffffff;
678
+ }
679
+
680
+ &.sku-action-danger {
681
+ background-color: #ff4444;
682
+ color: #ffffff;
683
+ }
684
+ }
685
+
686
+ .sku-action {
687
+ flex: 1;
688
+ padding: 12px 0;
689
+ border-radius: 20px;
690
+ font-size: 14px;
691
+ text-align: center;
692
+ font-weight: 500;
693
+ display: flex;
694
+ align-items: center;
695
+ justify-content: center;
696
+ gap: 6px;
697
+
698
+ &.sku-action-disabled {
699
+ opacity: 0.5;
700
+ cursor: not-allowed;
701
+ }
702
+
703
+ &.sku-action-cart {
704
+ background-color: #f5f5f5;
705
+ color: #333333;
706
+ }
707
+
708
+ &.sku-action-buy {
709
+ background-color: #ff4444;
710
+ color: #ffffff;
711
+ }
712
+
713
+ &.sku-action-confirm {
714
+ background-color: #1989fa;
715
+ color: #ffffff;
716
+ }
717
+ }
718
+ }
719
+ }
720
+ </style>