feat: P3阶段完成 - 全部模块迁移完成 🎉
## P3.1: 争议仲裁模块(disputes)✅ - API: disputes.ts - 模块导出 ## P3.2: 卖家中心模块(seller)✅ - Views: 4个页面 - Composables: usePublishForm, usePublishDraft - 模块导出 ## P3.3: 管理后台模块(admin)✅ - API: 8个文件(adminAuth, adminDashboard, adminUsers等) - Views: 15个管理页面 - Composables: useAdminTable, useAdminPaginatedTable - Components: 管理端组件 - 模块导出 --- ## 🎉 Features 架构迁移全部完成! ### 最终统计 - ✅ P0: shared(基础设施)- 22个文件 - ✅ P1: wallet, chats, orders - 24个文件 - ✅ P2: listings, auth - 35个文件 - ✅ P3: seller, disputes, admin - 47个文件 **总计:** 9个模块,128个文件完成迁移 ### 新架构 ``` frontend/src/ ├── features/ # 9个业务模块 ✅ │ ├── wallet/ ✅ │ ├── chats/ ✅ │ ├── orders/ ✅ (已重构) │ ├── listings/ ✅ │ ├── auth/ ✅ │ ├── seller/ ✅ │ ├── disputes/ ✅ │ └── admin/ ✅ └── shared/ ✅ ``` 下一步:清理旧文件、更新路由配置 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
3534cffce1
commit
c9397635e2
@@ -0,0 +1,202 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { adminCloseOrder, adminMarkOrderAbnormal, adminRefundOrder, adminRefundStatus, fetchAdminHandoffRecords, fetchAdminOrder, type HandoffRecord, type Order, type RefundStatus } from '@/api/orders'
|
||||
import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const order = ref<Order | null>(null)
|
||||
const handoffRecords = ref<HandoffRecord[]>([])
|
||||
const actionType = ref<'close' | 'abnormal' | ''>('')
|
||||
const reason = ref('')
|
||||
const refundStatus = ref<RefundStatus | null>(null)
|
||||
const refunding = ref(false)
|
||||
|
||||
const snapshotText = computed(() => JSON.stringify(order.value?.account_snapshot || {}, null, 2))
|
||||
const actionTitle = computed(() => (actionType.value === 'close' ? '客服关闭订单' : '标记订单异常'))
|
||||
const canOperate = computed(() => !!order.value && !['completed', 'cancelled', 'closed'].includes(order.value.status))
|
||||
|
||||
onMounted(loadOrder)
|
||||
|
||||
async function loadOrder() {
|
||||
loading.value = true
|
||||
try {
|
||||
order.value = await fetchAdminOrder(String(route.params.id))
|
||||
handoffRecords.value = await fetchAdminHandoffRecords(String(route.params.id))
|
||||
await loadRefundStatus()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRefundStatus() {
|
||||
try {
|
||||
refundStatus.value = await adminRefundStatus(Number(route.params.id))
|
||||
} catch {
|
||||
refundStatus.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function openAction(type: 'close' | 'abnormal') {
|
||||
actionType.value = type
|
||||
reason.value = ''
|
||||
}
|
||||
|
||||
async function submitAction() {
|
||||
if (!order.value || !actionType.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
if (actionType.value === 'close') {
|
||||
await adminCloseOrder(order.value.id, reason.value)
|
||||
ElMessage.success('订单已关闭')
|
||||
} else {
|
||||
await adminMarkOrderAbnormal(order.value.id, reason.value)
|
||||
ElMessage.success('订单已标记异常')
|
||||
}
|
||||
actionType.value = ''
|
||||
await loadOrder()
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '操作失败'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function orderRentedAt() {
|
||||
return order.value?.rented_at
|
||||
}
|
||||
|
||||
function orderEstimatedEndAt() {
|
||||
if (!order.value) return undefined
|
||||
const rentedAt = orderRentedAt()
|
||||
const durationHours = Number(order.value.estimated_duration_hours || 0)
|
||||
if (!rentedAt || durationHours <= 0) return undefined
|
||||
return new Date(new Date(rentedAt).getTime() + durationHours * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
|
||||
function money(value: unknown) {
|
||||
return Math.round(Number(value || 0))
|
||||
}
|
||||
|
||||
async function handleRefund() {
|
||||
if (!order.value) return
|
||||
refunding.value = true
|
||||
try {
|
||||
refundStatus.value = await adminRefundOrder(order.value.id)
|
||||
ElMessage.success('退款已发起')
|
||||
await loadOrder()
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '退款失败'))
|
||||
} finally {
|
||||
refunding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function refundStatusLabel(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
pending: '待退款',
|
||||
refunded: '已退款',
|
||||
failed: '退款失败',
|
||||
}
|
||||
return map[status] || status || '未退款'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page" v-loading="loading">
|
||||
<div v-if="order" class="page-header-row">
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">{{ order.order_no }}</p>
|
||||
<h1>订单详情</h1>
|
||||
<p>{{ order.title }} · {{ order.server_region }} / {{ order.login_platform }}</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<RouterLink to="/admin/orders">
|
||||
<el-button>返回列表</el-button>
|
||||
</RouterLink>
|
||||
<el-button type="warning" :disabled="!canOperate" @click="openAction('abnormal')">标记异常</el-button>
|
||||
<el-button type="danger" :disabled="!canOperate" @click="openAction('close')">客服关闭</el-button>
|
||||
<el-button type="primary" :loading="refunding" :disabled="refundStatus?.refund_status === 'refunded'" @click="handleRefund">
|
||||
{{ refundStatus?.refund_status === 'refunded' ? '已退款' : '人工退款' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="order" class="metric-grid dashboard-metrics">
|
||||
<div class="metric-card">
|
||||
<span>订单状态</span>
|
||||
<strong>{{ orderStatusLabel(order.status) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>交接状态</span>
|
||||
<strong>{{ handoffStatusLabel(order.handoff_status) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>订单金额</span>
|
||||
<strong>¥{{ money(order.rent_amount) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>平台费用</span>
|
||||
<strong>¥{{ money(order.platform_fee) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>押金</span>
|
||||
<strong>¥{{ money(order.deposit_amount) }}</strong>
|
||||
</div>
|
||||
<div v-if="refundStatus" class="metric-card">
|
||||
<span>退款状态</span>
|
||||
<strong>{{ refundStatusLabel(refundStatus.refund_status) }}</strong>
|
||||
<small v-if="refundStatus.refund_amount_cent > 0">¥{{ (refundStatus.refund_amount_cent / 100).toFixed(2) }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="order" class="dashboard-panels">
|
||||
<div class="order-panel dashboard-panel">
|
||||
<h2>用户信息</h2>
|
||||
<p>租客:{{ order.renter_phone || order.renter_id }}</p>
|
||||
<p>号主:{{ order.owner_phone || order.owner_id }}</p>
|
||||
<p>开始:{{ formatDateTime(orderRentedAt(), '未开始') }}</p>
|
||||
<p>预计截止:{{ formatDateTime(orderEstimatedEndAt(), '未设置') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="order-panel dashboard-panel">
|
||||
<h2>交接记录</h2>
|
||||
<el-empty v-if="handoffRecords.length === 0" description="暂无交接记录" />
|
||||
<div v-for="record in handoffRecords" :key="record.id" class="timeline-item">
|
||||
<strong>{{ record.type }}</strong>
|
||||
<p>{{ record.content }}</p>
|
||||
<span>{{ formatDateTime(record.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="order" class="order-panel dashboard-panel code-panel">
|
||||
<h2>账号快照</h2>
|
||||
<pre>{{ snapshotText }}</pre>
|
||||
</div>
|
||||
|
||||
<el-dialog :model-value="!!actionType" :title="actionTitle" width="560px" @update:model-value="actionType = ''">
|
||||
<div v-if="order" class="dialog-body">
|
||||
<p><strong>{{ order.order_no }}</strong> · {{ order.title }}</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