// 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 } }