From e6407094bd61d5f9ce36cb9a5022bb201c40e7c5 Mon Sep 17 00:00:00 2001 From: renjue Date: Sat, 9 May 2026 17:37:48 +0800 Subject: [PATCH] Improve media detail UX and preserve search context. Render structured media details instead of raw JSON, keep search state when navigating back from detail, and surface HDHive link validation and fallback resource-page links for clearer troubleshooting. Co-authored-by: Cursor --- backend/adapters/hdhive_adapter.py | 20 +++-- frontend/src/style.css | 101 +++++++++++++++++++++++ frontend/src/views/MediaDetailPage.vue | 108 ++++++++++++++++++++++--- frontend/src/views/SearchPage.vue | 37 +++++++-- 4 files changed, 245 insertions(+), 21 deletions(-) diff --git a/backend/adapters/hdhive_adapter.py b/backend/adapters/hdhive_adapter.py index 8a28caf..e2386f2 100644 --- a/backend/adapters/hdhive_adapter.py +++ b/backend/adapters/hdhive_adapter.py @@ -40,6 +40,15 @@ def normalize_resource(search_data, unlock_data): resolution = (search_data or {}).get("video_resolution") source = (search_data or {}).get("source") subtitle_language = (search_data or {}).get("subtitle_language") + unlock_url = (unlock_data or {}).get("full_url") or (unlock_data or {}).get("url") or "" + media_url = (search_data or {}).get("media_url") or "" + detail_url = media_url + if not detail_url and (search_data or {}).get("media_slug"): + detail_url = f"{Config.HDHIVE_BASE_URL}/movie/{(search_data or {}).get('media_slug')}" + + validate_status = (search_data or {}).get("validate_status") + validate_message = (search_data or {}).get("validate_message") + return { "resourceTitle": (search_data or {}).get("title", ""), "quality": ", ".join(resolution) if isinstance(resolution, list) else "", @@ -50,11 +59,10 @@ def normalize_resource(search_data, unlock_data): if isinstance(subtitle_language, list) else "", "slug": (search_data or {}).get("slug", ""), - "unlockUrl": (unlock_data or {}).get("full_url") - or (unlock_data or {}).get("url") - or "", - "availability": "available" - if ((unlock_data or {}).get("full_url") or (unlock_data or {}).get("url")) - else "unknown", + "unlockUrl": unlock_url, + "detailUrl": detail_url, + "availability": "available" if unlock_url else ("index_only" if detail_url else "unknown"), + "validateStatus": validate_status, + "validateMessage": validate_message, "raw": {"searchData": search_data, "unlockData": unlock_data}, } diff --git a/frontend/src/style.css b/frontend/src/style.css index 24fef10..4fd955c 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -97,6 +97,12 @@ button:disabled { cursor: not-allowed; } +.secondary-btn { + border: 1px solid #d0d7de; + background: #fff; + color: #1f2328; +} + .grid { display: grid; grid-template-columns: 1fr 1fr; @@ -143,6 +149,16 @@ pre { color: #cf222e; } +.warning { + color: #9a6700; + background: #fff8c5; + border: 1px solid #d4a72c; + border-radius: 6px; + padding: 6px 8px; + margin: 8px 0 0; + font-size: 12px; +} + .log-time { color: #57606a; margin-left: 8px; @@ -178,6 +194,11 @@ pre { font-size: 13px; } +.poster-subtitle { + color: #57606a; + font-size: 12px; +} + .resource-list { list-style: none; margin: 0; @@ -193,8 +214,88 @@ pre { padding: 10px; } +.resource-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; + margin-top: 8px; +} + +.link-btn { + border: none; + background: none; + color: #1f6feb; + padding: 0; + text-decoration: underline; +} + +.link-btn:disabled { + text-decoration: none; +} + +.detail-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} + +.detail-layout { + display: grid; + grid-template-columns: 240px 1fr; + gap: 16px; + margin-top: 12px; +} + +.detail-poster, +.detail-no-poster { + width: 100%; + height: 340px; + border-radius: 10px; + border: 1px solid #d0d7de; +} + +.detail-poster { + object-fit: cover; +} + +.detail-no-poster { + display: flex; + align-items: center; + justify-content: center; + background: #f6f8fa; + color: #57606a; +} + +.detail-content h3 { + margin-top: 2px; + margin-bottom: 8px; +} + +.detail-subtitle { + margin-top: 0; + color: #57606a; +} + +.detail-overview { + margin-top: 12px; + white-space: pre-wrap; + word-break: break-word; +} + @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } + + .detail-layout { + grid-template-columns: 1fr; + } + + .detail-poster, + .detail-no-poster { + max-width: 280px; + margin: 0 auto; + } } diff --git a/frontend/src/views/MediaDetailPage.vue b/frontend/src/views/MediaDetailPage.vue index d094d01..62dc072 100644 --- a/frontend/src/views/MediaDetailPage.vue +++ b/frontend/src/views/MediaDetailPage.vue @@ -1,5 +1,5 @@