@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,435 @@
1
+ <template>
2
+ <view class="prism-city-select" :class="{ 'dark-mode': appStore.isDarkMode }">
3
+ <!-- 触发器 -->
4
+ <view class="picker-trigger" @click="openPicker">
5
+ <slot>
6
+ <view class="prism-select-box">
7
+ <text :class="{ 'placeholder': !displayValue, 'select-text': displayValue }">
8
+ {{ displayValue || placeholder }}
9
+ </text>
10
+ <text class="select-arrow fa fa-map-marker-alt"></text>
11
+ </view>
12
+ </slot>
13
+ </view>
14
+
15
+ <!-- 全屏选择页面 -->
16
+ <view class="picker-page" v-if="showPicker">
17
+ <!-- 头部 -->
18
+ <view class="page-header">
19
+ <view class="header-back" @click="closePicker">
20
+ <text class="fa fa-arrow-left"></text>
21
+ </view>
22
+ <view class="header-title">{{ title }}</view>
23
+ <view class="header-right"></view>
24
+ </view>
25
+
26
+ <!-- 搜索框 -->
27
+ <view class="search-box">
28
+ <view class="search-input-wrap">
29
+ <text class="fa fa-search"></text>
30
+ <input
31
+ class="search-input"
32
+ type="text"
33
+ v-model="searchKeyword"
34
+ placeholder="搜索城市"
35
+ @input="onSearch"
36
+ />
37
+ <text class="fa fa-times-circle" v-if="searchKeyword" @click="clearSearch"></text>
38
+ </view>
39
+ </view>
40
+
41
+ <!-- 内容区域 -->
42
+ <view class="page-content">
43
+ <!-- 搜索结果 -->
44
+ <scroll-view class="search-results" scroll-y v-if="searchKeyword">
45
+ <view
46
+ v-for="item in searchResults"
47
+ :key="item.code"
48
+ class="result-item"
49
+ @click="selectCity(item)"
50
+ >
51
+ <text>{{ item.name }}</text>
52
+ <text class="result-province">{{ item.provinceName }}</text>
53
+ </view>
54
+ <view class="search-empty" v-if="!searchResults.length">
55
+ <text class="fa fa-search"></text>
56
+ <text>未找到相关城市</text>
57
+ </view>
58
+ </scroll-view>
59
+
60
+ <!-- 正常列表 -->
61
+ <view class="city-content" v-else>
62
+ <PrismGroupSticky
63
+ :indexList="indexList"
64
+ :showIndexBar="true"
65
+ height="100%"
66
+ >
67
+ <!-- 热门城市 -->
68
+ <view class="hot-section" v-if="showHotCities">
69
+ <view class="section-title">
70
+ <text class="fa fa-fire"></text>
71
+ <text>热门城市</text>
72
+ </view>
73
+ <view class="hot-cities-grid">
74
+ <view
75
+ v-for="city in hotCities"
76
+ :key="city.code"
77
+ class="hot-city-item"
78
+ @click="selectCity(city)"
79
+ >
80
+ {{ city.name }}
81
+ </view>
82
+ </view>
83
+ </view>
84
+
85
+ <!-- 按字母分组的城市列表 -->
86
+ <template v-for="group in groupedCities" :key="group.letter">
87
+ <view class="group-index" :id="'group-' + group.letter">{{ group.letter }}</view>
88
+ <view
89
+ v-for="city in group.cities"
90
+ :key="city.code"
91
+ class="group-item"
92
+ @click="selectCity(city)"
93
+ >
94
+ {{ city.name }}
95
+ </view>
96
+ </template>
97
+ </PrismGroupSticky>
98
+ </view>
99
+ </view>
100
+ </view>
101
+ </view>
102
+ </template>
103
+
104
+ <script setup>
105
+ import { ref, computed } from 'vue';
106
+ import { useAppStore } from '@/store/app';
107
+ import PrismGroupSticky from '@/components/PrismGroupSticky/PrismGroupSticky.vue';
108
+
109
+ const props = defineProps({
110
+ modelValue: {
111
+ type: Object,
112
+ default: () => null
113
+ },
114
+ placeholder: {
115
+ type: String,
116
+ default: '请选择城市'
117
+ },
118
+ title: {
119
+ type: String,
120
+ default: '选择城市'
121
+ },
122
+ showHotCities: {
123
+ type: Boolean,
124
+ default: true
125
+ },
126
+ hotCities: {
127
+ type: Array,
128
+ default: () => [
129
+ { name: '北京', code: '110000', provinceName: '北京市' },
130
+ { name: '上海', code: '310000', provinceName: '上海市' },
131
+ { name: '广州', code: '440100', provinceName: '广东省' },
132
+ { name: '深圳', code: '440300', provinceName: '广东省' },
133
+ { name: '杭州', code: '330100', provinceName: '浙江省' },
134
+ { name: '成都', code: '510100', provinceName: '四川省' },
135
+ { name: '武汉', code: '420100', provinceName: '湖北省' },
136
+ { name: '南京', code: '320100', provinceName: '江苏省' }
137
+ ]
138
+ },
139
+ disabled: {
140
+ type: Boolean,
141
+ default: false
142
+ }
143
+ });
144
+
145
+ const emit = defineEmits(['update:modelValue', 'change', 'confirm']);
146
+ const appStore = useAppStore();
147
+
148
+ const showPicker = ref(false);
149
+ const searchKeyword = ref('');
150
+ const searchResults = ref([]);
151
+
152
+ // 城市数据(按拼音首字母分组)
153
+ const cityData = [
154
+ { name: '北京', code: '110000', letter: 'B', provinceName: '北京市' },
155
+ { name: '天津', code: '120000', letter: 'T', provinceName: '天津市' },
156
+ { name: '上海', code: '310000', letter: 'S', provinceName: '上海市' },
157
+ { name: '重庆', code: '500000', letter: 'C', provinceName: '重庆市' },
158
+ { name: '广州', code: '440100', letter: 'G', provinceName: '广东省' },
159
+ { name: '深圳', code: '440300', letter: 'S', provinceName: '广东省' },
160
+ { name: '杭州', code: '330100', letter: 'H', provinceName: '浙江省' },
161
+ { name: '宁波', code: '330200', letter: 'N', provinceName: '浙江省' },
162
+ { name: '南京', code: '320100', letter: 'N', provinceName: '江苏省' },
163
+ { name: '苏州', code: '320500', letter: 'S', provinceName: '江苏省' },
164
+ { name: '成都', code: '510100', letter: 'C', provinceName: '四川省' },
165
+ { name: '武汉', code: '420100', letter: 'W', provinceName: '湖北省' },
166
+ { name: '长沙', code: '430100', letter: 'C', provinceName: '湖南省' },
167
+ { name: '西安', code: '610100', letter: 'X', provinceName: '陕西省' },
168
+ { name: '郑州', code: '410100', letter: 'Z', provinceName: '河南省' },
169
+ { name: '青岛', code: '370200', letter: 'Q', provinceName: '山东省' },
170
+ { name: '济南', code: '370100', letter: 'J', provinceName: '山东省' },
171
+ { name: '大连', code: '210200', letter: 'D', provinceName: '辽宁省' },
172
+ { name: '沈阳', code: '210100', letter: 'S', provinceName: '辽宁省' },
173
+ { name: '厦门', code: '350200', letter: 'X', provinceName: '福建省' },
174
+ { name: '福州', code: '350100', letter: 'F', provinceName: '福建省' },
175
+ { name: '合肥', code: '340100', letter: 'H', provinceName: '安徽省' },
176
+ { name: '昆明', code: '530100', letter: 'K', provinceName: '云南省' },
177
+ { name: '南昌', code: '360100', letter: 'N', provinceName: '江西省' },
178
+ { name: '南宁', code: '450100', letter: 'N', provinceName: '广西壮族自治区' },
179
+ { name: '贵阳', code: '520100', letter: 'G', provinceName: '贵州省' },
180
+ { name: '兰州', code: '620100', letter: 'L', provinceName: '甘肃省' },
181
+ { name: '太原', code: '140100', letter: 'T', provinceName: '山西省' },
182
+ { name: '石家庄', code: '130100', letter: 'S', provinceName: '河北省' },
183
+ { name: '哈尔滨', code: '230100', letter: 'H', provinceName: '黑龙江省' },
184
+ { name: '长春', code: '220100', letter: 'C', provinceName: '吉林省' }
185
+ ];
186
+
187
+ // 显示值
188
+ const displayValue = computed(() => {
189
+ if (!props.modelValue) return '';
190
+ return props.modelValue.name || '';
191
+ });
192
+
193
+ // 索引列表
194
+ const indexList = computed(() => {
195
+ const letters = [...new Set(cityData.map(c => c.letter))];
196
+ return letters.sort();
197
+ });
198
+
199
+ // 按字母分组的城市
200
+ const groupedCities = computed(() => {
201
+ const groups = {};
202
+ cityData.forEach(city => {
203
+ if (!groups[city.letter]) {
204
+ groups[city.letter] = [];
205
+ }
206
+ groups[city.letter].push(city);
207
+ });
208
+ return indexList.value.map(letter => ({
209
+ letter,
210
+ cities: groups[letter] || []
211
+ }));
212
+ });
213
+
214
+ // 打开选择器
215
+ function openPicker() {
216
+ if (props.disabled) return;
217
+ showPicker.value = true;
218
+ searchKeyword.value = '';
219
+ }
220
+
221
+ // 关闭选择器
222
+ function closePicker() {
223
+ showPicker.value = false;
224
+ }
225
+
226
+ // 选择城市
227
+ function selectCity(city) {
228
+ const result = { code: city.code, name: city.name, provinceName: city.provinceName };
229
+ emit('update:modelValue', result);
230
+ emit('change', result);
231
+ emit('confirm', result);
232
+ showPicker.value = false;
233
+ }
234
+
235
+ // 搜索
236
+ function onSearch() {
237
+ if (!searchKeyword.value.trim()) {
238
+ searchResults.value = [];
239
+ return;
240
+ }
241
+ const keyword = searchKeyword.value.trim().toLowerCase();
242
+ searchResults.value = cityData.filter(city =>
243
+ city.name.toLowerCase().includes(keyword)
244
+ );
245
+ }
246
+
247
+ // 清空搜索
248
+ function clearSearch() {
249
+ searchKeyword.value = '';
250
+ searchResults.value = [];
251
+ }
252
+ </script>
253
+
254
+ <style lang="scss">
255
+ .prism-city-select {
256
+ .prism-select-box {
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: space-between;
260
+ padding: 24rpx;
261
+ background: var(--prism-input-bg, #F7F8FA);
262
+ border-radius: 12rpx;
263
+
264
+ .placeholder { color: var(--prism-text-placeholder, #C9CDD4); font-size: 28rpx; }
265
+ .select-text { color: var(--prism-text-primary, #1D2129); font-size: 28rpx; }
266
+ .select-arrow { color: var(--prism-text-secondary, #86909C); font-size: 28rpx; }
267
+ }
268
+
269
+ .picker-page {
270
+ position: fixed;
271
+ top: 0;
272
+ left: 0;
273
+ right: 0;
274
+ bottom: -100rpx; // 向下延伸覆盖底部
275
+ padding-bottom: 100rpx; // 补偿底部延伸
276
+ background: var(--prism-bg-color-page, #F5F6F7);
277
+ z-index: 9999;
278
+ display: flex;
279
+ flex-direction: column;
280
+ overflow: hidden;
281
+ box-sizing: border-box;
282
+ }
283
+
284
+ .page-header {
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: space-between;
288
+ padding: 24rpx 32rpx;
289
+ padding-top: calc(24rpx + env(safe-area-inset-top));
290
+ background: var(--prism-bg-color-card, #FFFFFF);
291
+ }
292
+
293
+ .header-back {
294
+ width: 60rpx;
295
+ .fa { font-size: 36rpx; color: var(--prism-text-primary, #1D2129); }
296
+ }
297
+
298
+ .header-title {
299
+ font-size: 34rpx;
300
+ font-weight: 600;
301
+ color: var(--prism-text-primary, #1D2129);
302
+ }
303
+
304
+ .header-right { width: 60rpx; }
305
+
306
+ .search-box {
307
+ padding: 16rpx 32rpx;
308
+ background: var(--prism-bg-color-card, #FFFFFF);
309
+ }
310
+
311
+ .search-input-wrap {
312
+ display: flex;
313
+ align-items: center;
314
+ padding: 16rpx 24rpx;
315
+ background: var(--prism-bg-color-container, #F7F8FA);
316
+ border-radius: 8rpx;
317
+ gap: 16rpx;
318
+
319
+ .fa-search { color: var(--prism-text-placeholder, #C9CDD4); }
320
+ .fa-times-circle { color: var(--prism-text-secondary, #86909C); }
321
+ }
322
+
323
+ .search-input {
324
+ flex: 1;
325
+ font-size: 28rpx;
326
+ color: var(--prism-text-primary, #1D2129);
327
+ }
328
+
329
+ .page-content {
330
+ flex: 1;
331
+ height: 0;
332
+ }
333
+
334
+ .city-content {
335
+ height: 100%;
336
+ }
337
+
338
+ .hot-section {
339
+ background: var(--prism-bg-color-card, #FFFFFF);
340
+ }
341
+
342
+ .section-title {
343
+ display: flex;
344
+ align-items: center;
345
+ gap: 8rpx;
346
+ padding: 8rpx 24rpx 0;
347
+ margin-bottom: 8rpx;
348
+ font-size: 26rpx;
349
+ color: var(--prism-text-secondary, #86909C);
350
+
351
+ .fa-fire {
352
+ color: #FF6B00;
353
+ }
354
+ }
355
+
356
+ .hot-cities-grid {
357
+ display: flex;
358
+ flex-wrap: wrap;
359
+ padding: 0 24rpx 24rpx;
360
+ gap: 16rpx;
361
+ }
362
+
363
+ .hot-city-item {
364
+ padding: 16rpx 32rpx;
365
+ background: var(--prism-bg-color-container, #F7F8FA);
366
+ border-radius: 8rpx;
367
+ font-size: 28rpx;
368
+ color: var(--prism-text-primary, #1D2129);
369
+ }
370
+
371
+ .search-results {
372
+ height: 100%;
373
+ background: var(--prism-bg-color-card, #FFFFFF);
374
+ }
375
+
376
+ .result-item {
377
+ display: flex;
378
+ justify-content: space-between;
379
+ padding: 28rpx 32rpx;
380
+ border-bottom: 1rpx solid var(--prism-border-color-light, #E5E6EB);
381
+ font-size: 30rpx;
382
+ color: var(--prism-text-primary, #1D2129);
383
+
384
+ .result-province {
385
+ font-size: 26rpx;
386
+ color: var(--prism-text-secondary, #86909C);
387
+ }
388
+ }
389
+
390
+ .search-empty {
391
+ display: flex;
392
+ flex-direction: column;
393
+ align-items: center;
394
+ padding: 120rpx 32rpx;
395
+ gap: 24rpx;
396
+ .fa { font-size: 80rpx; color: var(--prism-text-placeholder, #C9CDD4); }
397
+ text { font-size: 28rpx; color: var(--prism-text-secondary, #86909C); }
398
+ }
399
+
400
+ // 城市列表箭头
401
+ .city-arrow {
402
+ font-size: 24rpx;
403
+ color: var(--prism-text-placeholder, #C9CDD4);
404
+ margin-left: 16rpx;
405
+ }
406
+
407
+ // 列表项左右边距
408
+ .group-index,
409
+ .group-item {
410
+ margin: 0 30rpx;
411
+ }
412
+
413
+ // 索引条贴在屏幕边缘
414
+ .prism-index-bar {
415
+ right: 0;
416
+ }
417
+ }
418
+
419
+ // 深色模式
420
+ .dark-mode.prism-city-select {
421
+ .picker-page { background: var(--prism-bg-color-page, #121212); }
422
+ .page-header, .search-box, .hot-section, .search-results { background: var(--prism-bg-color-card, #1A1A1A); }
423
+ .search-input-wrap, .hot-city-item { background: var(--prism-bg-color-container, #2A2A2A); }
424
+
425
+ .group-index {
426
+ background: var(--prism-bg-color-page, #121212);
427
+ }
428
+
429
+ .group-item {
430
+ background: var(--prism-bg-color-card, #1A1A1A);
431
+ color: var(--prism-text-primary, #E5E6EB);
432
+ border-bottom-color: var(--prism-border-color-light, #2D2D2D);
433
+ }
434
+ }
435
+ </style>