From 498e3d7bde8ee6fcf915171587a1d60b0688ea25 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Fri, 5 Jun 2026 00:37:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=A2=E5=8D=95=E7=BB=93?= =?UTF-8?q?=E7=AE=97=E9=87=91=E9=A2=9D=E5=B1=95=E7=A4=BA=E5=92=8C=E9=92=B1?= =?UTF-8?q?=E5=8C=85=E6=B5=81=E6=B0=B4=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/order/repository.go | 17 +++++++-- backend/internal/modules/wallet/repository.go | 9 +++-- .../modules/wallet/repository_test.go | 12 +++++++ .../orders/views/MobileOrderDetailView.vue | 28 +++++++++++++-- .../orders/views/MobileOrdersView.vue | 30 ++++++++++++++-- .../features/orders/views/OrderDetailView.vue | 36 +++++++++++++++++-- .../src/features/orders/views/OrdersView.vue | 31 +++++++++++++--- 7 files changed, 148 insertions(+), 15 deletions(-) diff --git a/backend/internal/modules/order/repository.go b/backend/internal/modules/order/repository.go index 40ab324..2c05994 100644 --- a/backend/internal/modules/order/repository.go +++ b/backend/internal/modules/order/repository.go @@ -698,7 +698,11 @@ func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) { } items := make([]OrderDTO, 0, len(rows)) for _, row := range rows { - items = append(items, row.toDTOForUser(userID)) + dto := row.toDTOForUser(userID) + if shouldAttachCheckout(row.Status) { + dto.Checkout = r.latestCheckoutDTOForUser(row.ID, userID, row.RentalOrder) + } + items = append(items, dto) } return items, nil } @@ -1312,6 +1316,15 @@ func (r *Repository) latestCheckoutAdminDTO(orderID uint64) *CheckoutDTO { return &dto } +func shouldAttachCheckout(status string) bool { + switch status { + case "pending_checkout_confirm", "pending_checkout_accept", "checkout_disputing", "completed": + return true + default: + return false + } +} + func (r *Repository) latestCheckoutDTOForUser(orderID uint64, userID uint64, order model.RentalOrder) *CheckoutDTO { var checkout model.OrderCheckout if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil { @@ -1598,7 +1611,7 @@ func newOrderNo() (string, error) { // 将2字节转为0-999的数字 randomNum := (int(buf[0])<<8 | int(buf[1])) % 1000 - return "RO" + timeStr + strconv.Itoa(1000+randomNum)[1:], nil + return "RO" + timeStr + strconv.Itoa(1000 + randomNum)[1:], nil } func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error { diff --git a/backend/internal/modules/wallet/repository.go b/backend/internal/modules/wallet/repository.go index 31e5f76..cb053f7 100644 --- a/backend/internal/modules/wallet/repository.go +++ b/backend/internal/modules/wallet/repository.go @@ -2,7 +2,6 @@ package wallet import ( "crypto/rand" - "encoding/hex" "fmt" "math" "time" @@ -290,9 +289,13 @@ func toLedgerDTO(row model.WalletLedger) LedgerDTO { } func newLedgerNo() (string, error) { - buf := make([]byte, 4) + // 生成格式:WL + YYYYMMDDHHMMSS + 毫秒 + 4位随机数 + // 例如:WL202606051234561230456,便于用户和客服识别。 + now := time.Now() + buf := make([]byte, 2) if _, err := rand.Read(buf); err != nil { return "", err } - return fmt.Sprintf("WL%d%s", time.Now().UnixNano(), hex.EncodeToString(buf)), nil + randomNum := (int(buf[0])<<8 | int(buf[1])) % 10000 + return fmt.Sprintf("WL%s%03d%04d", now.Format("20060102150405"), now.Nanosecond()/1_000_000, randomNum), nil } diff --git a/backend/internal/modules/wallet/repository_test.go b/backend/internal/modules/wallet/repository_test.go index d6b0c84..c4b8ad0 100644 --- a/backend/internal/modules/wallet/repository_test.go +++ b/backend/internal/modules/wallet/repository_test.go @@ -1,6 +1,7 @@ package wallet import ( + "regexp" "testing" "hfb_sys/backend/internal/model" @@ -29,3 +30,14 @@ func TestApplyEntryRoundsMoneyBeforeComparing(t *testing.T) { t.Fatalf("FrozenBalance = %v, want 0", account.FrozenBalance) } } + +func TestNewLedgerNoUsesReadableFormat(t *testing.T) { + ledgerNo, err := newLedgerNo() + if err != nil { + t.Fatalf("newLedgerNo() error = %v", err) + } + matched := regexp.MustCompile(`^WL\d{21}$`).MatchString(ledgerNo) + if !matched { + t.Fatalf("ledgerNo = %q, want WL + 21 digits", ledgerNo) + } +} diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.vue b/frontend/src/features/orders/views/MobileOrderDetailView.vue index 11d0e7a..ac67c2e 100644 --- a/frontend/src/features/orders/views/MobileOrderDetailView.vue +++ b/frontend/src/features/orders/views/MobileOrderDetailView.vue @@ -73,7 +73,10 @@ const showRejectPopup = ref(false); const isOwner = computed(() => order.value?.owner_id === session.userId); const isRenter = computed(() => order.value?.renter_id === session.userId); -const orderAmountLabel = computed(() => (isOwner.value ? "我的租金" : "订单金额")); +const orderAmountLabel = computed(() => (isOwner.value ? "预计租金" : "支付租金")); +const orderRentDisplayAmount = computed(() => (order.value ? orderRentAmount(order.value) : 0)); +const ownerIncomeDisplayAmount = computed(() => (order.value ? ownerActualIncome(order.value) : null)); +const ownerIncomeLabel = computed(() => (order.value?.status === "completed" ? "实际到手" : "结账预计到手")); const canOpenDispute = computed(() => { if (!order.value || (!isOwner.value && !isRenter.value)) return false; return !["completed", "cancelled", "closed", "disputing", "checkout_disputing", "abnormal"].includes(order.value.status); @@ -510,6 +513,18 @@ function money(value: unknown) { return `${roundMoney(readNumber(value))}`; } +function orderRentAmount(item: Order) { + if (item.owner_id === session.userId) return Number(item.owner_rent_amount ?? item.display_amount ?? 0); + if (item.renter_id === session.userId) return Number(item.rent_amount ?? item.display_amount ?? 0); + return Number(item.display_amount ?? 0); +} + +function ownerActualIncome(item: Order) { + if (item.owner_id !== session.userId) return null; + const value = item.checkout?.owner_income_amount; + return typeof value === "number" ? value : null; +} + function quantity(value: unknown) { const rounded = roundQuantity(readNumber(value)); return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2); @@ -593,7 +608,11 @@ function getStatusTagType(status: string) {
{{ orderAmountLabel }} - ¥{{ money(order.display_amount) }} + ¥{{ money(orderRentDisplayAmount) }} +
+
+ {{ ownerIncomeLabel }} + ¥{{ money(ownerIncomeDisplayAmount) }}
押金 @@ -1224,6 +1243,11 @@ function getStatusTagType(status: string) { font-weight: 700; } +.meta-value.income { + color: #16a34a; + font-weight: 700; +} + .action-card { padding: 16px; } diff --git a/frontend/src/features/orders/views/MobileOrdersView.vue b/frontend/src/features/orders/views/MobileOrdersView.vue index ff2918f..15d199b 100644 --- a/frontend/src/features/orders/views/MobileOrdersView.vue +++ b/frontend/src/features/orders/views/MobileOrdersView.vue @@ -116,6 +116,25 @@ function readError(error: unknown, fallback: string) { function money(value: unknown) { return Math.round(Number(value || 0)); } + +function isOwner(order: Order) { + return order.owner_id === session.userId; +} + +function amountLabel(order: Order) { + return isOwner(order) ? "预计租金" : "支付租金"; +} + +function orderRentAmount(order: Order) { + if (isOwner(order)) return Number(order.owner_rent_amount ?? order.display_amount ?? 0); + return Number(order.rent_amount ?? order.display_amount ?? 0); +} + +function ownerActualIncome(order: Order) { + if (!isOwner(order)) return null; + const value = order.checkout?.owner_income_amount; + return typeof value === "number" ? value : null; +} - + @@ -269,7 +282,11 @@ function getCountdownMinutes(order: Order) {
{{ amountLabel(order) }} - ¥{{ money(order.display_amount) }} + ¥{{ money(orderRentAmount(order)) }} +
+
+ 实际到手 + ¥{{ money(ownerActualIncome(order)) }}
押金 @@ -411,6 +428,12 @@ function getCountdownMinutes(order: Order) { color: #9ca3af; } +.amount-sub { + font-size: 12px; + color: #10b981; + font-weight: 600; +} + .status-cell { display: flex; flex-direction: column;