xcx-shop/temp.txt
2025-11-13 16:37:37 +08:00

900 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div :class="[prefixCls]">
<section :class="`${prefixCls}__header`">
<div :class="`${prefixCls}__search`">
<van-search v-model="searchValue" show-action shape="round" placeholder="鎧乞땐데 / <20>틔츰냔" @search="onSearch" @cancel="onCancel">
<template #action>
<span @click="onSearch(searchValue)">鎧乞</span>
</template>
</van-search>
</div>
<div :class="`${prefixCls}__status-bar`">
<button
v-for="tab in tabs"
:key="tab.value"
type="button"
:class="[
`${prefixCls}__status-item`,
{ [`${prefixCls}__status-item--active`]: activeTab === tab.value },
]"
@click="handleTabChange(tab.value)"
>
{{ tab.name }}
</button>
<van-icon name="filter-o" :class="`${prefixCls}__status-filter`" />
</div>
</section>
<div :class="`${prefixCls}__scroll-box`">
<section v-if="summaryMetrics.length" :class="`${prefixCls}__summary`">
<div v-for="metric in summaryMetrics" :key="metric.label" :class="`${prefixCls}__summary-card`">
<p class="summary-value">{{ metric.value }}</p>
<p class="summary-label">{{ metric.label }}</p>
<p class="summary-desc">{{ metric.desc }}</p>
</div>
</section>
<div :class="`${prefixCls}__list`">
<van-skeleton v-if="isListLoading && !orders.length" :row="4" title />
<template v-else>
<div v-for="order in orders" :key="order.id" :class="`${prefixCls}__card`">
<div :class="`${prefixCls}__card-header`">
<p class="store">
<van-icon name="shop-o" size=".16rem" />
<span>{{ order.storeName || '灌列' }}</span>
</p>
<div class="status" :class="getStatusMeta(order.displayStatus).className">
<span class="status-label">{{ getStatusMeta(order.displayStatus).label }}</span>
<p class="status-desc">{{ getStatusDescription(order) }}</p>
</div>
</div>
<div :class="`${prefixCls}__card-meta`">
<span>땐데긍뵀:{{ order.orderNo || order.id }}</span>
<span>苟데珂쇌:{{ order.formattedCreateTime }}</span>
</div>
<div :class="`${prefixCls}__goods`">
<div v-for="goods in order.goods" :key="goods.id || goods.productId" class="goods-item" @click="goDetail(order.id)">
<img :src="goods.productMainImageUrl || defaultCover" :alt="goods.productName" />
<div class="info">
<p class="name">{{ goods.productName || order.productName }}</p>
<p class="spec" v-if="goods.skuDesc">{{ goods.skuDesc }}</p>
</div>
<div class="price">
<p>{{ formatAmount(goods.payAmount ?? order.payAmount ?? order.promotionPrice) }}</p>
<span>x{{ goods.num || order.num || 1 }}</span>
</div>
</div>
</div>
<div v-if="order.logisticsInfo" :class="`${prefixCls}__card-logistics`">
<van-icon :name="order.logisticsInfo.icon" size=".14rem" />
<div class="logistics-text">
<p class="title">{{ order.logisticsInfo.title }}</p>
<p>{{ order.logisticsInfo.desc }}</p>
<p v-if="order.logisticsInfo.sub" class="sub">{{ order.logisticsInfo.sub }}</p>
</div>
<van-button v-if="order.logisticsInfo.trackNumber" size="mini" type="primary" plain @click.stop="handleViewLogistics(order)">꿴膠직</van-button>
</div>
<ul :class="`${prefixCls}__amount`">
<li>
<span><3E>틔쏜띨</span><b>{{ formatAmount(order.originPrice ?? order.payAmount) }}</b>
</li>
<li>
<span>膽쁨</span><b>-{{ formatAmount(order.discountAmount) }}</b>
</li>
<li>
<span>頓롤</span><b>{{ formatAmount(order.freightFee ?? order.shippingAmount) }}</b>
</li>
<li class="total">
<span>茄마</span><b>{{ formatAmount(order.payAmount ?? order.promotionPrice) }}</b>
</li>
</ul>
<div v-if="getTextActionKeys(order).length" :class="`${prefixCls}__tool-links`">
<button v-for="actionKey in getTextActionKeys(order)" :key="actionKey" @click.stop="triggerAction(actionKey, order)">
{{ ORDER_ACTION_CONFIG[actionKey]?.label }}
</button>
</div>
<div :class="`${prefixCls}__card-footer`">
<van-popover
:class="`${prefixCls}__popover`"
:show="activePopoverId === order.id"
placement="top-start"
:actions="moreActions"
@select="(action) => handleMoreAction(action, order)"
@update:show="(val) => onPopoverVisibleChange(order, val)"
>
<template #reference>
<span class="more-action">뫘뜩</span>
</template>
</van-popover>
<div :class="`${prefixCls}__action-buttons`">
<template v-for="buttonKeys in [getButtonActionKeys(order)]" :key="`buttons-${order.id}`">
<van-button
v-for="(actionKey, index) in buttonKeys"
:key="actionKey"
size="small"
:plain="index !== buttonKeys.length - 1"
:type="index === buttonKeys.length - 1 ? ORDER_ACTION_CONFIG[actionKey]?.type : undefined"
:class="[index === buttonKeys.length - 1 ? ORDER_ACTION_CONFIG[actionKey]?.className : '', index === buttonKeys.length - 1 ? 'is-main-button' : 'is-sub-button']"
@click.stop="triggerAction(actionKey, order)"
>
{{ ORDER_ACTION_CONFIG[actionKey]?.label }}
</van-button>
</template>
</div>
</div>
</div>
<van-empty v-if="!orders.length" image="search" description="董轟땐데" />
</template>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import api from '@/api'
import { showToast } from 'vant'
import { useDesign } from '@/hooks/web/useDesign.ts'
import type { OrderListItem, OrderStatus, PackageLogisticsInfo, TradeOrderLineEntity } from '@/api/types/shop'
type OrderTabValue = 'all' | 'wait_pay' | 'wait_shipping' | 'shipping' | 'after_sale'
interface OrderTab {
name: string
value: OrderTabValue
apiStatus?: string
}
type OrderActionKey = 'pay' | 'cancel' | 'remind' | 'viewLogistics' | 'extend' | 'confirm' | 'applyRefund' | 'delete' | 'contact' | 'buyAgain' | 'addToCart' | 'review'
interface GoodsItem {
id?: number | string
productId?: number
productName?: string
productMainImageUrl?: string
skuDesc?: string
num?: number
payAmount?: number
}
interface LogisticsInfo {
title: string
desc: string
sub?: string
trackNumber?: string
icon: string
}
interface EnhancedOrderItem extends OrderListItem {
displayStatus: OrderStatus
formattedCreateTime: string
goods: GoodsItem[]
logisticsInfo: LogisticsInfo | null
}
interface StatusActionConfig {
text?: OrderActionKey[]
buttons?: OrderActionKey[]
primary?: OrderActionKey
}
const tabs: OrderTab[] = [
{ name: '홍꼬', value: 'all' },
{ name: '덤마운', value: 'wait_pay', apiStatus: 'wait_pay' },
{ name: '덤랙새', value: 'wait_shipping', apiStatus: 'wait_shipping' },
{ name: '덤澗새', value: 'shipping', apiStatus: 'shipping' },
{ name: '藁운/簡빈', value: 'after_sale', apiStatus: 'after_sale' },
]
const ORDER_STATUS_META: Record<OrderStatus, { label: string; desc: string; className: string }> = {
wait_pay: { label: '덤마운', desc: '헝쐴우供냥連마', className: 'is-wait-pay' },
wait_shipping: { label: '덤랙새', desc: '<27>소攣瞳구새', className: 'is-wait-ship' },
shipping: { label: '頓渴櫓', desc: '관범綠랙놔', className: 'is-shipping' },
delivered: { label: '綠澗새', desc: '뻑短팀송굶늴뭔膠', className: 'is-delivered' },
all_refund: { label: '藁운供냥', desc: '綠覩쨌藁쀼', className: 'is-refund' },
part_refund: { label: '꼬롸藁운', desc: '藁운뇹잿櫓', className: 'is-refund' },
close: { label: '슥弄밑균', desc: '땐데綠밑균', className: 'is-close' },
unknown: { label: '뇹잿櫓', desc: '溝固뇹잿櫓', className: 'is-unknown' },
}
const ORDER_STATUS_ACTIONS: Record<OrderStatus, StatusActionConfig> = {
wait_pay: { text: ['cancel'], buttons: ['pay'], primary: 'pay' },
wait_shipping: { text: ['remind', 'contact'], buttons: ['buyAgain', 'applyRefund'], primary: 'applyRefund' },
shipping: { text: ['extend'], buttons: ['viewLogistics', 'buyAgain', 'confirm'], primary: 'confirm' },
delivered: { text: [], buttons: ['viewLogistics', 'buyAgain', 'review'], primary: 'review' },
all_refund: { text: ['contact'], buttons: ['buyAgain'], primary: 'buyAgain' },
part_refund: { text: ['contact'], buttons: ['buyAgain'], primary: 'buyAgain' },
close: { text: ['delete'], buttons: ['buyAgain'], primary: 'buyAgain' },
unknown: { text: ['contact'], buttons: ['buyAgain'], primary: 'buyAgain' },
}
const ORDER_ACTION_CONFIG: Record<OrderActionKey, { label: string; type?: 'primary' | 'success' | 'warning' | 'danger'; plain?: boolean; className?: string }> = {
pay: { label: '혼連마', type: 'warning' },
cancel: { label: '혤句땐데', plain: true },
remind: { label: '瓊今랙새', plain: true },
viewLogistics: { label: '꿴였膠직', plain: true },
extend: { label: '儺낀澗새', plain: true },
confirm: { label: '횅훰澗새', type: 'warning' },
applyRefund: { label: '<27>헝簡빈', plain: true },
delete: { label: '<27>뇜땐데', plain: true },
contact: { label: '젬溝와륩', plain: true },
buyAgain: { label: '疼찜寧데', type: 'warning', className: 'theme-bg-color theme-border-color color-white' },
addToCart: { label: '속흙뭔膠났', plain: true },
review: { label: '혼팀송', type: 'primary' },
}
const moreActions = [
{ text: '릿齡땐데뵀', value: 'copy' },
{ text: '젬溝와륩', value: 'service' },
{ text: '<27>뇜땐데', value: 'delete' },
]
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('order-page')
const router = useRouter()
const defaultCover = 'https://img.yzcdn.cn/vant/cat.jpeg'
const orders = ref<EnhancedOrderItem[]>([])
const searchValue = ref('')
const activeTab = ref<OrderTabValue>(tabs[0].value)
const isListLoading = ref(false)
const activePopoverId = ref<number | string | null>(null)
const queryParams = reactive({
status: tabs[0].apiStatus ?? '',
keyword: '',
})
const summaryMetrics = computed(() => {
if (!orders.value.length) return []
const stats = orders.value.reduce(
(acc, order) => {
acc.total += 1
if (order.displayStatus === 'wait_pay') acc.waitPay += 1
if (order.displayStatus === 'wait_shipping') acc.waitShip += 1
if (order.displayStatus === 'shipping') acc.waitReceive += 1
if (order.displayStatus === 'delivered') acc.received += 1
if (order.displayStatus === 'all_refund' || order.displayStatus === 'part_refund') acc.afterSale += 1
return acc
},
{ total: 0, waitPay: 0, waitShip: 0, waitReceive: 0, received: 0, afterSale: 0 },
)
return [
{ label: '悧땐데', value: stats.total, desc: '벵홍꼬슥弄션쩌' },
{ label: '덤마운', value: stats.waitPay, desc: '션돤섟珂連마' },
{ label: '덤랙새', value: stats.waitShip, desc: '<27>소구새櫓' },
{ label: '덤澗새', value: stats.waitReceive, desc: '밑鬧膠직땡檄' },
{ label: '綠澗새', value: stats.received, desc: '뻑短품鞏팀송' },
{ label: '簡빈/藁운', value: stats.afterSale, desc: '簡빈뇹잿櫓' },
]
})
const parseJSON = <T,>(value?: string | T): T | null => {
if (!value) return null
if (typeof value === 'object') return value as T
try {
return JSON.parse(value)
} catch (error) {
console.warn('[order] parseJSON failed', error)
return null
}
}
const parseSkuInfo = (skuInfo?: string | any[]) => {
const list = parseJSON<any[]>(skuInfo) || []
if (!Array.isArray(list)) return ''
return list
.map((item) => {
if (!item?.propertyName && !item?.propertyValue) return ''
return `${item.propertyName || ''}${item.propertyName ? '' : ''}${item.propertyValue || ''}`
})
.filter(Boolean)
.join(' · ')
}
const parseLogisticsInfo = (payload?: string | PackageLogisticsInfo) => parseJSON<PackageLogisticsInfo>(payload)
const formatTimestamp = (timestamp?: number | string) => {
if (!timestamp) return '--'
const value = typeof timestamp === 'string' ? Number(timestamp) : timestamp
if (!Number.isFinite(value)) return '--'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return '--'
const pad = (num: number) => num.toString().padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
const formatAmount = (value?: number | string) => {
const num = Number(value ?? 0)
return Number.isFinite(num) ? num.toFixed(2) : '0.00'
}
const hasTrackNumber = (order: OrderListItem) => Boolean(order.trackNumber || order.vvPackageEntity?.trackNumber || order.vvTradeOrderLineEntityList?.some((line) => Boolean(line.trackNumber)))
const deriveStatus = (order: OrderListItem): OrderStatus => {
const reversed = order.reverseStatus?.toLowerCase?.()
if (reversed === 'all_refund') return 'all_refund'
if (reversed === 'part_refund') return 'part_refund'
const raw = String(order.status || '').toLowerCase()
if (raw.includes('wait_pay')) return 'wait_pay'
if (raw.includes('wait_shipping')) return 'wait_shipping'
if (raw.includes('shipping')) return 'shipping'
if (raw.includes('delivered')) return 'delivered'
if (raw.includes('all_refund')) return 'all_refund'
if (raw.includes('part_refund')) return 'part_refund'
if (raw.includes('close')) return 'close'
if (hasTrackNumber(order)) return 'shipping'
return 'unknown'
}
const formatGoods = (order: OrderListItem): GoodsItem[] => {
const lines = order.vvTradeOrderLineEntityList
if (Array.isArray(lines) && lines.length) {
return lines.map((line) => ({
id: line.id,
productId: line.productId ?? order.productId,
productName: line.productName ?? order.productName,
productMainImageUrl: line.productMainImageUrl ?? order.productMainImageUrl,
skuDesc: parseSkuInfo(line.skuInfo),
num: line.num ?? order.num,
payAmount: line.singlePrice ?? line.payAmount ?? order.payAmount,
}))
}
return [
{
id: order.id,
productId: order.productId,
productName: order.productName,
productMainImageUrl: order.productMainImageUrl,
skuDesc: parseSkuInfo(order.skuInfo),
num: order.num,
payAmount: order.payAmount ?? order.promotionPrice,
},
]
}
const buildLogisticsInfo = (order: OrderListItem): LogisticsInfo | null => {
const pkg = order.vvPackageEntity
const trackNumber = order.trackNumber || pkg?.trackNumber
if (trackNumber) {
const info = parseLogisticsInfo(pkg?.packageLogisticsInfo)
const latest = info?.data?.[0]
return {
title: pkg?.logisticsCompany || info?.com || '넓頓<EB8493>뇹잿櫓',
desc: latest?.context || '관범頓渴櫓,헝켐懃된덤',
sub: latest?.areaName || pkg?.shippingTo,
trackNumber,
icon: 'logistics',
}
}
const firstLine: TradeOrderLineEntity | undefined = order.vvTradeOrderLineEntityList?.[0]
if (firstLine) {
return {
title: '<27>소구새櫓',
desc: firstLine.logisticsDesc || '<27>소攣瞳槨퀭硫구<E7A1AB>틔',
sub: order.tradeOrderEntity?.buyerDetailAddress || [order.province, order.city, order.district].filter(Boolean).join(' '),
trackNumber: firstLine.trackNumber,
icon: 'shop-o',
}
}
return null
}
const normalizeOrder = (item: OrderListItem): EnhancedOrderItem => {
const displayStatus = deriveStatus(item)
return {
...item,
displayStatus,
formattedCreateTime: formatTimestamp(item.createTime ?? item.tradeOrderEntity?.gmtDownOrder),
goods: formatGoods(item),
logisticsInfo: buildLogisticsInfo(item),
}
}
const extractOrderRows = (payload: any): OrderListItem[] => {
if (Array.isArray(payload?.rows)) return payload.rows
if (Array.isArray(payload?.data)) return payload.data
if (Array.isArray(payload)) return payload
return []
}
const getStatusMeta = (status: OrderStatus) => ORDER_STATUS_META[status] ?? ORDER_STATUS_META.unknown
const getStatusDescription = (order: EnhancedOrderItem) => {
if (order.displayStatus === 'shipping') {
return order.logisticsInfo?.desc || ORDER_STATUS_META.shipping.desc
}
if (order.displayStatus === 'wait_shipping') return ORDER_STATUS_META.wait_shipping.desc
return getStatusMeta(order.displayStatus).desc
}
const getTextActionKeys = (order: EnhancedOrderItem) => ORDER_STATUS_ACTIONS[order.displayStatus]?.text ?? []
const getButtonActionKeys = (order: EnhancedOrderItem) => {
const config = ORDER_STATUS_ACTIONS[order.displayStatus]
const keys = config?.buttons ? [...config.buttons] : ['buyAgain']
if (config?.primary && keys.includes(config.primary)) {
return [...keys.filter((key) => key !== config.primary), config.primary]
}
return keys
}
const handleTabChange = (value: OrderTabValue) => {
const target = tabs.find((tab) => tab.value === value)
if (!target) return
activeTab.value = target.value
queryParams.status = target.apiStatus ?? ''
handleGetList()
}
const onSearch = (value: string) => {
queryParams.keyword = value?.trim?.() || ''
handleGetList()
}
const onCancel = () => {
searchValue.value = ''
queryParams.keyword = ''
handleGetList()
}
const handleAddToCart = (order: EnhancedOrderItem) => {
showToast(`綠쉥「${order.productName || '맡<>틔'}」속흙뭔膠났`)
}
const handleBuyAgain = (order: EnhancedOrderItem | number | string) => {
const id = typeof order === 'object' ? order.productId || order.id : order
router.push({ name: 'commodity-detail', query: { id } })
}
const handleViewLogistics = (order: EnhancedOrderItem) => {
router.push({ path: '/order/detail', query: { id: order.id, scene: 'logistics' } })
}
const handleRemindDelivery = (order: EnhancedOrderItem) => {
showToast(`綠瓊今<E7938A>소쐴우槨땐데 ${order.orderNo || order.id} 랙새`)
}
const handleExtendReceive = (order: EnhancedOrderItem) => {
showToast(`綠槨땐데 ${order.orderNo || order.id} <20>헝儺낀澗새`)
}
const handleConfirmReceive = () => {
showToast('횅훰澗새냥묘,먁剋퀭돨連넣')
}
const handleApplyRefund = (order: EnhancedOrderItem) => {
router.push({ path: '/order/detail', query: { id: order.id, scene: 'afterSale' } })
}
const handleReview = (order: EnhancedOrderItem) => {
router.push({ name: 'review/write', query: { id: order.productId } })
}
const handleDeleteOrder = (order: EnhancedOrderItem) => {
showToast(`땐데 ${order.orderNo || order.id} 綠盧흙쀼澗籃`)
}
const handleContactService = () => {
showToast('와륩쉥쐴우宅퀭젬溝')
}
const actionHandlers: Record<OrderActionKey, (order: EnhancedOrderItem) => void> = {
pay: (order) => showToast(`契瘻땐데 ${order.orderNo || order.id} 連마`),
cancel: (order) => showToast(`땐데 ${order.orderNo || order.id} 돨혤句<ED98A4>헝綠瓊슥`),
remind: handleRemindDelivery,
viewLogistics: handleViewLogistics,
extend: handleExtendReceive,
confirm: handleConfirmReceive,
applyRefund: handleApplyRefund,
delete: handleDeleteOrder,
contact: () => handleContactService(),
buyAgain: (order) => handleBuyAgain(order),
addToCart: handleAddToCart,
review: handleReview,
}
const triggerAction = (actionKey: OrderActionKey, order: EnhancedOrderItem) => {
actionHandlers[actionKey]?.(order)
}
const handleCopyOrderNo = async (order: EnhancedOrderItem) => {
const text = String(order.orderNo || order.id || '')
if (!text) return
try {
if (typeof navigator !== 'undefined' && navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(text)
} else {
const textarea = document.createElement('textarea')
textarea.value = text
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
document.execCommand('copy')
document.body.removeChild(textarea)
}
showToast('땐데긍뵀綠릿齡')
} catch (error) {
console.warn('[order] copy failed', error)
showToast('릿齡呵겨,헝癎땡릿齡')
}
}
const handleMoreAction = (action: { value: string }, order: EnhancedOrderItem) => {
switch (action.value) {
case 'copy':
handleCopyOrderNo(order)
break
case 'service':
triggerAction('contact', order)
break
case 'delete':
triggerAction('delete', order)
break
default:
break
}
activePopoverId.value = null
}
const onPopoverVisibleChange = (order: EnhancedOrderItem, visible: boolean) => {
if (visible) {
activePopoverId.value = order.id
} else if (activePopoverId.value === order.id) {
activePopoverId.value = null
}
}
const goDetail = (id: number | string) => {
router.push({ path: '/order/detail', query: { id } })
}
const handleGetList = () => {
isListLoading.value = true
api.shop.orderList
.post({
keyword: queryParams.keyword,
status: queryParams.status || undefined,
})
.then((res) => {
const list = extractOrderRows(res?.data)
const normalized = list.map(normalizeOrder)
if (activeTab.value === 'after_sale') {
orders.value = normalized.filter((item) => item.displayStatus === 'all_refund' || item.displayStatus === 'part_refund')
return
}
orders.value = normalized
})
.catch((error) => {
console.warn('[order] handleGetList error', error)
orders.value = []
})
.finally(() => {
isListLoading.value = false
})
}
const init = () => {
handleGetList()
}
init()
</script>
<style lang="scss">
$prefix-cls: #{$namespace}-order-page;
.#{$prefix-cls} {
height: 100vh;
background: #f6f6f6;
overflow: hidden;
&__search {
padding: 0.12rem 0.12rem 0;
background: #fff;
}
&_scroll-box {
height: calc(100vh - 0.1rem);
overflow-y: scroll;
}
&__tabs {
background: #fff;
padding-bottom: 0.08rem;
:deep(.van-tabs__wrap) {
height: 0.32rem;
}
:deep(.van-tabs__line) {
background: var(--van-primary-color);
}
}
&__summary {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.12rem;
padding: 0.12rem;
}
&__summary-card {
padding: 0.12rem;
border-radius: 0.12rem;
background: linear-gradient(135deg, #fff, #fdf4ff);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
.summary-value {
font-size: 0.2rem;
font-weight: 600;
color: #111;
}
.summary-label {
margin-top: 0.04rem;
font-size: 0.13rem;
color: #666;
}
.summary-desc {
margin-top: 0.02rem;
font-size: 0.11rem;
color: #999;
}
}
&__list {
padding: 0.12rem;
}
&__card {
padding: 0.16rem;
border-radius: 0.16rem;
background: #fff;
box-shadow: 0 12px 24px rgba(17, 24, 39, 0.08);
& + & {
margin-top: 0.16rem;
}
}
&__card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
.store {
display: flex;
align-items: center;
font-weight: 500;
color: #111;
span {
margin-left: 0.06rem;
}
}
.status {
text-align: right;
.status-label {
font-size: 0.13rem;
font-weight: 600;
}
.status-desc {
margin-top: 0.02rem;
font-size: 0.11rem;
color: #999;
}
&.is-wait-pay .status-label,
&.is-wait-ship .status-label,
&.is-shipping .status-label {
color: #f97316;
}
&.is-delivered .status-label {
color: #10b981;
}
&.is-refund .status-label {
color: #6366f1;
}
&.is-close .status-label {
color: #94a3b8;
}
&.is-unknown .status-label {
color: #2563eb;
}
}
}
&__card-meta {
display: flex;
justify-content: space-between;
margin-top: 0.08rem;
font-size: 0.11rem;
color: #888;
}
&__goods {
margin-top: 0.12rem;
display: flex;
flex-direction: column;
gap: 0.12rem;
.goods-item {
display: flex;
align-items: center;
img {
width: 0.92rem;
height: 0.92rem;
border-radius: 0.12rem;
object-fit: cover;
background: #f3f4f6;
}
.info {
flex: 1;
margin: 0 0.12rem;
.name {
font-size: 0.14rem;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.spec {
margin-top: 0.04rem;
font-size: 0.12rem;
color: #999;
}
}
.price {
text-align: right;
p {
font-size: 0.16rem;
font-weight: 600;
color: #111;
}
span {
display: block;
margin-top: 0.04rem;
font-size: 0.12rem;
color: #999;
}
}
}
}
&__card-logistics {
margin-top: 0.12rem;
padding: 0.12rem;
border-radius: 0.12rem;
background: #f9fafb;
display: flex;
gap: 0.08rem;
align-items: flex-start;
font-size: 0.12rem;
color: #444;
:deep(.van-icon) {
color: #2563eb;
}
.logistics-text {
flex: 1;
}
.title {
font-weight: 600;
color: #111;
}
.sub {
color: #999;
}
:deep(.van-button) {
margin-left: auto;
border-radius: 999px;
}
}
&__amount {
margin-top: 0.12rem;
font-size: 0.12rem;
color: #666;
li {
display: flex;
justify-content: space-between;
align-items: center;
& + li {
margin-top: 0.04rem;
}
b {
font-weight: 500;
}
&.total {
margin-top: 0.08rem;
padding-top: 0.08rem;
border-top: 1px dashed #eee;
span {
font-weight: 600;
color: #333;
}
b {
font-size: 0.16rem;
color: #111;
}
}
}
}
&__tool-links {
margin-top: 0.12rem;
padding-top: 0.12rem;
border-top: 1px dashed #f0f0f0;
display: flex;
gap: 0.12rem;
flex-wrap: wrap;
button {
border: none;
background: none;
font-size: 0.12rem;
color: #666;
padding: 0;
position: relative;
&:not(:last-child)::after {
content: '';
position: absolute;
right: -0.06rem;
top: 50%;
width: 1px;
height: 0.12rem;
background: #e5e7eb;
transform: translateY(-50%);
}
}
}
&__card-footer {
margin-top: 0.16rem;
display: flex;
justify-content: space-between;
align-items: center;
.more-action {
font-size: 0.12rem;
color: #666;
}
}
&__action-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.08rem;
justify-content: flex-end;
:deep(.van-button) {
min-width: 0.86rem;
border-radius: 999px;
height: 0.3rem;
line-height: 0.3rem;
}
:deep(.van-button.is-sub-button) {
border-color: #e5e7eb;
color: #111;
}
:deep(.van-button.is-main-button) {
font-weight: 600;
}
}
&__popover {
:deep(.van-popover__content) {
.van-popover__action {
width: 1.3rem;
height: 0.34rem;
line-height: 0.34rem;
}
.van-popover__action-text {
font-size: 0.12rem;
}
}
}
}
</style>