修复仲裁退款并新增免押额度

This commit is contained in:
yml
2026-06-08 21:00:39 +08:00
parent 830cfcf33d
commit 0b419f5084
22 changed files with 559 additions and 72 deletions
@@ -10,6 +10,9 @@ export interface AdminUserItem {
realname_status: RealnameStatusValue
risk_status: RiskStatus
credit_score: number
deposit_free_quota: number
deposit_free_used: number
deposit_free_remaining: number
status: UserStatus
order_count: number
listing_count: number
@@ -40,3 +43,11 @@ export async function unfreezeAdminUser(id: number) {
const { data } = await apiClient.post<ApiResponse<AdminUserItem>>(`/admin/users/${id}/unfreeze`)
return data.data
}
export async function setAdminUserDepositFreeQuota(id: number, amount: number) {
const { data } = await apiClient.post<ApiResponse<AdminUserItem>>(
`/admin/users/${id}/deposit-free-quota`,
{ amount }
)
return data.data
}
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { arbitrateDispute, fetchAdminDisputes, type Dispute } from '@/features/disputes'
import { fetchAdminFileBlob } from '@/shared/api/files'
@@ -20,6 +20,13 @@ const amount = ref<number | undefined>()
const currentPage = ref(1)
const currentPageSize = ref(20)
const total = ref(0)
const isPartialRefund = computed(() => result.value === 'partial_refund')
const canSubmitArbitration = computed(() => {
if (submitting.value || !activeDispute.value) return false
if (!result.value || !remark.value.trim()) return false
if (isPartialRefund.value && (!amount.value || amount.value <= 0)) return false
return true
})
onMounted(loadDisputes)
@@ -79,11 +86,20 @@ async function openEvidence(url: string) {
async function handleArbitrate() {
if (!activeDispute.value) return
if (submitting.value) return
if (!remark.value.trim()) {
ElMessage.warning('请填写客服裁决说明')
return
}
if (isPartialRefund.value && (!amount.value || amount.value <= 0)) {
ElMessage.warning('请填写部分退款金额')
return
}
submitting.value = true
try {
await arbitrateDispute(activeDispute.value.id, {
result: result.value,
remark: remark.value,
remark: remark.value.trim(),
amount: amount.value,
})
ElMessage.success('仲裁结果已保存,双方已收到通知')
@@ -205,8 +221,12 @@ function readError(error: unknown, fallback: string) {
/>
</div>
<template #footer>
<el-button @click="activeDispute = null">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleArbitrate"
<el-button :disabled="submitting" @click="activeDispute = null">取消</el-button>
<el-button
type="primary"
:loading="submitting"
:disabled="!canSubmitArbitration"
@click="handleArbitrate"
>保存裁决</el-button
>
</template>
@@ -5,6 +5,7 @@ import { ref } from 'vue'
import {
fetchAdminUsers,
freezeAdminUser,
setAdminUserDepositFreeQuota,
unfreezeAdminUser,
type AdminUserItem,
} from '@/features/admin/api/adminUsers'
@@ -15,7 +16,9 @@ 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,
@@ -33,6 +36,26 @@ function openFreeze(row: AdminUserItem) {
freezeReason.value = ''
}
function openDepositQuota(row: AdminUserItem) {
quotaUser.value = row
quotaAmount.value = Number(row.deposit_free_quota || 0)
}
async function handleSetDepositQuota() {
if (!quotaUser.value) return
submitting.value = true
try {
await setAdminUserDepositFreeQuota(quotaUser.value.id, 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
@@ -68,6 +91,10 @@ function readError(error: unknown, fallback: string) {
}
return fallback
}
function money(value: number | string | undefined) {
return Number(value || 0).toFixed(2)
}
</script>
<template>
@@ -88,11 +115,21 @@ function readError(error: unknown, fallback: string) {
<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 }">¥{{ money(row.deposit_free_quota) }}</template>
</el-table-column>
<el-table-column label="已占用" width="120">
<template #default="{ row }">¥{{ money(row.deposit_free_used) }}</template>
</el-table-column>
<el-table-column label="剩余免押" width="120">
<template #default="{ row }">¥{{ money(row.deposit_free_remaining) }}</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="150">
<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"
@@ -145,5 +182,36 @@ function readError(error: unknown, fallback: string) {
<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>
已占用 ¥{{ money(quotaUser.deposit_free_used) }}剩余 ¥{{
money(quotaUser.deposit_free_remaining)
}}
</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>