Spaces:
Running
Running
Upload static/match_detail_v6.js with huggingface_hub
Browse files- static/match_detail_v6.js +212 -0
static/match_detail_v6.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// === VNEWS — Match Detail UI (v6-final)
|
| 2 |
+
(function(){
|
| 3 |
+
if(window._mdLoadedV6) return;
|
| 4 |
+
window._mdLoadedV6 = true;
|
| 5 |
+
|
| 6 |
+
if(!document.getElementById('mdv6-css')){
|
| 7 |
+
const s=document.createElement('style');
|
| 8 |
+
s.id='mdv6-css';
|
| 9 |
+
s.textContent=`
|
| 10 |
+
.mdv6-sec{margin-bottom:14px}
|
| 11 |
+
.mdv6-sec-title{font-size:12px;font-weight:800;color:#5cb87a;margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid #2a2a2a}
|
| 12 |
+
.mdv6-header{background:linear-gradient(135deg,#1a2a1f,#0d1117);border:1px solid #2d8659;border-radius:10px;padding:12px;text-align:center;margin-bottom:12px}
|
| 13 |
+
.mdv6-teams{display:flex;align-items:center;justify-content:center;gap:12px}
|
| 14 |
+
.mdv6-team{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1}
|
| 15 |
+
.mdv6-team img{width:44px;height:44px;object-fit:contain;background:#111;border-radius:50%;padding:4px;border:2px solid #333}
|
| 16 |
+
.mdv6-vs .mdv6-score{font-size:26px;font-weight:900;color:#f0c040}
|
| 17 |
+
.mdv6-vs .mdv6-status{font-size:9px;font-weight:700;padding:2px 10px;border-radius:4px}
|
| 18 |
+
.mdv6-vs .mdv6-status.finished{background:#2d8659;color:#a8ffb8}
|
| 19 |
+
.mdv6-vs .mdv6-status.live{background:#c0392b;color:#fff;animation:mdv6pulse 1.5s infinite}
|
| 20 |
+
.mdv6-tabs{display:flex;gap:4px;margin-bottom:10px}
|
| 21 |
+
.mdv6-tab-btn{flex:1;padding:9px;border:none;border-radius:6px;font-size:11px;font-weight:700;cursor:pointer}
|
| 22 |
+
.mdv6-tab-btn.active{background:#2d8659;color:#fff}
|
| 23 |
+
.mdv6-tab-btn:not(.active){background:#1a1a1a;color:#888}
|
| 24 |
+
.mdv6-tab-content{display:none}
|
| 25 |
+
.mdv6-tab-content.active{display:block}
|
| 26 |
+
.mdv6-timeline{}
|
| 27 |
+
.mdv6-period{font-size:11px;font-weight:800;color:#5cb87a;padding:6px 0;border-bottom:1px solid #2a2a2a;margin-bottom:2px}
|
| 28 |
+
.mdv6-ev{display:flex;align-items:flex-start;gap:8px;padding:7px 0;border-bottom:1px solid #1a1a1a}
|
| 29 |
+
.mdv6-ev-time{font-size:11px;font-weight:800;color:#f0c040;min-width:40px;text-align:center}
|
| 30 |
+
.mdv6-ev-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center}
|
| 31 |
+
.mdv6-ev-body{flex:1}
|
| 32 |
+
.mdv6-ev-team{font-size:8px;font-weight:800;padding:1px 5px;border-radius:3px;margin-right:4px}
|
| 33 |
+
.mdv6-ev-team.home{background:#1a3a2a;color:#5cb87a}
|
| 34 |
+
.mdv6-ev-team.away{background:#3a2a1a;color:#e85d04}
|
| 35 |
+
.mdv6-ev-title{font-size:12px;color:#fff;font-weight:700}
|
| 36 |
+
.mdv6-ev-detail{font-size:11px;color:#999;margin-top:3px}
|
| 37 |
+
.mdv6-stat-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:11px;border-bottom:1px solid #1a1a1a}
|
| 38 |
+
.mdv6-stat-home{width:36px;text-align:right;color:#5cb87a;font-weight:700}
|
| 39 |
+
.mdv6-stat-label{flex:1;text-align:center;color:#999;font-size:10px}
|
| 40 |
+
.mdv6-stat-away{width:36px;text-align:left;color:#e85d04;font-weight:700}
|
| 41 |
+
.mdv6-pred{background:linear-gradient(135deg,#1a2a1f,#0d1117);border:1px solid #2d8659;border-radius:10px;padding:10px;margin-bottom:12px}
|
| 42 |
+
.mdv6-pred-title{font-size:9px;color:#5cb87a;font-weight:700;text-align:center;margin-bottom:8px}
|
| 43 |
+
.mdv6-pred-teams{display:flex;align-items:center;justify-content:center;gap:8px}
|
| 44 |
+
.mdv6-pred-vs{font-size:14px;font-weight:800;color:#f0c040}
|
| 45 |
+
.mdv6-recent-row{display:flex;align-items:center;gap:8px;padding:6px 8px;background:#111;border-radius:6px;margin-bottom:3px;font-size:11px}
|
| 46 |
+
.mdv6-recent-date{font-size:9px;color:#888;min-width:55px}
|
| 47 |
+
.mdv6-recent-score{font-size:11px;font-weight:800;color:#f0c040;min-width:36px;text-align:center}
|
| 48 |
+
.mdv6-no-data{text-align:center;color:#666;padding:16px 8px;font-size:12px}
|
| 49 |
+
.mdv6-warn{color:#f0c040;font-size:12px;padding:8px;background:#2a2a1a;border-radius:6px}
|
| 50 |
+
.mdv6-source{font-size:9px;color:#5cb87a;text-align:center;padding:3px;background:#1a2a1f;border-radius:4px;margin-bottom:8px}
|
| 51 |
+
@keyframes mdv6spin{to{transform:rotate(360deg)}}
|
| 52 |
+
@keyframes mdv6pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
| 53 |
+
`;
|
| 54 |
+
document.head.appendChild(s);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML;}
|
| 58 |
+
|
| 59 |
+
// Hook: override loadMatchTab to use fast endpoint
|
| 60 |
+
window._origLoadMatchTab = window.loadMatchTab;
|
| 61 |
+
window.loadMatchTab = async function(tab){
|
| 62 |
+
if(tab!=='detail'){
|
| 63 |
+
if(window._origLoadMatchTab) return window._origLoadMatchTab.call(this, tab);
|
| 64 |
+
return;
|
| 65 |
+
}
|
| 66 |
+
document.querySelectorAll('.mo-tab').forEach(t=>t.classList.remove('active'));
|
| 67 |
+
const el=document.getElementById('mo-body');
|
| 68 |
+
if(!el)return;
|
| 69 |
+
el.innerHTML='<div class="mdv6-no-data"><div style="display:inline-block;width:20px;height:20px;border:2px solid #333;border-top-color:#5cb87a;border-radius:50%;animation:mdv6spin .8s linear infinite"></div><div style="margin-top:8px">Đang tải...</div></div>';
|
| 70 |
+
|
| 71 |
+
const eventId=window._currentEventId;
|
| 72 |
+
let matchUrl = window._currentMatchUrl;
|
| 73 |
+
if(!eventId){el.innerHTML='<div class="mdv6-warn">⚠️ Không tìm thấy mã trận đấu.</div>';return;}
|
| 74 |
+
|
| 75 |
+
// Auto-fetch slug if missing (for featured matches clicked without URL)
|
| 76 |
+
if(!matchUrl && eventId){
|
| 77 |
+
try{
|
| 78 |
+
matchUrl = 'https://bongda.com.vn/tran-dau/' + eventId;
|
| 79 |
+
// Store for next time
|
| 80 |
+
window._currentMatchUrl = matchUrl;
|
| 81 |
+
}catch(e){}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
try{
|
| 85 |
+
const url = '/api/match/' + eventId + '/detail' + (matchUrl ? '?url=' + encodeURIComponent(matchUrl) : '');
|
| 86 |
+
const r = await fetch(url, {signal: AbortSignal.timeout(25000)});
|
| 87 |
+
const data = await r.json();
|
| 88 |
+
if(data.found && data.info && data.info.home_team){
|
| 89 |
+
el.innerHTML = '<div class="mdv6-source">📡 Dữ liệu server-side nhanh</div>';
|
| 90 |
+
renderMatchDetail(el, data);
|
| 91 |
+
}else{
|
| 92 |
+
el.innerHTML = '<div class="mdv6-warn">⚠️ Không thể tải dữ liệu.<br>Event ID: ' + esc(String(eventId)) + '<br><a href="https://bongda.com.vn/tran-dau/' + eventId + '" target="_blank">Xem trên bongda.com.vn</a></div>';
|
| 93 |
+
}
|
| 94 |
+
}catch(e){
|
| 95 |
+
el.innerHTML = '<div class="mdv6-warn">⚠️ Lỗi: ' + esc(e.message) + '</div>';
|
| 96 |
+
}
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
function renderMatchDetail(el, data){
|
| 100 |
+
const info=data.info||{};
|
| 101 |
+
let h='';
|
| 102 |
+
|
| 103 |
+
h+='<div class="mdv6-header"><div class="mdv6-teams">';
|
| 104 |
+
h+='<div class="mdv6-team">';
|
| 105 |
+
if(info.home_logo)h+='<img src="' + esc(info.home_logo) + '" alt="' + esc(info.home_team) + '">';
|
| 106 |
+
h+='<span>' + esc(info.home_team) + '</span></div>';
|
| 107 |
+
h+='<div class="mdv6-vs">';
|
| 108 |
+
if(info.score)h+='<span class="mdv6-score">' + esc(info.score) + '</span>';
|
| 109 |
+
if(info.status_label){
|
| 110 |
+
const sl=info.status_label;let cls='finished';
|
| 111 |
+
if(sl==='LIVE'||sl==='H1'||sl==='H2')cls='live';
|
| 112 |
+
h+='<span class="mdv6-status ' + cls + '">' + esc(sl) + '</span>';
|
| 113 |
+
}
|
| 114 |
+
h+='</div><div class="mdv6-team">';
|
| 115 |
+
if(info.away_logo)h+='<img src="' + esc(info.away_logo) + '" alt="' + esc(info.away_team) + '">';
|
| 116 |
+
h+='<span>' + esc(info.away_team) + '</span></div></div></div>';
|
| 117 |
+
|
| 118 |
+
const hasEvents=data.events&&data.events.length>0;
|
| 119 |
+
const hasStats=data.h2h_stats_parsed||data.prediction||data.recent_matches;
|
| 120 |
+
|
| 121 |
+
if(hasStats||hasEvents){
|
| 122 |
+
h+='<div class="mdv6-tabs">';
|
| 123 |
+
if(hasStats)h+='<button class="mdv6-tab-btn active" onclick="window._mdv6ShowTab(\'stats\',this)">📊 Thống kê</button>';
|
| 124 |
+
if(hasEvents)h+='<button class="mdv6-tab-btn" onclick="window._mdv6ShowTab(\'events\',this)">📢 Diễn biến</button>';
|
| 125 |
+
h+='</div>';
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
if(hasStats){
|
| 129 |
+
h+='<div id="mdv6-tab-stats" class="mdv6-tab-content" style="display:' + (hasEvents?'none':'block') + '">';
|
| 130 |
+
if(data.h2h_stats_parsed){
|
| 131 |
+
h+='<div class="mdv6-sec"><div class="mdv6-sec-title">📊 Tỷ lệ đối đầu</div>';
|
| 132 |
+
for(const label in data.h2h_stats_parsed){
|
| 133 |
+
const sv=data.h2h_stats_parsed[label];
|
| 134 |
+
h+='<div class="mdv6-stat-row"><span class="mdv6-stat-home">' + esc(sv.home) + '</span><span class="mdv6-stat-label">' + esc(label) + '</span><span class="mdv6-stat-away">' + esc(sv.away) + '</span></div>';
|
| 135 |
+
}
|
| 136 |
+
h+='</div>';
|
| 137 |
+
}
|
| 138 |
+
if(data.prediction){
|
| 139 |
+
h+='<div class="mdv6-pred"><div class="mdv6-pred-title">🎯 Dự đoán</div><div class="mdv6-pred-teams">';
|
| 140 |
+
h+='<span>' + esc(data.prediction.home_name || '') + '</span><span class="mdv6-pred-vs">' + esc(data.prediction.result || 'VS') + '</span><span>' + esc(data.prediction.away_name || '') + '</span></div></div>';
|
| 141 |
+
}
|
| 142 |
+
if(data.recent_matches){
|
| 143 |
+
h+='<div class="mdv6-sec"><div class="mdv6-sec-title">📋 Kết quả gần nhất</div>';
|
| 144 |
+
data.recent_matches.slice(0,6).forEach(m=>{
|
| 145 |
+
h+='<div class="mdv6-recent-row"><span class="mdv6-recent-date">' + esc(m.date) + '</span><span>' + esc(m.home) + '</span><span class="mdv6-recent-score">' + esc(m.score) + '</span><span>' + esc(m.away) + '</span></div>';
|
| 146 |
+
});
|
| 147 |
+
h+='</div>';
|
| 148 |
+
}
|
| 149 |
+
h+='</div>';
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
if(hasEvents){
|
| 153 |
+
h+='<div id="mdv6-tab-events" class="mdv6-tab-content" style="display:' + (hasStats?'none':'block') + '">';
|
| 154 |
+
h+='<div class="mdv6-sec"><div class="mdv6-sec-title">📢 Diễn biến trận đấu</div><div class="mdv6-timeline">';
|
| 155 |
+
let lastPeriod='';
|
| 156 |
+
data.events.forEach(ev=>{
|
| 157 |
+
if(ev.period&&ev.period!==lastPeriod){
|
| 158 |
+
h+='<div class="mdv6-period">' + esc(ev.period) + '</div>';
|
| 159 |
+
lastPeriod=ev.period;
|
| 160 |
+
}
|
| 161 |
+
const isHome=ev.team==='home';
|
| 162 |
+
const badge=isHome?'<span class="mdv6-ev-team home">HOME</span>':'<span class="mdv6-ev-team away">AWAY</span>';
|
| 163 |
+
let icon='', title='', detail='';
|
| 164 |
+
|
| 165 |
+
if(ev.type==='goal'){icon='⚽';title='BÀN THẮNG';detail=esc(ev.players);}
|
| 166 |
+
else if(ev.type==='redcard'){icon='🟥';title='THỺ ĐỎ';detail=esc(ev.players);}
|
| 167 |
+
else if(ev.type==='yellowcard'){icon='🟨';title='THỺ VÀNG';detail=esc(ev.players);}
|
| 168 |
+
else if(ev.type==='substitution'){icon='↔️';title='THAY ĐỔI';detail=esc(ev.players);}
|
| 169 |
+
else{icon='•';detail=esc(ev.players);}
|
| 170 |
+
|
| 171 |
+
h+='<div class="mdv6-ev"><span class="mdv6-ev-time">' + esc(ev.time||'') + '</span><span class="mdv6-ev-icon">' + icon + '</span><div class="mdv6-ev-body">' + badge + ' <span class="mdv6-ev-title">' + title + '</span>';
|
| 172 |
+
if(detail)h+='<div class="mdv6-ev-detail">' + detail + '</div>';
|
| 173 |
+
h+='</div></div>';
|
| 174 |
+
});
|
| 175 |
+
h+='</div></div>';
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
el.innerHTML=h;
|
| 179 |
+
|
| 180 |
+
window._mdv6ShowTab=(tab,btn)=>{
|
| 181 |
+
document.querySelectorAll('.mdv6-tab-btn').forEach(b=>{b.classList.remove('active');b.style.background='#1a1a1a';b.style.color='#888';});
|
| 182 |
+
if(btn){btn.classList.add('active');btn.style.background='#2d8659';btn.style.color='#fff';}
|
| 183 |
+
const stats=document.getElementById('mdv6-tab-stats');
|
| 184 |
+
const events=document.getElementById('mdv6-tab-events');
|
| 185 |
+
if(stats)stats.style.display=tab==='stats'?'block':'none';
|
| 186 |
+
if(events)events.style.display=tab==='events'?'block':'none';
|
| 187 |
+
};
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
// Auto-extract match ID/slug on click
|
| 191 |
+
document.addEventListener('click',e=>{
|
| 192 |
+
const md=e.target.closest('.match-detail, .match-item');
|
| 193 |
+
if(!md)return;
|
| 194 |
+
const link=md.querySelector('a[href*="tran-dau"]');
|
| 195 |
+
if(link){
|
| 196 |
+
const href=link.getAttribute('href')||'';
|
| 197 |
+
const m=href.match(/\/tran-dau\/(\d+)\/(?:centre|preview)\/(.+)/);
|
| 198 |
+
if(m){
|
| 199 |
+
window._currentEventId=m[1];
|
| 200 |
+
window._currentSlug=m[2];
|
| 201 |
+
window._currentMatchUrl='https://bongda.com.vn' + href;
|
| 202 |
+
}else{
|
| 203 |
+
const m2=href.match(/\/tran-dau\/(\d+)/);
|
| 204 |
+
if(m2){
|
| 205 |
+
window._currentEventId=m2[1];
|
| 206 |
+
window._currentMatchUrl='https://bongda.com.vn' + href;
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
},true);
|
| 211 |
+
|
| 212 |
+
})();
|