// ==UserScript== // @name 腾讯视频片库 // @namespace https://github.com/local/tx-unified-list // @version 1.8.1 // @description 腾讯视频 8 频道整合:标签栏+居中弹窗筛选、竖版手机3列/PC5列、横版手机2列/PC4列、PC限宽居中、自动加载访问https://v.qq.com/tv-series-list/v-index.html即可使用 // @author 失辛向南 // @license MIT // @match *://v.qq.com/tv-series-list/v-index.html* // @run-at document-idle // @noframes // @grant GM_addStyle // ==/UserScript== (function () { 'use strict'; // ==================== 频道配置 ==================== const CHANNELS = [ { id: '100173', name: '电影', params: 'sort=75' }, { id: '100113', name: '电视剧', params: 'sort=75' }, { id: '100109', name: '综艺', params: 'sort=75' }, { id: '100119', name: '动漫', params: 'sort=75' }, { id: '100150', name: '少儿', params: 'itype=75' }, { id: '120188', name: '短剧', params: 'attraction=1' }, { id: '100105', name: '纪录片', params: 'sort=75' }, { id: '100103', name: '体育', params: 'irace=11' }, ]; const BASE = 'https://v.qq.com/tv-series-list/v-index.html'; function channelUrl(c) { return BASE + '?filter_params=' + c.params + '&page_id=channel_list_second_page&channel_id=' + c.id; } function currentChannelId() { try { return new URL(location.href).searchParams.get('channel_id') || ''; } catch (e) { return ''; } } const state = { activeTab: null }; // ==================== 样式 ==================== const CSS = ` /* ---------- 自然滚动布局(不锁高度) ---------- */ html, body { margin: 0 !important; padding: 0 !important; width: 100% !important; background: #fff !important; } *, *::before, *::after { box-sizing: border-box !important; } /* ---------- 隐藏腾讯外壳 ---------- */ .top-bar-wrap, .left-bar-wrap, .v-left-nav-area, .top-nav-wrap, .search-wrap, #ssi-user-bar, .back-button, .tag-bar, #login_win, .web-login-container, #toast-container, .gatherable-layer, [class*="gatherable"] { display: none !important; } /* ---------- 底部遮挡清除 ---------- */ .screen-list__footer, .screen-list__loading, .lazy-list__footer, .lazy-list__loading, .filter-expand, .quick_pop_footer, .app-download-bar, .app-download, .download-bar, .bottom-guide, .guide-bottom, .v-bottom-bar, .bottom-bar-wrap, .mobile-tab-bar, .screen-app-download, [class*="bottom-banner"], [class*="app-promote"], [class*="download-app"], [class*="footer-fixed"], [class*="fixed-bottom"], [class*="bottom-fixed"], [class*="guide-bar"], [class*="openapp"], [class*="open-app"], [class*="float-bar"], [class*="floatbar"] { display: none !important; visibility: hidden !important; height: 0 !important; pointer-events: none !important; } /* ---------- 强制全宽,去掉左右偏移 ---------- */ #app, .web-page, .v-station-screen-content, .v-station-content, #root, #app-root, .screen-page, .screen-page__main-content, .screen-list, .content-wrap, vstation-screen-content { width: 100% !important; max-width: 100% !important; min-width: 0 !important; margin: 0 !important; padding-left: 0 !important; padding-right: 0 !important; height: auto !important; overflow: visible !important; display: block !important; } .screen-list { padding: 0 !important; background: #fff !important; position: relative !important; height: calc(100vh - 110px) !important; overflow-y: auto !important; overflow-x: hidden !important; } .grid-waterfall.screen-list__grid { display: block !important; padding: 8px 12px 24px !important; margin: 0 !important; } /* ---------- 顶部 #tx-header(吸顶) ---------- */ #tx-header { position: sticky; top: 0; z-index: 20; background: #fff; padding: 10px 12px 0; box-shadow: 0 1px 4px rgba(0,0,0,.06); text-align: left !important; font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif !important; color: #1a1a1a !important; } #tx-header .tx-title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin: 0 0 8px; display: flex !important; align-items: center !important; justify-content: flex-start !important; text-align: left !important; gap: 6px; } #tx-header .tx-title * { text-align: left !important; } #tx-channels { display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; gap: 8px; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; padding-bottom: 8px; } #tx-channels::-webkit-scrollbar { display: none; } #tx-channels button { flex: 0 0 auto; border: 0; background: #f0f0f3; color: #555; padding: 7px 16px; border-radius: 16px; font-size: 14px; cursor: pointer; white-space: nowrap; font-family: inherit !important; min-width: 0; line-height: 1.2; } #tx-channels button.active { background: #e6f4ff; color: #1a8cff; font-weight: 700; } /* ---------- 筛选标签栏(放在 header 里) ---------- */ .tx-filter-tabs { display: flex !important; flex-wrap: nowrap !important; flex-direction: row !important; gap: 8px !important; overflow-x: auto !important; padding: 8px 0 !important; background: #fff !important; align-items: center !important; scrollbar-width: none; border-top: 1px solid #f0f0f0; } .tx-filter-tabs::-webkit-scrollbar { display: none; } .tx-tab { flex: 0 0 auto; border: 0; background: #f0f0f3; color: #333; padding: 6px 14px; border-radius: 16px; font-size: 13px; cursor: pointer; white-space: nowrap; font-family: inherit !important; line-height: 1.2; } .tx-tab.tx-active { background: #1a8cff; color: #fff; font-weight: 600; } /* 隐藏原站筛选 DOM */ .filter-line { display: none !important; } /* ---------- 居中弹窗 ---------- */ .tx-filter-modal { position: fixed; inset: 0; z-index: 99999; } .tx-filter-mask { position: absolute; inset: 0; background: rgba(0,0,0,.45); } .tx-filter-panel { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); background: #fff; border-radius: 16px; padding: 20px; width: 320px; max-width: 90vw; max-height: 70vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,.2); } .tx-modal-title { font-size: 16px; font-weight: 700; color: #222; margin-bottom: 14px; } .tx-opt { display: inline-block; padding: 7px 14px; margin: 3px; border-radius: 16px; background: #f5f5f7; border: 0; cursor: pointer; font-size: 13px; color: #555; font-family: inherit !important; line-height: 1.2; } .tx-opt.sel { background: #1a8cff; color: #fff; font-weight: 600; } /* ---------- 卡片墙 grid ---------- */ .grid-waterfall__content, .grid-waterfall__content--web { display: grid !important; grid-template-columns: repeat(3, 1fr) !important; gap: 10px !important; padding: 0 !important; width: 100% !important; max-width: 100% !important; margin: 0 !important; justify-items: stretch !important; align-items: stretch !important; justify-content: stretch !important; } .screen-cover, .screen-cover--pc { width: 100% !important; margin: 0 !important; justify-self: stretch !important; border-radius: 8px !important; background: #fff !important; box-shadow: 0 1px 4px rgba(0,0,0,.08); display: flex !important; flex-direction: column !important; overflow: visible !important; height: auto !important; } .screen-cover .poster-view, .screen-cover .item-poster { width: 100% !important; height: auto !important; aspect-ratio: 2 / 3 !important; position: relative !important; border-radius: 8px 8px 0 0 !important; overflow: hidden !important; } .screen-cover .poster-view__img { width: 100% !important; height: 100% !important; object-fit: cover !important; display: block !important; } .screen-cover .item-info { width: 100% !important; padding: 6px 8px 10px !important; overflow: visible !important; } .screen-cover .item-title { font-size: 13px !important; font-weight: 600 !important; color: #222 !important; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; line-height: 1.3; } .screen-cover .item-secondTitle { font-size: 11px !important; color: #999 !important; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; line-height: 1.3; } .screen-cover .item-tags { display: none !important; } /* ---------- 横版频道 ---------- */ body.tx-landscape .grid-waterfall__content, body.tx-landscape .grid-waterfall__content--web { grid-template-columns: repeat(2, 1fr) !important; gap: 12px !important; } body.tx-landscape .screen-cover .poster-view, body.tx-landscape .screen-cover .item-poster { aspect-ratio: 16 / 9 !important; } body.tx-landscape .screen-cover .item-info { padding: 8px 10px 12px !important; height: auto !important; flex: 1; } body.tx-landscape .screen-cover .item-title { font-size: 14px !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; display: block !important; line-height: 1.35; } body.tx-landscape .screen-cover .item-secondTitle { margin-top: 4px; overflow: hidden !important; text-overflow: ellipsis; display: block; } /* ---------- PC 宽屏 ---------- */ @media (min-width: 769px) { .grid-waterfall__content, .grid-waterfall__content--web { grid-template-columns: repeat(5, 1fr) !important; gap: 16px !important; } body.tx-landscape .grid-waterfall__content, body.tx-landscape .grid-waterfall__content--web { grid-template-columns: repeat(4, 1fr) !important; gap: 18px !important; } .grid-waterfall.screen-list__grid { padding: 16px 24px !important; } #tx-header { padding: 12px 24px 0 !important; } #tx-header .tx-title { font-size: 17px !important; } #tx-channels button { padding: 8px 20px !important; font-size: 14px !important; } .tx-filter-tabs { padding: 10px 0 !important; } } /* ---------- 窄屏手机 ---------- */ @media (max-width: 600px) { #tx-header { padding: 8px 8px 0 !important; } #tx-channels button { padding: 6px 12px; font-size: 13px; } .grid-waterfall.screen-list__grid { padding: 6px !important; } .grid-waterfall__content, .grid-waterfall__content--web { gap: 6px !important; } body.tx-landscape .grid-waterfall__content, body.tx-landscape .grid-waterfall__content--web { gap: 8px !important; } body.tx-landscape .screen-cover .item-title { font-size: 13px !important; } } `; // ==================== 注入 ==================== function injectViewport() { let vp = document.querySelector('meta[name="viewport"]'); if (!vp) { vp = document.createElement('meta'); vp.name = 'viewport'; (document.head || document.documentElement).appendChild(vp); } vp.content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'; } function injectCSS() { if (typeof GM_addStyle === 'function') GM_addStyle(CSS); else { const s = document.createElement('style'); s.textContent = CSS; (document.head || document.documentElement).appendChild(s); } } // ==================== 顶部频道栏 ==================== function buildHeader() { if (document.getElementById('tx-header')) return; const activeId = currentChannelId(); const header = document.createElement('div'); header.id = 'tx-header'; let btns = ''; CHANNELS.forEach(c => { btns += ``; }); header.innerHTML = `
腾讯视频 · 全频道榜单
${btns}
`; const main = document.querySelector('.screen-page__main-content') || document.querySelector('.screen-page') || document.getElementById('app-root'); if (!main) return; main.insertBefore(header, main.firstChild); header.querySelectorAll('#tx-channels button').forEach(btn => { btn.addEventListener('click', () => { const c = CHANNELS.find(x => x.id === btn.dataset.id); if (c) location.href = channelUrl(c); }); }); const act = header.querySelector('#tx-channels button.active'); if (act && act.scrollIntoView) act.scrollIntoView({ inline: 'center', block: 'nearest' }); } // ==================== 横竖版自动识别 ==================== function detectMode() { const imgs = document.querySelectorAll('.poster-view__img'); // 找第一张已加载、有 naturalWidth 的图 let chosen = null; imgs.forEach(im => { if (!chosen && im.complete && im.naturalWidth > 50) chosen = im; }); if (!chosen && imgs.length > 0) chosen = imgs[0]; if (!chosen) { setTimeout(detectMode, 500); return; } const apply = () => { const w = chosen.naturalWidth, h = chosen.naturalHeight; if (w && h) { const ratio = w / h; if (ratio > 1.25) document.body.classList.add('tx-landscape'); else document.body.classList.remove('tx-landscape'); } }; if (chosen.complete && chosen.naturalWidth) apply(); else chosen.addEventListener('load', apply, { once: true }); setTimeout(apply, 1000); setTimeout(apply, 2500); } // ==================== 自动加载更多 ==================== function autoFill() { const sl = document.querySelector('.screen-list'); if (!sl) return; const tryScroll = () => { if (sl.scrollHeight <= sl.clientHeight + 100) { sl.scrollTop = sl.scrollHeight; setTimeout(tryScroll, 1200); } }; setTimeout(tryScroll, 900); } // ==================== 兜底:干掉贴底的 fixed 浮层 ==================== function killBottomOverlays() { const vh = window.innerHeight; const vw = window.innerWidth; document.querySelectorAll('body *').forEach(el => { if (el.id === 'tx-header') return; if (el.closest && (el.closest('#tx-header') || el.closest('.tx-filter-modal'))) return; let cs; try { cs = getComputedStyle(el); } catch (e) { return; } if (cs.position !== 'fixed') return; const r = el.getBoundingClientRect(); if (r.height < 20 || r.height > vh * 0.5) return; if (r.width < vw * 0.6) return; if (r.bottom >= vh - 8 && r.top > vh * 0.5) { el.style.setProperty('display', 'none', 'important'); } }); } // ==================== ★ 弹窗是否打开 ==================== function isModalOpen() { const m = document.querySelector('.tx-filter-modal'); return !!(m && m.style.display !== 'none'); } // ==================== 筛选:标签栏(在 header 里)+ 居中弹窗 ==================== function buildFilterUI(force) { // ★ 弹窗打开时跳过重建,避免自动刷新把用户刚点开的弹窗关掉 if (!force && isModalOpen()) return; const sl = document.querySelector('.screen-list'); if (!sl) return; const lines = document.querySelectorAll('.filter-line'); if (!lines.length) return; // 保留当前激活 const prev = document.querySelector('.tx-tab.tx-active'); if (prev) state.activeTab = prev.textContent; // 清旧 document.querySelectorAll('.tx-filter-tabs, .tx-filter-modal').forEach(el => el.remove()); // 标签栏 const tabs = document.createElement('div'); tabs.className = 'tx-filter-tabs'; // 弹窗 const modal = document.createElement('div'); modal.className = 'tx-filter-modal'; modal.style.display = 'none'; modal.innerHTML = '
'; document.body.appendChild(modal); const panel = modal.querySelector('.tx-filter-panel'); modal.querySelector('.tx-filter-mask').addEventListener('click', () => { modal.style.display = 'none'; }); lines.forEach(line => { const col = line.querySelector(':scope > .filter-column'); if (!col) return; const label = col.textContent.trim(); const opts = line.querySelectorAll(':scope > .lazy-list__content .filter'); const tab = document.createElement('button'); tab.className = 'tx-tab'; tab.textContent = label; tab.addEventListener('click', () => { document.querySelectorAll('.tx-tab').forEach(t => t.classList.remove('tx-active')); tab.classList.add('tx-active'); state.activeTab = label; panel.innerHTML = '
' + label + '
'; opts.forEach(opt => { const b = document.createElement('button'); const isSel = opt.classList.contains('filter--selected') || opt.classList.contains('filter--selected--dark'); b.className = 'tx-opt' + (isSel ? ' sel' : ''); b.textContent = opt.textContent.trim(); b.addEventListener('click', () => { const want = b.textContent.trim(); let clicked = false; document.querySelectorAll('.filter-line').forEach(freshLine => { const freshCol = freshLine.querySelector(':scope > .filter-column'); if (!freshCol || freshCol.textContent.trim() !== label) return; freshLine.querySelectorAll(':scope > .lazy-list__content .filter').forEach(f => { if (f.textContent.trim() === want) { f.click(); clicked = true; } }); }); if (!clicked) { try { opt.click(); } catch (e) {} } modal.style.display = 'none'; // 先关,再重建 // 此时 isModalOpen() === false,force 也可不传 setTimeout(buildFilterUI, 60); setTimeout(buildFilterUI, 400); setTimeout(buildFilterUI, 1200); }); panel.appendChild(b); }); modal.style.display = 'block'; }); tabs.appendChild(tab); }); if (state.activeTab) { const target = [...tabs.querySelectorAll('.tx-tab')] .find(t => t.textContent === state.activeTab); if (target) target.classList.add('tx-active'); } // 放到 #tx-header 里 const header = document.getElementById('tx-header'); if (header) header.appendChild(tabs); else sl.insertBefore(tabs, sl.firstChild); } // ==================== 启动 ==================== function boot() { injectCSS(); buildHeader(); detectMode(); autoFill(); killBottomOverlays(); // 等展开按钮出现后点它,再等筛选 DOM 加载完建 UI function waitAndExpand() { const expand = document.querySelector('.filter-expand'); if (expand) { expand.click(); setTimeout(() => buildFilterUI(true), 1200); } else { setTimeout(waitAndExpand, 300); } } waitAndExpand(); setTimeout(buildHeader, 1500); setTimeout(() => { detectMode(); autoFill(); killBottomOverlays(); }, 3000); setTimeout(killBottomOverlays, 5000); if (!window._txObserver) { const mo = new MutationObserver(() => { const main = document.querySelector('.screen-page__main-content') || document.querySelector('.screen-page') || document.getElementById('app-root'); if (main && !document.getElementById('tx-header')) buildHeader(); // 只在筛选栏完全消失时重建(选完筛选后原站重渲染) if (!isModalOpen() && document.querySelector('.filter-line') && !document.querySelector('.tx-filter-tabs')) { setTimeout(() => { try { buildFilterUI(); } catch(e){} }, 300); } }); mo.observe(document.body, { childList: true, subtree: true }); window._txObserver = mo; } } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot(); })();