diff --git a/backend/internal/database/test_helper.go b/backend/internal/database/test_helper.go index 44d3358..6859f1d 100644 --- a/backend/internal/database/test_helper.go +++ b/backend/internal/database/test_helper.go @@ -4,6 +4,8 @@ import ( "fmt" "log" + "hfb_sys/backend/internal/model" + "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" @@ -31,3 +33,39 @@ func NewTestDBWithName(name string) *gorm.DB { } return db } + +// MigrateListingLifecycleTestSchema 创建商品生命周期测试所需的共享表结构。 +func MigrateListingLifecycleTestSchema(db *gorm.DB) error { + return db.AutoMigrate( + &model.User{}, + &model.GameAccount{}, + &model.RentalListing{}, + &model.ListingStatusEvent{}, + ) +} + +// MigrateRentalTransactionTestSchema 在商品生命周期基础上创建订单、支付和交接测试所需的共享表结构。 +func MigrateRentalTransactionTestSchema(db *gorm.DB) error { + if err := MigrateListingLifecycleTestSchema(db); err != nil { + return err + } + return db.AutoMigrate( + &model.RentalOrder{}, + &model.PaymentOrder{}, + &model.Notification{}, + &model.HandoffRecord{}, + &model.OrderCheckout{}, + &model.AuditLog{}, + &model.ChatConversation{}, + &model.ChatParticipant{}, + &model.ChatMessage{}, + ) +} + +// MigrateRentalDisputeTestSchema 在交易链路基础上创建申诉测试所需的表结构。 +func MigrateRentalDisputeTestSchema(db *gorm.DB) error { + if err := MigrateRentalTransactionTestSchema(db); err != nil { + return err + } + return db.AutoMigrate(&model.Dispute{}) +} diff --git a/backend/internal/modules/dispute/repository_test.go b/backend/internal/modules/dispute/repository_test.go index 122d078..cd38ad7 100644 --- a/backend/internal/modules/dispute/repository_test.go +++ b/backend/internal/modules/dispute/repository_test.go @@ -5,31 +5,16 @@ import ( "testing" "time" + "hfb_sys/backend/internal/database" "hfb_sys/backend/internal/model" - "gorm.io/driver/sqlite" "gorm.io/gorm" - "gorm.io/gorm/logger" ) func setupDisputeTestDB(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.GameAccount{}, - &model.RentalListing{}, - &model.RentalOrder{}, - &model.OrderCheckout{}, - &model.HandoffRecord{}, - &model.Dispute{}, - &model.Notification{}, - ); err != nil { + db := database.NewTestDB() + if err := database.MigrateRentalDisputeTestSchema(db); err != nil { t.Fatalf("数据库迁移失败: %v", err) } return db diff --git a/backend/internal/modules/listing/service_test.go b/backend/internal/modules/listing/service_test.go index 3593c49..7cd6b8a 100644 --- a/backend/internal/modules/listing/service_test.go +++ b/backend/internal/modules/listing/service_test.go @@ -143,7 +143,7 @@ func TestPublishCooldownCanBeDisabled(t *testing.T) { func TestRepositoryCreateRejectsRecentOwnerPublish(t *testing.T) { db := database.NewTestDB() - if err := db.AutoMigrate(&model.User{}, &model.GameAccount{}, &model.RentalListing{}); err != nil { + if err := database.MigrateListingLifecycleTestSchema(db); err != nil { t.Fatalf("failed to migrate test db: %v", err) } user := model.User{Phone: "18800000000", RealnameStatus: "verified", Status: "active"} @@ -175,7 +175,7 @@ func TestRepositoryCreateRejectsRecentOwnerPublish(t *testing.T) { func TestRepositoryUpdateAllowsOfflineListingResubmit(t *testing.T) { db := database.NewTestDB() - if err := db.AutoMigrate(&model.GameAccount{}, &model.RentalListing{}); err != nil { + if err := database.MigrateListingLifecycleTestSchema(db); err != nil { t.Fatalf("failed to migrate test db: %v", err) } repo := NewRepository(db, nil) @@ -215,7 +215,7 @@ func TestRepositoryUpdateAllowsOfflineListingResubmit(t *testing.T) { func TestRepositoryRejectsCompletedListingOwnerMutations(t *testing.T) { db := database.NewTestDB() - if err := db.AutoMigrate(&model.GameAccount{}, &model.RentalListing{}); err != nil { + if err := database.MigrateListingLifecycleTestSchema(db); err != nil { t.Fatalf("failed to migrate test db: %v", err) } repo := NewRepository(db, nil) diff --git a/backend/internal/modules/order/repository_integration_test.go b/backend/internal/modules/order/repository_integration_test.go index 68bcf9a..959eaaa 100644 --- a/backend/internal/modules/order/repository_integration_test.go +++ b/backend/internal/modules/order/repository_integration_test.go @@ -4,34 +4,18 @@ import ( "testing" "time" + "hfb_sys/backend/internal/database" "hfb_sys/backend/internal/model" "gorm.io/datatypes" - "gorm.io/driver/sqlite" "gorm.io/gorm" - "gorm.io/gorm/logger" ) -// setupTestDB 创建测试数据库 +// setupOrderTestDB 创建订单测试数据库。 func setupOrderTestDB(t *testing.T) *gorm.DB { - 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.GameAccount{}, - &model.RentalListing{}, - &model.RentalOrder{}, - &model.PaymentOrder{}, - &model.Notification{}, - &model.HandoffRecord{}, - &model.OrderCheckout{}, - &model.AuditLog{}, - ); err != nil { + t.Helper() + db := database.NewTestDB() + if err := database.MigrateRentalTransactionTestSchema(db); err != nil { t.Fatalf("数据库迁移失败: %v", err) } diff --git a/backend/internal/modules/payment/repository_integration_test.go b/backend/internal/modules/payment/repository_integration_test.go index e028f4c..3540bc1 100644 --- a/backend/internal/modules/payment/repository_integration_test.go +++ b/backend/internal/modules/payment/repository_integration_test.go @@ -6,34 +6,18 @@ import ( "testing" "time" + "hfb_sys/backend/internal/database" "hfb_sys/backend/internal/model" ordermodule "hfb_sys/backend/internal/modules/order" - "gorm.io/driver/sqlite" "gorm.io/gorm" - "gorm.io/gorm/logger" ) // setupPaymentTestDB 创建测试数据库 func setupPaymentTestDB(t *testing.T) *gorm.DB { - 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.PaymentOrder{}, - &model.RentalOrder{}, - &model.RentalListing{}, - &model.GameAccount{}, - &model.User{}, - &model.ChatConversation{}, - &model.ChatParticipant{}, - &model.ChatMessage{}, - &model.Notification{}, - ); err != nil { + t.Helper() + db := database.NewTestDB() + if err := database.MigrateRentalTransactionTestSchema(db); err != nil { t.Fatalf("数据库迁移失败: %v", err) } diff --git a/frontend/src/features/admin/api/adminPush.ts b/frontend/src/features/admin/api/adminPush.ts index 92c98f6..4cd20c7 100644 --- a/frontend/src/features/admin/api/adminPush.ts +++ b/frontend/src/features/admin/api/adminPush.ts @@ -42,7 +42,8 @@ export interface UpdateRulePayload { // ── 渠道 ── export async function fetchPushChannels() { - const { data } = await apiClient.get>('/admin/push-channels') + const { data } = + await apiClient.get>('/admin/push-channels') return data.data.items } @@ -52,17 +53,24 @@ export async function createPushChannel(payload: CreateChannelPayload) { } export async function updatePushChannel(id: number, payload: UpdateChannelPayload) { - const { data } = await apiClient.put>(`/admin/push-channels/${id}`, payload) + const { data } = await apiClient.put>( + `/admin/push-channels/${id}`, + payload + ) return data.data } export async function deletePushChannel(id: number) { - const { data } = await apiClient.delete>(`/admin/push-channels/${id}`) + const { data } = await apiClient.delete>( + `/admin/push-channels/${id}` + ) return data.data } export async function testPushChannel(id: number) { - const { data } = await apiClient.post>(`/admin/push-channels/${id}/test`) + const { data } = await apiClient.post>( + `/admin/push-channels/${id}/test` + ) return data.data } diff --git a/frontend/src/features/admin/components/AnnouncementDialog.vue b/frontend/src/features/admin/components/AnnouncementDialog.vue index 771650d..1389da5 100644 --- a/frontend/src/features/admin/components/AnnouncementDialog.vue +++ b/frontend/src/features/admin/components/AnnouncementDialog.vue @@ -2,7 +2,14 @@ import { computed, ref, watch } from 'vue' import type { FormInstance, FormRules } from 'element-plus' import { ElInput, ElMessage } from 'element-plus' -import { Bell, Document, Operation, Picture, QuestionFilled, Warning } from '@element-plus/icons-vue' +import { + Bell, + Document, + Operation, + Picture, + QuestionFilled, + Warning, +} from '@element-plus/icons-vue' import type { Announcement } from '@/features/announcement' import type { CreateAnnouncementRequest, @@ -134,7 +141,9 @@ async function handleImageChange(event: Event) { uploadingImage.value = true try { const uploaded = await uploadAdminFile(file, 'announcement') - insertContentAtCursor(`![${imageAltText(uploaded.filename)}|w=720](${uploaded.medium_url || uploaded.url})`) + insertContentAtCursor( + `![${imageAltText(uploaded.filename)}|w=720](${uploaded.medium_url || uploaded.url})` + ) ElMessage.success('图片已插入') } catch (error) { ElMessage.error(readError(error, '图片上传失败')) @@ -205,7 +214,9 @@ function applyImageSettings() { return } - const widthMark = imageSettingsForm.value.useCustomWidth ? `|w=${imageSettingsForm.value.width}` : '' + const widthMark = imageSettingsForm.value.useCustomWidth + ? `|w=${imageSettingsForm.value.width}` + : '' const nextMarkdown = `![${escapeMarkdownImageText(alt)}${widthMark}](${url})` const content = formData.value.content formData.value.content = `${content.slice(0, range.start)}${nextMarkdown}${content.slice(range.end)}` @@ -358,7 +369,11 @@ function escapeMarkdownImageText(text: string) {
- +
diff --git a/frontend/src/features/admin/views/AdminDashboardView.vue b/frontend/src/features/admin/views/AdminDashboardView.vue index c54ef6a..ac6093a 100644 --- a/frontend/src/features/admin/views/AdminDashboardView.vue +++ b/frontend/src/features/admin/views/AdminDashboardView.vue @@ -142,7 +142,9 @@ function shortDate(date: string) {

账号上下架 - 按北京时间自然日 · 近 {{ dashboard.listing_daily.days }} 日 + 按北京时间自然日 · 近 {{ dashboard.listing_daily.days }} 日

@@ -167,7 +169,10 @@ function shortDate(date: string) {
{{ shortDate(row.date) }} @@ -230,7 +235,10 @@ function shortDate(date: string) {
{{ shortDate(row.date) }} diff --git a/frontend/src/features/admin/views/AdminListingDetailView.vue b/frontend/src/features/admin/views/AdminListingDetailView.vue index f5fc860..cab8685 100644 --- a/frontend/src/features/admin/views/AdminListingDetailView.vue +++ b/frontend/src/features/admin/views/AdminListingDetailView.vue @@ -9,11 +9,7 @@ import { fetchAdminUsers, type AdminUserItem } from '@/features/admin/api/adminU import { fetchAdminFileBlob } from '@/shared/api/files' import AuthImage from '@/shared/components/business/AuthImage.vue' import { adminPath } from '@/shared/utils/adminPath' -import { - formatCentWithSymbol, - formatMoneyWithSymbol, - roundMoney, -} from '@/shared/utils/money' +import { formatCentWithSymbol, formatMoneyWithSymbol, roundMoney } from '@/shared/utils/money' import { adminMarkListingAbnormal, adminOfflineListing, diff --git a/frontend/src/features/admin/views/AdminPaymentConfigsView.vue b/frontend/src/features/admin/views/AdminPaymentConfigsView.vue index 24019e8..35e70e3 100644 --- a/frontend/src/features/admin/views/AdminPaymentConfigsView.vue +++ b/frontend/src/features/admin/views/AdminPaymentConfigsView.vue @@ -1,7 +1,16 @@ -
+
@@ -265,7 +271,9 @@ function handleTabChange(tab: string) { 通知 - {{ notificationUnreadCount > 99 ? '99+' : notificationUnreadCount }} + {{ + notificationUnreadCount > 99 ? '99+' : notificationUnreadCount + }} @@ -280,10 +288,15 @@ function handleTabChange(tab: string) { :loading="notiSubmitting" :disabled="notificationUnreadCount === 0" @click="markAllNotiRead" - >全部已读 + >全部已读
-
+
@@ -307,13 +320,15 @@ function handleTabChange(tab: string) { v-if="item.biz_type === 'order' && item.biz_id" size="small" @click="router.push(`/orders/${item.biz_id}`)" - >查看订单 + >查看订单 已读 + >已读
diff --git a/frontend/src/features/listings/components/StringFilter.vue b/frontend/src/features/listings/components/StringFilter.vue index e701fef..3ff6bb7 100644 --- a/frontend/src/features/listings/components/StringFilter.vue +++ b/frontend/src/features/listings/components/StringFilter.vue @@ -6,7 +6,7 @@ import { computed } from 'vue' interface Props { label: string placeholder: string - modelValue: string[] + modelValue?: string[] options: string[] popoverProps: Record wide?: boolean @@ -72,7 +72,11 @@ function toggleOption(value: string) { :class="{ active: isSelected(item) }" @click="toggleOption(item)" > -
diff --git a/frontend/src/features/listings/composables/useHomeFilterQuery.ts b/frontend/src/features/listings/composables/useHomeFilterQuery.ts index a1841de..c94c514 100644 --- a/frontend/src/features/listings/composables/useHomeFilterQuery.ts +++ b/frontend/src/features/listings/composables/useHomeFilterQuery.ts @@ -52,7 +52,14 @@ export function readQueryNumber(query: LocationQuery | LocationQueryRaw, key: st export function readQueryCSV(query: LocationQuery | LocationQueryRaw, key: string): string[] { const raw = readQueryString(query, key) if (!raw) return [] - return [...new Set(raw.split(',').map(item => item.trim()).filter(Boolean))] + return [ + ...new Set( + raw + .split(',') + .map(item => item.trim()) + .filter(Boolean) + ), + ] } /** 将多选数组序列化为 CSV;空数组返回 undefined 以便从 query 中省略。 */ diff --git a/frontend/src/features/listings/composables/useHomeFilters.ts b/frontend/src/features/listings/composables/useHomeFilters.ts index 2a59584..a7c7a81 100644 --- a/frontend/src/features/listings/composables/useHomeFilters.ts +++ b/frontend/src/features/listings/composables/useHomeFilters.ts @@ -134,11 +134,7 @@ export function useHomeFilters( } function setStringFilter(key: StringFilterKey, value: string | string[]) { - filters[key] = Array.isArray(value) - ? uniqueOptions(value) - : value.trim() - ? [value.trim()] - : [] + filters[key] = Array.isArray(value) ? uniqueOptions(value) : value.trim() ? [value.trim()] : [] closeFilterPopover() } diff --git a/frontend/src/features/listings/views/MobileHomeView.vue b/frontend/src/features/listings/views/MobileHomeView.vue index 5204947..8fd7ff1 100644 --- a/frontend/src/features/listings/views/MobileHomeView.vue +++ b/frontend/src/features/listings/views/MobileHomeView.vue @@ -49,10 +49,7 @@ import { hasAcceleratedSaleRatio, hasGiftResources, } from '@/shared/utils/listingDisplay' -import { - buildMobileHomeListSignature, - useMobileHomeCacheStore, -} from '@/stores/mobileHomeCache' +import { buildMobileHomeListSignature, useMobileHomeCacheStore } from '@/stores/mobileHomeCache' import { useSessionStore } from '@/stores/session' const router = useRouter() @@ -85,7 +82,6 @@ const mobilePageSize = 10 let listingRequestSeq = 0 let searchDebounceTimer: ReturnType | null = null - const sortOptions = [ { key: 'recommended', label: '综合推荐' }, { key: 'coinDesc', label: '哈夫币' }, @@ -734,254 +730,259 @@ syncMobileHomeQuery()