campus-errand/admin/src/views/Users.vue
18631081161 2d0c71721d
All checks were successful
continuous-integration/drone/push Build is passing
改bug
2026-03-29 21:13:50 +08:00

159 lines
4.9 KiB
Vue

<template>
<div class="users-page">
<div class="page-header">
<h2>用户管理</h2>
<div class="header-actions">
<el-input
v-model="keyword"
placeholder="搜索昵称/手机号/ID"
clearable
style="width: 240px"
@keyup.enter="fetchList"
@clear="fetchList"
>
<template #append>
<el-button @click="fetchList" :icon="Search" />
</template>
</el-input>
<el-tag type="info" size="large" style="margin-left: 12px">共 {{ list.length }} 名用户</el-tag>
</div>
</div>
<el-table :data="list" v-loading="loading" stripe style="width: 100%">
<el-table-column prop="id" label="UID" width="80" align="center" />
<el-table-column label="头像" width="70" align="center">
<template #default="{ row }">
<el-avatar :size="36" :src="row.avatarUrl || undefined">
<template #default>
<span style="font-size: 14px">{{ (row.nickname || '?')[0] }}</span>
</template>
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="nickname" label="昵称" min-width="120" show-overflow-tooltip />
<el-table-column label="角色" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getRoleType(row.role)" size="small" round>{{ getRoleLabel(row.role) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="下单数" width="90" align="center" prop="orderCount" />
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<el-tag :type="row.isBanned ? 'danger' : 'success'" round size="small">
{{ row.isBanned ? '已封禁' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="注册时间" min-width="170">
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template #default="{ row }">
<el-popconfirm
v-if="!row.isBanned"
title="确定封禁该用户?"
confirm-button-text="封禁"
confirm-button-type="danger"
@confirm="toggleBan(row, true)"
>
<template #reference>
<el-button size="small" type="danger" plain>封禁</el-button>
</template>
</el-popconfirm>
<el-popconfirm
v-else
title="确定解封该用户?"
confirm-button-text="解封"
@confirm="toggleBan(row, false)"
>
<template #reference>
<el-button size="small" type="success" plain>解封</el-button>
</template>
</el-popconfirm>
<el-popconfirm
v-if="row.role !== 'Admin'"
title="删除后不可恢复,确定删除?"
confirm-button-text="删除"
confirm-button-type="danger"
@confirm="deleteUser(row)"
>
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import request from '../utils/request'
const loading = ref(false)
const list = ref([])
const keyword = ref('')
async function fetchList() {
loading.value = true
try {
const params = keyword.value ? { keyword: keyword.value } : {}
list.value = await request.get('/admin/users', { params })
} finally {
loading.value = false
}
}
async function toggleBan(row, isBanned) {
const label = isBanned ? '封禁' : '解封'
await request.put(`/admin/users/${row.id}/ban`, { isBanned })
ElMessage.success(`${label}`)
fetchList()
}
async function deleteUser(row) {
await request.delete(`/admin/users/${row.id}`)
ElMessage.success('用户已删除')
fetchList()
}
function getRoleLabel(role) {
const map = { User: '普通用户', Runner: '跑腿', Admin: '管理员' }
return map[role] || role
}
function getRoleType(role) {
const map = { Admin: 'warning', Runner: 'success' }
return map[role] || ''
}
function formatTime(str) {
if (!str) return '-'
const d = new Date(str)
const pad = n => String(n).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
}
onMounted(fetchList)
</script>
<style scoped>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-header h2 {
margin: 0;
font-size: 20px;
}
.header-actions {
display: flex;
align-items: center;
}
</style>