浏览代码

fix(activity):活动时间收敛为 5 分钟粒度(只到分,小程序+后台)

zxyj-web 2 周之前
父节点
当前提交
707ea09ad
共有 2 个文件被更改,包括 70 次插入 和 11 次删除
  1. 34 7
      cfc-frontend/pages/activity/create.vue
  2. 36 4
      cfc-web/src/views/admin/ActivityEdit.vue

+ 34 - 7
cfc-frontend/pages/activity/create.vue

@@ -24,6 +24,7 @@
         <picker class="form-picker" mode="time" :value="timePart" @change="onStartTimeChange" style="margin-top: 16rpx;">
           <text class="picker-text">{{ timePart || '选择时间' }}</text>
         </picker>
+        <text class="picker-hint">以 5 分钟为一个单元(如 14:00、14:05)</text>
       </view>
 
       <!-- 结束时间 -->
@@ -35,6 +36,7 @@
         <picker class="form-picker" mode="time" :value="endTimePart" @change="onEndTimeChange" style="margin-top: 16rpx;">
           <text class="picker-text">{{ endTimePart || '选择时间(可选)' }}</text>
         </picker>
+        <text class="picker-hint">以 5 分钟为一个单元(如 14:00、14:05)</text>
       </view>
 
       <!-- 报名截止时间 -->
@@ -46,6 +48,7 @@
         <picker class="form-picker" mode="time" :value="regTimePart" @change="onRegTimeChange" style="margin-top: 16rpx;">
           <text class="picker-text">{{ regTimePart || '选择时间' }}</text>
         </picker>
+        <text class="picker-hint">以 5 分钟为一个单元(如 14:00、14:05)</text>
       </view>
 
       <!-- 活动地点 -->
@@ -297,7 +300,8 @@ export default {
             if (sd) {
               self.datePart = self.fmtDate(sd)
               self.timePart = self.fmtTime(sd)
-              self.form.startTime = sd.toISOString()
+              // 走 sync 统一组装:timePart 已被 fmtTime 收敛到 5 的倍数,保证"显示即提交"
+              self.syncStartTime()
             }
           }
           if (d.endTime) {
@@ -305,7 +309,7 @@ export default {
             if (ed) {
               self.endDatePart = self.fmtDate(ed)
               self.endTimePart = self.fmtTime(ed)
-              self.form.endTime = ed.toISOString()
+              self.syncEndTime()
             }
           }
           if (d.registrationDeadline) {
@@ -313,7 +317,7 @@ export default {
             if (rd) {
               self.regDatePart = self.fmtDate(rd)
               self.regTimePart = self.fmtTime(rd)
-              self.form.registrationDeadline = rd.toISOString()
+              self.syncRegDeadline()
             }
           }
           // sync type picker
@@ -332,7 +336,8 @@ export default {
       this.syncStartTime()
     },
     onStartTimeChange(e) {
-      this.timePart = e.detail.value
+      // 小程序原生 time picker 不支持步长,选中后把分钟向下取整到 5 的整数倍(14:07 → 14:05、14:08 → 14:05)
+      this.timePart = this.normalizeTimeTo5Min(e.detail.value)
       this.syncStartTime()
     },
     onEndDateChange(e) {
@@ -340,7 +345,8 @@ export default {
       this.syncEndTime()
     },
     onEndTimeChange(e) {
-      this.endTimePart = e.detail.value
+      // 同上:结束时间分钟同样收敛到 5 的整数倍
+      this.endTimePart = this.normalizeTimeTo5Min(e.detail.value)
       this.syncEndTime()
     },
     onRegDateChange(e) {
@@ -348,9 +354,21 @@ export default {
       this.syncRegDeadline()
     },
     onRegTimeChange(e) {
-      this.regTimePart = e.detail.value
+      // 同上:报名截止时间分钟同样收敛到 5 的整数倍
+      this.regTimePart = this.normalizeTimeTo5Min(e.detail.value)
       this.syncRegDeadline()
     },
+    // 活动时间统一为 5 分钟粒度:分钟向下取整到 5 的倍数(14:07 → 14:05、14:59 → 14:55)
+    normalizeTimeTo5Min: function(t) {
+      if (!t) return t
+      var parts = String(t).split(':')
+      if (parts.length < 2) return t
+      var h = parseInt(parts[0], 10)
+      var mi = parseInt(parts[1], 10)
+      if (isNaN(h) || isNaN(mi)) return t
+      mi = Math.floor(mi / 5) * 5
+      return ('0' + h).slice(-2) + ':' + ('0' + mi).slice(-2)
+    },
     syncStartTime() {
       if (this.datePart && this.timePart) {
         this.form.startTime = this.datePart + 'T' + this.timePart + ':00'
@@ -572,7 +590,8 @@ export default {
     fmtTime(d) {
       var h = ('0' + d.getHours()).slice(-2)
       var mi = ('0' + d.getMinutes()).slice(-2)
-      return h + ':' + mi
+      // 回填同样收敛到 5 分钟粒度,避免历史非 5 倍数分钟再次保存前产生"选了 7 分却变 5 分"的困惑
+      return this.normalizeTimeTo5Min(h + ':' + mi)
     }
   }
 }
@@ -651,6 +670,14 @@ 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;

+ 36 - 4
cfc-web/src/views/admin/ActivityEdit.vue

@@ -156,12 +156,26 @@
         <el-row :gutter="16">
           <el-col :span="12">
             <el-form-item label="开始时间" prop="startTime">
-              <el-date-picker v-model="form.startTime" type="datetime" placeholder="选择开始时间" class="full-width" :disabled="readonly" />
+              <el-date-picker
+                v-model="form.startTime"
+                type="datetime"
+                placeholder="选择开始时间(以 5 分钟为单元)"
+                class="full-width"
+                :disabled="readonly"
+                format="yyyy-MM-dd HH:mm"
+                @change="onStartTimeChange" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="结束时间" prop="endTime">
-              <el-date-picker v-model="form.endTime" type="datetime" placeholder="选择结束时间" class="full-width" :disabled="readonly" />
+              <el-date-picker
+                v-model="form.endTime"
+                type="datetime"
+                placeholder="选择结束时间(以 5 分钟为单元)"
+                class="full-width"
+                :disabled="readonly"
+                format="yyyy-MM-dd HH:mm"
+                @change="onEndTimeChange" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -352,6 +366,24 @@ export default {
     handleEditorCreated(editor) {
       this.editorInstance = editor
     },
+    // 活动时间收敛到 5 分钟粒度:分钟向下取整到 5 的倍数、秒清零(14:07 → 14:05)。
+    // Date 入参返回 Date(与 v-model 类型保持一致,提交时仍由 axios 序列化为带秒的 ISO 串);
+    // 字符串入参(详情回填)返回 ISO 串,均不改变后端既有解析契约。
+    floorTo5Minutes(value) {
+      if (!value) return value
+      const isDateObj = value instanceof Date
+      const d = isDateObj ? new Date(value.getTime()) : new Date(value)
+      if (isNaN(d.getTime())) return value
+      d.setMinutes(Math.floor(d.getMinutes() / 5) * 5, 0, 0)
+      return isDateObj ? d : d.toISOString()
+    },
+    // 选择后兜底:输入框可手动输入,非 5 倍数分钟在此收敛
+    onStartTimeChange(val) {
+      this.form.startTime = this.floorTo5Minutes(val)
+    },
+    onEndTimeChange(val) {
+      this.form.endTime = this.floorTo5Minutes(val)
+    },
     async loadDetail(id) {
       this.loading = true
       try {
@@ -381,8 +413,8 @@ export default {
             maxParticipants: d.maxParticipants || null,
             priceYuan: d.price ? d.price / 100 : null,
             memberPriceYuan: d.memberPrice && d.memberPrice > 0 ? d.memberPrice / 100 : null,
-            startTime: d.startTime || null,
-            endTime: d.endTime || null,
+            startTime: this.floorTo5Minutes(d.startTime),
+            endTime: this.floorTo5Minutes(d.endTime),
             visibility: d.visibility || 'public',
             visibleScope: d.visibleScope || '',
             visibleTo: d.visibleTo || '',