lepaoRecords.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <template>
  2. <div class="container">
  3. <Breadcrumb :items="['校园乐跑', '乐跑记录']" />
  4. <a-card title="乐跑记录">
  5. <a-row>
  6. <a-col :flex="'1000px'">
  7. <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
  8. label-align="left">
  9. <a-row :gutter="16">
  10. <a-col :span="12">
  11. <a-form-item field="name" label="账号名称">
  12. <a-input v-model="queryData.name" placeholder="请输入账号名称" allow-clear />
  13. </a-form-item>
  14. </a-col>
  15. <a-col :span="12">
  16. <a-form-item field="lepao_account" label="学号">
  17. <a-input-number v-model="queryData.lepao_account" placeholder="请输入学号" :step="1" :precision="0"
  18. allow-clear />
  19. </a-form-item>
  20. </a-col>
  21. <a-col :span="12">
  22. <a-form-item field="email" label="通知邮箱">
  23. <a-input v-model="queryData.email" placeholder="请输入通知邮箱" allow-clear />
  24. </a-form-item>
  25. </a-col>
  26. </a-row>
  27. </a-form>
  28. </a-col>
  29. <a-divider style="height: 84px" direction="vertical" />
  30. <a-col :flex="1">
  31. <a-space direction="vertical" :size="18">
  32. <a-button type="primary" @click="search">
  33. <template #icon>
  34. <icon-search />
  35. </template>
  36. 搜索
  37. </a-button>
  38. <a-button @click="reset">
  39. <template #icon>
  40. <icon-refresh />
  41. </template>
  42. 重置
  43. </a-button>
  44. </a-space>
  45. </a-col>
  46. </a-row>
  47. <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" :pagination="{
  48. showPageSize: true,
  49. showJumper: true,
  50. showTotal: true,
  51. pageSize: pagination.pagesize,
  52. current: pagination.current,
  53. total: pagination.total
  54. }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
  55. <template #name-filter="{ filterValue, setFilterValue, handleFilterConfirm, handleFilterReset }">
  56. <div class="custom-filter">
  57. <a-space direction="vertical">
  58. <a-input :model-value="filterValue[0]" @input="(value) => setFilterValue([value])" />
  59. <div class="custom-filter-footer">
  60. <a-button @click="handleFilterReset">重置</a-button>
  61. <a-button @click="handleFilterConfirm">确定</a-button>
  62. </div>
  63. </a-space>
  64. </div>
  65. </template>
  66. <template #columns>
  67. <a-table-column title="所属用户" :width="160">
  68. <template #cell="{ record }">
  69. <a-avatar :size="30">
  70. <img :alt="record.username ?? ''"
  71. :src="record.avatar || 'https://lepao-cloud.xxoo365.top/view.php/25aa126dc406974ff3579a99a2c6501a.png'" />
  72. </a-avatar>
  73. {{ record.username }}
  74. </template>
  75. </a-table-column>
  76. <a-table-column title="账号名称" :filterable="{
  77. filter: (value, record) => (record.name ?? '').includes(value),
  78. slotName: 'name-filter',
  79. icon: () => h(IconSearch)
  80. }" :width="140">
  81. <template #cell="{ record }">
  82. <a-avatar :size="30">
  83. <img :alt="record.name ?? ''"
  84. :src="record.user_avatar || 'https://lepao-cloud.xxoo365.top/view.php/25aa126dc406974ff3579a99a2c6501a.png'" />
  85. </a-avatar>
  86. {{ record.name }}
  87. </template>
  88. </a-table-column>
  89. <a-table-column title="学号" :width="120" data-index="lepao_account" ellipsis tooltip :filterable="{
  90. filter: (value, record) => (record.lepao_account ?? '').includes(value),
  91. slotName: 'name-filter',
  92. icon: () => h(IconSearch)
  93. }"></a-table-column>
  94. <a-table-column title="状态" ellipsis tooltip :width="230">
  95. <template #cell="{ record }">
  96. <div class="state">
  97. <div class="circle one" v-if="record.result.record_failed_reason === '自动确认有效'"></div>
  98. <div class="circle else" v-else></div>
  99. {{ record.result.record_failed_reason }}
  100. </div>
  101. </template>
  102. </a-table-column>
  103. <a-table-column title="跑区" :filterable="{
  104. filter: (value, record) => (record.result.pass_tit ?? '').includes(value),
  105. slotName: 'name-filter',
  106. icon: () => h(IconSearch)
  107. }" :width="220">
  108. <template #cell="{ record }">
  109. {{ record.result.pass_tit }}
  110. </template>
  111. </a-table-column>
  112. <a-table-column title="乐跑距离" :width="120" ellipsis tooltip>
  113. <template #cell="{ record }">
  114. {{ record.result.distance }} Km
  115. </template>
  116. </a-table-column>
  117. <a-table-column title="跑步时长" :width="120" ellipsis tooltip>
  118. <template #cell="{ record }">
  119. {{ formatSecondsToMinSec(record.result.time) }}
  120. </template>
  121. </a-table-column>
  122. <a-table-column title="平均配速" :width="120" ellipsis tooltip>
  123. <template #cell="{ record }">
  124. {{ calculatePace(record.result.time, record.result.distance) }}
  125. </template>
  126. </a-table-column>
  127. <a-table-column title="乐跑时间" :width="170" ellipsis tooltip>
  128. <template #cell="{ record }">
  129. {{ stramptoTime(record.time) }}
  130. </template>
  131. </a-table-column>
  132. <a-table-column title="" :width="100" fixed="right">
  133. <template #cell="{ record }">
  134. <a-button @click="$router.push(`/admin/lepaoRecords/${record.id}`)">查看详情</a-button>
  135. </template>
  136. </a-table-column>
  137. </template>
  138. </a-table>
  139. </a-card>
  140. </div>
  141. </template>
  142. <script setup>
  143. import { ref, reactive, h } from 'vue'
  144. import { adminLepaoRecords } from '@/api/lepao'
  145. import { Notification } from '@arco-design/web-vue'
  146. import { IconSearch } from '@arco-design/web-vue/es/icon'
  147. const data = ref([])
  148. const loading = ref(false)
  149. const queryData = reactive({
  150. name: '',
  151. lepao_account: '',
  152. email: ''
  153. })
  154. const pagination = reactive({
  155. total: 0,
  156. current: 1,
  157. pagesize: 20
  158. })
  159. const search = () => {
  160. pagination.current = 1
  161. getRecords()
  162. }
  163. const reset = () => {
  164. pagination.current = 1
  165. queryData.name = ''
  166. queryData.lepao_account = ''
  167. queryData.email = ''
  168. queryData.area = ''
  169. getRecords()
  170. }
  171. const getRecords = async () => {
  172. try {
  173. loading.value = true
  174. const reqData = {
  175. ...queryData,
  176. pagesize: pagination.pagesize,
  177. current: pagination.current
  178. }
  179. const res = await adminLepaoRecords(reqData)
  180. if (!res || res.code !== 0)
  181. return Notification.error({
  182. title: '获取乐跑记录失败!',
  183. content: res?.msg ?? '请稍后再试'
  184. })
  185. data.value = res.data
  186. pagination.total = res.pagination.total
  187. } catch (error) {
  188. Notification.error({
  189. title: '获取乐跑记录失败!',
  190. content: error.message || '请稍后再试'
  191. })
  192. } finally {
  193. loading.value = false
  194. }
  195. }
  196. // 分页 - 页码变化
  197. const handlePageChange = (page) => {
  198. pagination.current = page
  199. getRecords()
  200. }
  201. // 分页 - 每页条数变化
  202. const handlePageSizeChange = (size) => {
  203. pagination.pagesize = size
  204. pagination.current = 1 // 页大小变化后回到第一页
  205. getRecords()
  206. }
  207. const stramptoTime = (time) => {
  208. return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })
  209. }
  210. function calculatePace(seconds, kilometers) {
  211. const paceInSeconds = seconds / kilometers;
  212. const minutes = Math.floor(paceInSeconds / 60);
  213. const remainingSeconds = Math.round(paceInSeconds % 60);
  214. return `${minutes}'${remainingSeconds.toString().padStart(2, '0')}''`;
  215. }
  216. function formatSecondsToMinSec(totalSeconds) {
  217. const minutes = Math.floor(totalSeconds / 60);
  218. const seconds = totalSeconds % 60;
  219. return `${minutes}分${seconds.toString().padStart(2, '0')}秒`;
  220. }
  221. getRecords()
  222. </script>
  223. <style scoped lang="less">
  224. .container {
  225. padding: 0 20px 20px 20px;
  226. }
  227. .table {
  228. margin-top: 15px;
  229. .state {
  230. display: flex;
  231. align-items: center;
  232. .circle {
  233. border-radius: 50%;
  234. height: 8px;
  235. min-height: 8px;
  236. width: 8px;
  237. min-width: 8px;
  238. margin-right: 5px;
  239. }
  240. .zero {
  241. background-color: rgb(var(--orange-6));
  242. }
  243. .one {
  244. background-color: rgb(var(--green-6));
  245. }
  246. .else {
  247. background-color: rgb(var(--red-6));
  248. }
  249. }
  250. }
  251. .custom-filter {
  252. padding: 20px;
  253. background: var(--color-bg-5);
  254. border: 1px solid var(--color-neutral-3);
  255. border-radius: var(--border-radius-medium);
  256. box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
  257. }
  258. .custom-filter-footer {
  259. display: flex;
  260. justify-content: space-between;
  261. }
  262. </style>