@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,953 @@
1
+ <template>
2
+ <view class="prism-datetime-picker" :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-calendar-alt"></text>
11
+ </view>
12
+ </slot>
13
+ </view>
14
+
15
+ <!-- 选择器弹窗 -->
16
+ <view class="picker-popup" v-if="showPicker" @click="closePicker">
17
+ <view class="picker-content" @click.stop>
18
+ <!-- 头部 -->
19
+ <view class="picker-header">
20
+ <view class="picker-cancel" @click="closePicker">取消</view>
21
+ <view class="picker-title">{{ title }}</view>
22
+ <view class="picker-confirm" @click="confirmSelect">确定</view>
23
+ </view>
24
+
25
+ <!-- 滚轮选择器 -->
26
+ <view class="picker-body" v-if="mode === 'picker'">
27
+ <picker-view
28
+ class="picker-view"
29
+ :value="pickerIndex"
30
+ @change="onPickerChange"
31
+ indicator-class="picker-indicator"
32
+ :indicator-style="indicatorStyle"
33
+ :style="{ height: pickerHeight }"
34
+ >
35
+ <!-- 年份列 -->
36
+ <picker-view-column>
37
+ <view
38
+ v-for="(item, idx) in yearList"
39
+ :key="item"
40
+ class="picker-item"
41
+ :style="{
42
+ height: itemHeight + 'px',
43
+ lineHeight: itemHeight + 'px',
44
+ color: idx === pickerIndex[0] ? 'var(--prism-primary-color, #3478F6)' : 'var(--prism-text-secondary, #86909C)',
45
+ fontWeight: idx === pickerIndex[0] ? '600' : '400'
46
+ }"
47
+ >
48
+ {{ item }}年
49
+ </view>
50
+ </picker-view-column>
51
+ <!-- 月份列 -->
52
+ <picker-view-column>
53
+ <view
54
+ v-for="(item, idx) in monthList"
55
+ :key="item"
56
+ class="picker-item"
57
+ :style="{
58
+ height: itemHeight + 'px',
59
+ lineHeight: itemHeight + 'px',
60
+ color: idx === pickerIndex[1] ? 'var(--prism-primary-color, #3478F6)' : 'var(--prism-text-secondary, #86909C)',
61
+ fontWeight: idx === pickerIndex[1] ? '600' : '400'
62
+ }"
63
+ >
64
+ {{ padZero(item) }}月
65
+ </view>
66
+ </picker-view-column>
67
+ <!-- 日期列 -->
68
+ <picker-view-column>
69
+ <view
70
+ v-for="(item, idx) in dayList"
71
+ :key="item"
72
+ class="picker-item"
73
+ :style="{
74
+ height: itemHeight + 'px',
75
+ lineHeight: itemHeight + 'px',
76
+ color: idx === pickerIndex[2] ? 'var(--prism-primary-color, #3478F6)' : 'var(--prism-text-secondary, #86909C)',
77
+ fontWeight: idx === pickerIndex[2] ? '600' : '400'
78
+ }"
79
+ >
80
+ {{ padZero(item) }}日
81
+ </view>
82
+ </picker-view-column>
83
+ <!-- 小时列 -->
84
+ <picker-view-column v-if="showTime">
85
+ <view
86
+ v-for="(item, idx) in hourList"
87
+ :key="item"
88
+ class="picker-item"
89
+ :style="{
90
+ height: itemHeight + 'px',
91
+ lineHeight: itemHeight + 'px',
92
+ color: idx === pickerIndex[3] ? 'var(--prism-primary-color, #3478F6)' : 'var(--prism-text-secondary, #86909C)',
93
+ fontWeight: idx === pickerIndex[3] ? '600' : '400'
94
+ }"
95
+ >
96
+ {{ padZero(item) }}时
97
+ </view>
98
+ </picker-view-column>
99
+ <!-- 分钟列 -->
100
+ <picker-view-column v-if="showTime">
101
+ <view
102
+ v-for="(item, idx) in minuteList"
103
+ :key="item"
104
+ class="picker-item"
105
+ :style="{
106
+ height: itemHeight + 'px',
107
+ lineHeight: itemHeight + 'px',
108
+ color: idx === pickerIndex[4] ? 'var(--prism-primary-color, #3478F6)' : 'var(--prism-text-secondary, #86909C)',
109
+ fontWeight: idx === pickerIndex[4] ? '600' : '400'
110
+ }"
111
+ >
112
+ {{ padZero(item) }}分
113
+ </view>
114
+ </picker-view-column>
115
+ <!-- 秒列 -->
116
+ <picker-view-column v-if="showTime && showSeconds">
117
+ <view
118
+ v-for="(item, idx) in secondList"
119
+ :key="item"
120
+ class="picker-item"
121
+ :style="{
122
+ height: itemHeight + 'px',
123
+ lineHeight: itemHeight + 'px',
124
+ color: idx === pickerIndex[5] ? 'var(--prism-primary-color, #3478F6)' : 'var(--prism-text-secondary, #86909C)',
125
+ fontWeight: idx === pickerIndex[5] ? '600' : '400'
126
+ }"
127
+ >
128
+ {{ padZero(item) }}秒
129
+ </view>
130
+ </picker-view-column>
131
+ </picker-view>
132
+ </view>
133
+
134
+ <!-- 日历选择器 -->
135
+ <view class="calendar-body" v-else-if="mode === 'calendar'">
136
+ <!-- 暗纹月份水印 -->
137
+ <view class="calendar-watermark">{{ calendarMonth }}</view>
138
+
139
+ <!-- 年月切换 -->
140
+ <view class="calendar-nav">
141
+ <view class="nav-btn" @click="prevYear">
142
+ <text class="fa fa-angles-left"></text>
143
+ </view>
144
+ <view class="nav-btn" @click="prevMonth">
145
+ <text class="fa fa-chevron-left"></text>
146
+ </view>
147
+ <view class="nav-title">{{ calendarYear }}年{{ calendarMonth }}月</view>
148
+ <view class="nav-btn" @click="nextMonth">
149
+ <text class="fa fa-chevron-right"></text>
150
+ </view>
151
+ <view class="nav-btn" @click="nextYear">
152
+ <text class="fa fa-angles-right"></text>
153
+ </view>
154
+ </view>
155
+
156
+ <!-- 星期标题 -->
157
+ <view class="calendar-weekdays">
158
+ <view class="weekday" v-for="day in weekdays" :key="day">{{ day }}</view>
159
+ </view>
160
+
161
+ <!-- 日期网格 -->
162
+ <view class="calendar-days">
163
+ <view
164
+ class="calendar-day"
165
+ v-for="(day, index) in calendarDays"
166
+ :key="index"
167
+ :class="{
168
+ 'other-month': day.otherMonth,
169
+ 'today': day.isToday,
170
+ 'selected': day.isSelected,
171
+ 'range-start': day.isRangeStart,
172
+ 'range-end': day.isRangeEnd,
173
+ 'in-range': day.isInRange
174
+ }"
175
+ @click="selectCalendarDay(day)"
176
+ >
177
+ <text class="day-text">{{ day.day }}</text>
178
+ <text class="lunar-text" v-if="props.showLunar && day.lunar">{{ day.lunar }}</text>
179
+ <text class="today-label" v-if="day.isToday && !props.showLunar">今</text>
180
+ </view>
181
+ </view>
182
+ </view>
183
+ </view>
184
+ </view>
185
+ </view>
186
+ </template>
187
+
188
+ <script setup>
189
+ import { ref, computed, watch } from 'vue';
190
+ import { useAppStore } from '@/store/app';
191
+ import { rpx2px } from '@/utils/system';
192
+ import { getLunarDayStr } from '@/utils/lunar';
193
+
194
+ const props = defineProps({
195
+ modelValue: {
196
+ type: [String, Number, Date],
197
+ default: ''
198
+ },
199
+ // 选择模式:picker(滚轮)或 calendar(日历)
200
+ mode: {
201
+ type: String,
202
+ default: 'picker',
203
+ validator: (val) => ['picker', 'calendar'].includes(val)
204
+ },
205
+ // 是否显示时间选择
206
+ showTime: {
207
+ type: Boolean,
208
+ default: false
209
+ },
210
+ // 是否显示秒
211
+ showSeconds: {
212
+ type: Boolean,
213
+ default: false
214
+ },
215
+ // 日期格式
216
+ format: {
217
+ type: String,
218
+ default: ''
219
+ },
220
+ placeholder: {
221
+ type: String,
222
+ default: '请选择日期'
223
+ },
224
+ title: {
225
+ type: String,
226
+ default: '选择日期'
227
+ },
228
+ // 最小年份
229
+ minYear: {
230
+ type: Number,
231
+ default: () => new Date().getFullYear() - 50
232
+ },
233
+ // 最大年份
234
+ maxYear: {
235
+ type: Number,
236
+ default: () => new Date().getFullYear() + 50
237
+ },
238
+ disabled: {
239
+ type: Boolean,
240
+ default: false
241
+ },
242
+ // 是否为范围选择模式
243
+ range: {
244
+ type: Boolean,
245
+ default: false
246
+ },
247
+ // 是否显示农历
248
+ showLunar: {
249
+ type: Boolean,
250
+ default: false
251
+ }
252
+ });
253
+
254
+ const emit = defineEmits(['update:modelValue', 'change', 'confirm', 'cancel']);
255
+
256
+ const appStore = useAppStore();
257
+ const showPicker = ref(false);
258
+ const pickerIndex = ref([0, 0, 0, 0, 0, 0]);
259
+
260
+ // picker 配置 - 使用 rpx2px 换算,避免精度问题
261
+ const itemHeight = rpx2px(96);
262
+ const visibleItemCount = 5;
263
+ const pickerHeight = `${itemHeight * visibleItemCount}px`;
264
+ const indicatorStyle = `height: ${itemHeight}px`;
265
+
266
+ // 日历模式状态
267
+ const calendarYear = ref(new Date().getFullYear());
268
+ const calendarMonth = ref(new Date().getMonth() + 1);
269
+ const selectedDate = ref('');
270
+ const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
271
+
272
+ // 范围选择状态
273
+ const rangeStart = ref('');
274
+ const rangeEnd = ref('');
275
+
276
+ // 判断日期是否在范围内
277
+ function isInRange(dateStr) {
278
+ if (!props.range || !rangeStart.value || !rangeEnd.value) return false;
279
+ return dateStr > rangeStart.value && dateStr < rangeEnd.value;
280
+ }
281
+
282
+ // 日历天数
283
+ const calendarDays = computed(() => {
284
+ const days = [];
285
+ const firstDay = new Date(calendarYear.value, calendarMonth.value - 1, 1);
286
+ const lastDay = new Date(calendarYear.value, calendarMonth.value, 0);
287
+ const startWeekday = firstDay.getDay();
288
+ const totalDays = lastDay.getDate();
289
+ const today = new Date();
290
+ const todayStr = `${today.getFullYear()}-${padZero(today.getMonth() + 1)}-${padZero(today.getDate())}`;
291
+
292
+ // 上月填充
293
+ const prevMonthDate = new Date(calendarYear.value, calendarMonth.value - 1, 0);
294
+ const prevMonthYear = calendarMonth.value === 1 ? calendarYear.value - 1 : calendarYear.value;
295
+ const prevMonthNum = calendarMonth.value === 1 ? 12 : calendarMonth.value - 1;
296
+ for (let i = startWeekday - 1; i >= 0; i--) {
297
+ const d = prevMonthDate.getDate() - i;
298
+ const dateStr = `${prevMonthYear}-${padZero(prevMonthNum)}-${padZero(d)}`;
299
+ days.push({
300
+ day: d,
301
+ dateStr,
302
+ otherMonth: true,
303
+ lunar: props.showLunar ? getLunarDayStr(prevMonthYear, prevMonthNum, d) : ''
304
+ });
305
+ }
306
+
307
+ // 当月
308
+ for (let i = 1; i <= totalDays; i++) {
309
+ const dateStr = `${calendarYear.value}-${padZero(calendarMonth.value)}-${padZero(i)}`;
310
+ const isRangeStart = props.range && dateStr === rangeStart.value;
311
+ const isRangeEnd = props.range && dateStr === rangeEnd.value;
312
+ days.push({
313
+ day: i,
314
+ dateStr,
315
+ isToday: dateStr === todayStr,
316
+ isSelected: props.range ? (isRangeStart || isRangeEnd) : dateStr === selectedDate.value,
317
+ isRangeStart,
318
+ isRangeEnd,
319
+ isInRange: isInRange(dateStr),
320
+ lunar: props.showLunar ? getLunarDayStr(calendarYear.value, calendarMonth.value, i) : ''
321
+ });
322
+ }
323
+
324
+ // 下月填充
325
+ const nextMonthYear = calendarMonth.value === 12 ? calendarYear.value + 1 : calendarYear.value;
326
+ const nextMonthNum = calendarMonth.value === 12 ? 1 : calendarMonth.value + 1;
327
+ const remaining = 42 - days.length;
328
+ for (let i = 1; i <= remaining; i++) {
329
+ const dateStr = `${nextMonthYear}-${padZero(nextMonthNum)}-${padZero(i)}`;
330
+ days.push({
331
+ day: i,
332
+ dateStr,
333
+ otherMonth: true,
334
+ lunar: props.showLunar ? getLunarDayStr(nextMonthYear, nextMonthNum, i) : ''
335
+ });
336
+ }
337
+
338
+ return days;
339
+ });
340
+
341
+ // 年份列表
342
+ const yearList = computed(() => {
343
+ const list = [];
344
+ for (let i = props.minYear; i <= props.maxYear; i++) {
345
+ list.push(i);
346
+ }
347
+ return list;
348
+ });
349
+
350
+ // 月份列表
351
+ const monthList = computed(() => {
352
+ const list = [];
353
+ for (let i = 1; i <= 12; i++) {
354
+ list.push(i);
355
+ }
356
+ return list;
357
+ });
358
+
359
+ // 日期列表(根据年月动态计算)
360
+ const dayList = computed(() => {
361
+ const year = yearList.value[pickerIndex.value[0]];
362
+ const month = monthList.value[pickerIndex.value[1]];
363
+ const days = getDaysInMonth(year, month);
364
+ const list = [];
365
+ for (let i = 1; i <= days; i++) {
366
+ list.push(i);
367
+ }
368
+ return list;
369
+ });
370
+
371
+ // 小时列表
372
+ const hourList = computed(() => {
373
+ const list = [];
374
+ for (let i = 0; i <= 23; i++) {
375
+ list.push(i);
376
+ }
377
+ return list;
378
+ });
379
+
380
+ // 分钟列表
381
+ const minuteList = computed(() => {
382
+ const list = [];
383
+ for (let i = 0; i <= 59; i++) {
384
+ list.push(i);
385
+ }
386
+ return list;
387
+ });
388
+
389
+ // 秒列表
390
+ const secondList = computed(() => {
391
+ const list = [];
392
+ for (let i = 0; i <= 59; i++) {
393
+ list.push(i);
394
+ }
395
+ return list;
396
+ });
397
+
398
+ // 获取某月的天数
399
+ function getDaysInMonth(year, month) {
400
+ return new Date(year, month, 0).getDate();
401
+ }
402
+
403
+ // 补零
404
+ function padZero(num) {
405
+ return num < 10 ? '0' + num : String(num);
406
+ }
407
+
408
+ // 默认格式
409
+ const defaultFormat = computed(() => {
410
+ if (props.format) return props.format;
411
+ if (props.showTime) {
412
+ return props.showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm';
413
+ }
414
+ return 'YYYY-MM-DD';
415
+ });
416
+
417
+ // 显示值
418
+ const displayValue = computed(() => {
419
+ if (!props.modelValue) return '';
420
+ return formatDate(parseDate(props.modelValue), defaultFormat.value);
421
+ });
422
+
423
+ // 解析日期
424
+ function parseDate(value) {
425
+ if (!value) return new Date();
426
+ if (value instanceof Date) return value;
427
+ if (typeof value === 'number') return new Date(value);
428
+ if (typeof value === 'string') {
429
+ const d = new Date(value.replace(/-/g, '/'));
430
+ return isNaN(d.getTime()) ? new Date() : d;
431
+ }
432
+ return new Date();
433
+ }
434
+
435
+ // 格式化日期
436
+ function formatDate(date, format) {
437
+ const year = date.getFullYear();
438
+ const month = date.getMonth() + 1;
439
+ const day = date.getDate();
440
+ const hour = date.getHours();
441
+ const minute = date.getMinutes();
442
+ const second = date.getSeconds();
443
+
444
+ return format
445
+ .replace('YYYY', year)
446
+ .replace('MM', padZero(month))
447
+ .replace('DD', padZero(day))
448
+ .replace('HH', padZero(hour))
449
+ .replace('mm', padZero(minute))
450
+ .replace('ss', padZero(second));
451
+ }
452
+
453
+ // 打开选择器
454
+ function openPicker() {
455
+ if (props.disabled) return;
456
+
457
+ // 先初始化数据,再显示弹窗
458
+ if (props.mode === 'calendar') {
459
+ initCalendar();
460
+ } else {
461
+ initPickerIndex();
462
+ }
463
+ showPicker.value = true;
464
+ }
465
+
466
+ // 初始化日历
467
+ function initCalendar() {
468
+ const date = props.modelValue ? parseDate(props.modelValue) : new Date();
469
+ calendarYear.value = date.getFullYear();
470
+ calendarMonth.value = date.getMonth() + 1;
471
+ selectedDate.value = props.modelValue
472
+ ? `${date.getFullYear()}-${padZero(date.getMonth() + 1)}-${padZero(date.getDate())}`
473
+ : '';
474
+ }
475
+
476
+ // 初始化picker索引
477
+ function initPickerIndex() {
478
+ const date = props.modelValue ? parseDate(props.modelValue) : new Date();
479
+ const year = date.getFullYear();
480
+ const month = date.getMonth() + 1;
481
+ const day = date.getDate();
482
+ const hour = date.getHours();
483
+ const minute = date.getMinutes();
484
+ const second = date.getSeconds();
485
+
486
+ const yearIdx = yearList.value.indexOf(year);
487
+ pickerIndex.value = [
488
+ yearIdx >= 0 ? yearIdx : 0,
489
+ month - 1,
490
+ day - 1,
491
+ hour,
492
+ minute,
493
+ second
494
+ ];
495
+ }
496
+
497
+ // 关闭选择器
498
+ function closePicker() {
499
+ showPicker.value = false;
500
+ emit('cancel');
501
+ }
502
+
503
+ // 上一年
504
+ function prevYear() {
505
+ calendarYear.value--;
506
+ }
507
+
508
+ // 下一年
509
+ function nextYear() {
510
+ calendarYear.value++;
511
+ }
512
+
513
+ // 上一月
514
+ function prevMonth() {
515
+ if (calendarMonth.value === 1) {
516
+ calendarMonth.value = 12;
517
+ calendarYear.value--;
518
+ } else {
519
+ calendarMonth.value--;
520
+ }
521
+ }
522
+
523
+ // 下一月
524
+ function nextMonth() {
525
+ if (calendarMonth.value === 12) {
526
+ calendarMonth.value = 1;
527
+ calendarYear.value++;
528
+ } else {
529
+ calendarMonth.value++;
530
+ }
531
+ }
532
+
533
+ // 选择日历日期
534
+ function selectCalendarDay(day) {
535
+ if (day.otherMonth) return;
536
+
537
+ if (props.range) {
538
+ // 范围选择模式
539
+ if (!rangeStart.value || (rangeStart.value && rangeEnd.value)) {
540
+ // 开始新的选择
541
+ rangeStart.value = day.dateStr;
542
+ rangeEnd.value = '';
543
+ } else {
544
+ // 选择结束日期
545
+ if (day.dateStr < rangeStart.value) {
546
+ // 如果选择的日期比开始日期早,交换
547
+ rangeEnd.value = rangeStart.value;
548
+ rangeStart.value = day.dateStr;
549
+ } else {
550
+ rangeEnd.value = day.dateStr;
551
+ }
552
+ }
553
+ } else {
554
+ // 单选模式
555
+ selectedDate.value = day.dateStr;
556
+ }
557
+ }
558
+
559
+ // 回到今天
560
+ function goToday() {
561
+ const today = new Date();
562
+ calendarYear.value = today.getFullYear();
563
+ calendarMonth.value = today.getMonth() + 1;
564
+ selectedDate.value = `${today.getFullYear()}-${padZero(today.getMonth() + 1)}-${padZero(today.getDate())}`;
565
+ }
566
+
567
+ // picker变化
568
+ function onPickerChange(e) {
569
+ const values = e.detail.value;
570
+ // 年月变化时,检查日期是否超出范围
571
+ const year = yearList.value[values[0]];
572
+ const month = monthList.value[values[1]];
573
+ const maxDay = getDaysInMonth(year, month);
574
+ if (values[2] >= maxDay) {
575
+ values[2] = maxDay - 1;
576
+ }
577
+ pickerIndex.value = values;
578
+ }
579
+
580
+ // 确认选择
581
+ function confirmSelect() {
582
+ let result;
583
+
584
+ if (props.mode === 'calendar') {
585
+ if (props.range) {
586
+ // 范围选择模式
587
+ if (!rangeStart.value || !rangeEnd.value) {
588
+ uni.showToast({ title: '请选择完整的日期范围', icon: 'none' });
589
+ return;
590
+ }
591
+ result = [rangeStart.value, rangeEnd.value];
592
+ } else {
593
+ // 单选模式
594
+ if (!selectedDate.value) {
595
+ uni.showToast({ title: '请选择日期', icon: 'none' });
596
+ return;
597
+ }
598
+ result = selectedDate.value;
599
+ }
600
+ } else {
601
+ const year = yearList.value[pickerIndex.value[0]];
602
+ const month = monthList.value[pickerIndex.value[1]];
603
+ const day = dayList.value[pickerIndex.value[2]];
604
+ const hour = props.showTime ? hourList.value[pickerIndex.value[3]] : 0;
605
+ const minute = props.showTime ? minuteList.value[pickerIndex.value[4]] : 0;
606
+ const second = props.showTime && props.showSeconds ? secondList.value[pickerIndex.value[5]] : 0;
607
+
608
+ const date = new Date(year, month - 1, day, hour, minute, second);
609
+ result = formatDate(date, defaultFormat.value);
610
+ }
611
+
612
+ emit('update:modelValue', result);
613
+ emit('change', result);
614
+ emit('confirm', result);
615
+ showPicker.value = false;
616
+ }
617
+ </script>
618
+
619
+ <style lang="scss">
620
+ .prism-datetime-picker {
621
+ .picker-trigger {
622
+ cursor: pointer;
623
+ }
624
+
625
+ .picker-popup {
626
+ position: fixed;
627
+ top: 0;
628
+ left: 0;
629
+ right: 0;
630
+ bottom: 0;
631
+ background: var(--prism-mask-bg, rgba(0, 0, 0, 0.5));
632
+ display: flex;
633
+ align-items: flex-end;
634
+ justify-content: center;
635
+ z-index: 9999;
636
+ }
637
+
638
+ .picker-content {
639
+ width: 100%;
640
+ background: var(--prism-bg-color-card, #FFFFFF);
641
+ border-radius: 24rpx 24rpx 0 0;
642
+ }
643
+
644
+ .picker-header {
645
+ display: flex;
646
+ align-items: center;
647
+ justify-content: space-between;
648
+ padding: 24rpx 32rpx;
649
+ border-bottom: 1rpx solid var(--prism-border-color-light, #E5E6EB);
650
+ }
651
+
652
+ .picker-cancel {
653
+ font-size: 30rpx;
654
+ color: var(--prism-text-secondary, #86909C);
655
+ padding: 8rpx 16rpx;
656
+ }
657
+
658
+ .picker-title {
659
+ font-size: 32rpx;
660
+ font-weight: 600;
661
+ color: var(--prism-text-primary, #1D2129);
662
+ }
663
+
664
+ .picker-confirm {
665
+ font-size: 30rpx;
666
+ color: var(--prism-primary-color, #3478F6);
667
+ padding: 8rpx 16rpx;
668
+ font-weight: 500;
669
+ }
670
+
671
+ .picker-body {
672
+ // height 通过 JS 动态设置
673
+ padding-bottom: env(safe-area-inset-bottom);
674
+ box-sizing: content-box;
675
+ position: relative;
676
+ }
677
+
678
+ .picker-view {
679
+ // height 通过 JS 动态设置
680
+ }
681
+
682
+ .picker-item {
683
+ display: flex;
684
+ align-items: center;
685
+ justify-content: center;
686
+ font-size: 32rpx;
687
+ // height 和 lineHeight 通过 JS 动态设置
688
+ font-weight: 500;
689
+ }
690
+
691
+ // picker指示器样式 - 隐藏原有背景,用文字颜色区分激活项
692
+ :deep(.picker-indicator) {
693
+ // height 通过 indicatorStyle 动态设置
694
+ background: transparent !important;
695
+ background-color: transparent !important;
696
+ border: none !important;
697
+ border-width: 0 !important;
698
+ border-style: none !important;
699
+ border-color: transparent !important;
700
+ box-shadow: none !important;
701
+ z-index: 0 !important;
702
+ }
703
+
704
+ // 确保文字在背景色上方
705
+ .picker-item {
706
+ position: relative;
707
+ z-index: 2;
708
+ }
709
+
710
+ // 浅色模式遮罩层
711
+ :deep(.uni-picker-view-mask) {
712
+ z-index: 1 !important;
713
+ background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)),
714
+ linear-gradient(0deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)) !important;
715
+ }
716
+
717
+ // 覆盖 uni-app 默认边框
718
+ :deep(.uni-picker-view-indicator) {
719
+ border: none !important;
720
+ border-top: none !important;
721
+ border-bottom: none !important;
722
+ border-color: transparent !important;
723
+ }
724
+
725
+ // 日历模式
726
+ .calendar-body {
727
+ padding: 0 24rpx 24rpx;
728
+ padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
729
+ position: relative;
730
+ }
731
+
732
+ // 暗纹月份水印 - 相对于整个日历区域居中
733
+ .calendar-watermark {
734
+ position: absolute;
735
+ top: 50%;
736
+ left: 50%;
737
+ transform: translate(-50%, -50%);
738
+ font-size: 300rpx;
739
+ font-weight: bold;
740
+ color: var(--prism-text-primary, #1D2129);
741
+ opacity: 0.04;
742
+ pointer-events: none;
743
+ z-index: 0;
744
+ user-select: none;
745
+ }
746
+
747
+ .calendar-nav {
748
+ display: flex;
749
+ align-items: center;
750
+ justify-content: center;
751
+ padding: 16rpx 0;
752
+ gap: 16rpx;
753
+ }
754
+
755
+ .nav-btn {
756
+ width: 64rpx;
757
+ height: 64rpx;
758
+ display: flex;
759
+ align-items: center;
760
+ justify-content: center;
761
+ border-radius: 8rpx;
762
+ color: var(--prism-text-secondary, #86909C);
763
+
764
+ &:active {
765
+ background: var(--prism-bg-color-container, #F7F8FA);
766
+ }
767
+ }
768
+
769
+ .nav-title {
770
+ font-size: 32rpx;
771
+ font-weight: 600;
772
+ color: var(--prism-text-primary, #1D2129);
773
+ min-width: 200rpx;
774
+ text-align: center;
775
+ }
776
+
777
+ .today-btn {
778
+ position: absolute;
779
+ right: 0;
780
+ background: var(--prism-primary-light, rgba(52, 120, 246, 0.08));
781
+ color: var(--prism-primary-color, #3478F6);
782
+ font-size: 24rpx;
783
+ font-weight: 500;
784
+ }
785
+
786
+ .calendar-weekdays {
787
+ display: grid;
788
+ grid-template-columns: repeat(7, 1fr);
789
+ margin-bottom: 8rpx;
790
+ }
791
+
792
+ .weekday {
793
+ text-align: center;
794
+ font-size: 26rpx;
795
+ color: var(--prism-text-secondary, #86909C);
796
+ padding: 16rpx 0;
797
+ }
798
+
799
+ .calendar-days {
800
+ display: grid;
801
+ grid-template-columns: repeat(7, 1fr);
802
+ gap: 8rpx;
803
+ position: relative;
804
+ z-index: 1;
805
+ }
806
+
807
+ .calendar-day {
808
+ aspect-ratio: 1;
809
+ display: flex;
810
+ flex-direction: column;
811
+ align-items: center;
812
+ justify-content: center;
813
+ border-radius: 12rpx;
814
+ position: relative;
815
+
816
+ &.other-month {
817
+ .day-text {
818
+ color: var(--prism-text-placeholder, #C9CDD4);
819
+ }
820
+ }
821
+
822
+ &.today {
823
+ .day-text {
824
+ color: #FF6B00;
825
+ font-weight: 600;
826
+ }
827
+ }
828
+
829
+ &.selected {
830
+ background: var(--prism-primary-color, #3478F6);
831
+ .day-text {
832
+ color: #FFFFFF;
833
+ }
834
+ .today-label {
835
+ color: rgba(255, 255, 255, 0.8);
836
+ }
837
+ }
838
+
839
+ // 今天被选中时,使用选中样式
840
+ &.today.selected {
841
+ background: var(--prism-primary-color, #3478F6);
842
+ .day-text {
843
+ color: #FFFFFF;
844
+ }
845
+ .today-label {
846
+ color: rgba(255, 255, 255, 0.8);
847
+ }
848
+ }
849
+
850
+ // 范围选择 - 开始日期
851
+ &.range-start {
852
+ background: var(--prism-primary-color, #3478F6);
853
+ border-radius: 12rpx 0 0 12rpx;
854
+ .day-text, .lunar-text {
855
+ color: #FFFFFF;
856
+ }
857
+ }
858
+
859
+ // 范围选择 - 结束日期
860
+ &.range-end {
861
+ background: var(--prism-primary-color, #3478F6);
862
+ border-radius: 0 12rpx 12rpx 0;
863
+ .day-text, .lunar-text {
864
+ color: #FFFFFF;
865
+ }
866
+ }
867
+
868
+ // 范围选择 - 中间日期
869
+ &.in-range {
870
+ background: var(--prism-primary-light, rgba(52, 120, 246, 0.1));
871
+ border-radius: 0;
872
+ }
873
+
874
+ // 开始和结束是同一天
875
+ &.range-start.range-end {
876
+ border-radius: 12rpx;
877
+ }
878
+ }
879
+
880
+ .day-text {
881
+ font-size: 30rpx;
882
+ color: var(--prism-text-primary, #1D2129);
883
+ }
884
+
885
+ // 农历文字
886
+ .lunar-text {
887
+ font-size: 18rpx;
888
+ color: var(--prism-text-secondary, #86909C);
889
+ margin-top: 4rpx;
890
+ }
891
+
892
+ .today-label {
893
+ font-size: 20rpx;
894
+ color: #FF6B00;
895
+ position: absolute;
896
+ bottom: 6rpx;
897
+ }
898
+ }
899
+
900
+ // 深色模式
901
+ .dark-mode.prism-datetime-picker {
902
+ .picker-content {
903
+ background: var(--prism-bg-color-card, #1A1A1A);
904
+ }
905
+
906
+ // picker 滚轮模式
907
+ .picker-item {
908
+ color: var(--prism-text-primary, #E5E6EB);
909
+ }
910
+
911
+ :deep(.picker-indicator) {
912
+ background: transparent !important;
913
+ background-color: transparent !important;
914
+ border: none !important;
915
+ border-width: 0 !important;
916
+ border-style: none !important;
917
+ border-color: transparent !important;
918
+ }
919
+
920
+ :deep(.uni-picker-view-indicator) {
921
+ background: transparent !important;
922
+ border: none !important;
923
+ border-width: 0 !important;
924
+ border-color: transparent !important;
925
+ }
926
+
927
+ :deep(.uni-picker-view-indicator)::before,
928
+ :deep(.uni-picker-view-indicator)::after {
929
+ display: none !important;
930
+ border: none !important;
931
+ }
932
+
933
+ :deep(picker-view-column)::before,
934
+ :deep(picker-view-column)::after {
935
+ display: none !important;
936
+ }
937
+
938
+ // 修复 picker-view 上下遮罩渐变色
939
+ :deep(picker-view) {
940
+ --picker-mask-bg: linear-gradient(180deg, rgba(26, 26, 26, 0.95), rgba(26, 26, 26, 0.6));
941
+ }
942
+
943
+ :deep(.uni-picker-view-mask) {
944
+ background-image: linear-gradient(180deg, rgba(26, 26, 26, 0.95), rgba(26, 26, 26, 0.6)),
945
+ linear-gradient(0deg, rgba(26, 26, 26, 0.95), rgba(26, 26, 26, 0.6)) !important;
946
+ }
947
+
948
+ // 日历模式
949
+ .nav-btn:active {
950
+ background: var(--prism-bg-color-container, #2A2A2A);
951
+ }
952
+ }
953
+ </style>