Browse Source

✨ feat: 增加部分管理员页面

Pchen. 10 months ago
parent
commit
50e2990b7b

+ 9 - 0
src/api/lepao.js

@@ -2,6 +2,7 @@ import request from '../utils/request'
 
 
 const api = {
 const api = {
   Account: '/Lepao/Account',
   Account: '/Lepao/Account',
+  AdminAccount: '/Admin/Lepao/Account',
   Records: '/Lepao/Records',
   Records: '/Lepao/Records',
   ChangeAutoRun: '/Lepao/ChangeAutoRun',
   ChangeAutoRun: '/Lepao/ChangeAutoRun',
   SingleRun: '/Lepao/SingleRun',
   SingleRun: '/Lepao/SingleRun',
@@ -26,6 +27,14 @@ export function accountList (parameter) {
   })
   })
 }
 }
 
 
+export function adminAccountList (parameter) {
+  return request({
+    url: api.AdminAccount,
+    method: 'get',
+    params: parameter
+  })
+}
+
 export function deleteAccount (parameter) {
 export function deleteAccount (parameter) {
   return request({
   return request({
     url: api.Account,
     url: api.Account,

+ 10 - 1
src/api/user.js

@@ -5,7 +5,8 @@ const api = {
   ChangePassword: '/User/ChangePassword',
   ChangePassword: '/User/ChangePassword',
   UserInfo: '/User/Info',
   UserInfo: '/User/Info',
   BindEmail: '/User/BindEmail',
   BindEmail: '/User/BindEmail',
-  GetRepos: '/User/GetRepos'
+  GetRepos: '/User/GetRepos',
+  AdminUserList: '/Admin/User/GetUserList'
 }
 }
 
 
 export function ChangeUsername(parameter) {
 export function ChangeUsername(parameter) {
@@ -47,3 +48,11 @@ export function GetRepos(parameter) {
     params: parameter
     params: parameter
   })
   })
 }
 }
+
+export function adminGetUserList(parameter) {
+  return request({
+    url: api.AdminUserList,
+    method: 'get',
+    params: parameter
+  })
+}

+ 414 - 0
src/pages/admin/lepaoAccount/accountList.vue

@@ -0,0 +1,414 @@
+<template>
+    <div class="container">
+        <Breadcrumb :items="['网站管理', '乐跑账号管理']" />
+
+        <a-card title="乐跑账号管理">
+            <a-row>
+                <a-col :flex="1">
+                    <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
+                        label-align="left">
+
+                        <a-row :gutter="16">
+                            <a-col :span="8">
+                                <a-form-item field="area" label="跑区">
+                                    <a-select v-model="queryData.area" placeholder="请选择乐跑跑区" default-value="">
+                                        <a-option value="">所有</a-option>
+                                        <a-option v-for="(item, index) in areas" :key="index" :value="item">
+                                            {{ item }}
+                                        </a-option>
+                                    </a-select>
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="user_uuid" label="创建人UUID">
+                                    <a-input v-model="queryData.user_uuid" />
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="email" label="用户邮箱">
+                                    <a-input v-model="queryData.email" />
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="username" label="账号名称">
+                                    <a-input v-model="queryData.username" />
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="student_num" label="学号">
+                                    <a-input v-model="queryData.student_num" />
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="area" label="账号状态">
+                                    <a-select v-model="queryData.state" :options="state" placeholder="请选择账号状态"
+                                        :default-value="-1" />
+                                </a-form-item>
+                            </a-col>
+                        </a-row>
+                    </a-form>
+                </a-col>
+                <a-divider style="height: 84px" direction="vertical" />
+                <a-col :flex="'86px'" style="text-align: right">
+                    <a-space direction="vertical" :size="18">
+                        <a-button type="primary" @click="search">
+                            <template #icon>
+                                <icon-search />
+                            </template>
+                            搜索
+                        </a-button>
+                        <a-button @click="reset">
+                            <template #icon>
+                                <icon-refresh />
+                            </template>
+                            重置
+                        </a-button>
+                    </a-space>
+                </a-col>
+            </a-row>
+
+            <a-table :data="data" class="table" :loading="loading" :columns="columns" :pagination="{
+                showPageSize: true,
+                showJumper: true,
+                showTotal: true,
+                pageSize: pagination.pagesize,
+                current: pagination.current,
+                total: pagination.total
+            }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
+                <template #create_user="{ record }">
+                    <a-avatar :size="30">
+                        <IconUser v-if="!record.avatar" />
+                        <img :alt="record.create_user ?? ''" :src="record.avatar" v-else />
+                    </a-avatar>
+                    {{ record.create_user }}
+                </template>
+                <template #name="{ record }">
+                    <a-avatar :size="30">
+                        <IconUser v-if="!record.user_avatar" />
+                        <img :alt="record.name ?? ''" :src="record.user_avatar" v-else />
+                    </a-avatar>
+                    {{ record.name }}
+                </template>
+                <template #sex="{ record }">
+                    <icon-man v-if="record.sex === 1" />
+                    <icon-woman v-else-if="record.sex === 2" />
+                    {{ record.sex === 1 ? '男' : (record.sex === 2 ? '女' : '') }}
+                </template>
+                <template #auto_run="{ record }">
+                    <a-tag color="green" v-if="record.auto_run">开启</a-tag>
+                    <a-tag color="red" v-else>关闭</a-tag>
+                </template>
+                <template #num="{ record }">
+                    {{ record.term_num - record.total_num > 0 ? (record.term_num - record.total_num) : '已完成' }}
+                </template>
+                <template #state="{ 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>
+                <template #auto_time="{ record }">
+                    {{ autoTimeLabel(record.auto_time) }}
+                </template>
+                <template #create_time="{ record }">
+                    {{ stramptoTime(record.create_time) }}
+                </template>
+                <template #update_time="{ record }">
+                    {{ stramptoTime(record.update_time) }}
+                </template>
+                <template #optional="{ record }">
+                    <a-dropdown :popup-max-height="false" trigger="hover">
+                        <a-button>操作 <icon-down /></a-button>
+                        <template #content>
+                            <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-delete /> 解绑账号</a-doption>
+                        </template>
+                    </a-dropdown>
+                </template>
+            </a-table>
+        </a-card>
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, h } from 'vue'
+import { adminAccountList, deleteAccount, changeAutoRun, singleRun } from '@/api/lepao'
+import { Modal, Notification, Message } from '@arco-design/web-vue'
+
+const queryData = reactive({
+    area: '',
+    student_num: '',
+    user_uuid: '',
+    email: '',
+    username: '',
+    state: -1
+})
+
+const pagination = reactive({
+    total: 0,
+    current: 1, // 默认从第1页开始
+    pagesize: 20
+})
+
+const loading = ref(false)
+const data = ref([])
+
+const state = [
+    { label: '全部', value: -1 }, { label: '需登录', value: 0 }, { label: '正常', value: 1 }, { label: '状态异常', value: 2 }
+]
+const areas = ["兰花湖校区跑区", "主校区北跑区", "主校区南跑区"]
+
+const columns = [
+
+    {
+        title: 'ID',
+        dataIndex: 'id',
+        fixed: 'left',
+        width: 50
+    }, {
+        title: '创建用户',
+        slotName: 'create_user',
+        width: 180
+    }, {
+        title: '账号名称',
+        slotName: 'name',
+        width: 180
+    }, {
+        title: '学号',
+        dataIndex: 'student_num',
+        width: 115
+    }, {
+        title: '性别',
+        slotName: 'sex',
+        width: 80
+    }, {
+        title: '学院',
+        dataIndex: 'academy_name',
+        width: 200
+    }, {
+        title: '年级',
+        dataIndex: 'grade_id',
+        width: 80
+    }, {
+        title: '通知邮箱',
+        dataIndex: 'email',
+        width: 200
+    },
+    {
+        title: '乐跑跑区',
+        dataIndex: 'area',
+        width: 150
+    }, {
+        title: '累计次数',
+        dataIndex: 'total_num',
+        width: 90
+    }, {
+        title: '剩余次数',
+        slotName: 'num',
+        width: 90
+    }, {
+        title: '自动乐跑',
+        slotName: 'auto_run',
+        width: 90
+    }, {
+        title: '自动乐跑时段',
+        slotName: 'auto_time',
+        width: 130
+    }, {
+        title: '帐号状态',
+        slotName: 'state',
+        width: 100
+    }, {
+        title: '添加时间',
+        slotName: 'create_time',
+        width: 150
+    }, {
+        title: '上次更新时间',
+        slotName: 'update_time',
+        width: 150
+    }, {
+        title: '操作',
+        slotName: 'optional',
+        fixed: 'right',
+        width: 90
+    }]
+
+const search = () => {
+    pagination.current = 1
+    getAccounts()
+}
+
+const reset = () => {
+    queryData.area = ''
+    queryData.student_num = ''
+    queryData.user_uuid = ''
+    queryData.email = ''
+    queryData.username = ''
+    getAccounts()
+}
+
+const getAccounts = async () => {
+    try {
+        loading.value = true
+        const reqData = {
+            ...queryData,
+            pagesize: pagination.pagesize,
+            current: pagination.current
+        }
+        const res = await adminAccountList(reqData)
+        if (!res || res.code !== 0)
+            return Notification.error({
+                title: '获取路径数据失败!',
+                content: res?.msg ?? '请稍后再试'
+            })
+
+        data.value = res.data
+        pagination.total = res.pagination.total
+    } catch (error) {
+        Notification.error({
+            title: '获取路径数据失败!',
+            content: error.message || '请稍后再试'
+        })
+    } finally {
+        loading.value = false
+    }
+}
+
+// 分页 - 页码变化
+const handlePageChange = (page) => {
+    pagination.current = page
+    getAccounts()
+}
+
+// 分页 - 每页条数变化
+const handlePageSizeChange = (size) => {
+    pagination.pagesize = size
+    pagination.current = 1 // 页大小变化后回到第一页
+    getAccounts()
+}
+
+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 ChangeAutoRun = async (record) => {
+    const oldValue = record.auto_run;
+    record.auto_run = oldValue === 1 ? 0 : 1;
+    try {
+        const res = await changeAutoRun({ id: record.id });
+        if (!res || res.code !== 0) {
+            record.auto_run = oldValue;
+            Message.error('切换自动乐跑状态失败!')
+        } else {
+            Message.success(`自动乐跑状态已${record.auto_run === 1 ? '开启' : '关闭'}`)
+            getAccounts()
+        }
+    } catch (error) {
+        console.log(error)
+        record.auto_run = oldValue
+        Message.error('切换自动乐跑状态失败!')
+    }
+}
+
+const SingleRun = async (item) => {
+    if (item.state !== 1)
+        return Notification.error({
+            title: '当前乐跑账号需登录,请登录后再试',
+            content: '如有疑问请联系RunForge客服'
+        })
+    Modal.confirm({
+        title: '开始乐跑',
+        content: () => h('div', [
+            h('p', `您是否要为${item.name}乐跑?若乐跑成功将扣减乐跑次数`)
+        ]),
+        onOk: async () => {
+            const res = await singleRun({ student_num: item.student_num })
+            if (!res || res.code !== 0)
+                return Notification.error({
+                    title: '提交乐跑任务失败',
+                    content: res?.msg ?? '请稍后再试'
+                })
+            Message.success('提交乐跑任务成功!')
+        }
+    })
+}
+
+onMounted(() => {
+    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' })
+}
+
+const auto_time = Array.from({ length: 17 }, (_, i) => {
+    const hour = i + 7
+    return {
+        label: `${hour} ~ ${hour + 1}时`,
+        value: hour
+    }
+})
+
+const autoTimeLabel = (val) => {
+    const match = auto_time.find(item => item.value === val)
+    return match ? match.label : '-'
+}
+</script>
+
+<style scoped>
+.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));
+        }
+    }
+}
+</style>

+ 5 - 1
src/pages/admin/lepaoRecords/lepaoRecords.vue

@@ -27,7 +27,7 @@
           </a-form>
           </a-form>
         </a-col>
         </a-col>
         <a-divider style="height: 84px" direction="vertical" />
         <a-divider style="height: 84px" direction="vertical" />
-        <a-col :flex="1" >
+        <a-col :flex="1">
           <a-space direction="vertical" :size="18">
           <a-space direction="vertical" :size="18">
             <a-button type="primary" @click="search">
             <a-button type="primary" @click="search">
               <template #icon>
               <template #icon>
@@ -78,6 +78,10 @@
             icon: () => h(IconSearch)
             icon: () => h(IconSearch)
           }">
           }">
             <template #cell="{ record }">
             <template #cell="{ record }">
+              <a-avatar :size="30">
+                <IconUser v-if="!record.user_avatar" />
+                <img :alt="record.name ?? ''" :src="record.user_avatar" v-else />
+              </a-avatar>
               {{ record.name }}
               {{ record.name }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>

+ 208 - 0
src/pages/admin/user/userList.vue

@@ -0,0 +1,208 @@
+<template>
+    <div class="container">
+        <Breadcrumb :items="['网站管理', '用户管理']" />
+
+        <a-card title="用户管理">
+            <a-row>
+                <a-col :flex="1">
+                    <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
+                        label-align="left">
+                        <a-row :gutter="16">
+                            <a-col :span="8">
+                                <a-form-item field="user_uuid" label="UUID">
+                                    <a-input v-model="queryData.user_uuid" />
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="email" label="用户邮箱">
+                                    <a-input v-model="queryData.email" />
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="username" label="用户名">
+                                    <a-input v-model="queryData.username" />
+                                </a-form-item>
+                            </a-col>
+                            <a-col :span="8">
+                                <a-form-item field="nickname" label="昵称">
+                                    <a-input v-model="queryData.nickname" />
+                                </a-form-item>
+                            </a-col>
+                        </a-row>
+                    </a-form>
+                </a-col>
+                <a-divider style="height: 84px" direction="vertical" />
+                <a-col :flex="'86px'" style="text-align: right">
+                    <a-space direction="vertical" :size="18">
+                        <a-button type="primary" @click="search">
+                            <template #icon>
+                                <icon-search />
+                            </template>
+                            搜索
+                        </a-button>
+                        <a-button @click="reset">
+                            <template #icon>
+                                <icon-refresh />
+                            </template>
+                            重置
+                        </a-button>
+                    </a-space>
+                </a-col>
+            </a-row>
+
+            <a-table :data="data" stripe hoverable column-resizable class="table" :loading="loading" :columns="columns"
+                :pagination="{
+                    showPageSize: true,
+                    showJumper: true,
+                    showTotal: true,
+                    pageSize: pagination.pagesize,
+                    current: pagination.current,
+                    total: pagination.total
+                }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange">
+                <template #username="{ record }">
+                    <a-avatar>
+                        <IconUser v-if="!record.avatar" />
+                        <img :alt="record.username ?? ''" :src="record.avatar" v-else />
+                    </a-avatar>
+                    {{ record.username }}
+                </template>
+                <template #registTime="{ record }">
+                    {{ stramptoTime(record.registTime) }}
+                </template>
+                <template #lastTime="{ record }">
+                    {{ stramptoTime(record.lastTime) }}
+                </template>
+                <template #optional="{ record }">
+                    <a-button @click="$router.push(`/path/detail/${record.id}`)">查看详情</a-button>
+                </template>
+            </a-table>
+        </a-card>
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { adminGetUserList } from '@/api/user'
+import { Notification } from '@arco-design/web-vue'
+
+const queryData = reactive({
+    user_uuid: '',
+    email: '',
+    username: '',
+    nickname: '',
+})
+
+const pagination = reactive({
+    total: 0,
+    current: 1, // 默认从第1页开始
+    pagesize: 20
+})
+
+const loading = ref(false)
+const data = ref([])
+
+
+const columns = [
+
+    {
+        title: 'UUID',
+        dataIndex: 'uuid',
+    }, {
+        title: '用户名',
+        slotName: 'username',
+    }, {
+        title: '昵称',
+        dataIndex: 'nickname',
+    }, {
+        title: '邮箱',
+        dataIndex: 'email',
+    },
+    {
+        title: '登陆方式',
+        dataIndex: 'social_type',
+    }, {
+        title: '注册时间',
+        slotName: 'registTime'
+    }, {
+        title: '上次登录时间',
+        slotName: 'lastTime'
+    }, {
+        title: '剩余乐跑次数',
+        dataIndex: 'lepao_count',
+    }, {
+        title: '操作',
+        slotName: 'optional'
+    }]
+
+const search = () => {
+    pagination.current = 1
+    getUserList()
+}
+
+const reset = () => {
+    queryData.user_uuid = ''
+    queryData.email = ''
+    queryData.username = ''
+    queryData.nickname = ''
+    getUserList()
+}
+
+const getUserList = async () => {
+    try {
+        loading.value = true
+        const reqData = {
+            ...queryData,
+            pagesize: pagination.pagesize,
+            current: pagination.current
+        }
+        const res = await adminGetUserList(reqData)
+        if (!res || res.code !== 0)
+            return Notification.error({
+                title: '获取路径数据失败!',
+                content: res?.msg ?? '请稍后再试'
+            })
+
+        data.value = res.data
+        pagination.total = res.pagination.total
+    } catch (error) {
+        Notification.error({
+            title: '获取路径数据失败!',
+            content: error.message || '请稍后再试'
+        })
+    } finally {
+        loading.value = false
+    }
+}
+
+// 分页 - 页码变化
+const handlePageChange = (page) => {
+    pagination.current = page
+    getUserList()
+}
+
+// 分页 - 每页条数变化
+const handlePageSizeChange = (size) => {
+    pagination.pagesize = size
+    pagination.current = 1 // 页大小变化后回到第一页
+    getUserList()
+}
+
+onMounted(() => {
+    getUserList()
+})
+
+const stramptoTime = (time) => {
+    return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
+}
+
+</script>
+
+<style scoped>
+.container {
+    padding: 0 20px 20px 20px;
+}
+
+.table {
+    margin-top: 15px;
+}
+</style>

+ 2 - 1
src/pages/admin/workOrder/orderDetail.vue

@@ -197,7 +197,8 @@ const getOrderDetail = async () => {
         info.value = [
         info.value = [
             { label: '工单标题', value: res.data.title },
             { label: '工单标题', value: res.data.title },
             { label: '工单ID', value: res.data.id },
             { label: '工单ID', value: res.data.id },
-            { label: '发起人', value: res.data.userInfo[res.data.create_user]?.username},
+            { label: '发起人', value: res.data.userInfo[res.data.create_user]?.username },
+            { label: '发起人UUID', value: res.data.create_user },
             { label: '通知邮箱', value: res.data.email },
             { label: '通知邮箱', value: res.data.email },
             { label: '创建时间', value: stramptoTime(res.data.create_time) },
             { label: '创建时间', value: stramptoTime(res.data.create_time) },
             { label: '最后更新时间', value: stramptoTime(res.data.update_time) },
             { label: '最后更新时间', value: stramptoTime(res.data.update_time) },

+ 13 - 8
src/pages/lepao/accountList/index.vue

@@ -39,8 +39,8 @@
           <a-table-column title="" :width="60" data-index="user_avatar" tooltip>
           <a-table-column title="" :width="60" data-index="user_avatar" tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               <a-avatar>
               <a-avatar>
-                <IconUser v-if="!record.user_avatar"/>
-                <img :alt="record.name ?? ''" :src="record.user_avatar" v-else/>
+                <IconUser v-if="!record.user_avatar" />
+                <img :alt="record.name ?? ''" :src="record.user_avatar" v-else />
               </a-avatar>
               </a-avatar>
             </template>
             </template>
           </a-table-column>
           </a-table-column>
@@ -160,7 +160,7 @@
                   <a-doption @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
                   <a-doption @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
                   <a-doption @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
                   <a-doption @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
                     '开启' }}自动乐跑</a-doption>
                     '开启' }}自动乐跑</a-doption>
-                  <a-doption @click="DeleteAccount(record)"><icon-delete /> 删除账号</a-doption>
+                  <a-doption @click="DeleteAccount(record)"><icon-delete /> 解绑账号</a-doption>
                 </template>
                 </template>
               </a-dropdown>
               </a-dropdown>
             </template>
             </template>
@@ -378,6 +378,11 @@ const getAccounts = async () => {
 }
 }
 
 
 const SingleRun = async (item) => {
 const SingleRun = async (item) => {
+  if (item.state !== 1)
+    return Notification.error({
+      title: '当前乐跑账号需登录,请登录后再试',
+      content: '如有疑问请联系RunForge客服'
+    })
   Modal.confirm({
   Modal.confirm({
     title: '开始乐跑',
     title: '开始乐跑',
     content: () => h('div', [
     content: () => h('div', [
@@ -397,18 +402,18 @@ const SingleRun = async (item) => {
 
 
 const DeleteAccount = async (item) => {
 const DeleteAccount = async (item) => {
   Modal.confirm({
   Modal.confirm({
-    title: '删除账号',
+    title: '解绑账号',
     content: () => h('div', [
     content: () => h('div', [
-      h('p', '您是否要删除该账号?该操作不可逆')
+      h('p', '您是否要解绑该账号?该操作不可逆')
     ]),
     ]),
     onOk: async () => {
     onOk: async () => {
       const res = await deleteAccount({ id: item.id })
       const res = await deleteAccount({ id: item.id })
       if (!res || res.code !== 0)
       if (!res || res.code !== 0)
         return Notification.error({
         return Notification.error({
-          title: '删除失败',
+          title: '解绑失败',
           content: res?.msg ?? '请稍后再试'
           content: res?.msg ?? '请稍后再试'
         })
         })
-      Message.success('删除成功!')
+      Message.success('解绑成功!')
       getAccounts()
       getAccounts()
     }
     }
   })
   })
@@ -430,7 +435,7 @@ const ChangeAutoRun = async (record) => {
     record.auto_run = oldValue;
     record.auto_run = oldValue;
     Message.error('切换自动乐跑状态失败!');
     Message.error('切换自动乐跑状态失败!');
   }
   }
-};
+}
 
 
 
 
 const stramptoTime = (time) => {
 const stramptoTime = (time) => {

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

@@ -27,7 +27,7 @@
           </a-form>
           </a-form>
         </a-col>
         </a-col>
         <a-divider style="height: 84px" direction="vertical" />
         <a-divider style="height: 84px" direction="vertical" />
-        <a-col :flex="1" >
+        <a-col :flex="1">
           <a-space direction="vertical" :size="18">
           <a-space direction="vertical" :size="18">
             <a-button type="primary" @click="search">
             <a-button type="primary" @click="search">
               <template #icon>
               <template #icon>
@@ -78,6 +78,10 @@
             icon: () => h(IconSearch)
             icon: () => h(IconSearch)
           }">
           }">
             <template #cell="{ record }">
             <template #cell="{ record }">
+              <a-avatar :size="30">
+                <IconUser v-if="!record.user_avatar" />
+                <img :alt="record.name ?? ''" :src="record.user_avatar" v-else />
+              </a-avatar>
               {{ record.name }}
               {{ record.name }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>

+ 14 - 9
src/pages/openProxy/index.vue

@@ -9,7 +9,7 @@
                     </a-space>
                     </a-space>
                 </template>
                 </template>
             </a-result>
             </a-result>
-            <a-result status="success" title="启动命令已发送" subtitle="请根据下方输出进行操作" v-else>
+            <a-result status="success" title="启动命令已发送" subtitle="请根据下方输出进行操作,更新一个账号后若还要更新" v-else>
                 <template #extra>
                 <template #extra>
                     <a-space>
                     <a-space>
                         <a-button type='primary' @click="closeProxy" :loading="loading">关闭登录器</a-button>
                         <a-button type='primary' @click="closeProxy" :loading="loading">关闭登录器</a-button>
@@ -30,6 +30,7 @@ import { ref } from 'vue'
 const ready = ref(false)
 const ready = ref(false)
 const loading = ref(false)
 const loading = ref(false)
 const output = ref([])
 const output = ref([])
+const getData = ref([])
 
 
 const openProxy = async () => {
 const openProxy = async () => {
     try {
     try {
@@ -43,15 +44,19 @@ const openProxy = async () => {
             output.value.push(msg)
             output.value.push(msg)
 
 
         window.electron.onProcessOutput(data => {
         window.electron.onProcessOutput(data => {
-            console.log(data)
-            if (data.includes('>>>')) {
-                const parts = data.split('>>>');
-
-                for (let i = 1; i < parts.length; i++) {
-                    const part = parts[i].trim();
-                    if (part && part !== output.value[output.value.length - 1])
-                        output.value.push(part)
+            if (data !== getData.value[getData.value.length - 1]) {
+                getData.value.push(data)
+                if (data.includes('>>>')) {
+                    const parts = data.split('>>>');
 
 
+                    for (let i = 1; i < parts.length; i++) {
+                        const part = parts[i].trim();
+                        if (part && part !== output.value[output.value.length - 1]) {
+                            output.value.push(part)
+                            if(part.includes('✅ 更新乐跑账户登录信息成功!'))
+                            output.value.push('[系统消息] 若更新完一个乐跑账号后还需更新,请点击“关闭登录器”按钮后再次打开')
+                        }
+                    }
                 }
                 }
             }
             }
         })
         })

+ 18 - 0
src/router/index.js

@@ -194,6 +194,24 @@ const routes = [
             permission: ['admin', 'service', 'product']
             permission: ['admin', 'service', 'product']
         },
         },
         children: [
         children: [
+            {
+                path: 'userList',
+                name: 'admin.userList',
+                component: () => import('../pages/admin/user/userList.vue'),
+                meta: {
+                    title: '用户管理',
+                    permission: ['admin', 'userList']
+                }
+            },
+            {
+                path: 'lepaoAccount',
+                name: 'admin.lepaoAccount',
+                component: () => import('../pages/admin/lepaoAccount/accountList.vue'),
+                meta: {
+                    title: '乐跑账号管理',
+                    permission: ['admin', 'lepaoAccount']
+                }
+            },
             {
             {
                 path: 'lepaoRecords',
                 path: 'lepaoRecords',
                 name: 'admin.lepaoRecords',
                 name: 'admin.lepaoRecords',