fix SalesOrderList.vue style

This commit is contained in:
Timi
2026-09-05 00:26:31 +08:00
parent 71dc209a5a
commit 25bbdafcc8
4 changed files with 388 additions and 55 deletions
@@ -0,0 +1,272 @@
<template>
<div class="sales-order-cell bg-white">
<div class="header">
<div class="left">
<t-image
v-if="photoURL"
:src="photoURL"
:alt="customerName"
fit="cover"
class="avatar"
/>
<div v-else class="avatar fallback" v-text="avatarText" />
</div>
<div class="main">
<div class="customer-status">
<strong class="customer" v-text="customerName" />
<div class="status">
<t-tag
:content="getSalesOrderStatusText(props.order.status)"
size="small"
variant="light-outline"
:theme="getStatusTheme(props.order.status)"
/>
<t-tag
:content="getSalesPaymentStatusText(props.order.paymentStatus)"
:theme="getPaymentStatusTheme(props.order.paymentStatus)"
size="small"
variant="light-outline"
/>
</div>
</div>
<div class="order-no" v-text="orderNoText" />
</div>
</div>
<t-divider />
<div class="product">
<div class="items">
<t-tag
v-for="(item, index) in itemList"
:key="item.id || `${item.productId}-${index}`"
:content="item.productNameSnapshot || '未命名产品'"
theme="primary"
variant="light"
/>
<t-tag v-if="!itemList.length" content="暂无产品" />
</div>
</div>
<div class="summary">
<span v-text="productSummaryText" />
</div>
<t-divider />
<div class="time-payment">
<pass-time-label class="time gray" :timestamp="props.order.purchasedAt" underline />
<div class="amount">
<span class="red">¥</span>
<strong class="value red" v-text="formatPackageAmount(props.order.saleAmount)" />
</div>
</div>
<div v-if="showActions" class="actions" @click.stop>
<t-button
v-if="showReceiveAction"
variant="outline"
size="extra-small"
@click.stop="emit('receive')"
>登记收款</t-button>
<t-button
v-if="showFulfillAction"
variant="outline"
size="extra-small"
@click.stop="emit('fulfill')"
>履约产品</t-button>
<t-button
class="detail"
size="extra-small"
theme="primary"
content="详情"
@click="emit('click')"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { PassTimeLabel, Time } from "timi-web";
import type { SalesOrder, SalesPaymentStatus } from "@/types/SalesOrder";
import { getCustomerPhotoURL } from "@/utils/customer";
import { formatPackageAmount, getPackageQuantityText } from "@/utils/package";
import { getSalesOrderStatusText, getSalesPaymentStatusText, getStatusTheme } from "@/utils/salesOrder";
defineOptions({ name: "SalesOrderListCell" });
const props = defineProps<{
order: SalesOrder;
canReceive: boolean;
canFulfill: boolean;
}>();
const emit = defineEmits<{
click: [];
receive: [];
fulfill: [];
}>();
const itemList = computed(() => props.order.itemList || []);
const customerName = computed(() => props.order.customer?.name || props.order.customer?.code || props.order.customerId || "未知客户");
const avatarText = computed(() => customerName.value.slice(0, 1) || "客");
const photoURL = computed(() => getCustomerPhotoURL(props.order.customer, 64));
const orderNoText = computed(() => `订单号:${props.order.orderNo || props.order.id || "未知"}`);
const totalProductQuantity = computed(() => sumProductQuantity("quantity"));
const fulfilledProductQuantity = computed(() => sumProductQuantity("fulfilledQuantity"));
const productSummaryText = computed(() => `共 ${itemList.value.length} 项 · 总数量 ${totalProductQuantity.value} · 已履约 ${fulfilledProductQuantity.value}`);
const showReceiveAction = computed(() => props.canReceive && props.order.status !== "CANCELLED" && props.order.paymentStatus !== "PAID");
const showFulfillAction = computed(() => props.canFulfill && props.order.orderType === "DIRECT_PRODUCT" && props.order.status === "ACTIVE");
const showActions = computed(() => showReceiveAction.value || showFulfillAction.value);
function getPaymentStatusTheme(status?: SalesPaymentStatus): "success" | "warning" | "danger" | "default" {
if (status === "PAID") {
return "success";
}
if (status === "PARTIALLY_PAID") {
return "warning";
}
if (status === "UNPAID") {
return "danger";
}
return "default";
}
function sumProductQuantity(field: "quantity" | "fulfilledQuantity"): string {
let total = 0;
for (const item of itemList.value) {
const quantity = Number(item[field] || 0);
if (Number.isFinite(quantity)) {
total += quantity;
}
}
return getPackageQuantityText(total.toFixed(3));
}
</script>
<style scoped lang="less">
.sales-order-cell {
gap: .5rem;
padding: 10px;
display: flex;
flex-direction: column;
.header {
gap: .5rem;
display: flex;
.left {
.avatar {
width: 3.5rem;
height: 3.5rem;
}
}
.main {
gap: .5rem;
flex: 1;
display: flex;
flex-direction: column;
.customer-status {
display: flex;
align-items: center;
justify-content: space-between;
.customer {
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.status {
gap: .25rem;
display: flex;
}
}
.order-no {
color: var(--td-text-color-secondary);
font-size: .75rem;
line-height: 1.5;
}
}
}
.product {
gap: .5rem;
display: flex;
align-items: flex-start;
.items {
flex: 1;
gap: .35rem;
display: flex;
min-width: 0;
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
:deep(.t-tag) {
flex: 0 0 auto;
}
&::-webkit-scrollbar {
display: none;
}
}
}
.summary {
color: var(--td-text-color-secondary);
font-size: .75rem;
text-align: right;
line-height: 1.5;
}
.time-payment {
gap: 1rem;
display: flex;
align-items: baseline;
justify-content: space-between;
.time {
font-size: .75rem;
}
.amount {
display: flex;
align-items: baseline;
justify-content: end;
.value {
font-size: 1.5rem;
white-space: nowrap;
}
}
}
.actions {
--td-button-border-radius: .5rem;
gap: .5rem;
display: flex;
justify-content: end;
.detail {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
}
:deep(.sales-order-cell .avatar .t-image__img) {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
}
:global(html.theme-dark) .sales-order-cell {
.fallback {
background: #115e59;
}
}
</style>