@ruixinkeji/prism-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 (48) hide show
  1. package/README.md +141 -0
  2. package/components/PrismAIAssist/PrismAIAssist.vue +98 -0
  3. package/components/PrismAddressInput/PrismAddressInput.vue +597 -0
  4. package/components/PrismCityCascadeSelect/PrismCityCascadeSelect.vue +793 -0
  5. package/components/PrismCityPicker/PrismCityPicker.vue +1008 -0
  6. package/components/PrismCitySelect/PrismCitySelect.vue +435 -0
  7. package/components/PrismCode/PrismCode.vue +749 -0
  8. package/components/PrismCodeInput/PrismCodeInput.vue +156 -0
  9. package/components/PrismDateTimePicker/PrismDateTimePicker.vue +953 -0
  10. package/components/PrismDropdown/PrismDropdown.vue +77 -0
  11. package/components/PrismGroupSticky/PrismGroupSticky.vue +352 -0
  12. package/components/PrismIdCardInput/PrismIdCardInput.vue +253 -0
  13. package/components/PrismImagePicker/PrismImagePicker.vue +457 -0
  14. package/components/PrismIndexBar/PrismIndexBar.vue +243 -0
  15. package/components/PrismLicensePlateInput/PrismLicensePlateInput.vue +1100 -0
  16. package/components/PrismMusicPlayer/PrismMusicPlayer.vue +530 -0
  17. package/components/PrismNavBar/PrismNavBar.vue +199 -0
  18. package/components/PrismSecureInput/PrismSecureInput.vue +360 -0
  19. package/components/PrismSticky/PrismSticky.vue +173 -0
  20. package/components/PrismSwiper/PrismSwiper.vue +339 -0
  21. package/components/PrismSwitch/PrismSwitch.vue +202 -0
  22. package/components/PrismTabBar/PrismTabBar.vue +147 -0
  23. package/components/PrismTabs/PrismTabs.vue +49 -0
  24. package/components/PrismVoiceInput/PrismVoiceInput.vue +529 -0
  25. package/index.d.ts +24 -0
  26. package/index.esm.js +25 -0
  27. package/index.js +25 -0
  28. package/package.json +89 -0
  29. package/styles/base.scss +227 -0
  30. package/styles/button.scss +120 -0
  31. package/styles/card.scss +306 -0
  32. package/styles/colors.scss +877 -0
  33. package/styles/data.scss +1229 -0
  34. package/styles/effects.scss +407 -0
  35. package/styles/feedback.scss +698 -0
  36. package/styles/form.scss +1574 -0
  37. package/styles/index.scss +46 -0
  38. package/styles/list.scss +184 -0
  39. package/styles/navigation.scss +554 -0
  40. package/styles/overlay.scss +182 -0
  41. package/styles/utilities.scss +134 -0
  42. package/styles/variables.scss +138 -0
  43. package/theme/blue.scss +36 -0
  44. package/theme/cyan.scss +32 -0
  45. package/theme/green.scss +32 -0
  46. package/theme/orange.scss +32 -0
  47. package/theme/purple.scss +32 -0
  48. package/theme/red.scss +32 -0
@@ -0,0 +1,597 @@
1
+ <template>
2
+ <view class="prism-address-input" :class="{ 'dark-mode': appStore.isDarkMode }">
3
+ <!-- 模式1: textarea模式(默认) -->
4
+ <view class="address-wrapper" v-if="mode === 'textarea'">
5
+ <textarea
6
+ class="address-textarea"
7
+ v-model="addressValue"
8
+ :placeholder="placeholder"
9
+ placeholder-class="address-placeholder"
10
+ :auto-height="true"
11
+ :disabled="disabled"
12
+ @input="handleInput"
13
+ ></textarea>
14
+ <view class="location-btn" @click="chooseLocation">
15
+ <text class="fa fa-location-dot"></text>
16
+ </view>
17
+ </view>
18
+
19
+ <!-- 模式2: input模式(单行输入) -->
20
+ <view class="address-wrapper input-mode" v-else-if="mode === 'input'">
21
+ <view class="prism-input-box">
22
+ <input
23
+ :placeholder="placeholder"
24
+ v-model="addressValue"
25
+ :disabled="disabled"
26
+ @input="handleInput"
27
+ />
28
+ </view>
29
+ <view class="location-btn" @click="chooseLocation">
30
+ <text class="fa fa-location-crosshairs"></text>
31
+ </view>
32
+ </view>
33
+
34
+ <!-- 地图展示区域(两种模式都可以显示) -->
35
+ <view class="map-container" v-if="showMap && latitude && longitude" @click="chooseLocation">
36
+ <image
37
+ class="location-map"
38
+ :src="staticMapUrl"
39
+ mode="aspectFill"
40
+ ></image>
41
+ <view class="relocate-tip">
42
+ <text class="fa fa-location-crosshairs"></text>
43
+ <text>点击重新选择位置</text>
44
+ </view>
45
+ </view>
46
+
47
+ <!-- 选择模式弹窗(both模式时显示) -->
48
+ <view class="select-popup" v-if="showSelectPopup" @click="closePopup">
49
+ <view class="select-content" @click.stop>
50
+ <view class="select-title">选择方式</view>
51
+ <view class="select-options">
52
+ <view class="select-option" @click="handleSelectMode('map')">
53
+ <text class="fa fa-map-location-dot"></text>
54
+ <text>地图选择</text>
55
+ </view>
56
+ <view class="select-option" @click="handleSelectMode('list')">
57
+ <text class="fa fa-list-ul"></text>
58
+ <text>位置列表</text>
59
+ </view>
60
+ </view>
61
+ <view class="select-cancel" @click="closePopup">取消</view>
62
+ </view>
63
+ </view>
64
+
65
+ <!-- 位置列表弹窗 -->
66
+ <view class="list-popup" v-if="showListPopup" @click="closePopup">
67
+ <view class="list-content" @click.stop>
68
+ <view class="list-header">
69
+ <text class="list-title">选择位置</text>
70
+ <text class="fa fa-times list-close" @click="closePopup"></text>
71
+ </view>
72
+ <scroll-view class="list-scroll" scroll-y>
73
+ <view
74
+ v-for="(item, index) in locationList"
75
+ :key="index"
76
+ class="list-item"
77
+ @click="selectLocationItem(item)"
78
+ >
79
+ <view class="item-icon">
80
+ <text class="fa fa-location-dot"></text>
81
+ </view>
82
+ <view class="item-info">
83
+ <view class="item-name">{{ item.name }}</view>
84
+ <view class="item-address" v-if="item.address">{{ item.address }}</view>
85
+ </view>
86
+ </view>
87
+ <view class="list-empty" v-if="!locationList.length">
88
+ <text class="fa fa-inbox"></text>
89
+ <text>暂无位置数据</text>
90
+ </view>
91
+ </scroll-view>
92
+ <!-- 地图选择入口 -->
93
+ <view class="list-footer" v-if="selectMode === 'both'" @click="handleSelectMode('map')">
94
+ <text class="fa fa-map-location-dot"></text>
95
+ <text>从地图选择</text>
96
+ </view>
97
+ </view>
98
+ </view>
99
+ </view>
100
+ </template>
101
+
102
+ <script setup>
103
+ import { ref, computed, watch } from 'vue';
104
+ import { useAppStore } from '@/store/app';
105
+
106
+ const props = defineProps({
107
+ modelValue: {
108
+ type: String,
109
+ default: ''
110
+ },
111
+ // 输入模式:textarea(多行)或 input(单行)
112
+ mode: {
113
+ type: String,
114
+ default: 'textarea',
115
+ validator: (val) => ['textarea', 'input'].includes(val)
116
+ },
117
+ // 选择模式:map(地图选择)或 list(位置列表)或 both(都支持)
118
+ selectMode: {
119
+ type: String,
120
+ default: 'map',
121
+ validator: (val) => ['map', 'list', 'both'].includes(val)
122
+ },
123
+ // 位置列表数据(selectMode为list或both时使用)
124
+ locationList: {
125
+ type: Array,
126
+ default: () => []
127
+ },
128
+ latitude: {
129
+ type: [Number, String],
130
+ default: null
131
+ },
132
+ longitude: {
133
+ type: [Number, String],
134
+ default: null
135
+ },
136
+ placeholder: {
137
+ type: String,
138
+ default: '请输入地址'
139
+ },
140
+ disabled: {
141
+ type: Boolean,
142
+ default: false
143
+ },
144
+ showMap: {
145
+ type: Boolean,
146
+ default: true
147
+ },
148
+ mapKey: {
149
+ type: String,
150
+ default: ''
151
+ }
152
+ });
153
+
154
+ const emit = defineEmits(['update:modelValue', 'update:latitude', 'update:longitude', 'location-change']);
155
+
156
+ const appStore = useAppStore();
157
+ const addressValue = ref(props.modelValue);
158
+ const showListPopup = ref(false);
159
+ const showSelectPopup = ref(false);
160
+
161
+ // 静态地图URL
162
+ const staticMapUrl = computed(() => {
163
+ if (!props.latitude || !props.longitude || !props.mapKey) return '';
164
+ const lat = props.latitude;
165
+ const lng = props.longitude;
166
+ const style = appStore.isDarkMode ? '&style=4' : '';
167
+ return `https://apis.map.qq.com/ws/staticmap/v2/?center=${lat},${lng}&zoom=16&size=600*300&maptype=roadmap&markers=size:large|color:red|${lat},${lng}&key=${props.mapKey}${style}`;
168
+ });
169
+
170
+ function handleInput(e) {
171
+ const value = e.detail.value;
172
+ addressValue.value = value;
173
+ emit('update:modelValue', value);
174
+ }
175
+
176
+ // 点击选择位置
177
+ function chooseLocation() {
178
+ if (props.disabled) return;
179
+
180
+ if (props.selectMode === 'map') {
181
+ openMapPicker();
182
+ } else if (props.selectMode === 'list') {
183
+ showListPopup.value = true;
184
+ } else {
185
+ // both模式,显示选择弹窗
186
+ showSelectPopup.value = true;
187
+ }
188
+ }
189
+
190
+ // 打开地图选择
191
+ function openMapPicker() {
192
+ uni.chooseLocation({
193
+ latitude: props.latitude || undefined,
194
+ longitude: props.longitude || undefined,
195
+ success: (res) => {
196
+ if (res.name || res.address) {
197
+ const fullAddress = res.name
198
+ ? (res.address ? `${res.name}(${res.address})` : res.name)
199
+ : res.address;
200
+ addressValue.value = fullAddress;
201
+ emit('update:modelValue', fullAddress);
202
+ }
203
+ emit('update:latitude', res.latitude);
204
+ emit('update:longitude', res.longitude);
205
+ emit('location-change', {
206
+ address: addressValue.value,
207
+ latitude: res.latitude,
208
+ longitude: res.longitude,
209
+ name: res.name
210
+ });
211
+ },
212
+ fail: (err) => {
213
+ console.log('选择位置失败', err);
214
+ }
215
+ });
216
+ }
217
+
218
+ // 选择列表中的位置
219
+ function selectLocationItem(item) {
220
+ addressValue.value = item.address || item.name;
221
+ emit('update:modelValue', addressValue.value);
222
+ emit('update:latitude', item.latitude);
223
+ emit('update:longitude', item.longitude);
224
+ emit('location-change', {
225
+ address: addressValue.value,
226
+ latitude: item.latitude,
227
+ longitude: item.longitude,
228
+ name: item.name
229
+ });
230
+ showListPopup.value = false;
231
+ }
232
+
233
+ // 关闭弹窗
234
+ function closePopup() {
235
+ showListPopup.value = false;
236
+ showSelectPopup.value = false;
237
+ }
238
+
239
+ // 选择模式后的操作
240
+ function handleSelectMode(mode) {
241
+ showSelectPopup.value = false;
242
+ if (mode === 'map') {
243
+ openMapPicker();
244
+ } else {
245
+ showListPopup.value = true;
246
+ }
247
+ }
248
+
249
+ watch(() => props.modelValue, (val) => {
250
+ addressValue.value = val;
251
+ });
252
+
253
+ // 暴露方法
254
+ defineExpose({
255
+ chooseLocation,
256
+ openMapPicker
257
+ });
258
+ </script>
259
+
260
+ <style lang="scss">
261
+ // 白色常量(用于深色背景上的固定白色文字)
262
+ $color-white: #FFFFFF;
263
+
264
+ .prism-address-input {
265
+ .address-wrapper {
266
+ display: flex;
267
+ align-items: center;
268
+ min-height: 88rpx;
269
+ background: var(--prism-input-bg, #EBEEF2);
270
+ border-radius: 12rpx;
271
+ padding: 0 28rpx;
272
+
273
+ // input模式样式
274
+ &.input-mode {
275
+ padding: 0;
276
+ background: transparent;
277
+ gap: 16rpx;
278
+
279
+ .prism-input-box {
280
+ flex: 1;
281
+ }
282
+
283
+ .location-btn {
284
+ padding: 0;
285
+ width: 80rpx;
286
+ height: 80rpx;
287
+ background: var(--prism-primary-color, #3478F6);
288
+ border-radius: 12rpx;
289
+ flex-shrink: 0;
290
+
291
+ .fa {
292
+ color: #FFFFFF;
293
+ font-size: 36rpx;
294
+ }
295
+
296
+ &:active {
297
+ opacity: 0.8;
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ .address-textarea {
304
+ flex: 1;
305
+ min-height: 48rpx;
306
+ max-height: none;
307
+ font-size: 30rpx;
308
+ color: var(--prism-text-primary, #1D2129);
309
+ line-height: 1.5;
310
+ background: transparent;
311
+ }
312
+
313
+ .address-placeholder {
314
+ color: var(--prism-text-placeholder, #86909C);
315
+ }
316
+
317
+ .location-btn {
318
+ padding: 8rpx 0 8rpx 16rpx;
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ flex-shrink: 0;
323
+
324
+ .fa {
325
+ font-size: 40rpx;
326
+ color: var(--prism-primary-color, #3478F6);
327
+ }
328
+ }
329
+
330
+ .map-container {
331
+ margin-top: 16rpx;
332
+ position: relative;
333
+ border-radius: 16rpx;
334
+ overflow: hidden;
335
+ height: 300rpx;
336
+ }
337
+
338
+ .location-map {
339
+ width: 100%;
340
+ height: 100%;
341
+ border-radius: 16rpx;
342
+ }
343
+
344
+ .relocate-tip {
345
+ position: absolute;
346
+ bottom: 0;
347
+ left: 0;
348
+ right: 0;
349
+ background: var(--prism-mask-bg, rgba(0, 0, 0, 0.5));
350
+ padding: 16rpx;
351
+ display: flex;
352
+ align-items: center;
353
+ justify-content: center;
354
+ gap: 8rpx;
355
+
356
+ text, .fa {
357
+ font-size: 24rpx;
358
+ color: $color-white;
359
+ }
360
+ }
361
+
362
+ // 选择模式弹窗
363
+ .select-popup,
364
+ .list-popup {
365
+ position: fixed;
366
+ top: 0;
367
+ left: 0;
368
+ right: 0;
369
+ bottom: 0;
370
+ background: var(--prism-mask-bg, rgba(0, 0, 0, 0.5));
371
+ display: flex;
372
+ align-items: flex-end;
373
+ justify-content: center;
374
+ z-index: 9999;
375
+ }
376
+
377
+ .select-content {
378
+ width: 100%;
379
+ background: var(--prism-bg-color-card, #FFFFFF);
380
+ border-radius: 24rpx 24rpx 0 0;
381
+ padding: 32rpx;
382
+ padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
383
+ }
384
+
385
+ .select-title {
386
+ font-size: 32rpx;
387
+ font-weight: 600;
388
+ color: var(--prism-text-primary, #1D2129);
389
+ text-align: center;
390
+ margin-bottom: 32rpx;
391
+ }
392
+
393
+ .select-options {
394
+ display: flex;
395
+ gap: 24rpx;
396
+ }
397
+
398
+ .select-option {
399
+ flex: 1;
400
+ height: 160rpx;
401
+ background: var(--prism-bg-color-container, #F7F8FA);
402
+ border-radius: 16rpx;
403
+ display: flex;
404
+ flex-direction: column;
405
+ align-items: center;
406
+ justify-content: center;
407
+ gap: 16rpx;
408
+ transition: all 0.2s;
409
+
410
+ .fa {
411
+ font-size: 48rpx;
412
+ color: var(--prism-primary-color, #3478F6);
413
+ }
414
+
415
+ text:last-child {
416
+ font-size: 28rpx;
417
+ color: var(--prism-text-primary, #1D2129);
418
+ }
419
+
420
+ &:active {
421
+ background: var(--prism-primary-light, rgba(52, 120, 246, 0.1));
422
+ }
423
+ }
424
+
425
+ .select-cancel {
426
+ margin-top: 24rpx;
427
+ height: 88rpx;
428
+ display: flex;
429
+ align-items: center;
430
+ justify-content: center;
431
+ font-size: 32rpx;
432
+ color: var(--prism-text-secondary, #86909C);
433
+ }
434
+
435
+ // 位置列表弹窗
436
+ .list-content {
437
+ width: 100%;
438
+ max-height: 70vh;
439
+ background: var(--prism-bg-color-card, #FFFFFF);
440
+ border-radius: 24rpx 24rpx 0 0;
441
+ display: flex;
442
+ flex-direction: column;
443
+ }
444
+
445
+ .list-header {
446
+ display: flex;
447
+ align-items: center;
448
+ justify-content: space-between;
449
+ padding: 32rpx;
450
+ border-bottom: 1rpx solid var(--prism-border-color-light, #E5E6EB);
451
+ }
452
+
453
+ .list-title {
454
+ font-size: 32rpx;
455
+ font-weight: 600;
456
+ color: var(--prism-text-primary, #1D2129);
457
+ }
458
+
459
+ .list-close {
460
+ font-size: 36rpx;
461
+ color: var(--prism-text-secondary, #86909C);
462
+ padding: 8rpx;
463
+ }
464
+
465
+ .list-scroll {
466
+ flex: 1;
467
+ max-height: 600rpx;
468
+ }
469
+
470
+ .list-item {
471
+ display: flex;
472
+ align-items: center;
473
+ padding: 24rpx 32rpx;
474
+ border-bottom: 1rpx solid var(--prism-border-color-light, #E5E6EB);
475
+ transition: all 0.2s;
476
+
477
+ &:active {
478
+ background: var(--prism-bg-color-container, #F7F8FA);
479
+ }
480
+ }
481
+
482
+ .item-icon {
483
+ width: 64rpx;
484
+ height: 64rpx;
485
+ background: var(--prism-primary-light, rgba(52, 120, 246, 0.1));
486
+ border-radius: 50%;
487
+ display: flex;
488
+ align-items: center;
489
+ justify-content: center;
490
+ margin-right: 24rpx;
491
+ flex-shrink: 0;
492
+
493
+ .fa {
494
+ font-size: 28rpx;
495
+ color: var(--prism-primary-color, #3478F6);
496
+ }
497
+ }
498
+
499
+ .item-info {
500
+ flex: 1;
501
+ min-width: 0;
502
+ }
503
+
504
+ .item-name {
505
+ font-size: 30rpx;
506
+ color: var(--prism-text-primary, #1D2129);
507
+ margin-bottom: 8rpx;
508
+ }
509
+
510
+ .item-address {
511
+ font-size: 24rpx;
512
+ color: var(--prism-text-secondary, #86909C);
513
+ overflow: hidden;
514
+ text-overflow: ellipsis;
515
+ white-space: nowrap;
516
+ }
517
+
518
+ .list-empty {
519
+ padding: 80rpx 32rpx;
520
+ display: flex;
521
+ flex-direction: column;
522
+ align-items: center;
523
+ gap: 16rpx;
524
+
525
+ .fa {
526
+ font-size: 64rpx;
527
+ color: var(--prism-text-placeholder, #C9CDD4);
528
+ }
529
+
530
+ text:last-child {
531
+ font-size: 28rpx;
532
+ color: var(--prism-text-secondary, #86909C);
533
+ }
534
+ }
535
+
536
+ .list-footer {
537
+ display: flex;
538
+ align-items: center;
539
+ justify-content: center;
540
+ gap: 12rpx;
541
+ padding: 24rpx 32rpx;
542
+ padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
543
+ border-top: 1rpx solid var(--prism-border-color-light, #E5E6EB);
544
+ background: var(--prism-bg-color-card, #FFFFFF);
545
+
546
+ .fa {
547
+ font-size: 32rpx;
548
+ color: var(--prism-primary-color, #3478F6);
549
+ }
550
+
551
+ text:last-child {
552
+ font-size: 28rpx;
553
+ color: var(--prism-primary-color, #3478F6);
554
+ }
555
+
556
+ &:active {
557
+ opacity: 0.7;
558
+ }
559
+ }
560
+ }
561
+
562
+ .dark-mode .prism-address-input {
563
+ .address-wrapper {
564
+ background: var(--prism-input-bg, #2A2A2A);
565
+
566
+ &.input-mode {
567
+ background: transparent;
568
+ }
569
+ }
570
+
571
+ .address-textarea {
572
+ color: var(--prism-text-primary, #E5E6EB);
573
+ }
574
+
575
+ .address-placeholder {
576
+ color: var(--prism-text-placeholder, #6B7785);
577
+ }
578
+
579
+ .select-content,
580
+ .list-content,
581
+ .list-footer {
582
+ background: var(--prism-bg-color-card, #1A1A1A);
583
+ }
584
+
585
+ .select-option {
586
+ background: var(--prism-bg-color-container, #2A2A2A);
587
+
588
+ &:active {
589
+ background: rgba(52, 120, 246, 0.15);
590
+ }
591
+ }
592
+
593
+ .list-item:active {
594
+ background: var(--prism-bg-color-container, #2A2A2A);
595
+ }
596
+ }
597
+ </style>