Просмотр исходного кода

feat(activity): A1 创建活动页时间选择器改为 5 分钟粒度双列 picker

A1(设计文档 2026-10-26-activity-create-page-ux-and-audience-design.md §一):
- 3 处 <picker mode=time> 改为原生 multiSelector 双列:时 24 项 00..23,分 12 项 00/05/.../55,
  5 分钟粒度在选择过程中即生效,用户无法选中非 5 倍数分钟,不再出现选了 07 分却变 05 分
- 删除 3 处 picker-hint 提示文案「以 5 分钟为一个单元(如 14:00、14:05)」及对应 CSS 注释/样式
- data 新增 hourOptions/minuteOptions 与 3 组时/分索引;新增 applyTimeParts 统一做索引↔字符串双向同步
- 新增 onStartTimeColChange/onEndTimeColChange/onRegTimeColChange;日期逻辑与 syncXxx 组装逻辑不变
- 回填路径 fmtTime/loadDetail 归一化后反算分钟索引,picker 显示与提交值一致(14:07 → 显示与提交均为 14:05)
- normalizeTimeTo5Min 保留为兜底(历史脏数据/后台写入非 5 倍数仍收敛),提交串恒为 YYYY-MM-DDTHH:mm:00
zxyj-web 2 недель назад
Родитель
Сommit
7c0009bf11
1 измененных файлов с 42 добавлено и 23 удалено
  1. 42 23
      cfc-frontend/pages/activity/create.vue

+ 42 - 23
cfc-frontend/pages/activity/create.vue

@@ -21,10 +21,9 @@
         <picker class="form-picker" mode="date" :value="datePart" @change="onStartDateChange">
           <text class="picker-text">{{ datePart || '选择日期' }}</text>
         </picker>
-        <picker class="form-picker" mode="time" :value="timePart" @change="onStartTimeChange" style="margin-top: 16rpx;">
+        <picker class="form-picker" mode="multiSelector" :range="[hourOptions, minuteOptions]" :value="[hourIdx, minuteIdx]" @change="onStartTimeColChange" style="margin-top: 16rpx;">
           <text class="picker-text">{{ timePart || '选择时间' }}</text>
         </picker>
-        <text class="picker-hint">以 5 分钟为一个单元(如 14:00、14:05)</text>
       </view>
 
       <!-- 结束时间 -->
@@ -33,10 +32,9 @@
         <picker class="form-picker" mode="date" :value="endDatePart" @change="onEndDateChange">
           <text class="picker-text">{{ endDatePart || '选择日期(可选)' }}</text>
         </picker>
-        <picker class="form-picker" mode="time" :value="endTimePart" @change="onEndTimeChange" style="margin-top: 16rpx;">
+        <picker class="form-picker" mode="multiSelector" :range="[hourOptions, minuteOptions]" :value="[endHourIdx, endMinuteIdx]" @change="onEndTimeColChange" style="margin-top: 16rpx;">
           <text class="picker-text">{{ endTimePart || '选择时间(可选)' }}</text>
         </picker>
-        <text class="picker-hint">以 5 分钟为一个单元(如 14:00、14:05)</text>
       </view>
 
       <!-- 报名截止时间 -->
@@ -45,10 +43,9 @@
         <picker class="form-picker" mode="date" :value="regDatePart" @change="onRegDateChange">
           <text class="picker-text">{{ regDatePart || '选择日期(不填=活动开始前)' }}</text>
         </picker>
-        <picker class="form-picker" mode="time" :value="regTimePart" @change="onRegTimeChange" style="margin-top: 16rpx;">
+        <picker class="form-picker" mode="multiSelector" :range="[hourOptions, minuteOptions]" :value="[regHourIdx, regMinuteIdx]" @change="onRegTimeColChange" style="margin-top: 16rpx;">
           <text class="picker-text">{{ regTimePart || '选择时间' }}</text>
         </picker>
-        <text class="picker-hint">以 5 分钟为一个单元(如 14:00、14:05)</text>
       </view>
 
       <!-- 活动地点 -->
@@ -241,6 +238,15 @@ export default {
       endTimePart: '',
       regDatePart: '',
       regTimePart: '',
+      // 时间选择器(multiSelector 双列):时 24 项 00..23,分 12 项严格 5 分钟粒度
+      hourOptions: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
+      minuteOptions: ['00', '05', '10', '15', '20', '25', '30', '35', '40', '45', '50', '55'],
+      hourIdx: 0,
+      minuteIdx: 0,
+      endHourIdx: 0,
+      endMinuteIdx: 0,
+      regHourIdx: 0,
+      regMinuteIdx: 0,
       typeIdx: 0,
       typeOptions: [
         { key: 'offline', label: '线下活动' },
@@ -302,6 +308,8 @@ export default {
             if (sd) {
               self.datePart = self.fmtDate(sd)
               self.timePart = self.fmtTime(sd)
+              // 反算时/分列索引,picker 回填显示与提交值保持一致(均为 5 分钟粒度)
+              self.applyTimeParts(self.timePart, 'hourIdx', 'minuteIdx')
               // 走 sync 统一组装:timePart 已被 fmtTime 收敛到 5 的倍数,保证"显示即提交"
               self.syncStartTime()
             }
@@ -311,6 +319,7 @@ export default {
             if (ed) {
               self.endDatePart = self.fmtDate(ed)
               self.endTimePart = self.fmtTime(ed)
+              self.applyTimeParts(self.endTimePart, 'endHourIdx', 'endMinuteIdx')
               self.syncEndTime()
             }
           }
@@ -319,6 +328,7 @@ export default {
             if (rd) {
               self.regDatePart = self.fmtDate(rd)
               self.regTimePart = self.fmtTime(rd)
+              self.applyTimeParts(self.regTimePart, 'regHourIdx', 'regMinuteIdx')
               self.syncRegDeadline()
             }
           }
@@ -337,30 +347,38 @@ export default {
       this.datePart = e.detail.value
       this.syncStartTime()
     },
-    onStartTimeChange(e) {
-      // 小程序原生 time picker 不支持步长,选中后把分钟向下取整到 5 的整数倍(14:07 → 14:05、14:08 → 14:05)
-      this.timePart = this.normalizeTimeTo5Min(e.detail.value)
+    onStartTimeColChange(e) {
+      // multiSelector 双列:时/分列索引直取 options 值,选择过程中即 5 分钟粒度,无需事后取整
+      var v = e.detail.value
+      this.hourIdx = v[0]
+      this.minuteIdx = v[1]
+      this.timePart = this.hourOptions[v[0]] + ':' + this.minuteOptions[v[1]]
       this.syncStartTime()
     },
     onEndDateChange(e) {
       this.endDatePart = e.detail.value
       this.syncEndTime()
     },
-    onEndTimeChange(e) {
-      // 同上:结束时间分钟同样收敛到 5 的整数倍
-      this.endTimePart = this.normalizeTimeTo5Min(e.detail.value)
+    onEndTimeColChange(e) {
+      var v = e.detail.value
+      this.endHourIdx = v[0]
+      this.endMinuteIdx = v[1]
+      this.endTimePart = this.hourOptions[v[0]] + ':' + this.minuteOptions[v[1]]
       this.syncEndTime()
     },
     onRegDateChange(e) {
       this.regDatePart = e.detail.value
       this.syncRegDeadline()
     },
-    onRegTimeChange(e) {
-      // 同上:报名截止时间分钟同样收敛到 5 的整数倍
-      this.regTimePart = this.normalizeTimeTo5Min(e.detail.value)
+    onRegTimeColChange(e) {
+      var v = e.detail.value
+      this.regHourIdx = v[0]
+      this.regMinuteIdx = v[1]
+      this.regTimePart = this.hourOptions[v[0]] + ':' + this.minuteOptions[v[1]]
       this.syncRegDeadline()
     },
     // 活动时间统一为 5 分钟粒度:分钟向下取整到 5 的倍数(14:07 → 14:05、14:59 → 14:55)
+    // 兜底保留:历史脏数据、后台/接口写入非 5 倍数分钟时仍能收敛(选择器本身已无法选中非 5 倍数)
     normalizeTimeTo5Min: function(t) {
       if (!t) return t
       var parts = String(t).split(':')
@@ -371,6 +389,15 @@ export default {
       mi = Math.floor(mi / 5) * 5
       return ('0' + h).slice(-2) + ':' + ('0' + mi).slice(-2)
     },
+    // 把 HH:MM 字符串同步到 picker 时/分列索引(先归一化到 5 分钟粒度,保证"显示即提交")
+    // 供初始化、历史回填与列变更回写共用;hourIdxRef/minuteIdxRef 为 data 字段名
+    applyTimeParts: function(t, hourIdxRef, minuteIdxRef) {
+      var s = this.normalizeTimeTo5Min(t)
+      if (!s) return
+      var parts = String(s).split(':')
+      this[hourIdxRef] = parseInt(parts[0], 10)
+      this[minuteIdxRef] = Math.floor(parseInt(parts[1], 10) / 5)
+    },
     syncStartTime() {
       if (this.datePart && this.timePart) {
         this.form.startTime = this.datePart + 'T' + this.timePart + ':00'
@@ -674,14 +701,6 @@ export default {
   color: $uni-text-color;
 }
 
-/* 时间选择提示:活动时间以 5 分钟为一个单元 */
-.picker-hint {
-  display: block;
-  margin-top: 4rpx;
-  font-size: 20rpx;
-  color: $uni-text-color-secondary;
-}
-
 .form-textarea {
   width: 100%;
   min-height: 160rpx;