@atooyu/uxto-ui 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 (141) hide show
  1. package/README.md +259 -0
  2. package/dist/index.js +5055 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/index.mjs +5055 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/dist/style.css +2528 -0
  7. package/package.json +93 -0
  8. package/src/components/index.ts +51 -0
  9. package/src/components/u-avatar/u-avatar.vue +205 -0
  10. package/src/components/u-badge/u-badge.vue +145 -0
  11. package/src/components/u-button/u-button.vue +239 -0
  12. package/src/components/u-cell/u-cell.vue +179 -0
  13. package/src/components/u-cell-group/u-cell-group.vue +46 -0
  14. package/src/components/u-checkbox/u-checkbox.vue +174 -0
  15. package/src/components/u-checkbox-group/u-checkbox-group.vue +72 -0
  16. package/src/components/u-code-input/u-code-input.vue +248 -0
  17. package/src/components/u-count-down/u-count-down.vue +182 -0
  18. package/src/components/u-datetime-picker/u-datetime-picker.vue +377 -0
  19. package/src/components/u-divider/u-divider.vue +71 -0
  20. package/src/components/u-empty/u-empty.vue +98 -0
  21. package/src/components/u-grid/u-grid.vue +63 -0
  22. package/src/components/u-grid-item/u-grid-item.vue +170 -0
  23. package/src/components/u-icon/icons/account.svg +3 -0
  24. package/src/components/u-icon/icons/arrow-down.svg +3 -0
  25. package/src/components/u-icon/icons/arrow-left.svg +3 -0
  26. package/src/components/u-icon/icons/arrow-right.svg +3 -0
  27. package/src/components/u-icon/icons/arrow-up.svg +3 -0
  28. package/src/components/u-icon/icons/bell.svg +3 -0
  29. package/src/components/u-icon/icons/bookmark-o.svg +3 -0
  30. package/src/components/u-icon/icons/bookmark.svg +3 -0
  31. package/src/components/u-icon/icons/chat.svg +3 -0
  32. package/src/components/u-icon/icons/check-circle.svg +3 -0
  33. package/src/components/u-icon/icons/check.svg +3 -0
  34. package/src/components/u-icon/icons/chevron-left.svg +3 -0
  35. package/src/components/u-icon/icons/chevron-right.svg +3 -0
  36. package/src/components/u-icon/icons/clear-o.svg +3 -0
  37. package/src/components/u-icon/icons/clear.svg +3 -0
  38. package/src/components/u-icon/icons/clipboard.svg +3 -0
  39. package/src/components/u-icon/icons/clock.svg +3 -0
  40. package/src/components/u-icon/icons/close.svg +3 -0
  41. package/src/components/u-icon/icons/code.svg +3 -0
  42. package/src/components/u-icon/icons/copy.svg +3 -0
  43. package/src/components/u-icon/icons/delete.svg +3 -0
  44. package/src/components/u-icon/icons/download.svg +3 -0
  45. package/src/components/u-icon/icons/edit.svg +3 -0
  46. package/src/components/u-icon/icons/email.svg +3 -0
  47. package/src/components/u-icon/icons/error-o.svg +3 -0
  48. package/src/components/u-icon/icons/error.svg +3 -0
  49. package/src/components/u-icon/icons/exit-fullscreen.svg +3 -0
  50. package/src/components/u-icon/icons/expand-less.svg +3 -0
  51. package/src/components/u-icon/icons/expand-more.svg +3 -0
  52. package/src/components/u-icon/icons/eye-off.svg +3 -0
  53. package/src/components/u-icon/icons/eye.svg +3 -0
  54. package/src/components/u-icon/icons/flag-o.svg +3 -0
  55. package/src/components/u-icon/icons/flag.svg +3 -0
  56. package/src/components/u-icon/icons/fullscreen.svg +3 -0
  57. package/src/components/u-icon/icons/grid.svg +3 -0
  58. package/src/components/u-icon/icons/group.svg +3 -0
  59. package/src/components/u-icon/icons/heart-o.svg +3 -0
  60. package/src/components/u-icon/icons/heart.svg +3 -0
  61. package/src/components/u-icon/icons/info-o.svg +3 -0
  62. package/src/components/u-icon/icons/info.svg +3 -0
  63. package/src/components/u-icon/icons/keyboard-arrow-down.svg +3 -0
  64. package/src/components/u-icon/icons/keyboard-arrow-left.svg +3 -0
  65. package/src/components/u-icon/icons/keyboard-arrow-right.svg +3 -0
  66. package/src/components/u-icon/icons/keyboard-arrow-up.svg +3 -0
  67. package/src/components/u-icon/icons/like-o.svg +3 -0
  68. package/src/components/u-icon/icons/like.svg +3 -0
  69. package/src/components/u-icon/icons/link.svg +3 -0
  70. package/src/components/u-icon/icons/list.svg +3 -0
  71. package/src/components/u-icon/icons/loading.svg +3 -0
  72. package/src/components/u-icon/icons/lock.svg +3 -0
  73. package/src/components/u-icon/icons/menu-o.svg +3 -0
  74. package/src/components/u-icon/icons/menu.svg +3 -0
  75. package/src/components/u-icon/icons/message.svg +3 -0
  76. package/src/components/u-icon/icons/minus.svg +3 -0
  77. package/src/components/u-icon/icons/notification.svg +3 -0
  78. package/src/components/u-icon/icons/phone.svg +3 -0
  79. package/src/components/u-icon/icons/plus.svg +3 -0
  80. package/src/components/u-icon/icons/question.svg +3 -0
  81. package/src/components/u-icon/icons/redo.svg +3 -0
  82. package/src/components/u-icon/icons/refresh-o.svg +3 -0
  83. package/src/components/u-icon/icons/refresh.svg +3 -0
  84. package/src/components/u-icon/icons/reload.svg +3 -0
  85. package/src/components/u-icon/icons/search-o.svg +3 -0
  86. package/src/components/u-icon/icons/search.svg +3 -0
  87. package/src/components/u-icon/icons/setting.svg +3 -0
  88. package/src/components/u-icon/icons/share.svg +3 -0
  89. package/src/components/u-icon/icons/smile-o.svg +3 -0
  90. package/src/components/u-icon/icons/smile.svg +3 -0
  91. package/src/components/u-icon/icons/star-o.svg +3 -0
  92. package/src/components/u-icon/icons/star.svg +3 -0
  93. package/src/components/u-icon/icons/success-o.svg +3 -0
  94. package/src/components/u-icon/icons/success.svg +3 -0
  95. package/src/components/u-icon/icons/sync.svg +3 -0
  96. package/src/components/u-icon/icons/tick.svg +3 -0
  97. package/src/components/u-icon/icons/undo.svg +3 -0
  98. package/src/components/u-icon/icons/unlock.svg +3 -0
  99. package/src/components/u-icon/icons/upload.svg +3 -0
  100. package/src/components/u-icon/icons/user.svg +3 -0
  101. package/src/components/u-icon/icons/warning-o.svg +3 -0
  102. package/src/components/u-icon/icons/warning.svg +3 -0
  103. package/src/components/u-icon/icons/zoom-in.svg +3 -0
  104. package/src/components/u-icon/icons/zoom-out.svg +3 -0
  105. package/src/components/u-icon/index.ts +219 -0
  106. package/src/components/u-icon/u-icon.vue +117 -0
  107. package/src/components/u-image/u-image.vue +106 -0
  108. package/src/components/u-input/u-input.vue +208 -0
  109. package/src/components/u-keyboard/u-keyboard.vue +213 -0
  110. package/src/components/u-layout/u-layout.vue +58 -0
  111. package/src/components/u-line-progress/u-line-progress.vue +156 -0
  112. package/src/components/u-link/u-link.vue +113 -0
  113. package/src/components/u-list/u-list.vue +148 -0
  114. package/src/components/u-list-item/u-list-item.vue +180 -0
  115. package/src/components/u-loading/u-loading.vue +80 -0
  116. package/src/components/u-loading-page/u-loading-page.vue +94 -0
  117. package/src/components/u-modal/u-modal.vue +159 -0
  118. package/src/components/u-notice-bar/u-notice-bar.vue +113 -0
  119. package/src/components/u-number-box/u-number-box.vue +262 -0
  120. package/src/components/u-parse/u-parse.vue +197 -0
  121. package/src/components/u-picker/u-picker.vue +219 -0
  122. package/src/components/u-popup/u-popup.vue +257 -0
  123. package/src/components/u-radio/u-radio.vue +159 -0
  124. package/src/components/u-radio-group/u-radio-group.vue +61 -0
  125. package/src/components/u-rate/u-rate.vue +187 -0
  126. package/src/components/u-read-more/u-read-more.vue +117 -0
  127. package/src/components/u-search/u-search.vue +238 -0
  128. package/src/components/u-skeleton/u-skeleton.vue +192 -0
  129. package/src/components/u-slider/u-slider.vue +453 -0
  130. package/src/components/u-swiper/u-swiper.vue +301 -0
  131. package/src/components/u-swiper-item/u-swiper-item.vue +82 -0
  132. package/src/components/u-switch/u-switch.vue +105 -0
  133. package/src/components/u-tabbar/u-tabbar.vue +221 -0
  134. package/src/components/u-tag/u-tag.vue +144 -0
  135. package/src/components/u-textarea/u-textarea.vue +189 -0
  136. package/src/components/u-toast/u-toast.vue +186 -0
  137. package/src/components/u-tooltip/u-tooltip.vue +364 -0
  138. package/src/components/u-transition/u-transition.vue +216 -0
  139. package/src/components/u-upload/u-upload.vue +403 -0
  140. package/src/styles/index.scss +59 -0
  141. package/src/styles/variables.scss +68 -0
@@ -0,0 +1,377 @@
1
+ <template>
2
+ <view v-if="visible" class="u-datetime-picker" @click="handleOverlayClick">
3
+ <view class="u-datetime-picker__popup" @click.stop>
4
+ <view class="u-datetime-picker__toolbar">
5
+ <text class="u-datetime-picker__action" @click="handleCancel">取消</text>
6
+ <text class="u-datetime-picker__title">{{ title }}</text>
7
+ <text class="u-datetime-picker__action u-datetime-picker__action--confirm" @click="handleConfirm">确定</text>
8
+ </view>
9
+
10
+ <picker-view
11
+ class="u-datetime-picker__view"
12
+ :value="indexes"
13
+ indicator-class="u-datetime-picker__indicator"
14
+ @change="handleChange"
15
+ >
16
+ <picker-view-column v-for="(column, columnIndex) in displayColumns" :key="columnIndex">
17
+ <view
18
+ v-for="option in column"
19
+ :key="option.value"
20
+ class="u-datetime-picker__option"
21
+ >
22
+ <text>{{ option.label }}</text>
23
+ </view>
24
+ </picker-view-column>
25
+ </picker-view>
26
+ </view>
27
+ </view>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { computed, ref, watch } from 'vue'
32
+
33
+ type PickerType = 'datetime' | 'date' | 'year-month' | 'time'
34
+
35
+ interface PickerOption {
36
+ label: string
37
+ value: string
38
+ }
39
+
40
+ interface DateTimeValues {
41
+ year: string
42
+ month: string
43
+ day: string
44
+ hour: string
45
+ minute: string
46
+ }
47
+
48
+ interface Props {
49
+ visible?: boolean
50
+ type?: PickerType
51
+ modelValue?: Date | string | null
52
+ title?: string
53
+ minDate?: Date
54
+ maxDate?: Date
55
+ filter?: (type: string, values: string[]) => string[]
56
+ formatter?: (type: string, value: string) => string
57
+ closeOnClickOverlay?: boolean
58
+ }
59
+
60
+ const props = withDefaults(defineProps<Props>(), {
61
+ visible: false,
62
+ type: 'datetime',
63
+ modelValue: null,
64
+ title: '时间选择',
65
+ minDate: () => new Date(2000, 0, 1, 0, 0),
66
+ maxDate: () => new Date(2035, 11, 31, 23, 59),
67
+ closeOnClickOverlay: false
68
+ })
69
+
70
+ const emit = defineEmits<{
71
+ 'update:visible': [value: boolean]
72
+ 'update:modelValue': [value: Date | string]
73
+ change: [value: Date | string]
74
+ confirm: [value: Date | string]
75
+ cancel: []
76
+ }>()
77
+
78
+ const indexes = ref<number[]>([])
79
+ const innerValues = ref<DateTimeValues>(getInitialValues())
80
+
81
+ const columnTypes = computed(() => {
82
+ if (props.type === 'date') return ['year', 'month', 'day']
83
+ if (props.type === 'year-month') return ['year', 'month']
84
+ if (props.type === 'time') return ['hour', 'minute']
85
+ return ['year', 'month', 'day', 'hour', 'minute']
86
+ })
87
+
88
+ const displayColumns = computed(() => {
89
+ const values = innerValues.value
90
+ return columnTypes.value.map((type) => buildColumn(type, values))
91
+ })
92
+
93
+ watch(
94
+ () => props.modelValue,
95
+ () => {
96
+ innerValues.value = getInitialValues()
97
+ indexes.value = getIndexesByValues()
98
+ },
99
+ { immediate: true }
100
+ )
101
+
102
+ watch(
103
+ displayColumns,
104
+ () => {
105
+ alignValuesWithColumns()
106
+ indexes.value = getIndexesByValues()
107
+ },
108
+ { deep: true }
109
+ )
110
+
111
+ const currentValue = computed(() => formatOutput(innerValues.value))
112
+
113
+ const handleChange = (event: any) => {
114
+ const nextIndexes = event.detail.value as number[]
115
+ const nextValues = { ...innerValues.value }
116
+
117
+ columnTypes.value.forEach((type, index) => {
118
+ nextValues[type as keyof DateTimeValues] = displayColumns.value[index][nextIndexes[index] || 0]?.value || nextValues[type as keyof DateTimeValues]
119
+ })
120
+
121
+ innerValues.value = fixValues(nextValues)
122
+ indexes.value = getIndexesByValues()
123
+ emit('update:modelValue', currentValue.value)
124
+ emit('change', currentValue.value)
125
+ }
126
+
127
+ const handleCancel = () => {
128
+ emit('update:visible', false)
129
+ emit('cancel')
130
+ }
131
+
132
+ const handleConfirm = () => {
133
+ emit('update:visible', false)
134
+ emit('confirm', currentValue.value)
135
+ }
136
+
137
+ const handleOverlayClick = () => {
138
+ if (props.closeOnClickOverlay) {
139
+ emit('update:visible', false)
140
+ emit('cancel')
141
+ }
142
+ }
143
+
144
+ function getInitialValues(): DateTimeValues {
145
+ const date = normalizeModelValue(props.modelValue)
146
+ return {
147
+ year: String(date.getFullYear()),
148
+ month: pad(date.getMonth() + 1),
149
+ day: pad(date.getDate()),
150
+ hour: pad(date.getHours()),
151
+ minute: pad(date.getMinutes())
152
+ }
153
+ }
154
+
155
+ function normalizeModelValue(value: Date | string | null | undefined) {
156
+ if (props.type === 'time' && typeof value === 'string' && value.includes(':')) {
157
+ const [hour = '00', minute = '00'] = value.split(':')
158
+ const date = new Date(props.minDate)
159
+ date.setHours(Number(hour), Number(minute), 0, 0)
160
+ return clampDate(date)
161
+ }
162
+
163
+ const date = value instanceof Date ? new Date(value) : value ? new Date(value) : new Date()
164
+ return clampDate(date)
165
+ }
166
+
167
+ function clampDate(date: Date) {
168
+ const time = date.getTime()
169
+ const minTime = props.minDate.getTime()
170
+ const maxTime = props.maxDate.getTime()
171
+
172
+ if (time < minTime) return new Date(props.minDate)
173
+ if (time > maxTime) return new Date(props.maxDate)
174
+ return date
175
+ }
176
+
177
+ function buildColumn(type: string, values: DateTimeValues) {
178
+ let options: string[] = []
179
+
180
+ if (type === 'year') {
181
+ for (let year = props.minDate.getFullYear(); year <= props.maxDate.getFullYear(); year += 1) {
182
+ options.push(String(year))
183
+ }
184
+ }
185
+
186
+ if (type === 'month') {
187
+ const year = Number(values.year)
188
+ const start = year === props.minDate.getFullYear() ? props.minDate.getMonth() + 1 : 1
189
+ const end = year === props.maxDate.getFullYear() ? props.maxDate.getMonth() + 1 : 12
190
+ for (let month = start; month <= end; month += 1) {
191
+ options.push(pad(month))
192
+ }
193
+ }
194
+
195
+ if (type === 'day') {
196
+ const year = Number(values.year)
197
+ const month = Number(values.month)
198
+ const total = new Date(year, month, 0).getDate()
199
+ const minDay = isSameYearMonth(year, month, props.minDate) ? props.minDate.getDate() : 1
200
+ const maxDay = isSameYearMonth(year, month, props.maxDate) ? props.maxDate.getDate() : total
201
+ for (let day = minDay; day <= maxDay; day += 1) {
202
+ options.push(pad(day))
203
+ }
204
+ }
205
+
206
+ if (type === 'hour') {
207
+ const year = Number(values.year)
208
+ const month = Number(values.month || '1')
209
+ const day = Number(values.day || '1')
210
+ const minHour = isSameDate(year, month, day, props.minDate) ? props.minDate.getHours() : 0
211
+ const maxHour = isSameDate(year, month, day, props.maxDate) ? props.maxDate.getHours() : 23
212
+ for (let hour = minHour; hour <= maxHour; hour += 1) {
213
+ options.push(pad(hour))
214
+ }
215
+ }
216
+
217
+ if (type === 'minute') {
218
+ const year = Number(values.year || String(props.minDate.getFullYear()))
219
+ const month = Number(values.month || '1')
220
+ const day = Number(values.day || '1')
221
+ const hour = Number(values.hour)
222
+ const minMinute = isSameDateTimeHour(year, month, day, hour, props.minDate) ? props.minDate.getMinutes() : 0
223
+ const maxMinute = isSameDateTimeHour(year, month, day, hour, props.maxDate) ? props.maxDate.getMinutes() : 59
224
+ for (let minute = minMinute; minute <= maxMinute; minute += 1) {
225
+ options.push(pad(minute))
226
+ }
227
+ }
228
+
229
+ const filtered = props.filter ? props.filter(type, options) : options
230
+ return filtered.map((value) => ({
231
+ value,
232
+ label: props.formatter ? props.formatter(type, value) : defaultFormatter(type, value)
233
+ }))
234
+ }
235
+
236
+ function getIndexesByValues() {
237
+ return columnTypes.value.map((type, index) => {
238
+ const value = innerValues.value[type as keyof DateTimeValues]
239
+ const column = displayColumns.value[index] || []
240
+ const foundIndex = column.findIndex((item) => item.value === value)
241
+ return foundIndex >= 0 ? foundIndex : 0
242
+ })
243
+ }
244
+
245
+ function alignValuesWithColumns() {
246
+ innerValues.value = fixValues(innerValues.value)
247
+ }
248
+
249
+ function fixValues(values: DateTimeValues) {
250
+ const nextValues = { ...values }
251
+ columnTypes.value.forEach((type, index) => {
252
+ const column = displayColumns.value[index] || []
253
+ const current = nextValues[type as keyof DateTimeValues]
254
+ if (!column.some((item) => item.value === current) && column[0]) {
255
+ nextValues[type as keyof DateTimeValues] = column[0].value
256
+ }
257
+ })
258
+ return nextValues
259
+ }
260
+
261
+ function formatOutput(values: DateTimeValues) {
262
+ if (props.type === 'time') {
263
+ return `${values.hour}:${values.minute}`
264
+ }
265
+
266
+ const date = new Date(
267
+ Number(values.year),
268
+ Number(values.month || '1') - 1,
269
+ Number(values.day || '1'),
270
+ Number(values.hour || '0'),
271
+ Number(values.minute || '0')
272
+ )
273
+
274
+ return props.type === 'year-month'
275
+ ? `${values.year}-${values.month}`
276
+ : props.type === 'date'
277
+ ? new Date(date.getFullYear(), date.getMonth(), date.getDate())
278
+ : date
279
+ }
280
+
281
+ function defaultFormatter(type: string, value: string) {
282
+ if (type === 'year') return `${value}年`
283
+ if (type === 'month') return `${value}月`
284
+ if (type === 'day') return `${value}日`
285
+ if (type === 'hour') return `${value}时`
286
+ if (type === 'minute') return `${value}分`
287
+ return value
288
+ }
289
+
290
+ function isSameYearMonth(year: number, month: number, date: Date) {
291
+ return year === date.getFullYear() && month === date.getMonth() + 1
292
+ }
293
+
294
+ function isSameDate(year: number, month: number, day: number, date: Date) {
295
+ return isSameYearMonth(year, month, date) && day === date.getDate()
296
+ }
297
+
298
+ function isSameDateTimeHour(year: number, month: number, day: number, hour: number, date: Date) {
299
+ return isSameDate(year, month, day, date) && hour === date.getHours()
300
+ }
301
+
302
+ function pad(value: number) {
303
+ return String(value).padStart(2, '0')
304
+ }
305
+ </script>
306
+
307
+ <script lang="ts">
308
+ export default {
309
+ options: {
310
+ virtualHost: true,
311
+ styleIsolation: 'shared'
312
+ }
313
+ }
314
+ </script>
315
+
316
+ <style lang="scss" scoped>
317
+ .u-datetime-picker {
318
+ position: fixed;
319
+ inset: 0;
320
+ display: flex;
321
+ align-items: flex-end;
322
+ background: rgba(0, 0, 0, 0.45);
323
+ z-index: 1000;
324
+
325
+ &__popup {
326
+ width: 100%;
327
+ background: $--bg-color-2;
328
+ border-radius: $--border-radius-lg $--border-radius-lg 0 0;
329
+ overflow: hidden;
330
+ }
331
+
332
+ &__toolbar {
333
+ display: flex;
334
+ align-items: center;
335
+ justify-content: space-between;
336
+ height: 48px;
337
+ padding: 0 $--spacing-lg;
338
+ border-bottom: 1px solid $--border-color;
339
+ }
340
+
341
+ &__title {
342
+ font-size: $--font-size-md;
343
+ color: $--text-color;
344
+ font-weight: 600;
345
+ }
346
+
347
+ &__action {
348
+ font-size: $--font-size-md;
349
+ color: $--text-color-2;
350
+
351
+ &--confirm {
352
+ color: $--color-primary;
353
+ }
354
+ }
355
+
356
+ &__view {
357
+ width: 100%;
358
+ height: 240px;
359
+ }
360
+
361
+ &__indicator {
362
+ height: 48px;
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ }
367
+
368
+ &__option {
369
+ display: flex;
370
+ align-items: center;
371
+ justify-content: center;
372
+ height: 48px;
373
+ font-size: $--font-size-lg;
374
+ color: $--text-color;
375
+ }
376
+ }
377
+ </style>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <view class="u-divider" :class="{ 'u-divider--hairline': hairline, 'u-divider--dashed': dashed }">
3
+ <view v-if="$slots.default || content" class="u-divider__text">
4
+ <slot>{{ content }}</slot>
5
+ </view>
6
+ </view>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ interface Props {
11
+ content?: string
12
+ hairline?: boolean
13
+ dashed?: boolean
14
+ }
15
+
16
+ withDefaults(defineProps<Props>(), {
17
+ content: '',
18
+ hairline: false,
19
+ dashed: false
20
+ })
21
+ </script>
22
+
23
+ <script lang="ts">
24
+ export default {
25
+ options: {
26
+ virtualHost: true,
27
+ styleIsolation: 'shared'
28
+ }
29
+ }
30
+ </script>
31
+
32
+ <style lang="scss" scoped>
33
+ .u-divider {
34
+ display: flex;
35
+ align-items: center;
36
+ margin: $--spacing-lg 0;
37
+ color: $--text-color-2;
38
+ font-size: $--font-size-sm;
39
+ line-height: 1;
40
+
41
+ &::before,
42
+ &::after {
43
+ content: '';
44
+ flex: 1;
45
+ height: 1px;
46
+ background-color: $--border-color;
47
+ }
48
+
49
+ &--hairline {
50
+ &::before,
51
+ &::after {
52
+ transform: scaleY(0.5);
53
+ }
54
+ }
55
+
56
+ &--dashed {
57
+ &::before,
58
+ &::after {
59
+ border-style: dashed;
60
+ background: transparent;
61
+ border-top: 1px dashed $--border-color;
62
+ border-bottom: none;
63
+ height: 0;
64
+ }
65
+ }
66
+
67
+ &__text {
68
+ padding: 0 $--spacing-md;
69
+ }
70
+ }
71
+ </style>
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <view class="u-empty" :class="{ 'u-empty--inline': inline }">
3
+ <view class="u-empty__image">
4
+ <slot name="image">
5
+ <image v-if="image" class="u-empty__image-src" :src="image" mode="aspectFit" />
6
+ <view v-else class="u-empty__image-default">
7
+ <text class="u-empty__icon">📭</text>
8
+ </view>
9
+ </slot>
10
+ </view>
11
+ <view v-if="description || $slots.description" class="u-empty__description">
12
+ <slot name="description">{{ description }}</slot>
13
+ </view>
14
+ <view v-if="$slots.default" class="u-empty__bottom">
15
+ <slot />
16
+ </view>
17
+ </view>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ interface Props {
22
+ image?: string
23
+ description?: string
24
+ inline?: boolean
25
+ }
26
+
27
+ withDefaults(defineProps<Props>(), {
28
+ image: '',
29
+ description: '暂无数据',
30
+ inline: false
31
+ })
32
+ </script>
33
+
34
+ <script lang="ts">
35
+ export default {
36
+ options: {
37
+ virtualHost: true,
38
+ styleIsolation: 'shared'
39
+ }
40
+ }
41
+ </script>
42
+
43
+ <style lang="scss" scoped>
44
+ .u-empty {
45
+ display: flex;
46
+ flex-direction: column;
47
+ align-items: center;
48
+ justify-content: center;
49
+ padding: $--spacing-xl $--spacing-base;
50
+ box-sizing: border-box;
51
+
52
+ &--inline {
53
+ display: inline-flex;
54
+ padding: $--spacing-base;
55
+ }
56
+
57
+ &__image {
58
+ width: 160px;
59
+ height: 160px;
60
+ margin-bottom: $--spacing-base;
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ }
65
+
66
+ &__image-src {
67
+ width: 100%;
68
+ height: 100%;
69
+ }
70
+
71
+ &__image-default {
72
+ width: 100%;
73
+ height: 100%;
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ background-color: $--bg-color;
78
+ border-radius: $--radius-lg;
79
+ }
80
+
81
+ &__icon {
82
+ font-size: 64px;
83
+ color: $--text-color-3;
84
+ }
85
+
86
+ &__description {
87
+ font-size: $--font-size-base;
88
+ color: $--text-color-3;
89
+ line-height: 1.5;
90
+ text-align: center;
91
+ margin-bottom: $--spacing-base;
92
+ }
93
+
94
+ &__bottom {
95
+ margin-top: $--spacing-sm;
96
+ }
97
+ }
98
+ </style>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <view class="u-grid">
3
+ <slot />
4
+ </view>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, provide } from 'vue'
9
+
10
+ interface Props {
11
+ column?: number
12
+ border?: boolean
13
+ clickable?: boolean
14
+ }
15
+
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ column: 4,
18
+ border: false,
19
+ clickable: false
20
+ })
21
+
22
+ const emit = defineEmits<{
23
+ click: [item: unknown, index: number]
24
+ }>()
25
+
26
+ const column = computed(() => Math.max(1, Number(props.column) || 1))
27
+ let currentIndex = 0
28
+
29
+ const registerItem = () => {
30
+ const index = currentIndex
31
+ currentIndex += 1
32
+ return index
33
+ }
34
+
35
+ const onItemClick = (payload: { item: unknown; index: number }) => {
36
+ emit('click', payload.item, payload.index)
37
+ }
38
+
39
+ provide('u-grid', {
40
+ column,
41
+ border: computed(() => props.border),
42
+ clickable: computed(() => props.clickable),
43
+ registerItem,
44
+ onItemClick
45
+ })
46
+ </script>
47
+
48
+ <script lang="ts">
49
+ export default {
50
+ options: {
51
+ virtualHost: true,
52
+ styleIsolation: 'shared'
53
+ }
54
+ }
55
+ </script>
56
+
57
+ <style lang="scss" scoped>
58
+ .u-grid {
59
+ display: flex;
60
+ flex-wrap: wrap;
61
+ background-color: $--bg-color-2;
62
+ }
63
+ </style>