|
@@ -137,15 +137,15 @@
|
|
|
<a-alert v-if="notice" style="margin-bottom: 15px;">{{ notice }}</a-alert>
|
|
<a-alert v-if="notice" style="margin-bottom: 15px;">{{ notice }}</a-alert>
|
|
|
|
|
|
|
|
<a-table :data="data" :bordered="false" hoverable class="table table-clickable" :loading="loading" expandable :scroll="{
|
|
<a-table :data="data" :bordered="false" hoverable class="table table-clickable" :loading="loading" expandable :scroll="{
|
|
|
- x: 2400
|
|
|
|
|
- }" @row-click="onRowClick" :pagination="{
|
|
|
|
|
|
|
+ x: 2200
|
|
|
|
|
+ }" :pagination="{
|
|
|
showPageSize: true,
|
|
showPageSize: true,
|
|
|
showJumper: true,
|
|
showJumper: true,
|
|
|
showTotal: true,
|
|
showTotal: true,
|
|
|
pageSize: pagination.pagesize,
|
|
pageSize: pagination.pagesize,
|
|
|
current: pagination.current,
|
|
current: pagination.current,
|
|
|
total: pagination.total
|
|
total: pagination.total
|
|
|
- }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
|
|
|
|
|
|
|
+ }" @row-click="onRowClick" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
|
|
|
<template #name-filter="{ filterValue, setFilterValue, handleFilterConfirm, handleFilterReset }">
|
|
<template #name-filter="{ filterValue, setFilterValue, handleFilterConfirm, handleFilterReset }">
|
|
|
<div class="custom-filter">
|
|
<div class="custom-filter">
|
|
|
<a-space direction="vertical">
|
|
<a-space direction="vertical">
|
|
@@ -167,8 +167,8 @@
|
|
|
</a-avatar>
|
|
</a-avatar>
|
|
|
</template>
|
|
</template>
|
|
|
</a-table-column>
|
|
</a-table-column>
|
|
|
- <a-table-column title="学号" :width="117" data-index="student_num" ellipsis tooltip></a-table-column>
|
|
|
|
|
- <a-table-column title="姓名" :width="130">
|
|
|
|
|
|
|
+ <a-table-column title="学号" :width="110" data-index="student_num" ellipsis tooltip></a-table-column>
|
|
|
|
|
+ <a-table-column title="姓名" :width="120">
|
|
|
<template #cell="{ record }">
|
|
<template #cell="{ record }">
|
|
|
{{ record.name ?? '请使用乐跑登录器更新账号信息' }}
|
|
{{ record.name ?? '请使用乐跑登录器更新账号信息' }}
|
|
|
</template>
|
|
</template>
|
|
@@ -186,9 +186,8 @@
|
|
|
{{ record.sex === 1 ? '男' : (record.sex === 2 ? '女' : '') }}
|
|
{{ record.sex === 1 ? '男' : (record.sex === 2 ? '女' : '') }}
|
|
|
</template>
|
|
</template>
|
|
|
</a-table-column>
|
|
</a-table-column>
|
|
|
- <a-table-column title="年级" :width="70" data-index="grade_id" tooltip></a-table-column>
|
|
|
|
|
- <a-table-column title="学院" :width="220" data-index="academy_name" tooltip></a-table-column>
|
|
|
|
|
- <a-table-column title="跑区" :width="130">
|
|
|
|
|
|
|
+ <a-table-column title="班级" :width="200" data-index="class_id" ellipsis tooltip></a-table-column>
|
|
|
|
|
+ <a-table-column title="跑区" :width="120">
|
|
|
<template #cell="{ record }">
|
|
<template #cell="{ record }">
|
|
|
{{ record.area || '随机分配' }}
|
|
{{ record.area || '随机分配' }}
|
|
|
</template>
|
|
</template>
|
|
@@ -213,19 +212,6 @@
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
</a-table-column>
|
|
</a-table-column>
|
|
|
- <!-- <a-table-column title="人脸状态" :width="100" ellipsis tooltip>
|
|
|
|
|
- <template #cell="{ record }">
|
|
|
|
|
- <div v-if="record.face_state === 0" class="state">
|
|
|
|
|
- <div class="circle zero"></div>未采集
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-else-if="record.face_state === 1" class="state">
|
|
|
|
|
- <div class="circle one"></div>已通过
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-else class="state">
|
|
|
|
|
- <div class="circle else"></div>不通过
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </a-table-column> -->
|
|
|
|
|
<a-table-column title="自动乐跑" :width="105" ellipsis tooltip :filterable="{
|
|
<a-table-column title="自动乐跑" :width="105" ellipsis tooltip :filterable="{
|
|
|
filters: [
|
|
filters: [
|
|
|
{ text: '开启', value: 1 },
|
|
{ text: '开启', value: 1 },
|
|
@@ -234,8 +220,7 @@
|
|
|
filter: (value, record) => record.auto_run == value
|
|
filter: (value, record) => record.auto_run == value
|
|
|
}">
|
|
}">
|
|
|
<template #cell="{ record }">
|
|
<template #cell="{ record }">
|
|
|
- <a-tag color="green" v-if="record.auto_run">{{ record.target_count === 0 ? '开启-∞次' :
|
|
|
|
|
- `开启-${record.target_count}次` }}</a-tag>
|
|
|
|
|
|
|
+ <a-tag color="green" v-if="record.auto_run">开启</a-tag>
|
|
|
<a-tag color="red" v-else>关闭</a-tag>
|
|
<a-tag color="red" v-else>关闭</a-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</a-table-column>
|
|
</a-table-column>
|
|
@@ -254,10 +239,30 @@
|
|
|
{{ record.auto_run ? autoTimeLabel(record) : '-' }}
|
|
{{ record.auto_run ? autoTimeLabel(record) : '-' }}
|
|
|
</template>
|
|
</template>
|
|
|
</a-table-column>
|
|
</a-table-column>
|
|
|
- <a-table-column title="学期目标" :width="88" ellipsis tooltip>
|
|
|
|
|
|
|
+ <a-table-column title="目标里程" :width="100" ellipsis tooltip>
|
|
|
<template #cell="{ record }">
|
|
<template #cell="{ record }">
|
|
|
- {{ record.term_num != record.total_num ? `${record.total_num} / ${record.term_num}` :
|
|
|
|
|
- '已完成' }}
|
|
|
|
|
|
|
+ {{ record.target_count === 0 ? '不限' : formatKm(record.target_count) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-table-column>
|
|
|
|
|
+ <a-table-column title="自动距离区间" :width="155" 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="110" ellipsis tooltip>
|
|
|
|
|
+ <template #cell="{ record }">
|
|
|
|
|
+ {{ formatKm(record.total_num) }}
|
|
|
</template>
|
|
</template>
|
|
|
</a-table-column>
|
|
</a-table-column>
|
|
|
<a-table-column title="添加时间" :width="150" ellipsis tooltip :sortable="{
|
|
<a-table-column title="添加时间" :width="150" ellipsis tooltip :sortable="{
|
|
@@ -284,8 +289,6 @@
|
|
|
<a-button>操作 <icon-down /></a-button>
|
|
<a-button>操作 <icon-down /></a-button>
|
|
|
<template #content>
|
|
<template #content>
|
|
|
<a-doption v-if="hasPermission('action.lepao.updateAccount')" @click="editAccount(record)"><icon-edit /> 编辑账号</a-doption>
|
|
<a-doption v-if="hasPermission('action.lepao.updateAccount')" @click="editAccount(record)"><icon-edit /> 编辑账号</a-doption>
|
|
|
- <!-- <a-doption @click="faceRecoRef.openModal(record)"><icon-video-camera /> 人脸采集</a-doption> -->
|
|
|
|
|
- <!-- <a-doption @click="bindBotRef.openModal(record)"><icon-robot-add /> 绑定智能机器人</a-doption> -->
|
|
|
|
|
<a-doption v-if="hasPermission('action.lepao.singleRun')" @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
|
|
<a-doption v-if="hasPermission('action.lepao.singleRun')" @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
|
|
|
<a-doption v-if="hasPermission('action.lepao.changeAutoRun')" @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
|
|
<a-doption v-if="hasPermission('action.lepao.changeAutoRun')" @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
|
|
|
'开启' }}自动乐跑</a-doption>
|
|
'开启' }}自动乐跑</a-doption>
|
|
@@ -307,7 +310,6 @@
|
|
|
<a-form-item label="自动识别" v-if="!form.id">
|
|
<a-form-item label="自动识别" v-if="!form.id">
|
|
|
<a-textarea
|
|
<a-textarea
|
|
|
v-model="autoFillText"
|
|
v-model="autoFillText"
|
|
|
- no-trim
|
|
|
|
|
placeholder="粘贴包含学号、邮箱、跑区等信息的文本,点击“识别并填入”自动填写"
|
|
placeholder="粘贴包含学号、邮箱、跑区等信息的文本,点击“识别并填入”自动填写"
|
|
|
:auto-size="{ minRows: 2, maxRows: 4 }"
|
|
:auto-size="{ minRows: 2, maxRows: 4 }"
|
|
|
allow-clear
|
|
allow-clear
|
|
@@ -315,7 +317,7 @@
|
|
|
<template #extra>
|
|
<template #extra>
|
|
|
<a-space>
|
|
<a-space>
|
|
|
<a-button type="primary" @click="handleAutoFill" size="small">识别并填入</a-button>
|
|
<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>
|
|
</a-space>
|
|
|
</template>
|
|
</template>
|
|
|
</a-form-item>
|
|
</a-form-item>
|
|
@@ -325,7 +327,6 @@
|
|
|
<a-form-item field="notice_type" label="通知方式">
|
|
<a-form-item field="notice_type" label="通知方式">
|
|
|
<a-radio-group v-model="form.notice_type">
|
|
<a-radio-group v-model="form.notice_type">
|
|
|
<a-radio value="email"><icon-email /> 邮件</a-radio>
|
|
<a-radio value="email"><icon-email /> 邮件</a-radio>
|
|
|
- <!-- <a-radio value="bot"><icon-robot /> 智能机器人</a-radio> -->
|
|
|
|
|
<a-radio value="none"><icon-notification-close /> 无通知</a-radio>
|
|
<a-radio value="none"><icon-notification-close /> 无通知</a-radio>
|
|
|
</a-radio-group>
|
|
</a-radio-group>
|
|
|
</a-form-item>
|
|
</a-form-item>
|
|
@@ -338,7 +339,6 @@
|
|
|
<a-option v-for="(item, index) in area" :key="index" :value="item">
|
|
<a-option v-for="(item, index) in area" :key="index" :value="item">
|
|
|
<span class="vipcontent">
|
|
<span class="vipcontent">
|
|
|
<span>{{ item }} </span>
|
|
<span>{{ item }} </span>
|
|
|
- <!-- <img src="@/assets/vip.svg" alt="vip" height="20"> -->
|
|
|
|
|
</span>
|
|
</span>
|
|
|
</a-option>
|
|
</a-option>
|
|
|
</a-select>
|
|
</a-select>
|
|
@@ -346,10 +346,11 @@
|
|
|
<a-form-item field="auto_run" label="自动乐跑开关">
|
|
<a-form-item field="auto_run" label="自动乐跑开关">
|
|
|
<a-switch v-model="form.auto_run" :checked-value="1" :unchecked-value="0" />
|
|
<a-switch v-model="form.auto_run" :checked-value="1" :unchecked-value="0" />
|
|
|
</a-form-item>
|
|
</a-form-item>
|
|
|
- <a-form-item field="target_count" label="乐跑目标次数" v-if="form.auto_run">
|
|
|
|
|
- <a-input-number v-model="form.target_count" placeholder="请输入乐跑目标次数" mode="button" />
|
|
|
|
|
|
|
+ <a-form-item field="target_count" label="月度目标里程" v-if="form.auto_run">
|
|
|
|
|
+ <a-input-number v-model="form.target_count" placeholder="0–200,0 表示不限制" mode="button" :min="0" :max="200"
|
|
|
|
|
+ :precision="2" :step="0.5" />
|
|
|
<template #extra>
|
|
<template #extra>
|
|
|
- <div>当学期有效次数达到目标次数时自动乐跑将关闭,0为不限次</div>
|
|
|
|
|
|
|
+ <div>月度里程(Km)达到目标后自动乐跑将关闭;填 0 表示不限制</div>
|
|
|
</template>
|
|
</template>
|
|
|
</a-form-item>
|
|
</a-form-item>
|
|
|
<a-form-item field="auto_day" label="自动乐跑星期" v-if="form.auto_run">
|
|
<a-form-item field="auto_day" label="自动乐跑星期" v-if="form.auto_run">
|
|
@@ -358,8 +359,44 @@
|
|
|
<a-form-item field="auto_time" label="自动乐跑时段" v-if="form.auto_run">
|
|
<a-form-item field="auto_time" label="自动乐跑时段" v-if="form.auto_run">
|
|
|
<a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
|
|
<a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
|
|
|
</a-form-item>
|
|
</a-form-item>
|
|
|
|
|
+ <a-form-item field="auto_run_distance_range" label="自动距离区间" 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.1"
|
|
|
|
|
+ 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.1"
|
|
|
|
|
+ placeholder="上限"
|
|
|
|
|
+ />
|
|
|
|
|
+ </a-space>
|
|
|
|
|
+ <template #extra>
|
|
|
|
|
+ <div>每次自动乐跑在区间内随机取值;上下限可相同(固定距离);范围 1~10 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-form-item field="notes" label="备注">
|
|
|
- <a-textarea v-model="form.notes" no-trim placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
|
|
|
|
|
|
|
+ <a-textarea v-model="form.notes" placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
|
|
|
show-word-limit />
|
|
show-word-limit />
|
|
|
</a-form-item>
|
|
</a-form-item>
|
|
|
</a-form>
|
|
</a-form>
|
|
@@ -368,11 +405,16 @@
|
|
|
<faceModal :faceInfo="faceInfo" ref="faceRecoRef" />
|
|
<faceModal :faceInfo="faceInfo" ref="faceRecoRef" />
|
|
|
<bindBot ref="bindBotRef" />
|
|
<bindBot ref="bindBotRef" />
|
|
|
<accountDetailCard ref="accountDetailRef" />
|
|
<accountDetailCard ref="accountDetailRef" />
|
|
|
|
|
+ <SingleRunModal
|
|
|
|
|
+ v-model:visible="singleRunVisible"
|
|
|
|
|
+ :account="singleRunAccount"
|
|
|
|
|
+ @success="getAccountsAsync"
|
|
|
|
|
+ />
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref, reactive, onUnmounted, onMounted, h } from 'vue'
|
|
import { ref, reactive, onUnmounted, onMounted, h } from 'vue'
|
|
|
-import { accountList, deleteAccount, addAccount, changeAutoRun, singleRun, updateSelfAccount } from '@/api/lepao'
|
|
|
|
|
|
|
+import { accountList, deleteAccount, addAccount, changeAutoRun, updateSelfAccount } from '@/api/lepao'
|
|
|
import { Modal, Notification, Message } from '@arco-design/web-vue'
|
|
import { Modal, Notification, Message } from '@arco-design/web-vue'
|
|
|
import { IconSearch } from '@arco-design/web-vue/es/icon'
|
|
import { IconSearch } from '@arco-design/web-vue/es/icon'
|
|
|
import userCard from '@/components/userCard/userCard.vue'
|
|
import userCard from '@/components/userCard/userCard.vue'
|
|
@@ -380,16 +422,26 @@ import { isElectron } from '@/utils/electron'
|
|
|
import faceModal from '@/components/FaceModal/faceModal.vue'
|
|
import faceModal from '@/components/FaceModal/faceModal.vue'
|
|
|
import bindBot from '@/components/BindBot/bindBot.vue'
|
|
import bindBot from '@/components/BindBot/bindBot.vue'
|
|
|
import accountDetailCard from '@/components/LepaoAccountCard/accountDetailCard.vue'
|
|
import accountDetailCard from '@/components/LepaoAccountCard/accountDetailCard.vue'
|
|
|
|
|
+import SingleRunModal from '@/components/SingleRunModal/SingleRunModal.vue'
|
|
|
import { useRoute } from 'vue-router'
|
|
import { useRoute } from 'vue-router'
|
|
|
import { getNotice, getSemesterTimestamps } from '@/utils/util'
|
|
import { getNotice, getSemesterTimestamps } from '@/utils/util'
|
|
|
import { hasPermission } from '@/utils/permission'
|
|
import { hasPermission } from '@/utils/permission'
|
|
|
import { useResponsiveTable } from '@/hooks/useResponsiveTable'
|
|
import { useResponsiveTable } from '@/hooks/useResponsiveTable'
|
|
|
|
|
|
|
|
const { tableFixed } = useResponsiveTable()
|
|
const { tableFixed } = useResponsiveTable()
|
|
|
|
|
+import {
|
|
|
|
|
+ formatKm,
|
|
|
|
|
+ formatAutoPaceRangeLabel,
|
|
|
|
|
+ formatAutoDistanceRangeLabel,
|
|
|
|
|
+ formatSecPerKmAsMinSec,
|
|
|
|
|
+ parsePaceMinSecInput
|
|
|
|
|
+} from '@/utils/lepaoRecord'
|
|
|
|
|
|
|
|
-const notice = ref('')
|
|
|
|
|
|
|
+/** 与后端 jkes.autoSingleRunMaxKm 默认一致,保存时由服务端再校验 */
|
|
|
|
|
+const AUTO_SINGLE_RUN_MAX_KM = 10
|
|
|
|
|
|
|
|
const faceInfo = ref({})
|
|
const faceInfo = ref({})
|
|
|
|
|
+const faceRecoRef = ref(null)
|
|
|
const bindBotRef = ref(null)
|
|
const bindBotRef = ref(null)
|
|
|
const accountDetailRef = ref(null)
|
|
const accountDetailRef = ref(null)
|
|
|
|
|
|
|
@@ -405,6 +457,26 @@ const clearPendingDetail = () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const onRowClick = (record, ev) => {
|
|
|
|
|
+ if (ev?.target?.closest?.('.arco-dropdown, .arco-btn')) return
|
|
|
|
|
+
|
|
|
|
|
+ const accountId = record?.id
|
|
|
|
|
+ if (accountId == null) return
|
|
|
|
|
+
|
|
|
|
|
+ if (pendingDetailAccountId.value === accountId) {
|
|
|
|
|
+ clearPendingDetail()
|
|
|
|
|
+ accountDetailRef.value?.openModal(record)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ clearPendingDetail()
|
|
|
|
|
+ pendingDetailAccountId.value = accountId
|
|
|
|
|
+ Message.info('再点一次即可查看账号详情')
|
|
|
|
|
+ pendingDetailTimer = setTimeout(clearPendingDetail, ROW_DETAIL_CONFIRM_MS)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const notice = ref('')
|
|
|
|
|
+
|
|
|
const queryDataForm = reactive({
|
|
const queryDataForm = reactive({
|
|
|
area: '',
|
|
area: '',
|
|
|
student_num: '',
|
|
student_num: '',
|
|
@@ -432,7 +504,7 @@ const pagination = reactive({
|
|
|
pagesize: 20
|
|
pagesize: 20
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const area = ["兰花湖校区跑区", "主校区北跑区", "主校区南跑区", "重庆工商大学茶园校区"]
|
|
|
|
|
|
|
+const area = ["学府大道校区", "南山校区"]
|
|
|
const state = [
|
|
const state = [
|
|
|
{ label: '全部', value: -1 }, { label: '需登录', value: 0 }, { label: '正常', value: 1 }, { label: '状态异常', value: 2 }
|
|
{ label: '全部', value: -1 }, { label: '需登录', value: 0 }, { label: '正常', value: 1 }, { label: '状态异常', value: 2 }
|
|
|
]
|
|
]
|
|
@@ -530,6 +602,10 @@ const form = reactive({
|
|
|
auto_run: 1,
|
|
auto_run: 1,
|
|
|
notice_type: 'email',
|
|
notice_type: 'email',
|
|
|
target_count: 30,
|
|
target_count: 30,
|
|
|
|
|
+ auto_run_distance_min_km: 2.5,
|
|
|
|
|
+ auto_run_distance_max_km: 3,
|
|
|
|
|
+ pace_min_str: '6:00',
|
|
|
|
|
+ pace_max_str: '10:00',
|
|
|
auto_day: [0, 1, 2, 3, 4, 5, 6],
|
|
auto_day: [0, 1, 2, 3, 4, 5, 6],
|
|
|
notes: ''
|
|
notes: ''
|
|
|
})
|
|
})
|
|
@@ -544,10 +620,8 @@ const matchArea = (text) => {
|
|
|
if (exact) return exact
|
|
if (exact) return exact
|
|
|
|
|
|
|
|
const areaAliasMap = [
|
|
const areaAliasMap = [
|
|
|
- { keywords: ['兰花'], value: '兰花湖校区跑区' },
|
|
|
|
|
- { keywords: ['主校北', '主校区北', '北跑区', '北区', '北'], value: '主校区北跑区' },
|
|
|
|
|
- { keywords: ['主校南', '主校区南', '南跑区', '南区', '南'], value: '主校区南跑区' },
|
|
|
|
|
- { keywords: ['茶园', '茶园校区'], value: '重庆工商大学茶园校区' }
|
|
|
|
|
|
|
+ { keywords: ['南山'], value: '南山校区' },
|
|
|
|
|
+ { keywords: ['学府'], value: '学府大道校区' },
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
const matchedAlias = areaAliasMap.find(item => item.keywords.some(keyword => normalized.includes(keyword)))
|
|
const matchedAlias = areaAliasMap.find(item => item.keywords.some(keyword => normalized.includes(keyword)))
|
|
@@ -561,7 +635,7 @@ const handleAutoFill = () => {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const studentMatch = text.match(/\b20\d{8}\b/)
|
|
|
|
|
|
|
+ const studentMatch = text.match(/\b2\d{9}\b/)
|
|
|
const emailMatch = text.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/)
|
|
const emailMatch = text.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/)
|
|
|
const areaMatch = matchArea(text)
|
|
const areaMatch = matchArea(text)
|
|
|
|
|
|
|
@@ -589,24 +663,6 @@ const handleAutoFill = () => {
|
|
|
Message.success(`识别成功,已填入 ${hitCount} 项信息`)
|
|
Message.success(`识别成功,已填入 ${hitCount} 项信息`)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const onRowClick = (record, ev) => {
|
|
|
|
|
- if (ev?.target?.closest?.('.arco-dropdown, .arco-btn')) return
|
|
|
|
|
-
|
|
|
|
|
- const accountId = record?.id
|
|
|
|
|
- if (accountId == null) return
|
|
|
|
|
-
|
|
|
|
|
- if (pendingDetailAccountId.value === accountId) {
|
|
|
|
|
- clearPendingDetail()
|
|
|
|
|
- accountDetailRef.value?.openModal(record)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- clearPendingDetail()
|
|
|
|
|
- pendingDetailAccountId.value = accountId
|
|
|
|
|
- Message.info('再点一次即可查看账号详情')
|
|
|
|
|
- pendingDetailTimer = setTimeout(clearPendingDetail, ROW_DETAIL_CONFIRM_MS)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
const editAccount = (item) => {
|
|
const editAccount = (item) => {
|
|
|
if (item) {
|
|
if (item) {
|
|
|
form.id = item.id
|
|
form.id = item.id
|
|
@@ -615,7 +671,15 @@ const editAccount = (item) => {
|
|
|
form.area = item.area
|
|
form.area = item.area
|
|
|
form.auto_time = item.auto_time
|
|
form.auto_time = item.auto_time
|
|
|
form.auto_run = item.auto_run
|
|
form.auto_run = item.auto_run
|
|
|
- form.target_count = item.target_count
|
|
|
|
|
|
|
+ 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.notice_type = item.notice_type || 'email'
|
|
|
form.auto_day = item.auto_day
|
|
form.auto_day = item.auto_day
|
|
|
form.notes = item.notes
|
|
form.notes = item.notes
|
|
@@ -627,6 +691,10 @@ const editAccount = (item) => {
|
|
|
form.auto_run = 1
|
|
form.auto_run = 1
|
|
|
form.auto_time = -1
|
|
form.auto_time = -1
|
|
|
form.target_count = 30
|
|
form.target_count = 30
|
|
|
|
|
+ form.auto_run_distance_min_km = 2.5
|
|
|
|
|
+ form.auto_run_distance_max_km = 3
|
|
|
|
|
+ form.pace_min_str = '6:00'
|
|
|
|
|
+ form.pace_max_str = '10:00'
|
|
|
form.auto_day = [0, 1, 2, 3, 4, 5, 6]
|
|
form.auto_day = [0, 1, 2, 3, 4, 5, 6]
|
|
|
form.notice_type = 'email'
|
|
form.notice_type = 'email'
|
|
|
form.area = ''
|
|
form.area = ''
|
|
@@ -645,9 +713,9 @@ const handleBeforeOk = async (done) => {
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const studentNumRegex = /^\d{10}$/
|
|
|
|
|
- if (!studentNumRegex.test(student_num)) {
|
|
|
|
|
- Message.error('请检查学号格式是否正确')
|
|
|
|
|
|
|
+ const sid = String(student_num || '').trim()
|
|
|
|
|
+ if (!/^[A-Za-z0-9_-]{2,64}$/.test(sid)) {
|
|
|
|
|
+ Message.error('学号/账号编码格式不正确(2–64 位字母数字下划线横线)')
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -657,8 +725,61 @@ const handleBeforeOk = async (done) => {
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let data = {
|
|
|
|
|
- ...form
|
|
|
|
|
|
|
+ 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
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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)
|
|
const res = await addAccount(data)
|
|
@@ -728,27 +849,18 @@ const GetNotice = async () => {
|
|
|
notice.value = res
|
|
notice.value = res
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const SingleRun = async (item) => {
|
|
|
|
|
- if (item.state !== 1)
|
|
|
|
|
|
|
+const singleRunVisible = ref(false)
|
|
|
|
|
+const singleRunAccount = ref(null)
|
|
|
|
|
+
|
|
|
|
|
+const SingleRun = (item) => {
|
|
|
|
|
+ if (item.state !== 1) {
|
|
|
return Notification.warning({
|
|
return Notification.warning({
|
|
|
title: '当前乐跑账号需登录,请登录后再试',
|
|
title: '当前乐跑账号需登录,请登录后再试',
|
|
|
- content: '如有疑问请联系哪吒乐跑客服'
|
|
|
|
|
|
|
+ content: '如有疑问请联系沐晨乐跑客服'
|
|
|
})
|
|
})
|
|
|
- Modal.confirm({
|
|
|
|
|
- title: '开始乐跑',
|
|
|
|
|
- content: () => h('div', [
|
|
|
|
|
- h('p', `您是否要为 ${item.name}(${item.student_num}) 乐跑?若乐跑成功将扣减乐跑次数`)
|
|
|
|
|
- ]),
|
|
|
|
|
- onOk: async () => {
|
|
|
|
|
- const res = await singleRun({ student_num: item.student_num })
|
|
|
|
|
- if (!res || res.code !== 0)
|
|
|
|
|
- return Notification.error({
|
|
|
|
|
- title: '提交乐跑任务失败',
|
|
|
|
|
- content: res?.msg ?? '请稍后再试'
|
|
|
|
|
- })
|
|
|
|
|
- Message.success('提交乐跑任务成功!')
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ singleRunAccount.value = item
|
|
|
|
|
+ singleRunVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const DeleteAccount = async (item) => {
|
|
const DeleteAccount = async (item) => {
|
|
@@ -782,7 +894,7 @@ const ChangeAutoRun = async (record) => {
|
|
|
content: res?.msg ?? '请稍后再试'
|
|
content: res?.msg ?? '请稍后再试'
|
|
|
})
|
|
})
|
|
|
} else {
|
|
} else {
|
|
|
- Message.success(`自动乐跑状态已${record.auto_run === 1 ? '开启' : '关闭'}`)
|
|
|
|
|
|
|
+ Message.success(`自动乐跑状态已${record.auto_run === 1 ? '开启,请在编辑账号中配置自动乐跑参数' : '关闭'}`)
|
|
|
getAccounts()
|
|
getAccounts()
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
@@ -845,41 +957,21 @@ onMounted(async () => {
|
|
|
|
|
|
|
|
// 组件销毁时停止轮询
|
|
// 组件销毁时停止轮询
|
|
|
onUnmounted(() => {
|
|
onUnmounted(() => {
|
|
|
- clearPendingDetail()
|
|
|
|
|
stopPolling()
|
|
stopPolling()
|
|
|
|
|
+ clearPendingDetail()
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
<style scoped lang="less">
|
|
|
@import '@/styles/store-theme.less';
|
|
@import '@/styles/store-theme.less';
|
|
|
|
|
|
|
|
-.queryForm {
|
|
|
|
|
- padding: 0;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
-
|
|
|
|
|
- @media (min-width: (@app-breakpoint-md + 1px)) {
|
|
|
|
|
- margin-top: 20px;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.buttonGroup {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
.buttonGroup-mobile {
|
|
.buttonGroup-mobile {
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.query-main {
|
|
|
|
|
- min-width: 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.query-actions-space {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
|
|
+.queryForm {
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+ padding: 0 15px
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.table-clickable {
|
|
.table-clickable {
|
|
@@ -948,6 +1040,7 @@ onUnmounted(() => {
|
|
|
.buttonGroup-mobile {
|
|
.buttonGroup-mobile {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.query-row {
|
|
.query-row {
|