完善号主订单交接待办入口
This commit is contained in:
@@ -605,7 +605,7 @@ func (r *Repository) AcceptCheckout(userID uint64, orderID uint64) error {
|
|||||||
func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) {
|
func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) {
|
||||||
var rows []orderRow
|
var rows []orderRow
|
||||||
err := r.baseQuery().
|
err := r.baseQuery().
|
||||||
Where("o.renter_id = ?", userID).
|
Where("o.renter_id = ? OR o.owner_id = ?", userID, userID).
|
||||||
Order("o.id DESC").
|
Order("o.id DESC").
|
||||||
Scan(&rows).Error
|
Scan(&rows).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { onMounted, ref } from 'vue'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
import { fetchOrders, payOrder, type Order } from '@/api/orders'
|
import { fetchOrders, payOrder, type Order } from '@/api/orders'
|
||||||
|
import { useSessionStore } from '@/stores/session'
|
||||||
import { orderStatusLabel } from '@/utils/statusLabels'
|
import { orderStatusLabel } from '@/utils/statusLabels'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const orders = ref<Order[]>([])
|
const orders = ref<Order[]>([])
|
||||||
const payingOrderId = ref<number | null>(null)
|
const payingOrderId = ref<number | null>(null)
|
||||||
|
const session = useSessionStore()
|
||||||
|
|
||||||
onMounted(loadOrders)
|
onMounted(loadOrders)
|
||||||
|
|
||||||
@@ -40,6 +42,12 @@ function readError(error: unknown, fallback: string) {
|
|||||||
}
|
}
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function orderRole(order: Order) {
|
||||||
|
if (order.renter_id === session.userId) return '买家'
|
||||||
|
if (order.owner_id === session.userId) return '号主'
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -55,6 +63,9 @@ function readError(error: unknown, fallback: string) {
|
|||||||
<el-table-column prop="title" label="账号" min-width="180" />
|
<el-table-column prop="title" label="账号" min-width="180" />
|
||||||
<el-table-column prop="rent_amount" label="订单金额" width="100" />
|
<el-table-column prop="rent_amount" label="订单金额" width="100" />
|
||||||
<el-table-column prop="deposit_amount" label="押金" width="100" />
|
<el-table-column prop="deposit_amount" label="押金" width="100" />
|
||||||
|
<el-table-column label="身份" width="80">
|
||||||
|
<template #default="{ row }">{{ orderRole(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="状态" width="140">
|
<el-table-column label="状态" width="140">
|
||||||
<template #default="{ row }">{{ orderStatusLabel(row.status) }}</template>
|
<template #default="{ row }">{{ orderStatusLabel(row.status) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -64,6 +75,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
v-if="row.status === 'pending_payment'"
|
v-if="row.status === 'pending_payment'"
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
:disabled="row.renter_id !== session.userId"
|
||||||
:loading="payingOrderId === row.id"
|
:loading="payingOrderId === row.id"
|
||||||
@click="handlePay(row)"
|
@click="handlePay(row)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import { useRouter, useRoute } from "vue-router";
|
|||||||
import { showToast } from "vant";
|
import { showToast } from "vant";
|
||||||
|
|
||||||
import { fetchOrders, payOrder, type Order } from "@/api/orders";
|
import { fetchOrders, payOrder, type Order } from "@/api/orders";
|
||||||
|
import { useSessionStore } from "@/stores/session";
|
||||||
import { formatDateMinute } from "@/utils/time";
|
import { formatDateMinute } from "@/utils/time";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const session = useSessionStore();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const orders = ref<Order[]>([]);
|
const orders = ref<Order[]>([]);
|
||||||
const activeTab = ref("all");
|
const activeTab = ref("all");
|
||||||
@@ -188,7 +190,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<span class="order-time">{{ formatDateMinute(order.created_at) }}</span>
|
<span class="order-time">{{ formatDateMinute(order.created_at) }}</span>
|
||||||
<van-button
|
<van-button
|
||||||
v-if="order.status === 'pending_payment'"
|
v-if="order.status === 'pending_payment' && order.renter_id === session.userId"
|
||||||
size="mini"
|
size="mini"
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="payingOrderId === order.id"
|
:loading="payingOrderId === order.id"
|
||||||
|
|||||||
@@ -1,9 +1,126 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import { fetchOrders, type Order } from '@/api/orders'
|
||||||
|
import { useSessionStore } from '@/stores/session'
|
||||||
|
import { handoffStatusLabel, orderStatusLabel } from '@/utils/statusLabels'
|
||||||
|
import { formatDateTime } from '@/utils/time'
|
||||||
|
|
||||||
|
const session = useSessionStore()
|
||||||
|
const loading = ref(false)
|
||||||
|
const orders = ref<Order[]>([])
|
||||||
|
const status = ref('')
|
||||||
|
|
||||||
|
const sellerOrders = computed(() => orders.value.filter((order) => order.owner_id === session.userId))
|
||||||
|
const todoOrders = computed(() =>
|
||||||
|
sellerOrders.value.filter((order) =>
|
||||||
|
[
|
||||||
|
'pending_handoff',
|
||||||
|
'renting',
|
||||||
|
'overdue',
|
||||||
|
'pending_checkout_confirm',
|
||||||
|
'pending_checkout_accept',
|
||||||
|
'checkout_disputing',
|
||||||
|
'disputing',
|
||||||
|
'abnormal',
|
||||||
|
].includes(order.status),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const displayOrders = computed(() => {
|
||||||
|
const source = todoOrders.value
|
||||||
|
if (!status.value) return source
|
||||||
|
return source.filter((order) => order.status === status.value)
|
||||||
|
})
|
||||||
|
const pendingHandoffCount = computed(
|
||||||
|
() => sellerOrders.value.filter((order) => order.status === 'pending_handoff' && order.handoff_status === 'pending_owner').length,
|
||||||
|
)
|
||||||
|
const pendingCheckoutCount = computed(
|
||||||
|
() => sellerOrders.value.filter((order) => order.status === 'pending_checkout_confirm').length,
|
||||||
|
)
|
||||||
|
const abnormalCount = computed(
|
||||||
|
() => sellerOrders.value.filter((order) => ['overdue', 'checkout_disputing', 'disputing', 'abnormal'].includes(order.status)).length,
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(loadOrders)
|
||||||
|
|
||||||
|
async function loadOrders() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
orders.value = await fetchOrders()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function actionText(order: Order) {
|
||||||
|
if (order.status === 'pending_handoff' && order.handoff_status === 'pending_owner') return '提交交接'
|
||||||
|
if (order.status === 'pending_checkout_confirm') return '处理结账'
|
||||||
|
if (order.status === 'pending_checkout_accept') return '等待租客'
|
||||||
|
if (['overdue', 'checkout_disputing', 'disputing', 'abnormal'].includes(order.status)) return '查看处理'
|
||||||
|
return '详情'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="page">
|
<section class="page">
|
||||||
<div class="page-header">
|
<div class="page-header-row">
|
||||||
<p class="eyebrow">Handoffs</p>
|
<div class="page-header">
|
||||||
<h1>交接管理</h1>
|
<p class="eyebrow">Handoffs</p>
|
||||||
<p>处理待交接、待归还确认和超时订单。</p>
|
<h1>交接管理</h1>
|
||||||
|
<p>处理待交接、租赁中、待结账确认和异常订单。</p>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-actions">
|
||||||
|
<el-select v-model="status" clearable placeholder="待办状态" style="width: 190px">
|
||||||
|
<el-option label="待交接" value="pending_handoff" />
|
||||||
|
<el-option label="使用中" value="renting" />
|
||||||
|
<el-option label="逾期中" value="overdue" />
|
||||||
|
<el-option label="待确认结账" value="pending_checkout_confirm" />
|
||||||
|
<el-option label="待租客确认修正" value="pending_checkout_accept" />
|
||||||
|
<el-option label="结账争议中" value="checkout_disputing" />
|
||||||
|
<el-option label="申诉中" value="disputing" />
|
||||||
|
<el-option label="异常" value="abnormal" />
|
||||||
|
</el-select>
|
||||||
|
<el-button @click="loadOrders">刷新</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="metric-grid">
|
||||||
|
<div class="metric-card">
|
||||||
|
<span>待交接</span>
|
||||||
|
<strong>{{ pendingHandoffCount }} 单</strong>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<span>待结账</span>
|
||||||
|
<strong>{{ pendingCheckoutCount }} 单</strong>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<span>异常/争议</span>
|
||||||
|
<strong>{{ abnormalCount }} 单</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" class="table-panel" :data="displayOrders">
|
||||||
|
<el-table-column prop="order_no" label="订单号" min-width="220" />
|
||||||
|
<el-table-column prop="title" label="账号" min-width="180" />
|
||||||
|
<el-table-column label="金额" width="120">
|
||||||
|
<template #default="{ row }">¥{{ row.rent_amount }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="订单状态" width="150">
|
||||||
|
<template #default="{ row }">{{ orderStatusLabel(row.status) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="交接状态" width="180">
|
||||||
|
<template #default="{ row }">{{ handoffStatusLabel(row.handoff_status) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" min-width="180">
|
||||||
|
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<RouterLink :to="`/orders/${row.id}`">
|
||||||
|
<el-button size="small" type="primary">{{ actionText(row) }}</el-button>
|
||||||
|
</RouterLink>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user