index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. <template>
  2. <div class="container">
  3. <Breadcrumb :items="['校园乐跑', '乐跑账号']" />
  4. <userCard />
  5. <a-card title="账号列表" style="margin-top: 15px;">
  6. <div class="buttonGroup">
  7. <a-button type="primary" size="large" @click="editAccount()">
  8. <template #icon>
  9. <icon-plus />
  10. </template>
  11. 添加账号
  12. </a-button>
  13. <a-button size="large" @click="download('windows')" style="margin-left: 10px;">
  14. <template #icon>
  15. <icon-desktop />
  16. </template>
  17. Windows操作说明
  18. </a-button>
  19. <a-button size="large" @click="download('iphone')" style="margin-left: 10px;">
  20. <template #icon>
  21. <icon-mobile />
  22. </template>
  23. iPhone操作说明
  24. </a-button>
  25. <a-button size="large" @click="$router.push('/download/down')" style="margin-left: 10px;" v-if="!isElectron()">
  26. <template #icon>
  27. <icon-download />
  28. </template>
  29. 客户端/登录器下载
  30. </a-button>
  31. </div>
  32. <a-row class="queryForm">
  33. <a-col :flex="1">
  34. <a-form :model="queryDataForm" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
  35. label-align="left">
  36. <a-row :gutter="16">
  37. <a-col :span="8">
  38. <a-form-item field="area" label="跑区">
  39. <a-select v-model="queryDataForm.area" placeholder="请选择乐跑跑区" default-value="">
  40. <a-option value="">所有跑区</a-option>
  41. <a-option v-for="(item, index) in area" :key="index" :value="item">
  42. {{ item }}
  43. </a-option>
  44. </a-select>
  45. </a-form-item>
  46. </a-col>
  47. <a-col :span="8">
  48. <a-form-item field="email" label="邮箱">
  49. <a-input v-model="queryDataForm.email" allow-clear />
  50. </a-form-item>
  51. </a-col>
  52. <a-col :span="8">
  53. <a-form-item field="username" label="姓名">
  54. <a-input v-model="queryDataForm.username" allow-clear />
  55. </a-form-item>
  56. </a-col>
  57. <a-col :span="8">
  58. <a-form-item field="student_num" label="学号">
  59. <a-input v-model="queryDataForm.student_num" allow-clear />
  60. </a-form-item>
  61. </a-col>
  62. <a-col :span="8">
  63. <a-form-item field="area" label="状态">
  64. <a-select v-model="queryDataForm.state" :options="state" placeholder="请选择账号状态" :default-value="-1" />
  65. </a-form-item>
  66. </a-col>
  67. <a-col :span="8">
  68. <a-form-item field="auto_time" label="乐跑时段">
  69. <a-select v-model="queryDataForm.auto_time" placeholder="请选择自动乐跑时段" :default-value="-1">
  70. <a-option :value="0">所有时段</a-option>
  71. <a-option v-for="(item, index) in auto_time" :key="index" :value="item.value">
  72. {{ item.label }}
  73. </a-option>
  74. </a-select>
  75. </a-form-item>
  76. </a-col>
  77. </a-row>
  78. </a-form>
  79. </a-col>
  80. <a-divider style="height: 84px" direction="vertical" />
  81. <a-col :flex="'86px'" style="text-align: right">
  82. <a-space direction="vertical" :size="18">
  83. <a-button type="primary" @click="search">
  84. <template #icon>
  85. <icon-search />
  86. </template>
  87. 搜索
  88. </a-button>
  89. <a-button @click="reset">
  90. <template #icon>
  91. <icon-refresh />
  92. </template>
  93. 重置
  94. </a-button>
  95. </a-space>
  96. </a-col>
  97. </a-row>
  98. <a-alert v-if="notice" style="margin-bottom: 15px;">{{ notice }}</a-alert>
  99. <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" expandable :scroll="{
  100. x: 1600
  101. }" :pagination="{
  102. showPageSize: true,
  103. showJumper: true,
  104. showTotal: true,
  105. pageSize: pagination.pagesize,
  106. current: pagination.current,
  107. total: pagination.total
  108. }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
  109. <template #name-filter="{ filterValue, setFilterValue, handleFilterConfirm, handleFilterReset }">
  110. <div class="custom-filter">
  111. <a-space direction="vertical">
  112. <a-input :model-value="filterValue[0]" @input="(value) => setFilterValue([value])" />
  113. <div class="custom-filter-footer">
  114. <a-button @click="handleFilterReset">重置</a-button>
  115. <a-button @click="handleFilterConfirm">确定</a-button>
  116. </div>
  117. </a-space>
  118. </div>
  119. </template>
  120. <template #columns>
  121. <a-table-column title="" :width="50" data-index="user_avatar" tooltip>
  122. <template #cell="{ record }">
  123. <a-avatar>
  124. <img :alt="record.name ?? ''"
  125. :src="record.user_avatar || 'https://lepao-cloud.xxoo365.top/view.php/25aa126dc406974ff3579a99a2c6501a.png'" />
  126. </a-avatar>
  127. </template>
  128. </a-table-column>
  129. <a-table-column title="学号" :width="115" data-index="student_num" ellipsis tooltip></a-table-column>
  130. <a-table-column title="姓名" :width="130">
  131. <template #cell="{ record }">
  132. {{ record.name ?? '请使用乐跑登录器更新账号信息' }}
  133. </template>
  134. </a-table-column>
  135. <a-table-column title="性别" :width="80" ellipsis tooltip :filterable="{
  136. filters: [
  137. { text: '男', value: 1 },
  138. { text: '女', value: 2 }
  139. ],
  140. filter: (value, record) => record.sex == value
  141. }">
  142. <template #cell="{ record }">
  143. <icon-man v-if="record.sex === 1" />
  144. <icon-woman v-else-if="record.sex === 2" />
  145. {{ record.sex === 1 ? '男' : (record.sex === 2 ? '女' : '') }}
  146. </template>
  147. </a-table-column>
  148. <a-table-column title="年级" :width="70" data-index="grade_id" tooltip></a-table-column>
  149. <a-table-column title="学院" :width="220" data-index="academy_name" tooltip></a-table-column>
  150. <a-table-column title="跑区" :width="130">
  151. <template #cell="{ record }">
  152. {{ record.area || '随机分配' }}
  153. </template>
  154. </a-table-column>
  155. <a-table-column title="通知邮箱" :width="180" data-index="email" ellipsis tooltip></a-table-column>
  156. <a-table-column title="帐号状态" :width="100" ellipsis tooltip>
  157. <template #cell="{ record }">
  158. <div v-if="record.state === 0" class="state">
  159. <div class="circle zero"></div>需登录
  160. </div>
  161. <div v-else-if="record.state === 1" class="state">
  162. <div class="circle one"></div>正常
  163. </div>
  164. <div v-else class="state">
  165. <div class="circle else"></div>状态异常
  166. </div>
  167. </template>
  168. </a-table-column>
  169. <!-- <a-table-column title="人脸状态" :width="100" ellipsis tooltip>
  170. <template #cell="{ record }">
  171. <div v-if="record.face_state === 0" class="state">
  172. <div class="circle zero"></div>未采集
  173. </div>
  174. <div v-else-if="record.face_state === 1" class="state">
  175. <div class="circle one"></div>已通过
  176. </div>
  177. <div v-else class="state">
  178. <div class="circle else"></div>不通过
  179. </div>
  180. </template>
  181. </a-table-column> -->
  182. <a-table-column title="自动乐跑" :width="105" ellipsis tooltip :filterable="{
  183. filters: [
  184. { text: '开启', value: 1 },
  185. { text: '关闭', value: 0 }
  186. ],
  187. filter: (value, record) => record.auto_run == value
  188. }">
  189. <template #cell="{ record }">
  190. <a-tag color="green" v-if="record.auto_run">{{ record.target_count === 0 ? '开启-∞次' :
  191. `开启-${record.target_count}次` }}</a-tag>
  192. <a-tag color="red" v-else>关闭</a-tag>
  193. </template>
  194. </a-table-column>
  195. <a-table-column title="自动乐跑星期" :width="155" ellipsis tooltip>
  196. <template #cell="{ record }">
  197. <span v-if="record.auto_run && record.auto_day && record.auto_day.length > 0">
  198. {{record.auto_day.slice().sort((a, b) => {
  199. if (a === 0) return 1; if (b === 0) return -1; return a - b;
  200. }).map(day => auto_day.find(item => item.value === day)?.label).join(',').replace(/周/g, '')}}
  201. </span>
  202. <span v-else>-</span>
  203. </template>
  204. </a-table-column>
  205. <a-table-column title="自动乐跑时段" :width="130" ellipsis tooltip>
  206. <template #cell="{ record }">
  207. {{ autoTimeLabel(record) }}
  208. </template>
  209. </a-table-column>
  210. <a-table-column title="学期目标" :width="88" ellipsis tooltip>
  211. <template #cell="{ record }">
  212. {{ record.term_num != record.total_num ? `${record.total_num} / ${record.term_num}` :
  213. '已完成' }}
  214. </template>
  215. </a-table-column>
  216. <a-table-column title="添加时间" :width="145" ellipsis tooltip :sortable="{
  217. sortDirections: ['ascend', 'descend']
  218. }">
  219. <template #cell="{ record }">
  220. {{ stramptoTime(record.create_time) }}
  221. </template>
  222. </a-table-column>
  223. <a-table-column title="上次登录时间" :width="145" ellipsis tooltip>
  224. <template #cell="{ record }">
  225. {{ record.update_time ? stramptoTime(record.update_time) : '待登录' }}
  226. </template>
  227. </a-table-column>
  228. <a-table-column title="备注" :width="200" ellipsis tooltip>
  229. <template #cell="{ record }">
  230. {{ record.notes }}
  231. </template>
  232. </a-table-column>
  233. <a-table-column title="" fixed="right" :width="100">
  234. <template #cell="{ record }">
  235. <a-dropdown :popup-max-height="false" trigger="hover">
  236. <a-button>操作 <icon-down /></a-button>
  237. <template #content>
  238. <a-doption @click="editAccount(record)"><icon-edit /> 编辑账号</a-doption>
  239. <!-- <a-doption @click="faceRecoRef.openModal(record)"><icon-video-camera /> 人脸采集</a-doption> -->
  240. <a-doption @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
  241. <a-doption @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
  242. '开启' }}自动乐跑</a-doption>
  243. <a-doption @click="DeleteAccount(record)"><icon-minus-circle /> 解绑账号</a-doption>
  244. </template>
  245. </a-dropdown>
  246. </template>
  247. </a-table-column>
  248. </template>
  249. </a-table>
  250. </a-card>
  251. </div>
  252. <!-- 账号编辑对话框 -->
  253. <a-modal v-model:visible="visible" title="编辑账号" @cancel="handleCancel" @before-ok="handleBeforeOk" draggable
  254. :ok-loading="ok_loading" esc-to-close closable>
  255. <a-form :model="form">
  256. <a-form-item field="student_num" label="学号">
  257. <a-input v-model="form.student_num" placeholder="账号所有者学号,填写错误将无法登录" allow-clear />
  258. </a-form-item>
  259. <a-form-item field="email" label="通知邮箱">
  260. <a-auto-complete :data="email" @search="handleSearch" v-model="form.email" placeholder="用于接收乐跑失败、登录失效的通知"
  261. allow-clear />
  262. </a-form-item>
  263. <a-form-item field="area" label="乐跑跑区">
  264. <a-select v-model="form.area" placeholder="请选择乐跑跑区" default-value="">
  265. <a-option value="">随机分配</a-option>
  266. <a-option v-for="(item, index) in area" :key="index" :value="item">
  267. <span class="vipcontent">
  268. <span>{{ item }} </span>
  269. <!-- <img src="@/assets/vip.svg" alt="vip" height="20"> -->
  270. </span>
  271. </a-option>
  272. </a-select>
  273. </a-form-item>
  274. <a-form-item field="auto_run" label="自动乐跑开关">
  275. <a-switch v-model="form.auto_run" :checked-value="1" :unchecked-value="0" />
  276. </a-form-item>
  277. <a-form-item field="target_count" label="乐跑目标次数" v-if="form.auto_run">
  278. <a-input-number v-model="form.target_count" placeholder="请输入乐跑目标次数" mode="button" />
  279. <template #extra>
  280. <div>当学期有效次数达到目标次数时自动乐跑将关闭,0为不限次</div>
  281. </template>
  282. </a-form-item>
  283. <a-form-item field="auto_day" label="自动乐跑星期" v-if="form.auto_run">
  284. <a-checkbox-group v-model="form.auto_day" placeholder="请选择每天自动乐跑的星期" :options="auto_day" />
  285. </a-form-item>
  286. <a-form-item field="auto_time" label="自动乐跑时段" v-if="form.auto_run">
  287. <a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
  288. </a-form-item>
  289. <a-form-item field="notes" label="备注">
  290. <a-textarea v-model="form.notes" placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
  291. show-word-limit />
  292. </a-form-item>
  293. </a-form>
  294. </a-modal>
  295. <faceModal :faceInfo="faceInfo" ref="faceRecoRef" />
  296. </template>
  297. <script setup>
  298. import { ref, reactive, onUnmounted, onMounted, h } from 'vue'
  299. import { accountList, deleteAccount, addAccount, changeAutoRun, singleRun } from '@/api/lepao'
  300. import { Modal, Notification, Message } from '@arco-design/web-vue'
  301. import { IconSearch } from '@arco-design/web-vue/es/icon'
  302. import userCard from '@/components/userCard/userCard.vue'
  303. import { isElectron } from '@/utils/electron'
  304. import faceModal from '@/components/FaceModal/faceModal.vue'
  305. import { useRoute } from 'vue-router'
  306. import { getNotice } from '@/utils/util'
  307. const notice = ref('')
  308. const email = ref([])
  309. const faceInfo = ref({})
  310. const faceRecoRef = ref(null)
  311. const queryDataForm = reactive({
  312. area: '',
  313. student_num: '',
  314. email: '',
  315. username: '',
  316. state: -1,
  317. auto_time: 0
  318. })
  319. const queryData = reactive({
  320. area: '',
  321. student_num: '',
  322. email: '',
  323. username: '',
  324. state: -1,
  325. auto_time: 0
  326. })
  327. const pagination = reactive({
  328. total: 0,
  329. current: 1, // 默认从第1页开始
  330. pagesize: 20
  331. })
  332. const handleSearch = (value) => {
  333. const emailSuffix = ["qq.com", "ctbu.edu.cn", "163.com"]
  334. const input = (value || "").trim()
  335. if (!input) {
  336. email.value = []
  337. return
  338. }
  339. // 没有输入 @,直接拼接所有后缀
  340. if (!input.includes("@")) {
  341. email.value = emailSuffix.map(suffix => `${input}@${suffix}`)
  342. return
  343. }
  344. // 输入了 @ 但结尾是 @,拼接所有后缀
  345. if (input.endsWith("@")) {
  346. email.value = emailSuffix.map(suffix => `${input}${suffix}`)
  347. return
  348. }
  349. // 输入了 @ 且有部分后缀,智能匹配
  350. const [prefix, suffixPart] = input.split("@")
  351. email.value = emailSuffix
  352. .filter(suffix => suffix.startsWith(suffixPart))
  353. .map(suffix => `${prefix}@${suffix}`)
  354. }
  355. const area = ["兰花湖校区跑区", "主校区北跑区", "主校区南跑区", "重庆工商大学茶园校区"]
  356. const state = [
  357. { label: '全部', value: -1 }, { label: '需登录', value: 0 }, { label: '正常', value: 1 }, { label: '状态异常', value: 2 }
  358. ]
  359. const search = () => {
  360. pagination.current = 1
  361. queryData.area = queryDataForm.area
  362. queryData.student_num = queryDataForm.student_num
  363. queryData.email = queryDataForm.email
  364. queryData.username = queryDataForm.username
  365. queryData.state = queryDataForm.state
  366. queryData.auto_time = queryDataForm.auto_time
  367. getAccountsAsync()
  368. }
  369. const reset = () => {
  370. queryDataForm.area = ''
  371. queryDataForm.student_num = ''
  372. queryDataForm.email = ''
  373. queryDataForm.username = ''
  374. queryDataForm.state = -1
  375. queryDataForm.auto_time = 0
  376. queryData.area = ''
  377. queryData.student_num = ''
  378. queryData.email = ''
  379. queryData.username = ''
  380. queryData.state = -1
  381. queryData.auto_time = 0
  382. getAccountsAsync()
  383. }
  384. // 分页 - 页码变化
  385. const handlePageChange = (page) => {
  386. pagination.current = page
  387. getAccountsAsync()
  388. }
  389. // 分页 - 每页条数变化
  390. const handlePageSizeChange = (size) => {
  391. pagination.pagesize = size
  392. pagination.current = 1 // 页大小变化后回到第一页
  393. getAccountsAsync()
  394. }
  395. const auto_day = [
  396. { label: '周一', value: 1 },
  397. { label: '周二', value: 2 },
  398. { label: '周三', value: 3 },
  399. { label: '周四', value: 4 },
  400. { label: '周五', value: 5 },
  401. { label: '周六', value: 6 },
  402. { label: '周日', value: 0 }
  403. ]
  404. const auto_time = [
  405. { label: '随机分配', value: -1 },
  406. ...Array.from({ length: 17 }, (_, i) => {
  407. const hour = i + 7
  408. return { label: `${hour} ~ ${hour + 1}时`, value: hour }
  409. })
  410. ]
  411. const autoTimeLabel = (record) => {
  412. if (record.auto_time === -1) {
  413. if (record.today_auto_time)
  414. return `随机-今日${record.today_auto_time}时`
  415. return '随机-待分配'
  416. }
  417. const match = auto_time.find(item => item.value === record.auto_time)
  418. return match ? match.label : '-'
  419. }
  420. const data = ref([])
  421. const loading = ref(false)
  422. const visible = ref(false)
  423. const ok_loading = ref(false)
  424. const form = reactive({
  425. id: null,
  426. student_num: '',
  427. email: '',
  428. area: '',
  429. auto_time: -1,
  430. auto_run: 1,
  431. target_count: 30,
  432. auto_day: [0, 1, 2, 3, 4, 5, 6],
  433. notes: ''
  434. })
  435. const download = (device) => {
  436. const a = document.createElement('a')
  437. if (device === 'windows') {
  438. a.href = 'https:\/\/lepao-cloud.xxoo365.top/down.php\/682d99f9694c6fe76b64b86c5741a2d8.pdf'
  439. a.download = 'RunForge-Windows操作说明.pdf'
  440. } else if (device === 'iphone') {
  441. a.href = 'https:\/\/lepao-cloud.xxoo365.top/down.php\/fba1d571166b4c95592c7c4e624a9390.pdf'
  442. a.download = 'RunForge-iPhone操作说明.pdf'
  443. } else {
  444. return
  445. }
  446. a.target = '_blank'
  447. document.body.appendChild(a)
  448. a.click()
  449. document.body.removeChild(a)
  450. }
  451. const editAccount = (item) => {
  452. if (item) {
  453. form.id = item.id
  454. form.student_num = item.student_num
  455. form.email = item.email
  456. form.area = item.area
  457. form.auto_time = item.auto_time
  458. form.auto_run = item.auto_run
  459. form.target_count = item.target_count
  460. form.auto_day = item.auto_day
  461. form.notes = item.notes
  462. } else {
  463. form.id = null
  464. form.student_num = ''
  465. form.email = ''
  466. form.auto_run = 1
  467. form.auto_time = -1
  468. form.target_count = 30
  469. form.auto_day = [0, 1, 2, 3, 4, 5, 6]
  470. form.area = ''
  471. form.notes = ''
  472. }
  473. visible.value = true
  474. }
  475. const handleBeforeOk = async (done) => {
  476. try {
  477. ok_loading.value = true
  478. const { student_num, email } = form
  479. if (!student_num || !email) {
  480. Message.error('请填写完整的账号信息')
  481. return false
  482. }
  483. const studentNumRegex = /^\d{10}$/
  484. if (!studentNumRegex.test(student_num)) {
  485. Message.error('请检查学号格式是否正确')
  486. return false
  487. }
  488. const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
  489. if (!emailRegex.test(email)) {
  490. Message.error('请检查邮箱格式是否正确')
  491. return false
  492. }
  493. let data = {
  494. ...form
  495. }
  496. const res = await addAccount(data)
  497. if (!res || res.code !== 0) {
  498. Notification.error({
  499. title: '保存乐跑账号失败!',
  500. content: res?.msg ?? '请稍后再试'
  501. })
  502. return false
  503. }
  504. Message.success('保存成功!')
  505. done()
  506. getAccountsAsync()
  507. } catch (error) {
  508. Notification.error({
  509. title: '保存乐跑账号失败!',
  510. content: error.message || '请稍后再试'
  511. })
  512. return false
  513. } finally {
  514. ok_loading.value = false
  515. }
  516. }
  517. const handleCancel = () => {
  518. visible.value = false
  519. }
  520. const getAccountsAsync = async () => {
  521. loading.value = true
  522. await getAccounts()
  523. loading.value = false
  524. }
  525. const getAccounts = async () => {
  526. try {
  527. const reqData = {
  528. ...queryData,
  529. pagesize: pagination.pagesize,
  530. current: pagination.current
  531. }
  532. const res = await accountList(reqData)
  533. if (!res || res.code !== 0)
  534. return Notification.error({
  535. title: '获取乐跑账号数据失败!',
  536. content: res?.msg ?? '请稍后再试'
  537. })
  538. data.value = res.data
  539. pagination.total = res.pagination.total
  540. } catch (error) {
  541. Notification.error({
  542. title: '获取乐跑账号数据失败!',
  543. content: error.message || '请稍后再试'
  544. })
  545. }
  546. }
  547. const GetNotice = async () => {
  548. const { path } = useRoute()
  549. const res = await getNotice(path)
  550. notice.value = res
  551. }
  552. const SingleRun = async (item) => {
  553. if (item.state !== 1)
  554. return Notification.warning({
  555. title: '当前乐跑账号需登录,请登录后再试',
  556. content: '如有疑问请联系RunForge客服'
  557. })
  558. Modal.confirm({
  559. title: '开始乐跑',
  560. content: () => h('div', [
  561. h('p', `您是否要为 ${item.name}(${item.student_num}) 乐跑?若乐跑成功将扣减乐跑次数`)
  562. ]),
  563. onOk: async () => {
  564. const res = await singleRun({ student_num: item.student_num })
  565. if (!res || res.code !== 0)
  566. return Notification.error({
  567. title: '提交乐跑任务失败',
  568. content: res?.msg ?? '请稍后再试'
  569. })
  570. Message.success('提交乐跑任务成功!')
  571. }
  572. })
  573. }
  574. const DeleteAccount = async (item) => {
  575. Modal.confirm({
  576. title: '解绑账号',
  577. content: () => h('div', [
  578. h('p', '您是否要解绑该账号?')
  579. ]),
  580. onOk: async () => {
  581. const res = await deleteAccount({ id: item.id })
  582. if (!res || res.code !== 0)
  583. return Notification.error({
  584. title: '解绑失败',
  585. content: res?.msg ?? '请稍后再试'
  586. })
  587. Message.success('解绑成功!')
  588. getAccounts()
  589. }
  590. })
  591. }
  592. const ChangeAutoRun = async (record) => {
  593. const oldValue = record.auto_run;
  594. record.auto_run = oldValue === 1 ? 0 : 1;
  595. try {
  596. const res = await changeAutoRun({ id: record.id });
  597. if (!res || res.code !== 0) {
  598. record.auto_run = oldValue;
  599. Notification.error({
  600. title: '切换自动乐跑状态失败!',
  601. content: res?.msg ?? '请稍后再试'
  602. })
  603. } else {
  604. Message.success(`自动乐跑状态已${record.auto_run === 1 ? '开启' : '关闭'}`)
  605. getAccounts()
  606. }
  607. } catch (error) {
  608. record.auto_run = oldValue;
  609. Message.error('切换自动乐跑状态失败!');
  610. }
  611. }
  612. const stramptoTime = (time) => {
  613. return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
  614. }
  615. let timer = null
  616. // 轮询
  617. const startPolling = () => {
  618. if (!timer) {
  619. timer = setInterval(async () => {
  620. await getAccounts()
  621. }, 5000)
  622. }
  623. }
  624. // 停止轮询
  625. const stopPolling = () => {
  626. if (timer) {
  627. clearInterval(timer)
  628. timer = null
  629. }
  630. }
  631. onMounted(async () => {
  632. getAccountsAsync()
  633. GetNotice()
  634. startPolling()
  635. })
  636. // 组件销毁时停止轮询
  637. onUnmounted(() => {
  638. stopPolling()
  639. })
  640. </script>
  641. <style scoped lang="less">
  642. .container {
  643. padding: 0 20px 20px 20px;
  644. }
  645. .queryForm {
  646. margin-top: 20px;
  647. padding: 0 15px
  648. }
  649. .table {
  650. font-family: -apple-system, BlinkMacSystemFont;
  651. .state {
  652. display: flex;
  653. align-items: center;
  654. .circle {
  655. border-radius: 50%;
  656. height: 8px;
  657. min-height: 8px;
  658. width: 8px;
  659. min-width: 8px;
  660. margin-right: 5px;
  661. }
  662. .zero {
  663. background-color: rgb(var(--orange-6));
  664. }
  665. .one {
  666. background-color: rgb(var(--green-6));
  667. }
  668. .else {
  669. background-color: rgb(var(--red-6));
  670. }
  671. }
  672. }
  673. .custom-filter {
  674. padding: 20px;
  675. background: var(--color-bg-5);
  676. border: 1px solid var(--color-neutral-3);
  677. border-radius: var(--border-radius-medium);
  678. box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
  679. }
  680. .custom-filter-footer {
  681. display: flex;
  682. justify-content: space-between;
  683. }
  684. .vipcontent {
  685. display: flex;
  686. align-items: center;
  687. }
  688. </style>