Browse Source

✨ feat: 添加自动乐跑区间设置

Pchen0 2 months ago
parent
commit
d0b278bd98

+ 121 - 3
src/pages/admin/lepaoAccount/accountList.vue

@@ -140,6 +140,13 @@
                 <template #target_count="{ record }">
                     {{ record.target_count === 0 ? '不限' : formatKm(record.target_count) }}
                 </template>
+                <template #auto_run_distance="{ record }">
+                    {{ record.auto_run ? formatAutoDistanceRangeLabel(record.auto_run_distance_min_km ?? 2, record.auto_run_distance_max_km ?? 2) : '—' }}
+                </template>
+                <template #auto_run_pace="{ record }">
+                    <span v-if="record.auto_run">{{ formatAutoPaceRangeLabel(record.pace_min_sec_per_km ?? 180, record.pace_max_sec_per_km ?? 600) }}</span>
+                    <span v-else>—</span>
+                </template>
                 <template #term_num="{ record }">
                     {{ formatKm(record.term_num) }}
                 </template>
@@ -238,6 +245,40 @@
             <a-form-item field="auto_time" label="自动乐跑时段" v-if="form.auto_run">
                 <a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
             </a-form-item>
+            <a-form-item field="auto_run_distance_range" label="自动距离区间(km)" v-if="form.auto_run">
+                <a-space>
+                    <a-input-number
+                        v-model="form.auto_run_distance_min_km"
+                        mode="button"
+                        :min="1"
+                        :max="AUTO_SINGLE_RUN_MAX_KM"
+                        :precision="2"
+                        :step="0.5"
+                    />
+                    <span>~</span>
+                    <a-input-number
+                        v-model="form.auto_run_distance_max_km"
+                        mode="button"
+                        :min="1"
+                        :max="AUTO_SINGLE_RUN_MAX_KM"
+                        :precision="2"
+                        :step="0.5"
+                    />
+                </a-space>
+                <template #extra>
+                    <div>每次自动乐跑在区间内随机距离(0.5km 步进);上下限可相同;上限 {{ AUTO_SINGLE_RUN_MAX_KM }}km</div>
+                </template>
+            </a-form-item>
+            <a-form-item field="pace_range" label="自动配速区间" v-if="form.auto_run">
+                <a-space>
+                    <a-input v-model="form.pace_min_str" allow-clear placeholder="如 3:00" style="width: 7.5rem" />
+                    <span>–</span>
+                    <a-input v-model="form.pace_max_str" allow-clear placeholder="如 10:00" style="width: 7.5rem" />
+                </a-space>
+                <template #extra>
+                    <div>每公里 分:秒(如 5:30);范围 3:00–10:00/km,自动乐跑在此区间内随机</div>
+                </template>
+            </a-form-item>
             <a-form-item field="notes" label="备注">
                 <a-textarea v-model="form.notes" placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
                     show-word-limit />
@@ -262,7 +303,15 @@ import faceModal from '@/components/FaceModal/faceModal.vue'
 import bindBot from '@/components/BindBot/bindBot.vue'
 import SingleRunModal from '@/components/SingleRunModal/SingleRunModal.vue'
 import { getSemesterTimestamps } from '@/utils/util'
-import { formatKm } from '@/utils/lepaoRecord'
+import {
+    formatKm,
+    formatAutoPaceRangeLabel,
+    formatAutoDistanceRangeLabel,
+    formatSecPerKmAsMinSec,
+    parsePaceMinSecInput
+} from '@/utils/lepaoRecord'
+
+const AUTO_SINGLE_RUN_MAX_KM = 5
 
 const faceRecoRef = ref(null)
 const bindBotRef = ref(null)
@@ -299,6 +348,10 @@ const form = reactive({
     auto_run: 1,
     auto_time: -1,
     target_count: 30,
+    auto_run_distance_min_km: 2,
+    auto_run_distance_max_km: 2,
+    pace_min_str: '3:00',
+    pace_max_str: '10:00',
     auto_day: [0, 1, 2, 3, 4, 5, 6],
     notes: ''
 })
@@ -378,6 +431,14 @@ const columns = [
         title: '目标里程',
         slotName: 'target_count',
         width: 130
+    }, {
+        title: '自动距离(km)',
+        slotName: 'auto_run_distance',
+        width: 130
+    }, {
+        title: '自动配速区间',
+        slotName: 'auto_run_pace',
+        width: 175
     }, {
         title: '本月里程',
         slotName: 'term_num',
@@ -476,6 +537,14 @@ const editAccount = (item) => {
         form.auto_day = item.auto_day
         form.notice_type = item.notice_type || 'email'
         form.target_count = Number(item.target_count)
+        form.auto_run_distance_min_km = Number(item.auto_run_distance_min_km ?? 2)
+        form.auto_run_distance_max_km = Number(item.auto_run_distance_max_km ?? item.auto_run_distance_min_km ?? 2)
+        {
+            const a = formatSecPerKmAsMinSec(item.pace_min_sec_per_km ?? 180)
+            const b = formatSecPerKmAsMinSec(item.pace_max_sec_per_km ?? 600)
+            form.pace_min_str = a === '—' ? '3:00' : a
+            form.pace_max_str = b === '—' ? '10:00' : b
+        }
         form.notes = item.notes
     } else {
         form.id = null
@@ -483,6 +552,10 @@ const editAccount = (item) => {
         form.auto_run = 1
         form.auto_time = 7
         form.target_count = 30
+        form.auto_run_distance_min_km = 2
+        form.auto_run_distance_max_km = 2
+        form.pace_min_str = '3:00'
+        form.pace_max_str = '10:00'
         form.auto_day = [0, 1, 2, 3, 4, 5, 6]
         form.email = ''
         form.notes = ''
@@ -511,16 +584,61 @@ const handleBeforeOk = async (done) => {
             return false
         }
 
+        const pLo = parsePaceMinSecInput(form.pace_min_str)
+        const pHi = parsePaceMinSecInput(form.pace_max_str)
+        if (pLo == null || pHi == null) {
+            Message.error('配速格式不正确,请使用 分:秒,如 5:30 或 10:00')
+            return false
+        }
+        if (pLo < 180 || pLo > 600 || pHi < 180 || pHi > 600) {
+            Message.error('配速须在 3:00–10:00/km 范围内')
+            return false
+        }
+        if (pLo > pHi) {
+            Message.error('配速下限不能慢于上限(左侧应更快,分:秒更小)')
+            return false
+        }
+
         if (form.auto_run) {
             const tk = Number(form.target_count)
             if (!Number.isFinite(tk) || tk < 0 || tk > 200) {
                 Message.error('目标里程须在 0–200 公里之间')
                 return false
             }
+            const dMin = Number(form.auto_run_distance_min_km)
+            const dMax = Number(form.auto_run_distance_max_km)
+            if (
+                !Number.isFinite(dMin) ||
+                !Number.isFinite(dMax) ||
+                dMin < 1 ||
+                dMin > AUTO_SINGLE_RUN_MAX_KM ||
+                dMax < 1 ||
+                dMax > AUTO_SINGLE_RUN_MAX_KM
+            ) {
+                Message.error(`自动乐跑距离区间须在 1–${AUTO_SINGLE_RUN_MAX_KM} 公里之间`)
+                return false
+            }
+            if (dMin > dMax) {
+                Message.error('自动乐跑距离下限不能大于上限')
+                return false
+            }
         }
 
-        let data = {
-            ...form
+        const data = {
+            id: form.id,
+            student_num: form.student_num,
+            email: form.email,
+            area: form.area,
+            auto_run: form.auto_run,
+            auto_time: form.auto_time,
+            notice_type: form.notice_type,
+            target_count: form.target_count,
+            auto_run_distance_min_km: form.auto_run_distance_min_km,
+            auto_run_distance_max_km: form.auto_run_distance_max_km,
+            auto_day: form.auto_day,
+            notes: form.notes,
+            pace_min_sec_per_km: pLo,
+            pace_max_sec_per_km: pHi
         }
 
         const res = await addAccount(data)

+ 1 - 1
src/pages/admin/lepaoRecords/lepaoRecords.vue

@@ -139,7 +139,7 @@
               <span v-else>—</span>
             </template>
           </a-table-column>
-          <a-table-column title="乐跑距离" :width="120" ellipsis tooltip>
+          <a-table-column title="实际距离" :width="120" ellipsis tooltip>
             <template #cell="{ record }">
               {{ formatKm(recordDisplay(record).distance) }}
             </template>

+ 127 - 11
src/pages/lepao/accountList/index.vue

@@ -13,7 +13,7 @@
           添加账号
         </a-button>
 
-        <a-button size="large" @click="download('windows')" style="margin-left: 10px;">
+        <!-- <a-button size="large" @click="download('windows')" style="margin-left: 10px;">
           <template #icon>
             <icon-desktop />
           </template>
@@ -39,7 +39,7 @@
             <icon-download />
           </template>
           客户端/登录器下载
-        </a-button>
+        </a-button> -->
       </div>
 
       <a-row class="queryForm">
@@ -118,7 +118,7 @@
       <a-alert v-if="notice" style="margin-bottom: 15px;">{{ notice }}</a-alert>
 
       <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" expandable :scroll="{
-        x: 1600
+        x: 1780
       }" :pagination="{
         showPageSize: true,
         showJumper: true,
@@ -225,24 +225,35 @@
               {{ record.target_count === 0 ? '不限' : formatKm(record.target_count) }}
             </template>
           </a-table-column>
+          <a-table-column title="自动距离区间" :width="130" ellipsis tooltip>
+            <template #cell="{ record }">
+              {{ record.auto_run ? formatAutoDistanceRangeLabel(record.auto_run_distance_min_km ?? 2, record.auto_run_distance_max_km ?? 2) : '—' }}
+            </template>
+          </a-table-column>
+          <a-table-column title="自动配速区间" :width="150" ellipsis tooltip>
+            <template #cell="{ record }">
+              <span v-if="record.auto_run">{{ formatAutoPaceRangeLabel(record.pace_min_sec_per_km ?? 180, record.pace_max_sec_per_km ?? 600) }}</span>
+              <span v-else>—</span>
+            </template>
+          </a-table-column>
           <a-table-column title="本月里程" :width="100" ellipsis tooltip>
             <template #cell="{ record }">
               {{ formatKm(record.term_num) }}
             </template>
           </a-table-column>
-          <a-table-column title="累计里程" :width="100" ellipsis tooltip>
+          <a-table-column title="累计里程" :width="110" ellipsis tooltip>
             <template #cell="{ record }">
               {{ formatKm(record.total_num) }}
             </template>
           </a-table-column>
-          <a-table-column title="添加时间" :width="145" ellipsis tooltip :sortable="{
+          <a-table-column title="添加时间" :width="150" ellipsis tooltip :sortable="{
             sortDirections: ['ascend', 'descend']
           }">
             <template #cell="{ record }">
               {{ stramptoTime(record.create_time) }}
             </template>
           </a-table-column>
-          <a-table-column title="上次更新时间" :width="145" ellipsis tooltip>
+          <a-table-column title="上次更新时间" :width="150" ellipsis tooltip>
             <template #cell="{ record }">
               {{ record.update_time ? stramptoTime(record.update_time) : '待登录' }}
             </template>
@@ -289,7 +300,7 @@
         <template #extra>
           <a-space>
             <a-button type="primary" @click="handleAutoFill" size="small">识别并填入</a-button>
-            <span class="auto-fill-tip">示例:2023012345,abc@ctbu.edu.cn,南山校区</span>
+            <span class="auto-fill-tip">示例:2301234567,abc@qq.com,南山校区</span>
           </a-space>
         </template>
       </a-form-item>
@@ -333,6 +344,42 @@
       <a-form-item field="auto_time" label="自动乐跑时段" v-if="form.auto_run">
         <a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
       </a-form-item>
+      <a-form-item field="auto_run_distance_range" label="自动距离区间(km)" v-if="form.auto_run">
+        <a-space>
+          <a-input-number
+            v-model="form.auto_run_distance_min_km"
+            mode="button"
+            :min="1"
+            :max="AUTO_SINGLE_RUN_MAX_KM"
+            :precision="2"
+            :step="0.5"
+            placeholder="下限"
+          />
+          <span>~</span>
+          <a-input-number
+            v-model="form.auto_run_distance_max_km"
+            mode="button"
+            :min="1"
+            :max="AUTO_SINGLE_RUN_MAX_KM"
+            :precision="2"
+            :step="0.5"
+            placeholder="上限"
+          />
+        </a-space>
+        <template #extra>
+          <div>每次自动乐跑在区间内按 0.5km 步进随机取值;上下限可相同(固定距离);上限不超过 {{ AUTO_SINGLE_RUN_MAX_KM }}km</div>
+        </template>
+      </a-form-item>
+      <a-form-item field="pace_range" label="自动配速区间" v-if="form.auto_run">
+        <a-space>
+          <a-input v-model="form.pace_min_str" allow-clear placeholder="如 3:00" style="width: 7.5rem" />
+          <span>–</span>
+          <a-input v-model="form.pace_max_str" allow-clear placeholder="如 10:00" style="width: 7.5rem" />
+        </a-space>
+        <template #extra>
+          <div>每公里配速,格式为 分:秒(如 5:30);合法范围 3:00–10:00/km,每次自动乐跑在区间内随机</div>
+        </template>
+      </a-form-item>
       <a-form-item field="notes" label="备注">
         <a-textarea v-model="form.notes" placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
           show-word-limit />
@@ -355,13 +402,21 @@ import { accountList, deleteAccount, addAccount, changeAutoRun, updateSelfAccoun
 import { Modal, Notification, Message } from '@arco-design/web-vue'
 import { IconSearch } from '@arco-design/web-vue/es/icon'
 import userCard from '@/components/userCard/userCard.vue'
-import { isElectron } from '@/utils/electron'
 import faceModal from '@/components/FaceModal/faceModal.vue'
 import bindBot from '@/components/BindBot/bindBot.vue'
 import SingleRunModal from '@/components/SingleRunModal/SingleRunModal.vue'
 import { useRoute } from 'vue-router'
 import { getNotice, getSemesterTimestamps } from '@/utils/util'
-import { formatKm } from '@/utils/lepaoRecord'
+import {
+  formatKm,
+  formatAutoPaceRangeLabel,
+  formatAutoDistanceRangeLabel,
+  formatSecPerKmAsMinSec,
+  parsePaceMinSecInput
+} from '@/utils/lepaoRecord'
+
+/** 与后端 jkes.autoSingleRunMaxKm 默认一致,保存时由服务端再校验 */
+const AUTO_SINGLE_RUN_MAX_KM = 5
 
 const notice = ref('')
 
@@ -518,6 +573,10 @@ const form = reactive({
   auto_run: 1,
   notice_type: 'email',
   target_count: 30,
+  auto_run_distance_min_km: 2,
+  auto_run_distance_max_km: 2,
+  pace_min_str: '3:00',
+  pace_max_str: '10:00',
   auto_day: [0, 1, 2, 3, 4, 5, 6],
   notes: ''
 })
@@ -608,6 +667,14 @@ const editAccount = (item) => {
     form.auto_time = item.auto_time
     form.auto_run = item.auto_run
     form.target_count = Number(item.target_count)
+    form.auto_run_distance_min_km = Number(item.auto_run_distance_min_km ?? 2)
+    form.auto_run_distance_max_km = Number(item.auto_run_distance_max_km ?? item.auto_run_distance_min_km ?? 2)
+    {
+      const a = formatSecPerKmAsMinSec(item.pace_min_sec_per_km ?? 180)
+      const b = formatSecPerKmAsMinSec(item.pace_max_sec_per_km ?? 600)
+      form.pace_min_str = a === '—' ? '3:00' : a
+      form.pace_max_str = b === '—' ? '10:00' : b
+    }
     form.notice_type = item.notice_type || 'email'
     form.auto_day = item.auto_day
     form.notes = item.notes
@@ -619,6 +686,10 @@ const editAccount = (item) => {
     form.auto_run = 1
     form.auto_time = -1
     form.target_count = 30
+    form.auto_run_distance_min_km = 2
+    form.auto_run_distance_max_km = 2
+    form.pace_min_str = '3:00'
+    form.pace_max_str = '10:00'
     form.auto_day = [0, 1, 2, 3, 4, 5, 6]
     form.notice_type = 'email'
     form.area = ''
@@ -649,16 +720,61 @@ const handleBeforeOk = async (done) => {
       return false
     }
 
+    const pLo = parsePaceMinSecInput(form.pace_min_str)
+    const pHi = parsePaceMinSecInput(form.pace_max_str)
+    if (pLo == null || pHi == null) {
+      Message.error('配速格式不正确,请使用 分:秒,如 5:30 或 10:00')
+      return false
+    }
+    if (pLo < 180 || pLo > 600 || pHi < 180 || pHi > 600) {
+      Message.error('配速须在 3:00–10:00/km 范围内')
+      return false
+    }
+    if (pLo > pHi) {
+      Message.error('配速下限不能慢于上限(左侧应更快,分:秒更小)')
+      return false
+    }
+
     if (form.auto_run) {
       const tk = Number(form.target_count)
       if (!Number.isFinite(tk) || tk < 0 || tk > 200) {
         Message.error('目标里程须在 0–200 公里之间')
         return false
       }
+      const dMin = Number(form.auto_run_distance_min_km)
+      const dMax = Number(form.auto_run_distance_max_km)
+      if (
+        !Number.isFinite(dMin) ||
+        !Number.isFinite(dMax) ||
+        dMin < 1 ||
+        dMin > AUTO_SINGLE_RUN_MAX_KM ||
+        dMax < 1 ||
+        dMax > AUTO_SINGLE_RUN_MAX_KM
+      ) {
+        Message.error(`自动乐跑距离区间须在 1–${AUTO_SINGLE_RUN_MAX_KM} 公里之间`)
+        return false
+      }
+      if (dMin > dMax) {
+        Message.error('自动乐跑距离下限不能大于上限')
+        return false
+      }
     }
 
-    let data = {
-      ...form
+    const data = {
+      id: form.id,
+      student_num: form.student_num,
+      email: form.email,
+      area: form.area,
+      auto_time: form.auto_time,
+      auto_run: form.auto_run,
+      notice_type: form.notice_type,
+      target_count: form.target_count,
+      auto_run_distance_min_km: form.auto_run_distance_min_km,
+      auto_run_distance_max_km: form.auto_run_distance_max_km,
+      auto_day: form.auto_day,
+      notes: form.notes,
+      pace_min_sec_per_km: pLo,
+      pace_max_sec_per_km: pHi
     }
 
     const res = await addAccount(data)

+ 1 - 1
src/pages/lepao/lepaoRecords/index.vue

@@ -83,7 +83,7 @@
               </div>
             </template>
           </a-table-column>
-          <a-table-column title="学号" :width="105" data-index="lepao_account" ellipsis tooltip :filterable="{
+          <a-table-column title="学号" :width="110" data-index="lepao_account" ellipsis tooltip :filterable="{
             filter: (value, record) => (record.lepao_account ?? '').includes(value),
             slotName: 'name-filter',
             icon: () => h(IconSearch)

+ 48 - 0
src/utils/lepaoRecord.js

@@ -265,3 +265,51 @@ export function normalizePointData(pd) {
   }
   return []
 }
+
+/** 每公里秒数 → 分:秒 展示(如 3:00、10:00),不含单位 */
+export function formatSecPerKmAsMinSec(sec) {
+  const n = Math.round(Number(sec))
+  if (!Number.isFinite(n) || n <= 0) return '—'
+  const m = Math.floor(n / 60)
+  const s = n % 60
+  return `${m}:${String(s).padStart(2, '0')}`
+}
+
+/**
+ * 自动乐跑配速区间展示:3:00–10:00(每公里)
+ */
+export function formatAutoPaceRangeLabel(minSec, maxSec) {
+  return `${formatSecPerKmAsMinSec(minSec)}–${formatSecPerKmAsMinSec(maxSec)}`
+}
+
+/**
+ * 解析配速输入为「每公里秒数」:支持 5:30、5:30;或 ≥60 的纯数字(视为秒/公里,如 330)
+ * @returns {number | null}
+ */
+export function parsePaceMinSecInput(input) {
+  const raw = String(input ?? '').trim()
+  if (!raw) return null
+
+  if (/^\d+(\.\d+)?$/.test(raw)) {
+    const n = parseFloat(raw)
+    if (!Number.isFinite(n) || n < 60) return null
+    return Math.round(n)
+  }
+
+  const colon = raw.match(/^(\d+)[::](\d{1,2}(?:\.\d+)?)$/)
+  if (colon) {
+    const minutes = parseInt(colon[1], 10)
+    const seconds = parseFloat(colon[2])
+    if (!Number.isFinite(minutes) || !Number.isFinite(seconds) || seconds >= 60) return null
+    return Math.round(minutes * 60 + seconds)
+  }
+
+  return null
+}
+
+/** 自动乐跑距离区间(km)展示 */
+export function formatAutoDistanceRangeLabel(minKm, maxKm) {
+  const a = formatKm(minKm ?? 2)
+  const b = formatKm(maxKm ?? 2)
+  return a === b ? a : `${a} ~ ${b}`
+}