订单接口最小化与私有文件访问加固
- 订单列表使用独立最小 DTO 并分页,号主待办提供独立接口与统计 - 用户 token 增加版本控制,冻结/改密/退出即时撤销会话 - 移除 URL token 传参,SSE 与接口统一使用 HttpOnly Cookie - 私有文件按上传归属与业务关联授权,收款凭证转私有访问并校验归属 - 公开商品接口返回最小字段,隐藏号主身份与内部状态 - 每日清理超过 30 天未关联业务的上传归属,上传归属失败时补偿删除对象
This commit is contained in:
@@ -5,14 +5,13 @@ import { ElMessage } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { CopyDocument, Search } from '@element-plus/icons-vue'
|
||||
|
||||
import { fetchOrders, type Order } from '@/features/orders'
|
||||
import { fetchOrders, type UserOrderListItem } from '@/features/orders'
|
||||
import {
|
||||
fetchMyMohongOrders,
|
||||
mohongOrderStatusLabel,
|
||||
type MohongOrder,
|
||||
} from '@/features/mohong/api/mohong'
|
||||
import { centToYuan, formatCent, formatMoney } from '@/shared/utils/money'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import { orderStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { formatListingNo } from '@/shared/utils/listingDisplay'
|
||||
@@ -31,19 +30,21 @@ interface UnifiedOrderItem {
|
||||
coverUrl?: string
|
||||
quantity?: number
|
||||
unit?: string
|
||||
rental?: Order
|
||||
rental?: UserOrderListItem
|
||||
crash?: MohongOrder
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const rentalOrders = ref<Order[]>([])
|
||||
const rentalOrders = ref<UserOrderListItem[]>([])
|
||||
const crashOrders = ref<MohongOrder[]>([])
|
||||
const payingOrderId = ref<number | null>(null)
|
||||
const session = useSessionStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const searchKeyword = ref('')
|
||||
const fallbackPendingPaymentMinutes = 15
|
||||
const rentalTotal = ref(0)
|
||||
const rentalPage = ref(1)
|
||||
const rentalPageSize = 20
|
||||
|
||||
const statusTabs = [
|
||||
{ key: 'all', label: '全部' },
|
||||
@@ -107,8 +108,7 @@ const displayOrders = computed(() => {
|
||||
if (order.bizType === 'rental' && order.rental) {
|
||||
return (
|
||||
formatListingCode(order.rental).toLowerCase().includes(keyword) ||
|
||||
String(order.rental.listing_id).includes(keyword) ||
|
||||
String(order.rental.account_id).includes(keyword)
|
||||
String(order.rental.listing_id).includes(keyword)
|
||||
)
|
||||
}
|
||||
return false
|
||||
@@ -152,14 +152,16 @@ async function loadOrders() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [rentalResult, crashResult] = await Promise.allSettled([
|
||||
fetchOrders(),
|
||||
fetchOrders(rentalPage.value, rentalPageSize),
|
||||
fetchMyMohongOrders({ page: 1, page_size: 50 }),
|
||||
])
|
||||
|
||||
if (rentalResult.status === 'fulfilled') {
|
||||
rentalOrders.value = Array.isArray(rentalResult.value) ? rentalResult.value : []
|
||||
rentalOrders.value = rentalResult.value?.items || []
|
||||
rentalTotal.value = rentalResult.value?.total || 0
|
||||
} else {
|
||||
rentalOrders.value = []
|
||||
rentalTotal.value = 0
|
||||
ElMessage.error('租赁订单加载失败')
|
||||
}
|
||||
|
||||
@@ -174,6 +176,11 @@ async function loadOrders() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleRentalPageChange(page: number) {
|
||||
rentalPage.value = page
|
||||
void loadOrders()
|
||||
}
|
||||
|
||||
function goDetail(item: UnifiedOrderItem) {
|
||||
if (item.bizType === 'crash') {
|
||||
router.push(`/crash/orders/${item.id}`)
|
||||
@@ -182,7 +189,7 @@ function goDetail(item: UnifiedOrderItem) {
|
||||
router.push(`/orders/${item.id}`)
|
||||
}
|
||||
|
||||
async function handlePay(order: Order) {
|
||||
async function handlePay(order: UserOrderListItem) {
|
||||
payingOrderId.value = order.id
|
||||
try {
|
||||
await router.push(`/orders/${order.id}?pay=1`)
|
||||
@@ -193,21 +200,15 @@ async function handlePay(order: Order) {
|
||||
}
|
||||
}
|
||||
|
||||
function orderRole(order: Order) {
|
||||
if (order.renter_id === session.userId) return '租客'
|
||||
if (order.owner_id === session.userId) return '号主'
|
||||
return '-'
|
||||
function orderRole(order: UserOrderListItem) {
|
||||
return order.role === 'owner' ? '号主' : '租客'
|
||||
}
|
||||
|
||||
function isRenter(order: Order) {
|
||||
return order.renter_id === session.userId
|
||||
function isRenter(order: UserOrderListItem) {
|
||||
return order.role === 'renter'
|
||||
}
|
||||
|
||||
function isOwner(order: Order) {
|
||||
return order.owner_id === session.userId
|
||||
}
|
||||
|
||||
function amountLabel(order: Order) {
|
||||
function amountLabel(order: UserOrderListItem) {
|
||||
return isRenter(order) ? '支付租金' : '预计租金'
|
||||
}
|
||||
|
||||
@@ -216,19 +217,10 @@ function amountYuan(cent: unknown) {
|
||||
return 0
|
||||
}
|
||||
|
||||
function orderRentAmount(order: Order) {
|
||||
if (isOwner(order)) return amountYuan(order.owner_rent_amount_cent)
|
||||
if (isRenter(order)) return amountYuan(order.rent_amount_cent)
|
||||
function orderRentAmount(order: UserOrderListItem) {
|
||||
return amountYuan(order.display_amount_cent)
|
||||
}
|
||||
|
||||
function ownerActualIncome(order: Order) {
|
||||
if (!isOwner(order)) return null
|
||||
const value = order.checkout?.owner_income_amount_cent
|
||||
if (typeof value === 'number') return centToYuan(value)
|
||||
return null
|
||||
}
|
||||
|
||||
function money(value: unknown) {
|
||||
return formatMoney(Number(value || 0))
|
||||
}
|
||||
@@ -262,11 +254,11 @@ async function copyOrderNo(orderNo: unknown) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatListingCode(order: Order) {
|
||||
function formatListingCode(order: UserOrderListItem) {
|
||||
return formatListingNo(order.listing_no, order.listing_id)
|
||||
}
|
||||
|
||||
async function copyListingCode(order: Order) {
|
||||
async function copyListingCode(order: UserOrderListItem) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(formatListingCode(order))
|
||||
ElMessage.success('商品编号已复制')
|
||||
@@ -275,7 +267,7 @@ async function copyListingCode(order: Order) {
|
||||
}
|
||||
}
|
||||
|
||||
function getPaymentDeadline(order: Order) {
|
||||
function getPaymentDeadline(order: UserOrderListItem) {
|
||||
if (order.status !== 'pending_payment' || !order.created_at) return null
|
||||
if (order.payment_deadline_at) {
|
||||
const serverDeadline = new Date(order.payment_deadline_at)
|
||||
@@ -285,7 +277,7 @@ function getPaymentDeadline(order: Order) {
|
||||
return new Date(created.getTime() + fallbackPendingPaymentMinutes * 60 * 1000)
|
||||
}
|
||||
|
||||
function getCountdownMinutes(order: Order) {
|
||||
function getCountdownMinutes(order: UserOrderListItem) {
|
||||
const deadline = getPaymentDeadline(order)
|
||||
if (!deadline) return 0
|
||||
const now = new Date()
|
||||
@@ -366,9 +358,7 @@ function bizTypeLabel(bizType: BizType) {
|
||||
</div>
|
||||
<div class="crash-product-info">
|
||||
<strong>{{ row.title }}</strong>
|
||||
<span
|
||||
>x{{ row.quantity || 1 }}{{ row.unit ? ` · ${row.unit}` : '' }}</span
|
||||
>
|
||||
<span>x{{ row.quantity || 1 }}{{ row.unit ? ` · ${row.unit}` : '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="rental-product-cell">
|
||||
@@ -390,9 +380,6 @@ function bizTypeLabel(bizType: BizType) {
|
||||
<div v-if="row.bizType === 'rental' && row.rental" class="amount-cell">
|
||||
<span class="amount-value">¥{{ money(orderRentAmount(row.rental)) }}</span>
|
||||
<span class="amount-label">{{ amountLabel(row.rental) }}</span>
|
||||
<span v-if="ownerActualIncome(row.rental) !== null" class="amount-sub"
|
||||
>实际到手 ¥{{ money(ownerActualIncome(row.rental)) }}</span
|
||||
>
|
||||
</div>
|
||||
<div v-else class="amount-cell">
|
||||
<span class="amount-value">¥{{ row.amountText }}</span>
|
||||
@@ -403,8 +390,12 @@ function bizTypeLabel(bizType: BizType) {
|
||||
<el-table-column label="押金 / 数量" width="110">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.bizType === 'rental' && row.rental" class="amount-cell">
|
||||
<span class="amount-value">¥{{ money(amountYuan(row.rental.deposit_amount_cent)) }}</span>
|
||||
<span v-if="amountYuan(row.rental.deposit_waived_amount_cent) > 0" class="amount-label"
|
||||
<span class="amount-value"
|
||||
>¥{{ money(amountYuan(row.rental.deposit_amount_cent)) }}</span
|
||||
>
|
||||
<span
|
||||
v-if="amountYuan(row.rental.deposit_waived_amount_cent) > 0"
|
||||
class="amount-label"
|
||||
>免 ¥{{ money(amountYuan(row.rental.deposit_waived_amount_cent)) }}</span
|
||||
>
|
||||
</div>
|
||||
@@ -451,13 +442,11 @@ function bizTypeLabel(bizType: BizType) {
|
||||
<div class="action-buttons">
|
||||
<el-button
|
||||
v-if="
|
||||
row.bizType === 'rental' &&
|
||||
row.rental &&
|
||||
row.rental.status === 'pending_payment'
|
||||
row.bizType === 'rental' && row.rental && row.rental.status === 'pending_payment'
|
||||
"
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="row.rental.renter_id !== session.userId"
|
||||
:disabled="!isRenter(row.rental)"
|
||||
:loading="payingOrderId === row.rental.id"
|
||||
@click="handlePay(row.rental)"
|
||||
>
|
||||
@@ -511,10 +500,6 @@ function bizTypeLabel(bizType: BizType) {
|
||||
<span class="meta-label">{{ amountLabel(item.rental) }}</span>
|
||||
<span class="meta-value amount">¥{{ money(orderRentAmount(item.rental)) }}</span>
|
||||
</div>
|
||||
<div v-if="ownerActualIncome(item.rental) !== null" class="meta-row">
|
||||
<span class="meta-label">实际到手</span>
|
||||
<span class="meta-value amount">¥{{ money(ownerActualIncome(item.rental)) }}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">押金</span>
|
||||
<span class="meta-value">
|
||||
@@ -582,6 +567,15 @@ function bizTypeLabel(bizType: BizType) {
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<el-pagination
|
||||
v-if="rentalTotal > rentalPageSize"
|
||||
v-model:current-page="rentalPage"
|
||||
class="orders-pagination"
|
||||
layout="prev, pager, next"
|
||||
:page-size="rentalPageSize"
|
||||
:total="rentalTotal"
|
||||
@current-change="handleRentalPageChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user