订单接口最小化与私有文件访问加固
- 订单列表使用独立最小 DTO 并分页,号主待办提供独立接口与统计 - 用户 token 增加版本控制,冻结/改密/退出即时撤销会话 - 移除 URL token 传参,SSE 与接口统一使用 HttpOnly Cookie - 私有文件按上传归属与业务关联授权,收款凭证转私有访问并校验归属 - 公开商品接口返回最小字段,隐藏号主身份与内部状态 - 每日清理超过 30 天未关联业务的上传归属,上传归属失败时补偿删除对象
This commit is contained in:
@@ -1,66 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { fetchOrders, type Order } from '@/features/orders'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import {
|
||||
fetchSellerHandoffs,
|
||||
type SellerHandoffMetrics,
|
||||
type UserOrderListItem,
|
||||
} from '@/features/orders'
|
||||
import { handoffStatusLabel, orderStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
||||
import { formatListingNo } from '@/shared/utils/listingDisplay'
|
||||
|
||||
const session = useSessionStore()
|
||||
const loading = ref(false)
|
||||
const orders = ref<Order[]>([])
|
||||
const orders = ref<UserOrderListItem[]>([])
|
||||
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 total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const metrics = ref<SellerHandoffMetrics>({
|
||||
pending_handoff: 0,
|
||||
pending_checkout: 0,
|
||||
abnormal: 0,
|
||||
})
|
||||
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()
|
||||
const result = await fetchSellerHandoffs({ page: page.value, pageSize, status: status.value })
|
||||
orders.value = result.items
|
||||
total.value = result.total
|
||||
metrics.value = result.metrics
|
||||
} catch {
|
||||
orders.value = []
|
||||
total.value = 0
|
||||
ElMessage.error('交接待办加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function actionText(order: Order) {
|
||||
function handleStatusChange() {
|
||||
page.value = 1
|
||||
void loadOrders()
|
||||
}
|
||||
|
||||
function handlePageChange(nextPage: number) {
|
||||
page.value = nextPage
|
||||
void loadOrders()
|
||||
}
|
||||
|
||||
function actionText(order: UserOrderListItem) {
|
||||
if (order.status === 'pending_handoff' && order.handoff_status === 'pending_owner')
|
||||
return '提交交接'
|
||||
if (order.status === 'pending_checkout_confirm') return '处理结账'
|
||||
@@ -74,8 +66,8 @@ function money(value: unknown) {
|
||||
return formatMoney(Number(value || 0))
|
||||
}
|
||||
|
||||
function sellerAmount(order: Order) {
|
||||
return centToYuan(order.owner_rent_amount_cent ?? order.display_amount_cent)
|
||||
function sellerAmount(order: UserOrderListItem) {
|
||||
return centToYuan(order.display_amount_cent)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -88,7 +80,13 @@ function sellerAmount(order: Order) {
|
||||
<p>处理待交接、租赁中、待结账确认和异常订单。</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-select v-model="status" clearable placeholder="待办状态" style="width: 190px">
|
||||
<el-select
|
||||
v-model="status"
|
||||
clearable
|
||||
placeholder="待办状态"
|
||||
style="width: 190px"
|
||||
@change="handleStatusChange"
|
||||
>
|
||||
<el-option label="待交接" value="pending_handoff" />
|
||||
<el-option label="使用中" value="renting" />
|
||||
<el-option label="逾期中" value="overdue" />
|
||||
@@ -105,19 +103,19 @@ function sellerAmount(order: Order) {
|
||||
<div class="metric-grid">
|
||||
<div class="metric-card">
|
||||
<span>待交接</span>
|
||||
<strong>{{ pendingHandoffCount }} 单</strong>
|
||||
<strong>{{ metrics.pending_handoff }} 单</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>待结账</span>
|
||||
<strong>{{ pendingCheckoutCount }} 单</strong>
|
||||
<strong>{{ metrics.pending_checkout }} 单</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>异常/争议</span>
|
||||
<strong>{{ abnormalCount }} 单</strong>
|
||||
<strong>{{ metrics.abnormal }} 单</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" class="table-panel" :data="displayOrders">
|
||||
<el-table v-loading="loading" class="table-panel" :data="orders">
|
||||
<el-table-column label="商品编号" width="140">
|
||||
<template #default="{ row }">{{
|
||||
formatListingNo(row.listing_no, row.listing_id)
|
||||
@@ -145,5 +143,16 @@ function sellerAmount(order: Order) {
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-if="total > pageSize"
|
||||
v-model:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user