diff --git a/backend/internal/modules/chat/conversation.go b/backend/internal/modules/chat/conversation.go index 1d9171f..c94e485 100644 --- a/backend/internal/modules/chat/conversation.go +++ b/backend/internal/modules/chat/conversation.go @@ -87,8 +87,34 @@ func (r *Repository) FindConversation(ctx context.Context, principal Principal, return &dto, nil } func (r *Repository) FindOrderConversation(ctx context.Context, userID uint64, orderID uint64) (*ConversationDTO, error) { - var row conversationRow principal := Principal{Type: "user", ID: userID} + var order model.RentalOrder + if err := r.db.WithContext(ctx).First(&order, orderID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrConversationNotFound + } + return nil, err + } + + if order.ListingID > 0 { + var row conversationRow + err := r.conversationQuery(ctx, principal). + Where("c.listing_id = ? AND c.type = ?", order.ListingID, ConversationTypeListingGroup). + First(&row).Error + if err == nil { + participants, err := r.participants(ctx, row.ID) + if err != nil { + return nil, err + } + dto := row.toDTO(participants) + return &dto, nil + } + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + } + + var row conversationRow err := r.conversationQuery(ctx, principal).Where("c.order_id = ?", orderID).First(&row).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { diff --git a/backend/internal/modules/chat/conversation_test.go b/backend/internal/modules/chat/conversation_test.go new file mode 100644 index 0000000..237c147 --- /dev/null +++ b/backend/internal/modules/chat/conversation_test.go @@ -0,0 +1,152 @@ +package chat + +import ( + "testing" + "time" + + "hfb_sys/backend/internal/model" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +func setupConversationTestDB(t *testing.T) *gorm.DB { + t.Helper() + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + t.Fatalf("无法创建测试数据库: %v", err) + } + if err := db.AutoMigrate( + &model.User{}, + &model.RentalOrder{}, + &model.ChatConversation{}, + &model.ChatParticipant{}, + &model.ChatMessage{}, + ); err != nil { + t.Fatalf("数据库迁移失败: %v", err) + } + return db +} + +func TestFindOrderConversationPrefersListingGroup(t *testing.T) { + db := setupConversationTestDB(t) + repo := NewRepository(db, nil) + now := time.Date(2026, 6, 18, 12, 0, 0, 0, time.UTC) + + owner := model.User{Phone: "13800001001", Nickname: "号主"} + renter := model.User{Phone: "13800001002", Nickname: "租客"} + if err := db.Create(&owner).Error; err != nil { + t.Fatalf("创建号主失败: %v", err) + } + if err := db.Create(&renter).Error; err != nil { + t.Fatalf("创建租客失败: %v", err) + } + + order := model.RentalOrder{ + OrderNo: "ORDER-LISTING-GROUP", + ListingID: 101, + AccountID: 201, + OwnerID: owner.ID, + RenterID: renter.ID, + Status: "renting", + } + if err := db.Create(&order).Error; err != nil { + t.Fatalf("创建订单失败: %v", err) + } + orderConv := model.ChatConversation{ + OrderID: &order.ID, + Type: ConversationTypeOrderGroup, + Title: "旧订单群", + Status: "active", + } + listingConv := model.ChatConversation{ + ListingID: &order.ListingID, + Type: ConversationTypeListingGroup, + Title: "发布群", + Status: "active", + } + if err := db.Create(&orderConv).Error; err != nil { + t.Fatalf("创建订单群失败: %v", err) + } + if err := db.Create(&listingConv).Error; err != nil { + t.Fatalf("创建发布群失败: %v", err) + } + participants := []model.ChatParticipant{ + {ConversationID: orderConv.ID, ParticipantType: "user", ParticipantID: order.RenterID, Role: "renter", JoinedAt: now}, + {ConversationID: listingConv.ID, ParticipantType: "user", ParticipantID: order.RenterID, Role: "renter", JoinedAt: now}, + } + if err := db.Create(&participants).Error; err != nil { + t.Fatalf("创建成员失败: %v", err) + } + + dto, err := repo.FindOrderConversation(t.Context(), order.RenterID, order.ID) + if err != nil { + t.Fatalf("查询订单会话失败: %v", err) + } + if dto.ID != listingConv.ID { + t.Fatalf("会话 ID = %d, want 发布群 %d", dto.ID, listingConv.ID) + } + if dto.Type != ConversationTypeListingGroup { + t.Fatalf("会话类型 = %q, want %q", dto.Type, ConversationTypeListingGroup) + } +} + +func TestFindOrderConversationFallsBackToOrderGroup(t *testing.T) { + db := setupConversationTestDB(t) + repo := NewRepository(db, nil) + now := time.Date(2026, 6, 18, 12, 0, 0, 0, time.UTC) + + owner := model.User{Phone: "13800001003", Nickname: "号主"} + renter := model.User{Phone: "13800001004", Nickname: "租客"} + if err := db.Create(&owner).Error; err != nil { + t.Fatalf("创建号主失败: %v", err) + } + if err := db.Create(&renter).Error; err != nil { + t.Fatalf("创建租客失败: %v", err) + } + + order := model.RentalOrder{ + OrderNo: "ORDER-LEGACY-GROUP", + ListingID: 102, + AccountID: 202, + OwnerID: owner.ID, + RenterID: renter.ID, + Status: "renting", + } + if err := db.Create(&order).Error; err != nil { + t.Fatalf("创建订单失败: %v", err) + } + orderConv := model.ChatConversation{ + OrderID: &order.ID, + Type: ConversationTypeOrderGroup, + Title: "旧订单群", + Status: "active", + } + if err := db.Create(&orderConv).Error; err != nil { + t.Fatalf("创建订单群失败: %v", err) + } + participant := model.ChatParticipant{ + ConversationID: orderConv.ID, + ParticipantType: "user", + ParticipantID: order.RenterID, + Role: "renter", + JoinedAt: now, + } + if err := db.Create(&participant).Error; err != nil { + t.Fatalf("创建成员失败: %v", err) + } + + dto, err := repo.FindOrderConversation(t.Context(), order.RenterID, order.ID) + if err != nil { + t.Fatalf("查询订单会话失败: %v", err) + } + if dto.ID != orderConv.ID { + t.Fatalf("会话 ID = %d, want 订单群 %d", dto.ID, orderConv.ID) + } + if dto.Type != ConversationTypeOrderGroup { + t.Fatalf("会话类型 = %q, want %q", dto.Type, ConversationTypeOrderGroup) + } +} diff --git a/frontend/src/features/seller/composables/usePublishForm.ts b/frontend/src/features/seller/composables/usePublishForm.ts index 92d4f20..7c507ef 100644 --- a/frontend/src/features/seller/composables/usePublishForm.ts +++ b/frontend/src/features/seller/composables/usePublishForm.ts @@ -47,6 +47,7 @@ const multiScreenshotKeys = new Set(['tencentSecurity', 'skin']) interface UsePublishFormOptions { draftKey: string submitSuccessPath: string + listingGroupChatPathPrefix?: string persistBeforeUnload?: boolean confirmReset?: () => Promise notifySuccess: (message: string) => void @@ -701,7 +702,8 @@ export function usePublishForm(options: UsePublishFormOptions) { ) // 新建发布成功后,若已自动建立发布群,跳进该群让号主立即看到欢迎语+二维码 if (!isEditMode.value && listing.listing_group_conversation_id) { - await router.push(`/messages/${listing.listing_group_conversation_id}`) + const chatPathPrefix = options.listingGroupChatPathPrefix || '/messages/' + await router.push(`${chatPathPrefix}${listing.listing_group_conversation_id}`) } else { await router.push(options.submitSuccessPath) } diff --git a/frontend/src/features/seller/views/MobileSellerListingCreateView.vue b/frontend/src/features/seller/views/MobileSellerListingCreateView.vue index 522fcdc..e13c752 100644 --- a/frontend/src/features/seller/views/MobileSellerListingCreateView.vue +++ b/frontend/src/features/seller/views/MobileSellerListingCreateView.vue @@ -91,6 +91,7 @@ const { } = usePublishForm({ draftKey: 'hfb.mobile.publish.draft', submitSuccessPath: '/m/profile', + listingGroupChatPathPrefix: '/m/chats/', async confirmReset() { await showDialog({ title: '重置发布内容', diff --git a/frontend/src/features/seller/views/SellerListingCreateView.vue b/frontend/src/features/seller/views/SellerListingCreateView.vue index fbc1c49..e1ca8fd 100644 --- a/frontend/src/features/seller/views/SellerListingCreateView.vue +++ b/frontend/src/features/seller/views/SellerListingCreateView.vue @@ -100,6 +100,7 @@ const { } = usePublishForm({ draftKey: 'hfb.pc.publish.draft', submitSuccessPath: '/seller/listings', + listingGroupChatPathPrefix: '/messages/', persistBeforeUnload: true, async confirmReset() { await ElMessageBox.confirm('将清空当前填写内容和本地草稿。', '重置发布内容', {