282 lines
7.3 KiB
JavaScript
282 lines
7.3 KiB
JavaScript
// Author: fruit.com.cn
|
|
|
|
import { onMounted, reactive, ref } from 'vue'
|
|
import dayjs from 'dayjs'
|
|
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: '中国' }
|
|
]
|
|
|
|
/** 国家码 → 中文名,与后端 CountryProfile 保持一致;未收录的仍显示原码 */
|
|
const COUNTRY_TEXT = {
|
|
ID: '印度尼西亚',
|
|
MY: '马来西亚',
|
|
SG: '新加坡',
|
|
TH: '泰国',
|
|
VN: '越南',
|
|
PH: '菲律宾',
|
|
KH: '柬埔寨',
|
|
MM: '缅甸',
|
|
LA: '老挝',
|
|
BN: '文莱',
|
|
CN: '中国',
|
|
TW: '中国台湾',
|
|
HK: '中国香港',
|
|
MO: '中国澳门',
|
|
JP: '日本',
|
|
KR: '韩国',
|
|
IN: '印度',
|
|
PK: '巴基斯坦',
|
|
BD: '孟加拉国',
|
|
LK: '斯里兰卡',
|
|
NP: '尼泊尔',
|
|
US: '美国',
|
|
CA: '加拿大',
|
|
MX: '墨西哥',
|
|
BR: '巴西',
|
|
AR: '阿根廷',
|
|
CL: '智利',
|
|
CO: '哥伦比亚',
|
|
PE: '秘鲁',
|
|
VE: '委内瑞拉',
|
|
EC: '厄瓜多尔',
|
|
GT: '危地马拉',
|
|
DO: '多米尼加',
|
|
GB: '英国',
|
|
DE: '德国',
|
|
FR: '法国',
|
|
ES: '西班牙',
|
|
IT: '意大利',
|
|
NL: '荷兰',
|
|
PL: '波兰',
|
|
RU: '俄罗斯',
|
|
UA: '乌克兰',
|
|
TR: '土耳其',
|
|
SA: '沙特阿拉伯',
|
|
AE: '阿联酋',
|
|
IL: '以色列',
|
|
EG: '埃及',
|
|
NG: '尼日利亚',
|
|
ZA: '南非',
|
|
KE: '肯尼亚',
|
|
MA: '摩洛哥',
|
|
AU: '澳大利亚',
|
|
NZ: '新西兰'
|
|
}
|
|
|
|
/** 国家列统一展示中文名,无值时显示横线 */
|
|
export function countryText(code) {
|
|
const value = (code || '').toUpperCase()
|
|
return COUNTRY_TEXT[value] || value || '—'
|
|
}
|
|
|
|
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' }
|
|
|
|
/** 客户端平台,只有 iOS / Android 两种取值(缺记录显示横线)。 */
|
|
export function platformText(row) {
|
|
const platform = row?.platform || ''
|
|
return PLATFORM_TEXT[platform] || platform || '—'
|
|
}
|
|
|
|
/** 后端返回 ISO 时间串,统一格式化成后台可读的本地时间 */
|
|
export function formatDateTime(value) {
|
|
return value && dayjs(value).isValid() ? dayjs(value).format('YYYY-MM-DD HH:mm:ss') : '—'
|
|
}
|
|
|
|
/** 毫秒转 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 platformFilter = 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,
|
|
platform: platformFilter.value || undefined
|
|
})
|
|
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
|
|
platformFilter.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,
|
|
platformFilter,
|
|
currentPage,
|
|
pageSize,
|
|
detailVisible,
|
|
detailLoading,
|
|
detail,
|
|
banRecords,
|
|
watchHistory,
|
|
detailTabs,
|
|
statusDialogVisible,
|
|
statusSaving,
|
|
statusTarget,
|
|
statusForm,
|
|
load,
|
|
queryUsers,
|
|
resetFilters,
|
|
handleSizeChange,
|
|
openDetail,
|
|
openStatusDialog,
|
|
confirmStatus,
|
|
restoreUser
|
|
}
|
|
}
|