accountList.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. <template>
  2. <div class="container">
  3. <Breadcrumb :items="['宿舍电费', '定制电费提醒']" />
  4. <a-card title="定制电费提醒" style="margin-top: 15px; ">
  5. <a-result status="403" subtitle="即将上线,敬请期待">
  6. <template #extra>
  7. <a-space>
  8. <!-- <a-button type="primary">Back</a-button> -->
  9. </a-space>
  10. </template>
  11. </a-result>
  12. </a-card>
  13. <a-card title="定制电费提醒" style="display: none;">
  14. <a-button type="primary" size="large" @click="editAccount()">
  15. <template #icon>
  16. <icon-plus />
  17. </template>
  18. 添加账号
  19. </a-button>
  20. <a-table :data="data" stripe hoverable column-resizable class="table" :loading="loading" :scroll="{
  21. x: 1600
  22. }" :pagination="{ showPageSize: true, showJumper: true, defaultPageSize: 15 }">
  23. <template #name-filter="{ filterValue, setFilterValue, handleFilterConfirm, handleFilterReset }">
  24. <div class="custom-filter">
  25. <a-space direction="vertical">
  26. <a-input :model-value="filterValue[0]" @input="(value) => setFilterValue([value])" />
  27. <div class="custom-filter-footer">
  28. <a-button @click="handleFilterReset">重置</a-button>
  29. <a-button @click="handleFilterConfirm">确定</a-button>
  30. </div>
  31. </a-space>
  32. </div>
  33. </template>
  34. <template #columns>
  35. <a-table-column title="" :width="60" data-index="user_avatar" tooltip>
  36. <template #cell="{ record }">
  37. <a-avatar>
  38. <IconUser v-if="!record.user_avatar" />
  39. <img :alt="record.name ?? ''" :src="record.user_avatar" v-else />
  40. </a-avatar>
  41. </template>
  42. </a-table-column>
  43. <a-table-column title="学号" :width="120" data-index="student_num" ellipsis tooltip :filterable="{
  44. filter: (value, record) => (record.student_num ?? '').includes(value),
  45. slotName: 'name-filter',
  46. icon: () => h(IconSearch)
  47. }"></a-table-column>
  48. <a-table-column title="用户名" :width="130" :filterable="{
  49. filter: (value, record) => (record.name ?? '').includes(value),
  50. slotName: 'name-filter',
  51. icon: () => h(IconSearch)
  52. }">
  53. <template #cell="{ record }">
  54. {{ record.name ?? '请使用乐跑登录器更新信息' }}
  55. </template>
  56. </a-table-column>
  57. <a-table-column title="性别" :width="80" ellipsis tooltip :filterable="{
  58. filters: [
  59. { text: '男', value: 1 },
  60. { text: '女', value: 2 }
  61. ],
  62. filter: (value, record) => record.sex == value
  63. }">
  64. <template #cell="{ record }">
  65. <icon-man v-if="record.sex === 1" />
  66. <icon-woman v-else-if="record.sex === 2" />
  67. {{ record.sex === 1 ? '男' : (record.sex === 2 ? '女' : '') }}
  68. </template>
  69. </a-table-column>
  70. <a-table-column title="学院" :width="220" data-index="academy_name" ellipsis tooltip :filterable="{
  71. filter: (value, record) => (record.academy_name ?? '').includes(value),
  72. slotName: 'name-filter',
  73. icon: () => h(IconSearch)
  74. }"></a-table-column>
  75. <a-table-column title="跑区" :width="130" :filterable="{
  76. filter: (value, record) => (record.name ?? '').includes(value),
  77. slotName: 'name-filter',
  78. icon: () => h(IconSearch)
  79. }">
  80. <template #cell="{ record }">
  81. <div class="vipcontent">
  82. <span>{{ record.area || '随机分配' }} </span>
  83. <!-- <img src="@/assets/vip.svg" alt="vip" height="20" v-if="record.area"> -->
  84. </div>
  85. </template>
  86. </a-table-column>
  87. <a-table-column title="通知邮箱" :width="180" data-index="email" ellipsis tooltip :filterable="{
  88. filter: (value, record) => (record.email ?? '').includes(value),
  89. slotName: 'name-filter',
  90. icon: () => h(IconSearch)
  91. }"></a-table-column>
  92. <a-table-column title="帐号状态" :width="100" ellipsis tooltip :filterable="{
  93. filters: [
  94. { text: '正常', value: 1 },
  95. { text: '需登录', value: 0 },
  96. { text: '状态异常', value: 2 },
  97. ],
  98. filter: (value, record) => record.state == value
  99. }">
  100. <template #cell="{ record }">
  101. <div v-if="record.state === 0" class="state">
  102. <div class="circle zero"></div>需登录
  103. </div>
  104. <div v-else-if="record.state === 1" class="state">
  105. <div class="circle one"></div>正常
  106. </div>
  107. <div v-else class="state">
  108. <div class="circle else"></div>状态异常
  109. </div>
  110. </template>
  111. </a-table-column>
  112. <a-table-column title="自动乐跑" :width="100" ellipsis tooltip :filterable="{
  113. filters: [
  114. { text: '开启', value: 1 },
  115. { text: '关闭', value: 0 }
  116. ],
  117. filter: (value, record) => record.auto_run == value
  118. }">
  119. <template #cell="{ record }">
  120. <a-tag color="green" v-if="record.auto_run">开启</a-tag>
  121. <a-tag color="red" v-else>关闭</a-tag>
  122. </template>
  123. </a-table-column>
  124. <a-table-column title="自动乐跑时段" :width="120" ellipsis tooltip>
  125. <template #cell="{ record }">
  126. {{ autoTimeLabel(record.auto_time) }}
  127. </template>
  128. </a-table-column>
  129. <a-table-column title="累计次数" data-index="total_num" :width="110" ellipsis tooltip :sortable="{
  130. sortDirections: ['ascend', 'descend']
  131. }"></a-table-column>
  132. <a-table-column title="剩余次数" :width="110" ellipsis tooltip>
  133. <template #cell="{ record }">
  134. {{ record.term_num - record.total_num > 0 ? (record.term_num - record.total_num) : '已完成' }}
  135. </template>
  136. </a-table-column>
  137. <a-table-column title="添加时间" :width="170" ellipsis tooltip :sortable="{
  138. sortDirections: ['ascend', 'descend']
  139. }">
  140. <template #cell="{ record }">
  141. {{ stramptoTime(record.create_time) }}
  142. </template>
  143. </a-table-column>
  144. <a-table-column title="更新时间" :width="170" ellipsis tooltip>
  145. <template #cell="{ record }">
  146. {{ stramptoTime(record.update_time || record.create_time) }}
  147. </template>
  148. </a-table-column>
  149. <a-table-column title="备注" :width="200" ellipsis tooltip>
  150. <template #cell="{ record }">
  151. {{ record.notes }}
  152. </template>
  153. </a-table-column>
  154. <a-table-column title="" fixed="right" :width="100">
  155. <template #cell="{ record }">
  156. <a-dropdown :popup-max-height="false" trigger="hover">
  157. <a-button>操作 <icon-down /></a-button>
  158. <template #content>
  159. <a-doption @click="editAccount(record)"><icon-edit /> 编辑账号</a-doption>
  160. <a-doption @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
  161. <a-doption @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
  162. '开启' }}自动乐跑</a-doption>
  163. <a-doption @click="DeleteAccount(record)"><icon-minus-circle /> 解绑账号</a-doption>
  164. </template>
  165. </a-dropdown>
  166. </template>
  167. </a-table-column>
  168. </template>
  169. </a-table>
  170. </a-card>
  171. </div>
  172. <!-- 账号编辑对话框 -->
  173. <a-modal v-model:visible="visible" title="编辑账号" @cancel="handleCancel" @before-ok="handleBeforeOk" draggable
  174. :ok-loading="ok_loading" esc-to-close closable>
  175. <a-form :model="form">
  176. <a-form-item field="student_num" label="学号">
  177. <a-input v-model="form.student_num" placeholder="账号所有者学号,填写错误将无法登录" />
  178. </a-form-item>
  179. <a-form-item field="email" label="通知邮箱">
  180. <a-auto-complete :data="email" @search="handleSearch" v-model="form.email" placeholder="用于接收乐跑失败、登录失效的通知" />
  181. </a-form-item>
  182. <a-form-item field="area" label="乐跑跑区">
  183. <a-select v-model="form.area" placeholder="请选择乐跑跑区" default-value="">
  184. <a-option value="">随机分配</a-option>
  185. <a-option v-for="(item, index) in area" :key="index" :value="item">
  186. <span class="vipcontent">
  187. <span>{{ item }} </span>
  188. <!-- <img src="@/assets/vip.svg" alt="vip" height="20"> -->
  189. </span>
  190. </a-option>
  191. </a-select>
  192. </a-form-item>
  193. <a-form-item field="auto_run" label="自动乐跑开关">
  194. <a-switch v-model="form.auto_run" :checked-value="1" :unchecked-value="0" />
  195. </a-form-item>
  196. <a-form-item field="area" label="自动乐跑时段">
  197. <a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
  198. </a-form-item>
  199. <a-form-item field="notes" label="备注">
  200. <a-textarea v-model="form.notes" placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
  201. show-word-limit />
  202. </a-form-item>
  203. </a-form>
  204. </a-modal>
  205. </template>
  206. <script setup>
  207. import { ref, reactive, onMounted, h } from 'vue'
  208. import { getPowerData, getChangeRecord, addAccount, deleteAccount, getAccount } from '@/api/power'
  209. import { Modal, Notification, Message } from '@arco-design/web-vue'
  210. import { IconSearch } from '@arco-design/web-vue/es/icon'
  211. const data = ref([])
  212. const loading = ref(false)
  213. const visible = ref(false)
  214. const ok_loading = ref(false)
  215. const form = reactive({
  216. id: null,
  217. student_num: '',
  218. email: '',
  219. area: '',
  220. auto_time: 8,
  221. auto_run: 1,
  222. notes: ''
  223. })
  224. const editAccount = (item) => {
  225. if (item) {
  226. form.id = item.id
  227. form.student_num = item.student_num
  228. form.email = item.email
  229. form.area = item.area
  230. form.auto_time = item.auto_time
  231. form.auto_run = item.auto_run
  232. form.notes = item.notes
  233. } else {
  234. form.id = null
  235. form.student_num = ''
  236. form.email = ''
  237. form.auto_run = 1
  238. form.auto_time = 7
  239. form.area = ''
  240. form.notes = ''
  241. }
  242. visible.value = true
  243. }
  244. const handleBeforeOk = async (done) => {
  245. try {
  246. ok_loading.value = true
  247. const { student_num, email } = form
  248. if (!student_num || !email) {
  249. Message.error('请填写完整的账号信息')
  250. return false
  251. }
  252. const studentNumRegex = /^\d{10}$/
  253. if (!studentNumRegex.test(student_num)) {
  254. Message.error('请检查学号格式是否正确')
  255. return false
  256. }
  257. const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
  258. if (!emailRegex.test(email)) {
  259. Message.error('请检查邮箱格式是否正确')
  260. return false
  261. }
  262. let data = {
  263. ...form,
  264. min_distance: form.distance[0],
  265. max_distance: form.distance[1]
  266. }
  267. const res = await addAccount(data)
  268. if (!res || res.code !== 0) {
  269. Notification.error({
  270. title: '保存乐跑账号失败!',
  271. content: res?.msg ?? '请稍后再试'
  272. })
  273. return false
  274. }
  275. Message.success('保存成功!')
  276. done()
  277. getAccounts()
  278. } catch (error) {
  279. Notification.error({
  280. title: '保存乐跑账号失败!',
  281. content: error.message || '请稍后再试'
  282. })
  283. return false
  284. } finally {
  285. ok_loading.value = false
  286. }
  287. }
  288. const handleCancel = () => {
  289. visible.value = false
  290. }
  291. const getAccounts = async () => {
  292. try {
  293. loading.value = true
  294. const res = await getAccount()
  295. if (!res || res.code !== 0)
  296. return Notification.error({
  297. title: '获取账号列表失败!',
  298. content: res?.msg ?? '请稍后再试'
  299. })
  300. data.value = res.data
  301. } catch (error) {
  302. Notification.error({
  303. title: '获取账号列表失败!',
  304. content: error.message || '请稍后再试'
  305. })
  306. } finally {
  307. loading.value = false
  308. }
  309. }
  310. const DeleteAccount = async (item) => {
  311. Modal.confirm({
  312. title: '解绑账号',
  313. content: () => h('div', [
  314. h('p', '您是否要解绑该账号?该操作不可逆')
  315. ]),
  316. onOk: async () => {
  317. const res = await deleteAccount({ id: item.id })
  318. if (!res || res.code !== 0)
  319. return Notification.error({
  320. title: '解绑失败',
  321. content: res?.msg ?? '请稍后再试'
  322. })
  323. Message.success('解绑成功!')
  324. getAccounts()
  325. }
  326. })
  327. }
  328. const stramptoTime = (time) => {
  329. return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })
  330. }
  331. onMounted(() => {
  332. })
  333. </script>
  334. <style scoped lang="less">
  335. .container {
  336. padding: 0 20px 20px 20px;
  337. }
  338. .table {
  339. margin-top: 15px;
  340. .state {
  341. display: flex;
  342. align-items: center;
  343. .circle {
  344. border-radius: 50%;
  345. height: 8px;
  346. min-height: 8px;
  347. width: 8px;
  348. min-width: 8px;
  349. margin-right: 5px;
  350. }
  351. .zero {
  352. background-color: rgb(var(--orange-6));
  353. }
  354. .one {
  355. background-color: rgb(var(--green-6));
  356. }
  357. .else {
  358. background-color: rgb(var(--red-6));
  359. }
  360. }
  361. }
  362. .custom-filter {
  363. padding: 20px;
  364. background: var(--color-bg-5);
  365. border: 1px solid var(--color-neutral-3);
  366. border-radius: var(--border-radius-medium);
  367. box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
  368. }
  369. .custom-filter-footer {
  370. display: flex;
  371. justify-content: space-between;
  372. }
  373. </style>