211 lines
5.7 KiB
JavaScript
211 lines
5.7 KiB
JavaScript
// Author: fruit.com.cn
|
|
|
|
import { onMounted, reactive, ref } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import {
|
|
getAppUserApi,
|
|
listAppUserBanRecordsApi,
|
|
listAppUserWatchHistoryApi,
|
|
pageAppUsersApi,
|
|
updateAppUserStatusApi
|
|
} from '@/api/admin/appUser'
|
|
|
|
/** 账号状态与展示文案,与后端 AppUserStatus 保持一致 */
|
|
export const APP_USER_STATUS = [
|
|
{ value: 'normal', label: '正常', tag: 'success' },
|
|
{ value: 'limited', label: '受限', tag: 'warning' },
|
|
{ value: 'banned', label: '封禁', tag: 'danger' },
|
|
{ value: 'deactivated', label: '已注销', tag: 'info' }
|
|
]
|
|
|
|
export const COUNTRY_OPTIONS = [
|
|
{ value: 'ID', label: '印度尼西亚' },
|
|
{ value: 'SG', label: '新加坡' },
|
|
{ value: 'MY', label: '马来西亚' },
|
|
{ value: 'PH', label: '菲律宾' },
|
|
{ value: 'TH', label: '泰国' },
|
|
{ value: 'VN', label: '越南' },
|
|
{ value: 'CN', label: '中国' }
|
|
]
|
|
|
|
export function statusMeta(status) {
|
|
return (
|
|
APP_USER_STATUS.find((item) => item.value === status) || { label: status || '-', tag: 'info' }
|
|
)
|
|
}
|
|
|
|
const PLATFORM_TEXT = { ANDROID: 'Android', IOS: 'iOS', WEB: 'Web' }
|
|
|
|
/** 登录会话上的系统,例如 Android 16。没有记录时显示横线。 */
|
|
export function systemText(row) {
|
|
const name = PLATFORM_TEXT[row?.platform] || row?.platform || ''
|
|
const version = row?.osVersion || ''
|
|
const text = `${name} ${version}`.trim()
|
|
return text || '—'
|
|
}
|
|
|
|
/** 毫秒转 mm:ss,观看流水里的进度用 */
|
|
export function formatDuration(ms) {
|
|
const total = Math.max(0, Math.floor((ms || 0) / 1000))
|
|
const minute = String(Math.floor(total / 60)).padStart(2, '0')
|
|
const second = String(total % 60).padStart(2, '0')
|
|
return `${minute}:${second}`
|
|
}
|
|
|
|
export function useAppUsers() {
|
|
const users = ref([])
|
|
const total = ref(0)
|
|
const loading = ref(false)
|
|
|
|
const keyword = ref('')
|
|
const statusFilter = ref(undefined)
|
|
const countryFilter = ref(undefined)
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(10)
|
|
|
|
const detailVisible = ref(false)
|
|
const detailLoading = ref(false)
|
|
const detail = ref(null)
|
|
const banRecords = ref([])
|
|
const watchHistory = ref([])
|
|
|
|
const statusDialogVisible = ref(false)
|
|
const statusSaving = ref(false)
|
|
const statusTarget = ref(null)
|
|
const statusForm = reactive({ status: 'banned', reason: '' })
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
const res = await pageAppUsersApi({
|
|
pageIndex: currentPage.value,
|
|
pageSize: pageSize.value,
|
|
keyword: keyword.value.trim() || undefined,
|
|
status: statusFilter.value,
|
|
countryCode: countryFilter.value
|
|
})
|
|
users.value = res.data?.list || []
|
|
total.value = res.data?.total || 0
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function queryUsers() {
|
|
currentPage.value = 1
|
|
load()
|
|
}
|
|
|
|
function resetFilters() {
|
|
keyword.value = ''
|
|
statusFilter.value = undefined
|
|
countryFilter.value = undefined
|
|
queryUsers()
|
|
}
|
|
|
|
function handleSizeChange() {
|
|
currentPage.value = 1
|
|
load()
|
|
}
|
|
|
|
const detailTabs = reactive({ active: 'watch' })
|
|
|
|
async function openDetail(row) {
|
|
detailVisible.value = true
|
|
detailLoading.value = true
|
|
detail.value = row
|
|
banRecords.value = []
|
|
watchHistory.value = []
|
|
detailTabs.active = 'watch'
|
|
try {
|
|
// 三个请求互不依赖,并发拉取,抽屉打开更快
|
|
const [profile, bans, history] = await Promise.all([
|
|
getAppUserApi(row.id),
|
|
listAppUserBanRecordsApi(row.id),
|
|
listAppUserWatchHistoryApi(row.id, 50)
|
|
])
|
|
detail.value = profile.data || row
|
|
banRecords.value = bans.data || []
|
|
watchHistory.value = history.data || []
|
|
} finally {
|
|
detailLoading.value = false
|
|
}
|
|
}
|
|
|
|
function openStatusDialog(row, preset = 'banned') {
|
|
statusTarget.value = row
|
|
statusForm.status = preset
|
|
statusForm.reason = ''
|
|
statusDialogVisible.value = true
|
|
}
|
|
|
|
async function confirmStatus() {
|
|
const row = statusTarget.value
|
|
if (!row) return
|
|
const label = statusMeta(statusForm.status).label
|
|
statusSaving.value = true
|
|
try {
|
|
await updateAppUserStatusApi(row.id, statusForm.status, statusForm.reason.trim() || undefined)
|
|
ElMessage.success(`已${label},该用户全部登录会话已失效`)
|
|
statusDialogVisible.value = false
|
|
await load()
|
|
if (detailVisible.value && detail.value?.id === row.id) {
|
|
detail.value = { ...detail.value, status: statusForm.status }
|
|
banRecords.value = (await listAppUserBanRecordsApi(row.id)).data || []
|
|
}
|
|
} finally {
|
|
statusSaving.value = false
|
|
}
|
|
}
|
|
|
|
/** 恢复正常:不需要填原因,但仍要二次确认,避免误点 */
|
|
async function restoreUser(row) {
|
|
try {
|
|
await ElMessageBox.confirm(
|
|
`确认把「${row.nickname || row.uid}」恢复为正常账号?`,
|
|
'恢复正常',
|
|
{ confirmButtonText: '确认恢复', cancelButtonText: '取消', type: 'warning' }
|
|
)
|
|
} catch {
|
|
return
|
|
}
|
|
await updateAppUserStatusApi(row.id, 'normal')
|
|
ElMessage.success('账号已恢复正常')
|
|
await load()
|
|
if (detailVisible.value && detail.value?.id === row.id) {
|
|
detail.value = { ...detail.value, status: 'normal' }
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
|
|
return {
|
|
users,
|
|
total,
|
|
loading,
|
|
keyword,
|
|
statusFilter,
|
|
countryFilter,
|
|
currentPage,
|
|
pageSize,
|
|
detailVisible,
|
|
detailLoading,
|
|
detail,
|
|
banRecords,
|
|
watchHistory,
|
|
detailTabs,
|
|
statusDialogVisible,
|
|
statusSaving,
|
|
statusTarget,
|
|
statusForm,
|
|
load,
|
|
queryUsers,
|
|
resetFilters,
|
|
handleSizeChange,
|
|
openDetail,
|
|
openStatusDialog,
|
|
confirmStatus,
|
|
restoreUser
|
|
}
|
|
}
|