Files
hfb_sys/frontend/src/views/admin/AdminListingDetailView.vue
T

179 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { fetchAdminFileBlob } from '@/api/files'
import { adminMarkListingAbnormal, adminOfflineListing, fetchAdminListing, type Listing } from '@/api/listings'
import { listingReviewStatusLabel, listingStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time'
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 listingPrice(row: Listing) {
return money(row.price || row.price_daily || row.price_hourly)
}
function extractObjectKey(url: string) {
try {
const parsed = new URL(url, window.location.origin)
return parsed.searchParams.get('key') || url
} catch {
return url
}
}
async function openScreenshot(url: string) {
try {
const blob = await fetchAdminFileBlob(extractObjectKey(url))
window.open(URL.createObjectURL(blob), '_blank')
} catch {
window.open(url, '_blank')
}
}
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>{{ listingStatusLabel(listing.status) }}</strong>
</div>
<div class="metric-card">
<span>审核状态</span>
<strong>{{ listingReviewStatusLabel(listing.review_status) }}</strong>
</div>
<div class="metric-card">
<span>价格</span>
<strong>{{ listingPrice(listing) }}</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>
<p>资产截图{{ listing.screenshot_urls?.length || 0 }} </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>价格{{ listingPrice(listing) }}</p>
<p>押金{{ money(listing.deposit_amount) }}</p>
</div>
</div>
<div v-if="listing" class="order-panel dashboard-panel">
<h2>说明与审核原因</h2>
<p>{{ listing.description || '暂无商品说明' }}</p>
<p>审核/后台原因{{ listing.review_reason || '-' }}</p>
<p>上架时间{{ formatDateTime(listing.published_at) }}</p>
<p>更新时间{{ formatDateTime(listing.updated_at) }}</p>
</div>
<div v-if="listing" class="order-panel dashboard-panel">
<h2>资产截图</h2>
<div v-if="listing.screenshot_urls?.length" class="evidence-list">
<div v-for="url in listing.screenshot_urls" :key="url" class="evidence-row">
<span>{{ url }}</span>
<el-button size="small" @click="openScreenshot(url)">打开</el-button>
</div>
</div>
<p v-else>暂无截图</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>