From ef6737513266bc3b30e20a15cb234c6e7fcaa722 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 9 Jul 2026 23:55:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=95=86=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=AD=9B=E9=80=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/listing/dto.go | 4 + .../internal/modules/listing/handler_parse.go | 4 + backend/internal/modules/listing/query.go | 33 ++++++ .../admin/views/AdminListingsView.vue | 86 ++++++++++----- .../src/features/listings/api/listings.ts | 8 +- .../listings/views/ListingDetailView.vue | 32 ++++-- .../views/MobileListingDetailView.vue | 34 +++++- .../shared/components/business/AuthImage.vue | 5 + frontend/src/styles/admin.css | 102 ++++++++++++++---- 9 files changed, 252 insertions(+), 56 deletions(-) diff --git a/backend/internal/modules/listing/dto.go b/backend/internal/modules/listing/dto.go index 44eaaf6..127cdf5 100644 --- a/backend/internal/modules/listing/dto.go +++ b/backend/internal/modules/listing/dto.go @@ -81,6 +81,10 @@ type AdminListQuery struct { Load []string SkinGroup []string SkinName []string + MinCoin *float64 + MaxCoin *float64 + MinDeposit *float64 + MaxDeposit *float64 Limit int Page int PageSize int diff --git a/backend/internal/modules/listing/handler_parse.go b/backend/internal/modules/listing/handler_parse.go index c21b34c..be7105f 100644 --- a/backend/internal/modules/listing/handler_parse.go +++ b/backend/internal/modules/listing/handler_parse.go @@ -79,6 +79,10 @@ func parseAdminListQuery(c *gin.Context) (AdminListQuery, bool) { query.Load = parseCSVQuery(c.Query("load")) query.SkinGroup = parseCSVQuery(c.Query("skin_group")) query.SkinName = parseCSVQuery(firstNonEmpty(c.Query("skin_name"), c.Query("skin"))) + query.MinCoin = parseOptionalFloat(c.Query("min_coin")) + query.MaxCoin = parseOptionalFloat(c.Query("max_coin")) + query.MinDeposit = parseOptionalFloat(c.Query("min_deposit")) + query.MaxDeposit = parseOptionalFloat(c.Query("max_deposit")) return query, true } diff --git a/backend/internal/modules/listing/query.go b/backend/internal/modules/listing/query.go index 306ca1a..a486844 100644 --- a/backend/internal/modules/listing/query.go +++ b/backend/internal/modules/listing/query.go @@ -3,6 +3,7 @@ package listing import ( "context" "fmt" + "math" "strconv" "strings" "time" @@ -113,9 +114,41 @@ func (r *Repository) applyAdminListFilters(db *gorm.DB, query AdminListQuery) *g if query.ReviewStatus != "" { db = db.Where("l.review_status = ?", query.ReviewStatus) } + if query.MinCoin != nil { + db = db.Where( + "EXISTS (SELECT 1 FROM game_accounts AS coin_account WHERE coin_account.id = l.account_id AND coin_account.haf_coin_amount >= ?)", + coinMToAdminAmount(*query.MinCoin), + ) + } + if query.MaxCoin != nil { + db = db.Where( + "EXISTS (SELECT 1 FROM game_accounts AS coin_account WHERE coin_account.id = l.account_id AND coin_account.haf_coin_amount <= ?)", + coinMToAdminAmount(*query.MaxCoin), + ) + } + if query.MinDeposit != nil { + db = db.Where("l.deposit_amount_cent >= ?", yuanToAdminCent(*query.MinDeposit)) + } + if query.MaxDeposit != nil { + db = db.Where("l.deposit_amount_cent <= ?", yuanToAdminCent(*query.MaxDeposit)) + } return db } +func coinMToAdminAmount(value float64) int64 { + if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) { + return 0 + } + return int64(math.Round(value * 1000000)) +} + +func yuanToAdminCent(value float64) int64 { + if value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) { + return 0 + } + return int64(math.Round(value * 100)) +} + func (r *Repository) FindAdmin(ctx context.Context, listingID uint64) (*ListingDTO, error) { return r.findDTO(ctx, "l.id = ?", listingID) } diff --git a/frontend/src/features/admin/views/AdminListingsView.vue b/frontend/src/features/admin/views/AdminListingsView.vue index 62f813b..8f63564 100644 --- a/frontend/src/features/admin/views/AdminListingsView.vue +++ b/frontend/src/features/admin/views/AdminListingsView.vue @@ -36,6 +36,10 @@ const filters = reactive({ load: '', skin_group: '', skin_name: '', + min_coin: undefined, + max_coin: undefined, + min_deposit: undefined, + max_deposit: undefined, }) type FilterPopover = 'asset' | 'skin' @@ -148,6 +152,10 @@ function resetFilters() { filters.owner_id = '' filters.status = '' filters.review_status = '' + filters.min_coin = undefined + filters.max_coin = undefined + filters.min_deposit = undefined + filters.max_deposit = undefined clearAssetFilters(false) clearSkinFilters(false) void queryListings() @@ -551,32 +559,63 @@ function formatQuantity(value: number) {
- - + + - - + + - - + +
+ + - + +
+
+ +
+ + - + +
+
+ + @@ -585,20 +624,15 @@ function formatQuantity(value: number) { - - + + - + - + value !== '' && value !== undefined) + Object.entries(query).filter( + ([, value]) => value !== '' && value !== undefined && value !== null + ) ) const { data } = await apiClient.get>('/admin/listings', { params }) return normalizeAdminListingPage(data.data, query) diff --git a/frontend/src/features/listings/views/ListingDetailView.vue b/frontend/src/features/listings/views/ListingDetailView.vue index cb55ac8..8b2d8ce 100644 --- a/frontend/src/features/listings/views/ListingDetailView.vue +++ b/frontend/src/features/listings/views/ListingDetailView.vue @@ -131,6 +131,7 @@ const detailScreenshots = computed(() => { url, })) }) +const detailScreenshotUrls = computed(() => detailScreenshots.value.map(shot => shot.url)) function readGroupedScreenshots(item: Listing) { const groups = item.asset_summary?.screenshot_groups @@ -462,8 +463,20 @@ function handleToggleFavorite() { {{ detailScreenshots.length }} 张
-
- +
+
{{ shot.label }}
@@ -908,12 +921,20 @@ function handleToggleFavorite() { margin: 0; } -.detail-screenshot img { +.detail-screenshot-image { width: 100%; aspect-ratio: 16 / 10; - object-fit: cover; + overflow: hidden; border-radius: 12px; border: 1px solid #edf0f4; + background: #f8fafc; + cursor: zoom-in; +} + +.detail-screenshot-image :deep(.el-image__inner) { + width: 100%; + height: 100%; + object-fit: cover; } .detail-screenshot figcaption { @@ -996,8 +1017,7 @@ function handleToggleFavorite() { margin-bottom: 0; border: 1px solid rgba(245, 158, 11, 0.2); background: - linear-gradient(135deg, rgba(255, 251, 235, 0.96), rgba(255, 247, 237, 0.92)), - #fffbeb; + linear-gradient(135deg, rgba(255, 251, 235, 0.96), rgba(255, 247, 237, 0.92)), #fffbeb; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86); } diff --git a/frontend/src/features/listings/views/MobileListingDetailView.vue b/frontend/src/features/listings/views/MobileListingDetailView.vue index 48ecd10..f1ae552 100644 --- a/frontend/src/features/listings/views/MobileListingDetailView.vue +++ b/frontend/src/features/listings/views/MobileListingDetailView.vue @@ -133,6 +133,7 @@ const detailScreenshots = computed(() => { url, })) }) +const detailScreenshotUrls = computed(() => detailScreenshots.value.map(shot => shot.url)) function readGroupedScreenshots(item: Listing) { const groups = item.asset_summary?.screenshot_groups @@ -328,7 +329,9 @@ function handleToggleFavorite() { :source="detailScreenshots[0].url" :alt="getListingTitle(listing)" image-class="cover-img" + fit="cover" loading="eager" + :preview-src-list="detailScreenshotUrls" />
@@ -449,8 +452,20 @@ function handleToggleFavorite() {

账号截图

-
- +
+
{{ shot.label }}
@@ -630,6 +645,12 @@ function handleToggleFavorite() { } .cover-img { + width: 100%; + height: 100%; + cursor: zoom-in; +} + +.cover-img :deep(.el-image__inner) { width: 100%; height: 100%; object-fit: cover; @@ -876,11 +897,18 @@ function handleToggleFavorite() { .screenshot-thumb { width: 100%; aspect-ratio: 4 / 3; - object-fit: cover; + overflow: hidden; border-radius: 8px; + background: #f8fafc; cursor: pointer; } +.screenshot-thumb :deep(.el-image__inner) { + width: 100%; + height: 100%; + object-fit: cover; +} + .screenshot-item figcaption { margin-top: 4px; color: #6b7280; diff --git a/frontend/src/shared/components/business/AuthImage.vue b/frontend/src/shared/components/business/AuthImage.vue index bb1053f..7443051 100644 --- a/frontend/src/shared/components/business/AuthImage.vue +++ b/frontend/src/shared/components/business/AuthImage.vue @@ -14,6 +14,7 @@ const props = withDefaults( decoding?: 'async' | 'sync' | 'auto' previewSrcList?: string[] previewTeleported?: boolean + previewInitialIndex?: number fit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' imageStyle?: any }>(), @@ -25,6 +26,7 @@ const props = withDefaults( loading: 'lazy', decoding: 'async', previewTeleported: true, + previewInitialIndex: 0, } ) @@ -105,11 +107,14 @@ onBeforeUnmount(cleanup)