feat(quality-owner): 增加优质号主自助取消与权益校验

This commit is contained in:
yml2213
2026-11-04 18:54:44 +08:00
parent e9102026ae
commit c00d691301
11 changed files with 396 additions and 49 deletions
@@ -327,7 +327,7 @@ async function handleToggleQualityOwner(row: AdminUserItem) {
const { value } = await ElMessageBox.prompt(
enabling
? '后台手动开通将直接发放优质号主资格并跳过保证金缴纳,请填写原因。'
: '取消后用户将无法继续发布到极品专区,已发布的极品商品会下架;用户自缴保证金将退回可用余额。',
: '取消前需确保极品专区没有进行中的账号,否则操作会被拒绝;取消后用户自缴保证金将退回可用余额。',
enabling ? '设置优质号主' : '取消优质号主',
{
inputPlaceholder: '请填写 2-200 字操作原因',
@@ -20,6 +20,7 @@ import { setPassword } from '@/features/auth/api/auth'
import { fetchUserInvitationSummary } from '@/features/invitation/api/invitation'
import {
activateQualityOwner,
cancelQualityOwner,
fetchQualityOwnerStatus,
queryQualityOwnerPayment,
startQualityOwnerPayment,
@@ -48,6 +49,7 @@ const availableBalanceCent = ref(0)
const qualityOwnerStatus = ref<QualityOwnerStatus | null>(null)
const qualityOwnerLoading = ref(false)
const qualityOwnerOnlineLoading = ref(false)
const qualityOwnerCancelling = ref(false)
const qualityOwnerPayWaySelect = useMobilePayWaySelect()
const qualityOwnerCashier = useMobilePaymentCashier({
queryPayment: () => queryQualityOwnerPayment(),
@@ -215,6 +217,48 @@ async function handleQualityOwnerPaid() {
await loadBalance()
}
async function handleCancelQualityOwner() {
const status = qualityOwnerStatus.value
if (!status?.active) return
const activeListings = status.active_premium_listing_count || 0
if (activeListings > 0) {
showToast({
message: `极品专区还有 ${activeListings} 个进行中账号,请先处理`,
icon: 'warning-o',
})
return
}
const depositHint =
status.deposit_source === 'paid' && status.deposit_amount_cent > 0
? `保证金 ¥${formatCent(status.deposit_amount_cent)} 将退回可用余额。`
: '该资格由后台开通,无保证金退回。'
try {
await showDialog({
title: '取消优质号主',
message: `取消后将立即失去极品专区发布权限。${depositHint}`,
showCancelButton: true,
confirmButtonText: '确认取消',
cancelButtonText: '暂不取消',
})
} catch {
return
}
qualityOwnerCancelling.value = true
try {
qualityOwnerStatus.value = await cancelQualityOwner()
await session.loadMe()
await loadBalance()
showToast({ message: '优质号主已取消', icon: 'passed' })
} catch (error) {
showToast({
message: readError(error, '取消优质号主失败,请先处理极品专区进行中账号'),
icon: 'cross',
})
} finally {
qualityOwnerCancelling.value = false
}
}
async function loadBalance() {
try {
const wallet = await fetchWalletBalance()
@@ -510,7 +554,20 @@ function resolveAvatarURL(url: string | undefined | null) {
>
</div>
</div>
<div v-if="!qualityOwnerStatus?.active" class="quality-owner-actions">
<div v-if="qualityOwnerStatus?.active" class="quality-owner-actions">
<van-button
block
round
plain
type="danger"
:loading="qualityOwnerCancelling"
:disabled="qualityOwnerStatus.active_premium_listing_count > 0"
@click="handleCancelQualityOwner"
>
取消优质号主
</van-button>
</div>
<div v-else class="quality-owner-actions">
<van-button
block
round
@@ -536,12 +593,15 @@ function resolveAvatarURL(url: string | undefined | null) {
微信 / 支付宝支付开通
</van-button>
</div>
<p v-else>
{{
qualityOwnerStatus?.deposit_source === 'manual'
? '已由后台开通优质号主。'
: '保证金已冻结,取消优质号主资格时退回可用余额。'
}}
<p v-if="qualityOwnerStatus?.active">
<template v-if="qualityOwnerStatus.active_premium_listing_count > 0">
极品专区还有 {{ qualityOwnerStatus.active_premium_listing_count }}
个进行中账号,请先下架或完成订单后再取消。
</template>
<template v-else-if="qualityOwnerStatus.deposit_source === 'manual'">
已由后台开通优质号主,可随时取消。
</template>
<template v-else> 保证金已冻结,取消后可退回可用余额。 </template>
</p>
</section>
@@ -23,6 +23,7 @@ import { realnameStatusLabel } from '@/shared/utils/statusLabels'
import { setPassword } from '@/features/auth/api/auth'
import {
activateQualityOwner,
cancelQualityOwner,
fetchQualityOwnerStatus,
queryQualityOwnerPayment,
startQualityOwnerPayment,
@@ -38,6 +39,7 @@ const router = useRouter()
const saving = ref(false)
const qualityOwnerLoading = ref(false)
const qualityOwnerOnlineLoading = ref(false)
const qualityOwnerCancelling = ref(false)
const qualityOwnerPayWayVisible = ref(false)
const qualityOwnerStatus = ref<QualityOwnerStatus | null>(null)
const uploadingAvatar = ref(false)
@@ -154,6 +156,39 @@ async function handleQualityOwnerPaymentSucceeded() {
await session.loadMe()
}
async function handleCancelQualityOwner() {
const status = qualityOwnerStatus.value
if (!status?.active) return
const activeListings = status.active_premium_listing_count || 0
if (activeListings > 0) {
ElMessage.warning(`极品专区还有 ${activeListings} 个进行中账号,请先下架或完成订单`)
return
}
const depositHint =
status.deposit_source === 'paid' && status.deposit_amount_cent > 0
? `保证金 ¥${formatCent(status.deposit_amount_cent)} 将退回可用余额。`
: '该资格由后台开通,无保证金退回。'
try {
await ElMessageBox.confirm(`取消后将立即失去极品专区发布权限,${depositHint}`, '取消优质号主', {
type: 'warning',
confirmButtonText: '确认取消',
cancelButtonText: '暂不取消',
})
} catch {
return
}
qualityOwnerCancelling.value = true
try {
qualityOwnerStatus.value = await cancelQualityOwner()
await session.loadMe()
ElMessage.success('优质号主已取消,极品专区权限已关闭')
} catch (error) {
ElMessage.error(readError(error, '取消优质号主失败,请先处理极品专区进行中账号'))
} finally {
qualityOwnerCancelling.value = false
}
}
function resetForm() {
form.nickname = session.nickname || session.displayName
form.avatar_url = session.avatarUrl || ''
@@ -439,11 +474,28 @@ async function savePassword() {
</div>
<div class="quality-owner-actions">
<span v-if="qualityOwnerStatus?.active">
<template v-if="qualityOwnerStatus.deposit_source === 'manual'">已由后台开通</template>
<template v-if="qualityOwnerStatus.active_premium_listing_count > 0">
极品专区还有 {{ qualityOwnerStatus.active_premium_listing_count }}
个进行中账号,请先处理
</template>
<template v-else-if="qualityOwnerStatus.deposit_source === 'manual'"
>已由后台开通</template
>
<template v-else>保证金已冻结</template>
</span>
<span v-else>保证金取消资格时原路退回余额。</span>
<div v-if="!qualityOwnerStatus?.active" class="quality-owner-action-buttons">
<div v-if="qualityOwnerStatus?.active" class="quality-owner-action-buttons">
<el-button
type="danger"
plain
:loading="qualityOwnerCancelling"
:disabled="qualityOwnerStatus.active_premium_listing_count > 0"
@click="handleCancelQualityOwner"
>
取消优质号主
</el-button>
</div>
<div v-else class="quality-owner-action-buttons">
<el-button
type="primary"
:loading="qualityOwnerLoading"
@@ -6,6 +6,8 @@ export interface QualityOwnerStatus {
user_id: number
active: boolean
status: 'none' | 'active' | string
can_cancel: boolean
active_premium_listing_count: number
deposit_amount_cent: number
deposit_source: 'paid' | 'manual' | '' | string
activated_at?: string
@@ -25,6 +27,11 @@ export async function activateQualityOwner() {
return data.data
}
export async function cancelQualityOwner() {
const { data } = await apiClient.post<ApiResponse<QualityOwnerStatus>>('/quality-owner/cancel')
return data.data
}
export async function startQualityOwnerPayment(payWay: PaymentPayWay) {
const { data } = await apiClient.post<ApiResponse<PaymentOrder>>('/quality-owner/payment', {
pay_way: payWay,
+1
View File
@@ -30,6 +30,7 @@ export const AUDIT_ACTION_OPTIONS: AuditOption[] = [
{ value: 'admin_user.wallet_adjust', label: '调整用户余额', group: '用户', highRisk: true },
{ value: 'admin_user.set_quality_owner', label: '设置优质号主', group: '用户', highRisk: true },
{ value: 'quality_owner.activate', label: '缴纳优质号主保证金', group: '用户', highRisk: true },
{ value: 'quality_owner.cancel', label: '取消优质号主', group: '用户', highRisk: true },
// 商品
{ value: 'listing.admin_offline', label: '后台下架商品', group: '商品', highRisk: true },
{ value: 'listing.mark_abnormal', label: '商品标记异常', group: '商品', highRisk: true },