39 个文件按 .prettierrc.json (printWidth 100 / no semi / singleQuote) 统一格式, 纯空白与换行调整, 无逻辑变更。为接入代码门禁做准备, 让 npm run check (format:check + lint:strict + typecheck) 干净通过。
212 lines
6.4 KiB
Vue
212 lines
6.4 KiB
Vue
<script setup lang="ts">
|
||
import { readError } from '@/shared/utils/error'
|
||
import { ElMessage } from 'element-plus'
|
||
import { ref } from 'vue'
|
||
|
||
import {
|
||
fetchAdminUsers,
|
||
freezeAdminUser,
|
||
setAdminUserDepositFreeQuota,
|
||
unfreezeAdminUser,
|
||
type AdminUserItem,
|
||
} from '@/features/admin/api/adminUsers'
|
||
import { centToYuan, formatCent, yuanToCent } from '@/shared/utils/money'
|
||
import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable'
|
||
import { userStatusLabel } from '@/shared/utils/statusLabels'
|
||
import { formatDateTime } from '@/shared/utils/time'
|
||
import AdminTablePagination from '../components/AdminTablePagination.vue'
|
||
|
||
const submitting = ref(false)
|
||
const activeUser = ref<AdminUserItem | null>(null)
|
||
const quotaUser = ref<AdminUserItem | null>(null)
|
||
const freezeReason = ref('')
|
||
const quotaAmount = ref(0)
|
||
|
||
const {
|
||
loading,
|
||
data: users,
|
||
total,
|
||
currentPage,
|
||
currentPageSize,
|
||
load: loadUsers,
|
||
} = useAdminPaginatedTable<AdminUserItem>({
|
||
fetchFn: fetchAdminUsers,
|
||
})
|
||
|
||
function openFreeze(row: AdminUserItem) {
|
||
activeUser.value = row
|
||
freezeReason.value = ''
|
||
}
|
||
|
||
function openDepositQuota(row: AdminUserItem) {
|
||
quotaUser.value = row
|
||
quotaAmount.value = centToYuan(row.deposit_free_quota_cent)
|
||
}
|
||
|
||
async function handleSetDepositQuota() {
|
||
if (!quotaUser.value) return
|
||
submitting.value = true
|
||
try {
|
||
await setAdminUserDepositFreeQuota(quotaUser.value.id, yuanToCent(quotaAmount.value))
|
||
ElMessage.success('免押额度已更新')
|
||
quotaUser.value = null
|
||
await loadUsers()
|
||
} catch (error) {
|
||
ElMessage.error(readError(error, '设置免押额度失败'))
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
async function handleFreeze() {
|
||
if (!activeUser.value) return
|
||
submitting.value = true
|
||
try {
|
||
await freezeAdminUser(activeUser.value.id, freezeReason.value)
|
||
ElMessage.success('用户已冻结')
|
||
activeUser.value = null
|
||
await loadUsers()
|
||
} catch (error) {
|
||
ElMessage.error(readError(error, '冻结失败'))
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
async function handleUnfreeze(row: AdminUserItem) {
|
||
submitting.value = true
|
||
try {
|
||
await unfreezeAdminUser(row.id)
|
||
ElMessage.success('用户已解冻')
|
||
await loadUsers()
|
||
} catch (error) {
|
||
ElMessage.error(readError(error, '解冻失败'))
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
function moneyCent(value: number | string | undefined) {
|
||
return formatCent(Number(value || 0))
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<section class="page">
|
||
<div class="page-header-row">
|
||
<div class="page-header">
|
||
<p class="eyebrow">Users</p>
|
||
<h1>用户管理</h1>
|
||
<p>查看用户信息和状态,处理冻结与解冻。</p>
|
||
</div>
|
||
<el-button @click="loadUsers">刷新</el-button>
|
||
</div>
|
||
|
||
<el-table v-loading="loading" class="table-panel" :data="users">
|
||
<el-table-column prop="id" label="用户ID" width="80" />
|
||
<el-table-column prop="nickname" label="昵称" min-width="130" />
|
||
<el-table-column prop="phone" label="手机号" min-width="130" />
|
||
<el-table-column label="状态" width="110">
|
||
<template #default="{ row }">{{ userStatusLabel(row.status) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="免押额度" width="130">
|
||
<template #default="{ row }">¥{{ moneyCent(row.deposit_free_quota_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="已占用" width="120">
|
||
<template #default="{ row }">¥{{ moneyCent(row.deposit_free_used_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="剩余免押" width="120">
|
||
<template #default="{ row }">¥{{ moneyCent(row.deposit_free_remaining_cent) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="注册时间" min-width="180">
|
||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="220">
|
||
<template #default="{ row }">
|
||
<el-button size="small" @click="openDepositQuota(row)">免押</el-button>
|
||
<el-button
|
||
v-if="row.status === 'active'"
|
||
size="small"
|
||
type="danger"
|
||
:loading="submitting"
|
||
@click="openFreeze(row)"
|
||
>
|
||
冻结
|
||
</el-button>
|
||
<el-button
|
||
v-else
|
||
size="small"
|
||
type="primary"
|
||
:loading="submitting"
|
||
@click="handleUnfreeze(row)"
|
||
>解冻</el-button
|
||
>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<AdminTablePagination
|
||
v-if="total > 0"
|
||
v-model:current-page="currentPage"
|
||
v-model:page-size="currentPageSize"
|
||
:total="total"
|
||
:loading="loading"
|
||
@page-change="loadUsers"
|
||
/>
|
||
|
||
<el-dialog
|
||
:model-value="!!activeUser"
|
||
title="冻结用户"
|
||
width="560px"
|
||
@update:model-value="activeUser = null"
|
||
>
|
||
<div v-if="activeUser" class="dialog-body">
|
||
<p>
|
||
<strong>{{ activeUser.phone }}</strong> · {{ activeUser.nickname }}
|
||
</p>
|
||
<el-input
|
||
v-model="freezeReason"
|
||
type="textarea"
|
||
:rows="4"
|
||
placeholder="填写冻结原因,便于审计追踪"
|
||
/>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="activeUser = null">取消</el-button>
|
||
<el-button type="danger" :loading="submitting" @click="handleFreeze">确认冻结</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:model-value="!!quotaUser"
|
||
title="设置免押额度"
|
||
width="520px"
|
||
@update:model-value="quotaUser = null"
|
||
>
|
||
<div v-if="quotaUser" class="dialog-body">
|
||
<p>
|
||
<strong>{{ quotaUser.phone }}</strong> · {{ quotaUser.nickname }}
|
||
</p>
|
||
<p>
|
||
已占用 ¥{{ moneyCent(quotaUser.deposit_free_used_cent) }},剩余 ¥{{
|
||
moneyCent(quotaUser.deposit_free_remaining_cent)
|
||
}}
|
||
</p>
|
||
<el-input-number
|
||
v-model="quotaAmount"
|
||
class="full-control"
|
||
:min="0"
|
||
:precision="0"
|
||
:step="100"
|
||
/>
|
||
</div>
|
||
<template #footer>
|
||
<el-button :disabled="submitting" @click="quotaUser = null">取消</el-button>
|
||
<el-button type="primary" :loading="submitting" @click="handleSetDepositQuota"
|
||
>保存额度</el-button
|
||
>
|
||
</template>
|
||
</el-dialog>
|
||
</section>
|
||
</template>
|