订单接口最小化与私有文件访问加固

- 订单列表使用独立最小 DTO 并分页,号主待办提供独立接口与统计
- 用户 token 增加版本控制,冻结/改密/退出即时撤销会话
- 移除 URL token 传参,SSE 与接口统一使用 HttpOnly Cookie
- 私有文件按上传归属与业务关联授权,收款凭证转私有访问并校验归属
- 公开商品接口返回最小字段,隐藏号主身份与内部状态
- 每日清理超过 30 天未关联业务的上传归属,上传归属失败时补偿删除对象
This commit is contained in:
yml2213
2026-08-16 21:47:46 +08:00
parent f48da14ed2
commit 85332df2bd
63 changed files with 1827 additions and 290 deletions
@@ -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>