Files
2026-10-17 19:04:05 +08:00

242 lines
7.1 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-container">
<!-- 查询条件 -->
<div class="filter-bar">
<el-input
v-model="filters.keyword"
placeholder="用户 ID / 手机号 / 邮箱"
clearable
style="width: 220px"
@keyup.enter="search"
/>
<el-select v-model="filters.status" placeholder="状态" clearable style="width: 140px">
<el-option label="正常" :value="1" />
<el-option label="封禁" :value="2" />
<el-option label="已注销" :value="3" />
</el-select>
<el-button type="primary" :icon="Search" @click="search">查询</el-button>
<el-button :icon="Refresh" @click="reset">重置</el-button>
</div>
<!-- 用户列表 -->
<el-table v-loading="loading" :data="rows" border stripe>
<el-table-column prop="user_id" label="ID" width="80" />
<el-table-column label="头像" width="80">
<template #default="{ row }">
<el-avatar :size="36" :src="row.avatar_url || undefined">
{{ (row.nickname || '?').charAt(0) }}
</el-avatar>
</template>
</el-table-column>
<el-table-column prop="nickname" label="昵称" min-width="140" show-overflow-tooltip />
<el-table-column prop="phone" label="手机号" width="130" />
<el-table-column prop="email" label="邮箱" min-width="160" show-overflow-tooltip />
<el-table-column prop="points" label="积分" width="90" />
<el-table-column label="会员" width="90">
<template #default="{ row }">
<el-tag :type="row.is_member ? 'warning' : 'info'" size="small">
{{ row.is_member ? '会员' : '非会员' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="video_count" label="作品数" width="90" />
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="STATUS_TAG[row.status] || 'info'" size="small">
{{ row.status_text || '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="created_at" label="注册时间" width="170" />
<el-table-column label="操作" width="230" fixed="right">
<template #default="{ row }">
<el-button link type="primary" :icon="View" @click="toDetail(row)">详情</el-button>
<el-button
v-if="row.status !== 3"
link
type="warning"
:icon="Key"
@click="openResetPwd(row)"
>
改密
</el-button>
<el-button
v-if="row.status === 1"
link
type="danger"
:icon="Lock"
@click="toggleStatus(row, 2)"
>
封禁
</el-button>
<el-button
v-else-if="row.status === 2"
link
type="success"
:icon="Unlock"
@click="toggleStatus(row, 1)"
>
解封
</el-button>
<el-button v-else link disabled>解封</el-button>
</template>
</el-table-column>
<template #empty><el-empty description="暂无数据" /></template>
</el-table>
<div class="pagination-wrap">
<el-pagination
v-model:current-page="page"
:page-size="pageSize"
:total="total"
layout="total, prev, pager, next"
@current-change="load"
/>
</div>
<!-- 重置密码弹窗 -->
<el-dialog v-model="pwdDialog.visible" title="重置用户密码" width="420px">
<el-form label-width="80px">
<el-form-item label="用户">
<span>{{ pwdDialog.nickname }} #{{ pwdDialog.userId }}</span>
</el-form-item>
<el-form-item label="新密码">
<el-input
v-model="pwdDialog.newPassword"
type="password"
show-password
placeholder="6-100 位"
maxlength="100"
/>
</el-form-item>
<el-form-item label="操作原因">
<el-input
v-model="pwdDialog.reason"
type="textarea"
:rows="2"
placeholder="必填,将记录操作日志"
maxlength="200"
/>
</el-form-item>
</el-form>
<div style="color: var(--el-color-warning); font-size: 12px; margin-top: -8px">
重置后该用户所有端登录态立即失效,需用新密码重新登录
</div>
<template #footer>
<el-button @click="pwdDialog.visible = false">取消</el-button>
<el-button type="primary" :loading="pwdDialog.submitting" @click="submitResetPwd">
确定
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { Key, Lock, Refresh, Search, Unlock, View } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { listUsers, resetUserPassword, setUserStatus } from '@/api/users'
import { askReason } from '@/utils/reason'
const STATUS_TAG = { 1: 'success', 2: 'danger', 3: 'info' }
const router = useRouter()
const loading = ref(false)
const rows = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const filters = reactive({ keyword: '', status: '' })
async function load() {
loading.value = true
try {
const data = await listUsers({
keyword: filters.keyword,
status: filters.status,
page: page.value,
page_size: pageSize.value,
})
rows.value = data.list || []
total.value = data.total || 0
} finally {
loading.value = false
}
}
function search() {
page.value = 1
load()
}
function reset() {
filters.keyword = ''
filters.status = ''
search()
}
function toDetail(row) {
router.push('/users/' + row.user_id)
}
async function toggleStatus(row, target) {
const isBan = target === 2
let reason
try {
reason = await askReason({
title: isBan ? '封禁用户' : '解封用户',
message: `${isBan ? '封禁' : '解封'}用户 ${row.nickname || ''} #${row.user_id}`,
})
} catch {
return
}
await setUserStatus(row.user_id, { status: target, reason })
ElMessage.success(isBan ? '已封禁' : '已解封')
load()
}
const pwdDialog = reactive({
visible: false,
userId: 0,
nickname: '',
newPassword: '',
reason: '',
submitting: false,
})
function openResetPwd(row) {
pwdDialog.userId = row.user_id
pwdDialog.nickname = row.nickname || ''
pwdDialog.newPassword = ''
pwdDialog.reason = ''
pwdDialog.visible = true
}
async function submitResetPwd() {
const pwd = pwdDialog.newPassword.trim()
const reason = pwdDialog.reason.trim()
if (pwd.length < 6 || pwd.length > 100) {
ElMessage.warning('新密码长度需为 6-100 位')
return
}
if (!reason) {
ElMessage.warning('请填写操作原因')
return
}
pwdDialog.submitting = true
try {
await resetUserPassword(pwdDialog.userId, { new_password: pwd, reason })
ElMessage.success('密码已重置,该用户全端登录态已失效')
pwdDialog.visible = false
} finally {
pwdDialog.submitting = false
}
}
onMounted(load)
</script>