feat: add admin risk actions and arbitration settlement
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { adminMarkListingAbnormal, adminOfflineListing, fetchAdminListing, type Listing } from '@/api/listings'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const listing = ref<Listing | null>(null)
|
||||
const actionType = ref<'offline' | 'abnormal' | ''>('')
|
||||
const reason = ref('')
|
||||
|
||||
const actionTitle = computed(() => (actionType.value === 'offline' ? '强制下架商品' : '标记商品异常'))
|
||||
const canOperate = computed(() => !!listing.value && listing.value.status !== 'rented' && !['offline', 'abnormal'].includes(listing.value.status))
|
||||
|
||||
onMounted(loadListing)
|
||||
|
||||
async function loadListing() {
|
||||
loading.value = true
|
||||
try {
|
||||
listing.value = await fetchAdminListing(String(route.params.id))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openAction(type: 'offline' | 'abnormal') {
|
||||
actionType.value = type
|
||||
reason.value = ''
|
||||
}
|
||||
|
||||
async function submitAction() {
|
||||
if (!listing.value || !actionType.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
if (actionType.value === 'offline') {
|
||||
listing.value = await adminOfflineListing(listing.value.id, reason.value)
|
||||
ElMessage.success('商品已强制下架')
|
||||
} else {
|
||||
listing.value = await adminMarkListingAbnormal(listing.value.id, reason.value)
|
||||
ElMessage.success('商品已标记异常')
|
||||
}
|
||||
actionType.value = ''
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '操作失败'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function money(value: number) {
|
||||
return `¥${Number(value || 0).toFixed(2)}`
|
||||
}
|
||||
|
||||
function readError(error: unknown, fallback: string) {
|
||||
if (typeof error === 'object' && error && 'response' in error) {
|
||||
const response = (error as { response?: { data?: { message?: string } } }).response
|
||||
return response?.data?.message || fallback
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page" v-loading="loading">
|
||||
<div v-if="listing" class="page-header-row">
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Listing #{{ listing.id }}</p>
|
||||
<h1>商品详情</h1>
|
||||
<p>{{ listing.title }} · {{ listing.server_region }} / {{ listing.login_platform }}</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<RouterLink to="/admin/listings">
|
||||
<el-button>返回列表</el-button>
|
||||
</RouterLink>
|
||||
<el-button type="warning" :disabled="!canOperate" @click="openAction('offline')">强制下架</el-button>
|
||||
<el-button type="danger" :disabled="!canOperate" @click="openAction('abnormal')">标记异常</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="listing" class="metric-grid dashboard-metrics">
|
||||
<div class="metric-card">
|
||||
<span>商品状态</span>
|
||||
<strong>{{ listing.status }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>审核状态</span>
|
||||
<strong>{{ listing.review_status }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>时租</span>
|
||||
<strong>{{ money(listing.price_hourly) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>押金</span>
|
||||
<strong>{{ money(listing.deposit_amount) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="listing" class="dashboard-panels">
|
||||
<div class="order-panel dashboard-panel">
|
||||
<h2>账号信息</h2>
|
||||
<p>账号 ID:{{ listing.account_id }}</p>
|
||||
<p>游戏:{{ listing.game_name }}</p>
|
||||
<p>区服:{{ listing.server_region }}</p>
|
||||
<p>平台:{{ listing.login_platform }}</p>
|
||||
<p>段位:{{ listing.rank_level || '-' }}</p>
|
||||
<p>哈夫币:{{ listing.haf_coin_amount }}</p>
|
||||
</div>
|
||||
|
||||
<div class="order-panel dashboard-panel">
|
||||
<h2>号主与租期</h2>
|
||||
<p>号主:{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</p>
|
||||
<p>号主 ID:{{ listing.owner_id }}</p>
|
||||
<p>最短租期:{{ listing.min_rent_hours }} 小时</p>
|
||||
<p>最长租期:{{ listing.max_rent_hours }} 小时</p>
|
||||
<p>日租:{{ money(listing.price_daily) }}</p>
|
||||
<p>周租:{{ money(listing.price_weekly) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="listing" class="order-panel dashboard-panel">
|
||||
<h2>说明与审核原因</h2>
|
||||
<p>{{ listing.description || '暂无商品说明' }}</p>
|
||||
<p>审核/后台原因:{{ listing.review_reason || '-' }}</p>
|
||||
<p>上架时间:{{ listing.published_at || '-' }}</p>
|
||||
<p>更新时间:{{ listing.updated_at }}</p>
|
||||
</div>
|
||||
|
||||
<el-dialog :model-value="!!actionType" :title="actionTitle" width="560px" @update:model-value="actionType = ''">
|
||||
<div v-if="listing" class="dialog-body">
|
||||
<p><strong>{{ listing.title }}</strong></p>
|
||||
<el-input v-model="reason" type="textarea" :rows="4" placeholder="填写后台操作原因,会写入审计日志并通知号主" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="actionType = ''">取消</el-button>
|
||||
<el-button type="danger" :loading="submitting" @click="submitAction">确认操作</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user