index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <template>
  2. <div class="store-page">
  3. <Breadcrumb />
  4. <a-card title="抢课回报记录">
  5. <AppQueryFilter>
  6. <a-row class="query-row">
  7. <a-col :flex="'1000px'" class="query-main">
  8. <a-form class="queryForm app-query-form" :model="query" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }" 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="query.name" placeholder="任务备注姓名" allow-clear />
  13. </a-form-item>
  14. </a-col>
  15. <a-col :span="12">
  16. <a-form-item field="student_num" label="学号">
  17. <a-input v-model="query.student_num" placeholder="学号" allow-clear />
  18. </a-form-item>
  19. </a-col>
  20. <a-col :span="12">
  21. <a-form-item field="username" label="用户名">
  22. <a-input v-model="query.username" placeholder="平台用户名" allow-clear />
  23. </a-form-item>
  24. </a-col>
  25. <a-col :span="12">
  26. <a-form-item field="client_label" label="客户端名称">
  27. <a-input v-model="query.client_label" placeholder="客户端备注名" allow-clear />
  28. </a-form-item>
  29. </a-col>
  30. <a-col :span="12">
  31. <a-form-item field="task_id" label="任务 ID">
  32. <a-input v-model="query.task_id" placeholder="任务 ID" allow-clear />
  33. </a-form-item>
  34. </a-col>
  35. <a-col :span="12">
  36. <a-form-item field="client_id" label="客户端 ID">
  37. <a-input v-model="query.client_id" placeholder="client_id" allow-clear />
  38. </a-form-item>
  39. </a-col>
  40. <a-col :span="12">
  41. <a-form-item field="event" label="回报类型">
  42. <a-select v-model="query.event" placeholder="全部" allow-clear>
  43. <a-option value="request_result">单次请求</a-option>
  44. <a-option value="progress_snapshot">定时进度</a-option>
  45. <a-option value="grab_success">抢课成功</a-option>
  46. <a-option value="grab_fail">抢课失败</a-option>
  47. </a-select>
  48. </a-form-item>
  49. </a-col>
  50. <a-col :span="12">
  51. <a-form-item field="queryTime" label="回报时间">
  52. <a-range-picker v-model="query.queryTime" show-time format="YYYY-MM-DD HH:mm" value-format="x" />
  53. </a-form-item>
  54. </a-col>
  55. </a-row>
  56. </a-form>
  57. </a-col>
  58. <a-divider class="query-divider" style="height: 120px" direction="vertical" />
  59. <a-col :flex="1" class="app-query-actions">
  60. <a-space class="query-actions-space" direction="vertical" :size="18">
  61. <a-button type="primary" @click="search">
  62. <template #icon><icon-search /></template>
  63. 搜索
  64. </a-button>
  65. <a-button @click="reset">
  66. <template #icon><icon-refresh /></template>
  67. 重置
  68. </a-button>
  69. </a-space>
  70. </a-col>
  71. </a-row>
  72. </AppQueryFilter>
  73. <a-table
  74. row-key="id"
  75. :data="data"
  76. :bordered="false"
  77. hoverable
  78. class="table"
  79. :loading="loading"
  80. :scroll="{ x: 1500 }"
  81. :pagination="{
  82. showPageSize: true,
  83. showJumper: true,
  84. showTotal: true,
  85. pageSize: pagination.pagesize,
  86. current: pagination.current,
  87. total: pagination.total
  88. }"
  89. @page-change="onPageChange"
  90. @page-size-change="onPageSizeChange"
  91. >
  92. <template #columns>
  93. <a-table-column title="ID" data-index="id" :width="80" />
  94. <a-table-column title="任务" :width="240">
  95. <template #cell="{ record }">
  96. <div>#{{ record.task_id }} {{ record.task_name || '' }}</div>
  97. <div class="sub-text">学号:{{ record.student_num || '-' }}</div>
  98. <div v-if="record.username" class="sub-text">用户:{{ record.username }}</div>
  99. </template>
  100. </a-table-column>
  101. <a-table-column title="客户端" :width="220">
  102. <template #cell="{ record }">
  103. <div>{{ record.client_label || record.client_id || '-' }}</div>
  104. <div class="sub-text">{{ record.client_id || '-' }}</div>
  105. </template>
  106. </a-table-column>
  107. <a-table-column title="类型" data-index="event" :width="120">
  108. <template #cell="{ record }">
  109. <a-tag :color="eventColor(record.event)">{{ eventLabel(record.event) }}</a-tag>
  110. </template>
  111. </a-table-column>
  112. <a-table-column title="课程" :width="180">
  113. <template #cell="{ record }">
  114. <AppTableEllipsis :row-key="`${record.id}-course`" :content="record.course_name || '-'" />
  115. </template>
  116. </a-table-column>
  117. <a-table-column title="回报信息" :width="320">
  118. <template #cell="{ record }">
  119. <AppTableEllipsis
  120. :row-key="`${record.id}-message`"
  121. :content="record.display_message || record.message || '-'"
  122. />
  123. </template>
  124. </a-table-column>
  125. <a-table-column title="任务状态" data-index="task_status" :width="100">
  126. <template #cell="{ record }">
  127. {{ taskStatusLabel(record.task_status) }}
  128. </template>
  129. </a-table-column>
  130. <a-table-column title="回报时间" :width="180">
  131. <template #cell="{ record }">
  132. {{ formatTime(record.create_time) }}
  133. </template>
  134. </a-table-column>
  135. <a-table-column title="详情" :width="90" fixed="right">
  136. <template #cell="{ record }">
  137. <a-button type="text" size="small" @click="openDetail(record)">查看</a-button>
  138. </template>
  139. </a-table-column>
  140. </template>
  141. <template #empty>
  142. <a-empty description="暂无回报记录" />
  143. </template>
  144. </a-table>
  145. </a-card>
  146. <a-modal v-model:visible="detailVisible" title="回报详情" :footer="false" width="720px">
  147. <a-descriptions :column="1" bordered size="small">
  148. <a-descriptions-item label="任务 ID">{{ detail.task_id }}</a-descriptions-item>
  149. <a-descriptions-item label="客户端">{{ detail.client_id }}</a-descriptions-item>
  150. <a-descriptions-item label="类型">{{ eventLabel(detail.event) }}</a-descriptions-item>
  151. <a-descriptions-item label="课程">{{ detail.course_name || '-' }}</a-descriptions-item>
  152. <a-descriptions-item label="信息">{{ detail.display_message || detail.message || '-' }}</a-descriptions-item>
  153. <a-descriptions-item label="时间">{{ formatTime(detail.create_time) }}</a-descriptions-item>
  154. <a-descriptions-item label="原始数据">
  155. <pre class="payload-pre">{{ formatPayload(detail.payload_json) }}</pre>
  156. </a-descriptions-item>
  157. </a-descriptions>
  158. </a-modal>
  159. </div>
  160. </template>
  161. <script setup>
  162. import { onMounted, reactive, ref } from 'vue'
  163. import { Notification } from '@arco-design/web-vue'
  164. import { IconRefresh, IconSearch } from '@arco-design/web-vue/es/icon'
  165. import Breadcrumb from '@/components/Breadcrumb/index.vue'
  166. import { adminQkReportList } from '@/api/qk'
  167. const loading = ref(false)
  168. const data = ref([])
  169. const detailVisible = ref(false)
  170. const detail = ref({})
  171. const pagination = reactive({
  172. current: 1,
  173. pagesize: 20,
  174. total: 0
  175. })
  176. const query = reactive({
  177. name: '',
  178. student_num: '',
  179. username: '',
  180. client_label: '',
  181. task_id: '',
  182. client_id: '',
  183. event: '',
  184. queryTime: []
  185. })
  186. const eventMap = {
  187. request_result: '单次请求',
  188. progress_snapshot: '定时进度',
  189. grab_success: '抢课成功',
  190. grab_fail: '抢课失败'
  191. }
  192. const taskStatusMap = {
  193. paused: '未开始',
  194. pending: '待分配',
  195. assigned: '已分配',
  196. running: '抢课中',
  197. success: '成功',
  198. failed: '失败',
  199. cancelled: '已取消'
  200. }
  201. function eventLabel(event) {
  202. return eventMap[event] || event || '-'
  203. }
  204. function eventColor(event) {
  205. if (event === 'grab_success') return 'green'
  206. if (event === 'grab_fail') return 'red'
  207. if (event === 'progress_snapshot') return 'orange'
  208. return 'blue'
  209. }
  210. function taskStatusLabel(status) {
  211. return taskStatusMap[status] || status || '-'
  212. }
  213. function formatTime(value) {
  214. if (!value) return '-'
  215. return new Date(Number(value)).toLocaleString()
  216. }
  217. function formatPayload(payload) {
  218. if (!payload) return '-'
  219. try {
  220. return JSON.stringify(payload, null, 2)
  221. } catch (_) {
  222. return String(payload)
  223. }
  224. }
  225. function buildParams() {
  226. const params = {
  227. current: pagination.current,
  228. pagesize: pagination.pagesize
  229. }
  230. if (query.name) params.name = query.name
  231. if (query.student_num) params.student_num = query.student_num
  232. if (query.username) params.username = query.username
  233. if (query.client_label) params.client_label = query.client_label
  234. if (query.task_id) params.task_id = query.task_id
  235. if (query.client_id) params.client_id = query.client_id
  236. if (query.event) params.event = query.event
  237. if (Array.isArray(query.queryTime) && query.queryTime.length === 2) {
  238. params.start_time = query.queryTime[0]
  239. params.end_time = query.queryTime[1]
  240. }
  241. return params
  242. }
  243. function fetchList() {
  244. loading.value = true
  245. adminQkReportList(buildParams())
  246. .then((res) => {
  247. if (res?.code === 0) {
  248. data.value = res.data?.list || []
  249. pagination.total = res.data?.total || 0
  250. pagination.current = res.data?.current || pagination.current
  251. pagination.pagesize = res.data?.pagesize || pagination.pagesize
  252. } else {
  253. Notification.error({ title: '获取回报记录失败', content: res?.msg || '请稍后再试' })
  254. }
  255. })
  256. .catch((error) => {
  257. Notification.error({ title: '获取回报记录失败', content: error.message || '请稍后再试' })
  258. })
  259. .finally(() => {
  260. loading.value = false
  261. })
  262. }
  263. function search() {
  264. pagination.current = 1
  265. fetchList()
  266. }
  267. function reset() {
  268. query.name = ''
  269. query.student_num = ''
  270. query.username = ''
  271. query.client_label = ''
  272. query.task_id = ''
  273. query.client_id = ''
  274. query.event = ''
  275. query.queryTime = []
  276. search()
  277. }
  278. function onPageChange(page) {
  279. pagination.current = page
  280. fetchList()
  281. }
  282. function onPageSizeChange(size) {
  283. pagination.pagesize = size
  284. pagination.current = 1
  285. fetchList()
  286. }
  287. function openDetail(record) {
  288. detail.value = record
  289. detailVisible.value = true
  290. }
  291. onMounted(() => {
  292. fetchList()
  293. })
  294. </script>
  295. <style scoped>
  296. .sub-text {
  297. color: var(--color-text-3);
  298. font-size: 12px;
  299. }
  300. .payload-pre {
  301. margin: 0;
  302. white-space: pre-wrap;
  303. word-break: break-all;
  304. max-height: 320px;
  305. overflow: auto;
  306. }
  307. </style>