支持客服主动发起申诉
This commit is contained in:
@@ -103,6 +103,13 @@ function userPhone(row: Dispute, userID: number) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
function initiatorLabel(row: Dispute) {
|
||||
if (row.initiator_type === 'admin') {
|
||||
return row.initiator_admin_id ? `客服 ID ${row.initiator_admin_id}` : '客服'
|
||||
}
|
||||
return `${roleLabel(row, row.initiator_id)} ${userPhone(row, row.initiator_id)}`
|
||||
}
|
||||
|
||||
function evidenceCount(row: Dispute) {
|
||||
return evidenceItems(row).length
|
||||
}
|
||||
@@ -388,10 +395,7 @@ async function handleArbitrate() {
|
||||
<el-table-column label="双方信息" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<div class="party-cell">
|
||||
<span
|
||||
>申诉方:{{ roleLabel(row, row.initiator_id) }}
|
||||
{{ userPhone(row, row.initiator_id) }}</span
|
||||
>
|
||||
<span>申诉方:{{ initiatorLabel(row) }}</span>
|
||||
<span
|
||||
>被申诉方:{{ roleLabel(row, row.target_user_id) }}
|
||||
{{ userPhone(row, row.target_user_id) }}</span
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
type Order,
|
||||
type RefundStatus,
|
||||
} from '@/features/orders'
|
||||
import { adminCreateOrderDispute } from '@/features/disputes'
|
||||
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
|
||||
import {
|
||||
getSnapshotHafCoinM,
|
||||
@@ -76,6 +77,14 @@ const platformCheckoutCounterForm = ref({
|
||||
const offlineSettlementVisible = ref(false)
|
||||
const offlineSettlementRemark = ref('')
|
||||
const platformSubmitting = ref(false)
|
||||
const adminDisputeVisible = ref(false)
|
||||
const adminDisputeSubmitting = ref(false)
|
||||
const adminDisputeForm = ref({
|
||||
type: 'cannot_login',
|
||||
targetRole: 'renter',
|
||||
description: '',
|
||||
evidenceText: '',
|
||||
})
|
||||
|
||||
type FundSplitRow = {
|
||||
label: string
|
||||
@@ -144,6 +153,7 @@ const canPlatformCheckoutDispute = computed(
|
||||
() => platformCheckoutDisputeAction.value?.enabled === true
|
||||
)
|
||||
const canOfflineSettlement = computed(() => offlineSettlementAction.value?.enabled === true)
|
||||
const canAdminCreateDispute = computed(() => canOperate.value && !order.value?.active_dispute)
|
||||
const isPlatformManaged = computed(
|
||||
() =>
|
||||
order.value?.handoff_mode === 'platform' ||
|
||||
@@ -199,6 +209,16 @@ const depositReleaseActionTip = computed(() =>
|
||||
const refundActionTip =
|
||||
'仅发起后台人工原路退款,不关闭订单或调整商品/账号状态;押金已暂扣时仅退非暂扣部分。'
|
||||
const refundButtonDisabled = computed(() => refundStatus.value?.refund_status === 'refunded')
|
||||
const disputeTypeOptions = [
|
||||
{ label: '无法登录', value: 'cannot_login' },
|
||||
{ label: '描述不符', value: 'false_description' },
|
||||
{ label: '账号封禁', value: 'account_banned' },
|
||||
{ label: '资产损失', value: 'asset_loss' },
|
||||
{ label: '哈夫币争议', value: 'haf_coin_dispute' },
|
||||
{ label: '交接超时', value: 'handoff_timeout' },
|
||||
{ label: '归还超时', value: 'return_timeout' },
|
||||
{ label: '结账金额争议', value: 'checkout_amount' },
|
||||
]
|
||||
const orderTotalCent = computed(
|
||||
() => Number(order.value?.rent_amount_cent || 0) + Number(order.value?.deposit_amount_cent || 0)
|
||||
)
|
||||
@@ -493,6 +513,43 @@ async function submitPlatformCheckoutCounter() {
|
||||
}
|
||||
}
|
||||
|
||||
function openAdminDispute() {
|
||||
adminDisputeForm.value = {
|
||||
type: ['pending_checkout_confirm', 'pending_checkout_accept'].includes(order.value?.status || '')
|
||||
? 'checkout_amount'
|
||||
: 'cannot_login',
|
||||
targetRole: 'renter',
|
||||
description: '',
|
||||
evidenceText: '',
|
||||
}
|
||||
adminDisputeVisible.value = true
|
||||
}
|
||||
|
||||
async function submitAdminDispute() {
|
||||
if (!order.value) return
|
||||
const form = adminDisputeForm.value
|
||||
if (!form.description.trim()) {
|
||||
ElMessage.warning('请填写申诉说明')
|
||||
return
|
||||
}
|
||||
adminDisputeSubmitting.value = true
|
||||
try {
|
||||
await adminCreateOrderDispute(order.value.id, {
|
||||
type: form.type,
|
||||
target_role: form.targetRole,
|
||||
description: form.description.trim(),
|
||||
evidence_urls: linesToList(form.evidenceText),
|
||||
})
|
||||
ElMessage.success('申诉已发起,订单进入仲裁流程')
|
||||
adminDisputeVisible.value = false
|
||||
await loadOrder()
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '发起申诉失败'))
|
||||
} finally {
|
||||
adminDisputeSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openOfflineSettlement() {
|
||||
offlineSettlementRemark.value = ''
|
||||
offlineSettlementVisible.value = true
|
||||
@@ -706,10 +763,28 @@ function disputeTypeLabel(type: string) {
|
||||
handoff: '交接申诉',
|
||||
checkout: '结账争议',
|
||||
order: '订单争议',
|
||||
cannot_login: '无法登录',
|
||||
false_description: '描述不符',
|
||||
account_banned: '账号封禁',
|
||||
asset_loss: '资产损失',
|
||||
haf_coin_dispute: '哈夫币争议',
|
||||
handoff_timeout: '交接超时',
|
||||
return_timeout: '归还超时',
|
||||
checkout_amount: '结账金额争议',
|
||||
checkout_dispute: '结账争议',
|
||||
}
|
||||
return map[type] || type || '-'
|
||||
}
|
||||
|
||||
function disputeInitiatorLabel() {
|
||||
const active = order.value?.active_dispute
|
||||
if (!active) return '-'
|
||||
if (active.initiator_type === 'admin') {
|
||||
return active.initiator_admin_id ? `客服 ID ${active.initiator_admin_id}` : '客服'
|
||||
}
|
||||
return `用户 ID ${active.initiator_id}`
|
||||
}
|
||||
|
||||
function userDisplay(phone: string | undefined, id: number) {
|
||||
return phone ? `${phone} / ID ${id}` : `ID ${id}`
|
||||
}
|
||||
@@ -796,6 +871,14 @@ function paymentPaidAt(record: AdminPayment) {
|
||||
<el-button v-if="canOfflineSettlement" type="success" @click="openOfflineSettlement">
|
||||
{{ offlineSettlementAction?.label || '确认线下结算' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canAdminCreateDispute"
|
||||
type="warning"
|
||||
plain
|
||||
@click="openAdminDispute"
|
||||
>
|
||||
发起申诉
|
||||
</el-button>
|
||||
<el-button type="warning" :disabled="!canOperate" @click="openAction('abnormal')"
|
||||
>标记异常</el-button
|
||||
>
|
||||
@@ -1135,7 +1218,7 @@ function paymentPaidAt(record: AdminPayment) {
|
||||
</div>
|
||||
<div>
|
||||
<dt>发起人</dt>
|
||||
<dd>ID {{ order.active_dispute.initiator_id }}</dd>
|
||||
<dd>{{ disputeInitiatorLabel() }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>当前状态</dt>
|
||||
@@ -1297,6 +1380,61 @@ function paymentPaidAt(record: AdminPayment) {
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="adminDisputeVisible"
|
||||
title="发起订单申诉"
|
||||
width="620px"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="order" class="dialog-body">
|
||||
<p>
|
||||
<strong>{{ order.order_no }}</strong> · 商品编号 {{ listingCode }} · {{ order.title }}
|
||||
</p>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
<span>申诉类型</span>
|
||||
<el-select v-model="adminDisputeForm.type" placeholder="选择申诉类型">
|
||||
<el-option
|
||||
v-for="item in disputeTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</label>
|
||||
<label>
|
||||
<span>被申诉方</span>
|
||||
<el-select v-model="adminDisputeForm.targetRole" placeholder="选择被申诉方">
|
||||
<el-option label="租客" value="renter" />
|
||||
<el-option label="号主" value="owner" />
|
||||
</el-select>
|
||||
</label>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="adminDisputeForm.description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="填写申诉说明,会通知订单双方并进入仲裁中心"
|
||||
/>
|
||||
<el-input
|
||||
v-model="adminDisputeForm.evidenceText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="证据链接,一行一个,可留空"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="adminDisputeVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="adminDisputeSubmitting"
|
||||
@click="submitAdminDispute"
|
||||
>
|
||||
发起申诉
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="platformCheckoutCounterVisible"
|
||||
title="客服修改结账方案"
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface Dispute {
|
||||
owner_phone: string
|
||||
renter_phone: string
|
||||
initiator_id: number
|
||||
initiator_type?: 'user' | 'admin' | string
|
||||
initiator_admin_id?: number
|
||||
target_user_id: number
|
||||
type: string
|
||||
status: DisputeStatus
|
||||
@@ -49,6 +51,22 @@ export async function createDispute(
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function adminCreateOrderDispute(
|
||||
orderId: number,
|
||||
payload: {
|
||||
type: string
|
||||
description: string
|
||||
target_role: 'owner' | 'renter' | string
|
||||
evidence_urls?: string[]
|
||||
}
|
||||
) {
|
||||
const { data } = await apiClient.post<ApiResponse<Dispute>>(
|
||||
`/admin/orders/${orderId}/dispute`,
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function cancelOrderDispute(orderId: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<Dispute>>(`/orders/${orderId}/dispute/cancel`)
|
||||
return data.data
|
||||
|
||||
@@ -74,6 +74,8 @@ export interface AdminAction {
|
||||
export interface ActiveDispute {
|
||||
id: number
|
||||
initiator_id: number
|
||||
initiator_type?: 'user' | 'admin' | string
|
||||
initiator_admin_id?: number
|
||||
type: string
|
||||
status: string
|
||||
}
|
||||
|
||||
@@ -224,6 +224,7 @@ export function useOrderActions(options: UseOrderActionsOptions) {
|
||||
!!order.value &&
|
||||
(isOwner.value || isRenter.value) &&
|
||||
['disputing', 'checkout_disputing'].includes(order.value.status) &&
|
||||
order.value.active_dispute?.initiator_type !== 'admin' &&
|
||||
order.value.active_dispute?.initiator_id === session.userId
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user