Browse Source

✨ feat: 新增若干内容

Pchen. 9 months ago
parent
commit
76119566d9

+ 47 - 0
src/api/power.js

@@ -0,0 +1,47 @@
+import request from '../utils/request'
+
+const api = {
+  Account: '/Power/Account',
+  GetChangeRecord: '/Power/GetChangeRecord',
+  GetPowerData: '/Power/GetPowerData'
+}
+
+export function getPowerData(parameter) {
+  return request({
+    url: api.GetPowerData,
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function getChangeRecord(parameter) {
+  return request({
+    url: api.GetChangeRecord,
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function addAccount(parameter) {
+  return request({
+    url: api.Account,
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function deleteAccount(parameter) {
+  return request({
+    url: api.Account,
+    method: 'delete',
+    params: parameter
+  })
+}
+
+export function getAccount(parameter) {
+  return request({
+    url: api.Account,
+    method: 'get',
+    params: parameter
+  })
+}

+ 3 - 3
src/pages/lepao/accountList/index.vue

@@ -62,7 +62,7 @@
             icon: () => h(IconSearch)
             icon: () => h(IconSearch)
           }">
           }">
             <template #cell="{ record }">
             <template #cell="{ record }">
-              {{ record.name ?? '请使用乐跑登录器更新信息' }}
+              {{ record.name ?? '请使用乐跑登录器更新账号信息' }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
           <a-table-column title="性别" :width="80" ellipsis tooltip :filterable="{
           <a-table-column title="性别" :width="80" ellipsis tooltip :filterable="{
@@ -152,9 +152,9 @@
               {{ stramptoTime(record.create_time) }}
               {{ stramptoTime(record.create_time) }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="更新时间" :width="170" ellipsis tooltip>
+          <a-table-column title="上次登录时间" :width="170" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
-              {{ stramptoTime(record.update_time || record.create_time) }}
+              {{  record.update_time ? stramptoTime(record.update_time) : '待登录' }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
           <a-table-column title="备注" :width="200" ellipsis tooltip>
           <a-table-column title="备注" :width="200" ellipsis tooltip>

+ 19 - 1
src/pages/lepao/lepaoRecords/index.vue

@@ -67,6 +67,14 @@
         </template>
         </template>
 
 
         <template #columns>
         <template #columns>
+          <a-table-column :width="80">
+            <template #cell="{ record }">
+              <div>
+                <a-tag color="arcoblue" v-if="record.uuid === user.uuid" size="small">我的记录</a-tag>
+                <a-tag color="orangered" size="small" v-else>关联记录</a-tag>
+              </div>
+            </template>
+          </a-table-column>
           <a-table-column title="学号" :width="120" data-index="lepao_account" ellipsis tooltip :filterable="{
           <a-table-column title="学号" :width="120" data-index="lepao_account" ellipsis tooltip :filterable="{
             filter: (value, record) => (record.lepao_account ?? '').includes(value),
             filter: (value, record) => (record.lepao_account ?? '').includes(value),
             slotName: 'name-filter',
             slotName: 'name-filter',
@@ -84,10 +92,11 @@
               </a-avatar>
               </a-avatar>
               {{ record.name }}
               {{ record.name }}
             </template>
             </template>
+
           </a-table-column>
           </a-table-column>
           <a-table-column title="状态" ellipsis tooltip>
           <a-table-column title="状态" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
-              <div  class="state">
+              <div class="state">
                 <div class="circle one" v-if="record.result.record_failed_reason === '自动确认有效'"></div>
                 <div class="circle one" v-if="record.result.record_failed_reason === '自动确认有效'"></div>
                 <div class="circle else" v-else></div>
                 <div class="circle else" v-else></div>
                 {{ record.result.record_failed_reason }}
                 {{ record.result.record_failed_reason }}
@@ -140,9 +149,18 @@ import { ref, reactive, h } from 'vue'
 import { lepaoRecords } from '@/api/lepao'
 import { lepaoRecords } from '@/api/lepao'
 import { Notification } from '@arco-design/web-vue'
 import { Notification } from '@arco-design/web-vue'
 import { IconSearch } from '@arco-design/web-vue/es/icon'
 import { IconSearch } from '@arco-design/web-vue/es/icon'
+import { useUserStore } from '@/store/modules/user'
 
 
 const data = ref([])
 const data = ref([])
 const loading = ref(false)
 const loading = ref(false)
+const user = ref('')
+const getuser = async () => {
+  const userStore = useUserStore()
+  let userInfo = await userStore.getInfo()
+  user.value = userInfo
+}
+
+getuser()
 
 
 const queryData = reactive({
 const queryData = reactive({
   name: '',
   name: '',

+ 4 - 4
src/pages/lepao/lepaoRecords/recordDetail.vue

@@ -74,11 +74,11 @@ const stramptoTime = (time) => {
 }
 }
 
 
 function calculatePace(seconds, kilometers) {
 function calculatePace(seconds, kilometers) {
-    const paceInSeconds = seconds / kilometers;
-    const minutes = Math.floor(paceInSeconds / 60);
-    const remainingSeconds = Math.round(paceInSeconds % 60);
+    const paceInSeconds = seconds / kilometers
+    const minutes = Math.floor(paceInSeconds / 60)
+    const remainingSeconds = Math.round(paceInSeconds % 60)
 
 
-    return `${minutes}'${remainingSeconds.toString().padStart(2, '0')}''`;
+    return `${minutes}'${remainingSeconds.toString().padStart(2, '0')}''`
 }
 }
 
 
 function formatSecondsToMinSec(totalSeconds) {
 function formatSecondsToMinSec(totalSeconds) {

+ 406 - 0
src/pages/power/accountList.vue

@@ -0,0 +1,406 @@
+<template>
+  <div class="container">
+    <Breadcrumb :items="['宿舍电费', '定制电费提醒']" />
+
+    <a-card title="定制电费提醒" style="margin-top: 15px; ">
+      <a-result status="403" subtitle="即将上线,敬请期待">
+        <template #extra>
+          <a-space>
+            <!-- <a-button type="primary">Back</a-button> -->
+          </a-space>
+        </template>
+      </a-result>
+    </a-card>
+
+    <a-card title="定制电费提醒" style="margin-top: 15px; display: none;">
+      <a-button type="primary" size="large" @click="editAccount()">
+        <template #icon>
+          <icon-plus />
+        </template>
+        添加账号
+      </a-button>
+
+      <a-table :data="data" stripe hoverable column-resizable class="table" :loading="loading" :scroll="{
+        x: 1600
+      }" :pagination="{ showPageSize: true, showJumper: true, defaultPageSize: 15 }">
+
+        <template #name-filter="{ filterValue, setFilterValue, handleFilterConfirm, handleFilterReset }">
+          <div class="custom-filter">
+            <a-space direction="vertical">
+              <a-input :model-value="filterValue[0]" @input="(value) => setFilterValue([value])" />
+              <div class="custom-filter-footer">
+                <a-button @click="handleFilterReset">重置</a-button>
+                <a-button @click="handleFilterConfirm">确定</a-button>
+              </div>
+            </a-space>
+          </div>
+        </template>
+
+        <template #columns>
+          <a-table-column title="" :width="60" data-index="user_avatar" tooltip>
+            <template #cell="{ record }">
+              <a-avatar>
+                <IconUser v-if="!record.user_avatar" />
+                <img :alt="record.name ?? ''" :src="record.user_avatar" v-else />
+              </a-avatar>
+            </template>
+          </a-table-column>
+          <a-table-column title="学号" :width="120" data-index="student_num" ellipsis tooltip :filterable="{
+            filter: (value, record) => (record.student_num ?? '').includes(value),
+            slotName: 'name-filter',
+            icon: () => h(IconSearch)
+          }"></a-table-column>
+          <a-table-column title="用户名" :width="130" :filterable="{
+            filter: (value, record) => (record.name ?? '').includes(value),
+            slotName: 'name-filter',
+            icon: () => h(IconSearch)
+          }">
+            <template #cell="{ record }">
+              {{ record.name ?? '请使用乐跑登录器更新信息' }}
+            </template>
+          </a-table-column>
+          <a-table-column title="性别" :width="80" ellipsis tooltip :filterable="{
+            filters: [
+              { text: '男', value: 1 },
+              { text: '女', value: 2 }
+            ],
+            filter: (value, record) => record.sex == value
+          }">
+            <template #cell="{ record }">
+              <icon-man v-if="record.sex === 1" />
+              <icon-woman v-else-if="record.sex === 2" />
+              {{ record.sex === 1 ? '男' : (record.sex === 2 ? '女' : '') }}
+            </template>
+          </a-table-column>
+          <a-table-column title="学院" :width="220" data-index="academy_name" ellipsis tooltip :filterable="{
+            filter: (value, record) => (record.academy_name ?? '').includes(value),
+            slotName: 'name-filter',
+            icon: () => h(IconSearch)
+          }"></a-table-column>
+          <a-table-column title="跑区" :width="130" :filterable="{
+            filter: (value, record) => (record.name ?? '').includes(value),
+            slotName: 'name-filter',
+            icon: () => h(IconSearch)
+          }">
+            <template #cell="{ record }">
+              <div class="vipcontent">
+                <span>{{ record.area || '随机分配' }} </span>
+                <!-- <img src="@/assets/vip.svg" alt="vip" height="20" v-if="record.area"> -->
+              </div>
+            </template>
+          </a-table-column>
+          <a-table-column title="通知邮箱" :width="180" data-index="email" ellipsis tooltip :filterable="{
+            filter: (value, record) => (record.email ?? '').includes(value),
+            slotName: 'name-filter',
+            icon: () => h(IconSearch)
+          }"></a-table-column>
+          <a-table-column title="帐号状态" :width="100" ellipsis tooltip :filterable="{
+            filters: [
+              { text: '正常', value: 1 },
+              { text: '需登录', value: 0 },
+              { text: '状态异常', value: 2 },
+            ],
+            filter: (value, record) => record.state == value
+          }">
+            <template #cell="{ record }">
+              <div v-if="record.state === 0" class="state">
+                <div class="circle zero"></div>需登录
+              </div>
+              <div v-else-if="record.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="100" ellipsis tooltip :filterable="{
+            filters: [
+              { text: '开启', value: 1 },
+              { text: '关闭', value: 0 }
+            ],
+            filter: (value, record) => record.auto_run == value
+          }">
+            <template #cell="{ record }">
+              <a-tag color="green" v-if="record.auto_run">开启</a-tag>
+              <a-tag color="red" v-else>关闭</a-tag>
+            </template>
+          </a-table-column>
+          <a-table-column title="自动乐跑时段" :width="120" ellipsis tooltip>
+            <template #cell="{ record }">
+              {{ autoTimeLabel(record.auto_time) }}
+            </template>
+          </a-table-column>
+          <a-table-column title="累计次数" data-index="total_num" :width="110" ellipsis tooltip :sortable="{
+            sortDirections: ['ascend', 'descend']
+          }"></a-table-column>
+          <a-table-column title="剩余次数" :width="110" ellipsis tooltip>
+            <template #cell="{ record }">
+              {{ record.term_num - record.total_num > 0 ? (record.term_num - record.total_num) : '已完成' }}
+            </template>
+          </a-table-column>
+          <a-table-column title="添加时间" :width="170" ellipsis tooltip :sortable="{
+            sortDirections: ['ascend', 'descend']
+          }">
+            <template #cell="{ record }">
+              {{ stramptoTime(record.create_time) }}
+            </template>
+          </a-table-column>
+          <a-table-column title="更新时间" :width="170" ellipsis tooltip>
+            <template #cell="{ record }">
+              {{ stramptoTime(record.update_time || record.create_time) }}
+            </template>
+          </a-table-column>
+          <a-table-column title="备注" :width="200" ellipsis tooltip>
+            <template #cell="{ record }">
+              {{ record.notes }}
+            </template>
+          </a-table-column>
+
+          <a-table-column title="" fixed="right" :width="100">
+            <template #cell="{ record }">
+              <a-dropdown :popup-max-height="false" trigger="hover">
+                <a-button>操作 <icon-down /></a-button>
+                <template #content>
+                  <a-doption @click="editAccount(record)"><icon-edit /> 编辑账号</a-doption>
+                  <a-doption @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
+                  <a-doption @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
+                    '开启' }}自动乐跑</a-doption>
+                  <a-doption @click="DeleteAccount(record)"><icon-minus-circle /> 解绑账号</a-doption>
+                </template>
+              </a-dropdown>
+            </template>
+          </a-table-column>
+        </template>
+      </a-table>
+    </a-card>
+  </div>
+
+  <!-- 账号编辑对话框 -->
+  <a-modal v-model:visible="visible" title="编辑账号" @cancel="handleCancel" @before-ok="handleBeforeOk" draggable
+    :ok-loading="ok_loading" esc-to-close closable>
+    <a-form :model="form">
+      <a-form-item field="student_num" label="学号">
+        <a-input v-model="form.student_num" placeholder="账号所有者学号,填写错误将无法登录" />
+      </a-form-item>
+      <a-form-item field="email" label="通知邮箱">
+        <a-auto-complete :data="email" @search="handleSearch" v-model="form.email" placeholder="用于接收乐跑失败、登录失效的通知" />
+      </a-form-item>
+      <a-form-item field="area" label="乐跑跑区">
+        <a-select v-model="form.area" placeholder="请选择乐跑跑区" default-value="">
+          <a-option value="">随机分配</a-option>
+          <a-option v-for="(item, index) in area" :key="index" :value="item">
+            <span class="vipcontent">
+              <span>{{ item }} </span>
+              <!-- <img src="@/assets/vip.svg" alt="vip" height="20"> -->
+            </span>
+          </a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="auto_run" label="自动乐跑开关">
+        <a-switch v-model="form.auto_run" :checked-value="1" :unchecked-value="0" />
+      </a-form-item>
+      <a-form-item field="area" label="自动乐跑时段">
+        <a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
+      </a-form-item>
+      <a-form-item field="notes" label="备注">
+        <a-textarea v-model="form.notes" placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
+          show-word-limit />
+      </a-form-item>
+    </a-form>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, h } from 'vue'
+import { getPowerData, getChangeRecord, addAccount, deleteAccount, getAccount } from '@/api/power'
+import { Modal, Notification, Message } from '@arco-design/web-vue'
+import { IconSearch } from '@arco-design/web-vue/es/icon'
+
+const data = ref([])
+const loading = ref(false)
+
+const visible = ref(false)
+const ok_loading = ref(false)
+const form = reactive({
+  id: null,
+  student_num: '',
+  email: '',
+  area: '',
+  auto_time: 8,
+  auto_run: 1,
+  notes: ''
+})
+
+const editAccount = (item) => {
+  if (item) {
+    form.id = item.id
+    form.student_num = item.student_num
+    form.email = item.email
+    form.area = item.area
+    form.auto_time = item.auto_time
+    form.auto_run = item.auto_run
+    form.notes = item.notes
+  } else {
+    form.id = null
+    form.student_num = ''
+    form.email = ''
+    form.auto_run = 1
+    form.auto_time = 7
+    form.area = ''
+    form.notes = ''
+  }
+  visible.value = true
+}
+
+const handleBeforeOk = async (done) => {
+  try {
+    ok_loading.value = true
+    const { student_num, email } = form
+    if (!student_num || !email) {
+      Message.error('请填写完整的账号信息')
+      return false
+    }
+
+    const studentNumRegex = /^\d{10}$/
+    if (!studentNumRegex.test(student_num)) {
+      Message.error('请检查学号格式是否正确')
+      return false
+    }
+
+    const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
+    if (!emailRegex.test(email)) {
+      Message.error('请检查邮箱格式是否正确')
+      return false
+    }
+
+    let data = {
+      ...form,
+      min_distance: form.distance[0],
+      max_distance: form.distance[1]
+    }
+
+    const res = await addAccount(data)
+    if (!res || res.code !== 0) {
+      Notification.error({
+        title: '保存乐跑账号失败!',
+        content: res?.msg ?? '请稍后再试'
+      })
+      return false
+    }
+
+    Message.success('保存成功!')
+    done()
+    getAccounts()
+  } catch (error) {
+    Notification.error({
+      title: '保存乐跑账号失败!',
+      content: error.message || '请稍后再试'
+    })
+    return false
+  } finally {
+    ok_loading.value = false
+  }
+}
+
+const handleCancel = () => {
+  visible.value = false
+}
+
+const getAccounts = async () => {
+  try {
+    loading.value = true
+    const res = await getAccount()
+    if (!res || res.code !== 0)
+      return Notification.error({
+        title: '获取账号列表失败!',
+        content: res?.msg ?? '请稍后再试'
+      })
+    data.value = res.data
+  } catch (error) {
+    Notification.error({
+      title: '获取账号列表失败!',
+      content: error.message || '请稍后再试'
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+const DeleteAccount = async (item) => {
+  Modal.confirm({
+    title: '解绑账号',
+    content: () => h('div', [
+      h('p', '您是否要解绑该账号?该操作不可逆')
+    ]),
+    onOk: async () => {
+      const res = await deleteAccount({ id: item.id })
+      if (!res || res.code !== 0)
+        return Notification.error({
+          title: '解绑失败',
+          content: res?.msg ?? '请稍后再试'
+        })
+      Message.success('解绑成功!')
+      getAccounts()
+    }
+  })
+}
+
+const stramptoTime = (time) => {
+  return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })
+}
+
+onMounted(() => {
+  getAccounts()
+})
+
+</script>
+
+<style scoped lang="less">
+.container {
+  padding: 0 20px 20px 20px;
+}
+
+.table {
+  margin-top: 15px;
+
+  .state {
+    display: flex;
+    align-items: center;
+
+    .circle {
+      border-radius: 50%;
+      height: 8px;
+      min-height: 8px;
+      width: 8px;
+      min-width: 8px;
+      margin-right: 5px;
+    }
+
+    .zero {
+      background-color: rgb(var(--orange-6));
+    }
+
+    .one {
+      background-color: rgb(var(--green-6));
+    }
+
+    .else {
+      background-color: rgb(var(--red-6));
+    }
+  }
+}
+
+.custom-filter {
+  padding: 20px;
+  background: var(--color-bg-5);
+  border: 1px solid var(--color-neutral-3);
+  border-radius: var(--border-radius-medium);
+  box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
+}
+
+.custom-filter-footer {
+  display: flex;
+  justify-content: space-between;
+}
+</style>

+ 20 - 1
src/router/index.js

@@ -116,7 +116,7 @@ const routes = [
         component: DEFAULT_LAYOUT,
         component: DEFAULT_LAYOUT,
         meta: {
         meta: {
             title: '校园乐跑',
             title: '校园乐跑',
-            icon: 'icon-thunderbolt'
+            icon: 'icon-dashboard'
         },
         },
         children: [
         children: [
             {
             {
@@ -165,6 +165,25 @@ const routes = [
             }
             }
         ]
         ]
     },
     },
+    {
+        path: "/power",
+        name: "power",
+        component: DEFAULT_LAYOUT,
+        meta: {
+            title: '宿舍电费',
+            icon: 'icon-thunderbolt'
+        },
+        children: [
+            {
+                path: 'accountList',
+                name: 'power.accountList',
+                component: () => import('../pages/power/accountList.vue'),
+                meta: {
+                    title: '定制电费提醒'
+                }
+            }
+        ]
+    },
     {
     {
         path: "/service",
         path: "/service",
         name: 'service',
         name: 'service',