@xlui/xux-ui 0.1.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/README.md +55 -0
- package/dist/index.css +1 -0
- package/dist/index.js +128 -0
- package/dist/index.mjs +4819 -0
- package/package.json +57 -0
- package/src/components/Accordion/index.vue +355 -0
- package/src/components/Button/index.vue +440 -0
- package/src/components/Card/index.vue +386 -0
- package/src/components/Checkboxes/index.vue +416 -0
- package/src/components/CountrySelect/data/countries.json +2084 -0
- package/src/components/CountrySelect/index.vue +319 -0
- package/src/components/Input/index.vue +293 -0
- package/src/components/Modal/index.vue +360 -0
- package/src/components/Select/index.vue +411 -0
- package/src/components/Skeleton/index.vue +110 -0
- package/src/components/ThumbnailContainer/index.vue +451 -0
- package/src/composables/Msg.ts +349 -0
- package/src/index.ts +28 -0
- package/src/styles/theme.css +120 -0
@@ -0,0 +1,386 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="x-card-container">
|
3
|
+
<!-- 瀑布流布局 -->
|
4
|
+
<div v-if="layout === 'masonry'" :class="['masonry-layout', masonryColsClass, 'gap-4 sm:gap-6']">
|
5
|
+
<div
|
6
|
+
v-for="product in displayProducts"
|
7
|
+
:key="product.id + '-masonry'"
|
8
|
+
class="mb-4 sm:mb-6 break-inside-avoid group cursor-pointer product-card"
|
9
|
+
@click="handleProductClick(product.id)"
|
10
|
+
>
|
11
|
+
<div class="relative p-2">
|
12
|
+
<!-- 瀑布流保持自然高度 -->
|
13
|
+
<img
|
14
|
+
:src="product.image"
|
15
|
+
:alt="product.name"
|
16
|
+
loading="lazy"
|
17
|
+
class="w-full h-auto rounded-lg bg-gray-100 object-cover transition-transform duration-300 group-hover:scale-[1.01]"
|
18
|
+
/>
|
19
|
+
|
20
|
+
<div v-if="product.label" class="absolute top-3 left-3">
|
21
|
+
<span class="label-badge">{{ product.label }}</span>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class="product-info p-5 ml-5">
|
26
|
+
<h3 class="text-sm lg:text-base text-gray-700 leading-relaxed line-clamp-2 mb-3">{{ product.name }}</h3>
|
27
|
+
<div class="flex items-center justify-between gap-2">
|
28
|
+
<span class="text-base lg:text-lg font-bold text-[var(--x-color-primary)]">{{ product.price }}</span>
|
29
|
+
<XButton
|
30
|
+
@click.stop="handleLike(product)"
|
31
|
+
type="text"
|
32
|
+
size="small"
|
33
|
+
class="like-button !min-w-0 !p-1"
|
34
|
+
>
|
35
|
+
<i v-if="!product.isWish" class="bi bi-heart text-gray-500"></i>
|
36
|
+
<i v-else class="bi bi-heart-fill text-[#FE374F]"></i>
|
37
|
+
</XButton>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<!-- 网格布局 -->
|
44
|
+
<div v-else :class="['grid', gridColsClass, 'gap-4 lg:gap-6']">
|
45
|
+
<div v-for="product in displayProducts" :key="product.id + '-grid'" class="h-full">
|
46
|
+
<div
|
47
|
+
class="group cursor-pointer product-card h-full flex flex-col"
|
48
|
+
@click="handleProductClick(product.id)"
|
49
|
+
>
|
50
|
+
<div class="relative flex-shrink-0 p-2">
|
51
|
+
<!-- 网格布局使用固定比例 -->
|
52
|
+
<div class="aspect-square bg-gray-100 rounded-lg overflow-hidden">
|
53
|
+
<img
|
54
|
+
:src="product.image"
|
55
|
+
:alt="product.name"
|
56
|
+
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
57
|
+
loading="lazy"
|
58
|
+
/>
|
59
|
+
</div>
|
60
|
+
|
61
|
+
<div v-if="product.label" class="absolute top-3 left-3">
|
62
|
+
<span class="label-badge">{{ product.label }}</span>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
|
66
|
+
<div class="flex-1 flex flex-col justify-between product-info p-5 ml-5">
|
67
|
+
<h3 class="text-sm lg:text-base text-gray-700 font-medium line-clamp-2 leading-relaxed mb-3">
|
68
|
+
{{ product.name }}
|
69
|
+
</h3>
|
70
|
+
<div class="flex items-center justify-between gap-2 mt-auto">
|
71
|
+
<span class="text-base lg:text-lg font-bold text-[var(--x-color-primary)]">{{ product.price }}</span>
|
72
|
+
<XButton
|
73
|
+
@click.stop="handleLike(product)"
|
74
|
+
type="text"
|
75
|
+
size="small"
|
76
|
+
class="like-button !min-w-0 !p-1"
|
77
|
+
>
|
78
|
+
<Icon v-if="!product.isWish" icon="bi:heart" class="bi bi-heart text-gray-500"></Icon>
|
79
|
+
<Icon v-else icon="bi:heart-fill" class="bi bi-heart-fill text-[#FE374F]"></Icon>
|
80
|
+
</XButton>
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
</template>
|
88
|
+
|
89
|
+
<script setup lang="ts">
|
90
|
+
import XButton from '../Button/index.vue'
|
91
|
+
import { ref, computed, onMounted } from 'vue'
|
92
|
+
import { Icon } from '@iconify/vue'
|
93
|
+
/**
|
94
|
+
* Card 产品卡片组件
|
95
|
+
* @displayName XCard
|
96
|
+
*/
|
97
|
+
|
98
|
+
interface Product {
|
99
|
+
id: string
|
100
|
+
name: string
|
101
|
+
image: string
|
102
|
+
label: string
|
103
|
+
price: string
|
104
|
+
isWish: boolean
|
105
|
+
}
|
106
|
+
|
107
|
+
export interface CardProps {
|
108
|
+
/** 布局模式: masonry(瀑布流) | grid(网格) */
|
109
|
+
layout?: 'masonry' | 'grid'
|
110
|
+
/** 产品数据列表 */
|
111
|
+
products?: Product[]
|
112
|
+
/** 是否使用模拟数据 */
|
113
|
+
useMockData?: boolean
|
114
|
+
/** 列数: 1-4 */
|
115
|
+
columns?: number
|
116
|
+
}
|
117
|
+
|
118
|
+
const props = withDefaults(defineProps<CardProps>(), {
|
119
|
+
layout: 'grid',
|
120
|
+
products: () => [],
|
121
|
+
useMockData: true,
|
122
|
+
columns: 4
|
123
|
+
})
|
124
|
+
|
125
|
+
const emit = defineEmits<{
|
126
|
+
/** 点击产品卡片时触发 */
|
127
|
+
productClick: [id: string]
|
128
|
+
/** 点击收藏按钮时触发 */
|
129
|
+
like: [product: Product]
|
130
|
+
}>()
|
131
|
+
|
132
|
+
// 模拟数据
|
133
|
+
const mockProducts: Product[] = [
|
134
|
+
{
|
135
|
+
id: '1',
|
136
|
+
name: '北欧风简约陶瓷花瓶',
|
137
|
+
image: 'https://images.unsplash.com/photo-1578500494198-246f612d3b3d?w=400',
|
138
|
+
price: '¥299',
|
139
|
+
label: '热卖',
|
140
|
+
isWish: false
|
141
|
+
},
|
142
|
+
{
|
143
|
+
id: '2',
|
144
|
+
name: '手工编织棉麻抱枕套',
|
145
|
+
image: 'https://images.unsplash.com/photo-1584100936595-c0654b55a2e2?w=400',
|
146
|
+
price: '¥89',
|
147
|
+
label: '新品',
|
148
|
+
isWish: true
|
149
|
+
},
|
150
|
+
{
|
151
|
+
id: '3',
|
152
|
+
name: '实木原木置物架收纳架',
|
153
|
+
image: 'https://images.unsplash.com/photo-1595428774223-ef52624120d2?w=400',
|
154
|
+
price: '¥459',
|
155
|
+
label: '',
|
156
|
+
isWish: false
|
157
|
+
},
|
158
|
+
{
|
159
|
+
id: '4',
|
160
|
+
name: 'ins风格桌面装饰摆件',
|
161
|
+
image: 'https://images.unsplash.com/photo-1513506003901-1e6a229e2d15?w=400',
|
162
|
+
price: '¥129',
|
163
|
+
label: '特价',
|
164
|
+
isWish: false
|
165
|
+
},
|
166
|
+
{
|
167
|
+
id: '5',
|
168
|
+
name: '现代简约台灯护眼学习灯',
|
169
|
+
image: 'https://images.unsplash.com/photo-1507473885765-e6ed057f782c?w=400',
|
170
|
+
price: '¥399',
|
171
|
+
label: '',
|
172
|
+
isWish: true
|
173
|
+
},
|
174
|
+
{
|
175
|
+
id: '6',
|
176
|
+
name: '北欧风格装饰画挂画',
|
177
|
+
image: 'https://images.unsplash.com/photo-1561214115-f2f134cc4912?w=400',
|
178
|
+
price: '¥199',
|
179
|
+
label: '热卖',
|
180
|
+
isWish: false
|
181
|
+
},
|
182
|
+
{
|
183
|
+
id: '7',
|
184
|
+
name: '多功能收纳盒桌面整理',
|
185
|
+
image: 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?w=400',
|
186
|
+
price: '¥68',
|
187
|
+
label: '',
|
188
|
+
isWish: false
|
189
|
+
},
|
190
|
+
{
|
191
|
+
id: '8',
|
192
|
+
name: '创意陶瓷马克杯咖啡杯',
|
193
|
+
image: 'https://images.unsplash.com/photo-1514228742587-6b1558fcca3d?w=400',
|
194
|
+
price: '¥59',
|
195
|
+
label: '新品',
|
196
|
+
isWish: false
|
197
|
+
},
|
198
|
+
{
|
199
|
+
id: '9',
|
200
|
+
name: '手工玻璃花瓶透明花器',
|
201
|
+
image: 'https://images.unsplash.com/photo-1523293182086-7651a899d37f?w=400',
|
202
|
+
price: '¥179',
|
203
|
+
label: '',
|
204
|
+
isWish: true
|
205
|
+
},
|
206
|
+
{
|
207
|
+
id: '10',
|
208
|
+
name: '日式和风招财猫摆件',
|
209
|
+
image: 'https://images.unsplash.com/photo-1615529182904-14819c35db37?w=400',
|
210
|
+
price: '¥149',
|
211
|
+
label: '热卖',
|
212
|
+
isWish: false
|
213
|
+
},
|
214
|
+
{
|
215
|
+
id: '11',
|
216
|
+
name: '北欧风木质挂钟客厅',
|
217
|
+
image: 'https://images.unsplash.com/photo-1563861826100-9cb868fdbe1c?w=400',
|
218
|
+
price: '¥259',
|
219
|
+
label: '',
|
220
|
+
isWish: false
|
221
|
+
},
|
222
|
+
{
|
223
|
+
id: '12',
|
224
|
+
name: '现代简约落地花架',
|
225
|
+
image: 'https://images.unsplash.com/photo-1545127398-14699f92334b?w=400',
|
226
|
+
price: '¥329',
|
227
|
+
label: '特价',
|
228
|
+
isWish: false
|
229
|
+
}
|
230
|
+
]
|
231
|
+
|
232
|
+
const internalProducts = ref<Product[]>([])
|
233
|
+
|
234
|
+
// 计算显示的产品列表
|
235
|
+
const displayProducts = computed(() => {
|
236
|
+
if (props.products && props.products.length > 0) {
|
237
|
+
return props.products
|
238
|
+
}
|
239
|
+
return internalProducts.value
|
240
|
+
})
|
241
|
+
|
242
|
+
// 计算网格列数类名
|
243
|
+
const gridColsClass = computed(() => {
|
244
|
+
const cols = Math.min(Math.max(props.columns || 4, 1), 4)
|
245
|
+
const colsMap: Record<number, string> = {
|
246
|
+
1: 'grid-cols-1',
|
247
|
+
2: 'grid-cols-1 md:grid-cols-2',
|
248
|
+
3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
249
|
+
4: 'grid-cols-2 md:grid-cols-3 lg:grid-cols-4'
|
250
|
+
}
|
251
|
+
return colsMap[cols] || colsMap[4]
|
252
|
+
})
|
253
|
+
|
254
|
+
// 计算瀑布流列数类名
|
255
|
+
const masonryColsClass = computed(() => {
|
256
|
+
const cols = Math.min(Math.max(props.columns || 4, 1), 4)
|
257
|
+
const colsMap: Record<number, string> = {
|
258
|
+
1: 'columns-1',
|
259
|
+
2: 'columns-1 md:columns-2',
|
260
|
+
3: 'columns-2 md:columns-2 lg:columns-3',
|
261
|
+
4: 'columns-2 sm:columns-2 md:columns-3 lg:columns-4'
|
262
|
+
}
|
263
|
+
return colsMap[cols] || colsMap[4]
|
264
|
+
})
|
265
|
+
|
266
|
+
// 加载模拟数据
|
267
|
+
onMounted(() => {
|
268
|
+
if (props.useMockData && (!props.products || props.products.length === 0)) {
|
269
|
+
internalProducts.value = mockProducts
|
270
|
+
}
|
271
|
+
})
|
272
|
+
|
273
|
+
// 点击产品卡片
|
274
|
+
const handleProductClick = (id: string) => {
|
275
|
+
emit('productClick', id)
|
276
|
+
}
|
277
|
+
|
278
|
+
// 点击收藏
|
279
|
+
const handleLike = (product: Product) => {
|
280
|
+
// 切换收藏状态
|
281
|
+
product.isWish = !product.isWish
|
282
|
+
emit('like', product)
|
283
|
+
}
|
284
|
+
</script>
|
285
|
+
|
286
|
+
<style scoped>
|
287
|
+
.x-card-container {
|
288
|
+
width: 100%;
|
289
|
+
}
|
290
|
+
|
291
|
+
.product-card {
|
292
|
+
background: var(--x-color-white);
|
293
|
+
border-radius: var(--x-radius-lg);
|
294
|
+
overflow: hidden;
|
295
|
+
transition: all var(--x-transition);
|
296
|
+
box-shadow: var(--x-shadow-sm);
|
297
|
+
}
|
298
|
+
|
299
|
+
.product-card:hover {
|
300
|
+
box-shadow: var(--x-shadow-md);
|
301
|
+
transform: translateY(-2px);
|
302
|
+
}
|
303
|
+
|
304
|
+
.product-info {
|
305
|
+
padding: 0;
|
306
|
+
}
|
307
|
+
|
308
|
+
/* 标签徽章 */
|
309
|
+
.label-badge {
|
310
|
+
display: inline-block;
|
311
|
+
background: var(--x-color-primary);
|
312
|
+
color: var(--x-color-white);
|
313
|
+
font-size: 12px;
|
314
|
+
padding: 4px 8px;
|
315
|
+
border-radius: var(--x-radius-sm);
|
316
|
+
font-weight: 500;
|
317
|
+
}
|
318
|
+
|
319
|
+
/* 收藏按钮 */
|
320
|
+
.like-button {
|
321
|
+
flex-shrink: 0;
|
322
|
+
}
|
323
|
+
|
324
|
+
.like-button i {
|
325
|
+
font-size: 18px;
|
326
|
+
transition: all var(--x-transition-fast);
|
327
|
+
}
|
328
|
+
|
329
|
+
.like-button:hover i {
|
330
|
+
transform: scale(1.1);
|
331
|
+
}
|
332
|
+
|
333
|
+
/* 文本截断 */
|
334
|
+
.line-clamp-1 {
|
335
|
+
display: -webkit-box;
|
336
|
+
-webkit-line-clamp: 1;
|
337
|
+
line-clamp: 1;
|
338
|
+
-webkit-box-orient: vertical;
|
339
|
+
overflow: hidden;
|
340
|
+
}
|
341
|
+
|
342
|
+
.line-clamp-2 {
|
343
|
+
display: -webkit-box;
|
344
|
+
-webkit-line-clamp: 2;
|
345
|
+
line-clamp: 2;
|
346
|
+
-webkit-box-orient: vertical;
|
347
|
+
overflow: hidden;
|
348
|
+
}
|
349
|
+
|
350
|
+
/* 瀑布流布局优化 - 支持所有屏幕尺寸 */
|
351
|
+
.masonry-layout {
|
352
|
+
column-gap: 1rem;
|
353
|
+
}
|
354
|
+
|
355
|
+
.masonry-layout.columns-2 {
|
356
|
+
column-count: 2;
|
357
|
+
}
|
358
|
+
|
359
|
+
@media (min-width: 640px) {
|
360
|
+
.masonry-layout.sm\:columns-2 {
|
361
|
+
column-count: 2;
|
362
|
+
}
|
363
|
+
.masonry-layout {
|
364
|
+
column-gap: 1.5rem;
|
365
|
+
}
|
366
|
+
}
|
367
|
+
|
368
|
+
@media (min-width: 768px) {
|
369
|
+
.masonry-layout.md\:columns-3 {
|
370
|
+
column-count: 3;
|
371
|
+
}
|
372
|
+
}
|
373
|
+
|
374
|
+
@media (min-width: 1024px) {
|
375
|
+
.masonry-layout.lg\:columns-4 {
|
376
|
+
column-count: 4;
|
377
|
+
}
|
378
|
+
}
|
379
|
+
|
380
|
+
/* 防止卡片被截断 */
|
381
|
+
.break-inside-avoid {
|
382
|
+
break-inside: avoid;
|
383
|
+
page-break-inside: avoid;
|
384
|
+
}
|
385
|
+
</style>
|
386
|
+
|