AI生成不一定完美,有朋友手动调整过的可把修改更完美的版本发出来给大伙用用
排序类型
sort_type: 200

<!doctype html><html lang="zh-CN"><head> <meta charset="utf-8">
<!-- 视口配置: width=device-width 页面宽度跟随设备宽度 initial-scale=1 初始缩放比例 maximum-scale=1 最大缩放比例 user-scalable=no 禁止用户缩放 --> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<style> /* html/body 全屏铺满 overflow:hidden 防止页面整体滚动 background:transparent 方便嵌入浮岛/卡片容器 */ html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; background: transparent; font-family: "Microsoft YaHei", sans-serif; }
/* 全局盒模型: width/height 包含 padding 和 border */ * { box-sizing: border-box; }
/* 主卡片容器 整个组件 UI 的主体 */ .card { position: relative; width: 100%; height: 100%; padding: 16px;
/* 圆角 */ border-radius: 18px;
/* 半透明边框 */ border: 1px solid rgba(138, 190, 255, 0.3);
/* 文字颜色 */ color: rgba(245, 249, 255, 0.96);
/* 多层背景: 1. 顶部高光 2. 左上蓝色光斑 3. 右上绿色光斑 4. 深色渐变底色 */ background: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.03) 28%, rgba(255, 255, 255, 0) 56%), radial-gradient(circle at 10% -10%, rgba(76, 151, 255, 0.32), transparent 35%), radial-gradient(circle at 96% 8%, rgba(61, 214, 184, 0.16), transparent 28%), linear-gradient(145deg, #171d29 0%, #111821 47%, #0a0f17 100%);
/* 阴影: inset 为内部高光 最后一层为外部投影 */ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), inset 0 -1px 0 rgba(255, 255, 255, 0.04), 0 18px 48px rgba(0, 0, 0, 0.28);
/* flex 纵向布局 */ display: flex; flex-direction: column;
/* 子元素间距 */ gap: 11px;
overflow: hidden; }
/* 顶部发光细线 */ .card::before { content: ""; position: absolute; left: 18px; right: 18px; top: 0; height: 1px;
background: linear-gradient( 90deg, transparent, rgba(214, 235, 255, 0.75), transparent );
opacity: 0.68; pointer-events: none; }
/* 底部暗色渐变遮罩 */ .card::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 54px;
background: linear-gradient( 180deg, transparent, rgba(8, 13, 20, 0.58) );
pointer-events: none; }
/* 顶部栏 包含标题、分类选择、刷新按钮 */ .topbar { display: flex; align-items: center; justify-content: space-between;
gap: 10px; min-height: 48px;
/* 固定高度,不参与 flex 拉伸 */ flex: 0 0 auto;
position: relative; z-index: 1; }
/* 标题区域 使用 grid 布局: 左边图标 + 右边文字 */ .title { min-width: 0;
display: grid;
grid-template-columns: 36px minmax(0, 1fr); grid-template-rows: auto auto;
column-gap: 10px; align-items: center; }
/* Logo 容器 */ .mark { grid-row: 1 / 3;
width: 36px; height: 36px;
border-radius: 12px; border: 1px solid rgba(165, 205, 255, 0.34);
background: linear-gradient( 160deg, rgba(81, 157, 255, 0.95), rgba(77, 218, 181, 0.88) );
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.38), 0 10px 24px rgba(31, 128, 255, 0.18);
display: flex; align-items: center; justify-content: center;
overflow: hidden; padding: 6px; }
/* Logo 图片 */ .mark img { width: 100%; height: 100%; display: block;
/* contain 保持比例完整显示 */ object-fit: contain; }
/* 主标题 */ .title strong { font-size: 18px; line-height: 22px; font-weight: 700;
color: #f7fbff;
/* 禁止换行 */ white-space: nowrap; }
/* 副标题 */ .title span { font-size: 11px; line-height: 15px;
color: rgba(204, 222, 245, 0.72);
/* 超出隐藏省略号 */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 操作区: 下拉框 + 按钮 */ .actions { display: flex; align-items: center; gap: 8px;
flex: 0 0 auto; }
/* select 和 button 共用样式 */ select, button { height: 32px;
border: 1px solid rgba(150, 193, 255, 0.32); border-radius: 11px;
background: linear-gradient( 180deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.02) ), rgba(13, 20, 31, 0.72);
color: rgba(245, 249, 255, 0.94);
font-family: "Microsoft YaHei", sans-serif; font-size: 12px;
outline: none;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
/* hover/active 动画过渡 */ transition: border-color 160ms ease, background 160ms ease, transform 120ms ease, color 160ms ease, box-shadow 160ms ease; }
/* 分类下拉框 */ select { max-width: 122px; padding: 0 28px 0 10px; cursor: pointer; }
/* 刷新按钮 */ button { width: 32px; padding: 0;
display: inline-flex; align-items: center; justify-content: center;
cursor: pointer;
font-size: 15px; line-height: 1; }
/* hover 高亮效果 */ select:hover, button:hover { border-color: rgba(168, 209, 255, 0.62);
background: linear-gradient( 180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.04) ), rgba(27, 43, 63, 0.84);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 8px 22px rgba(16, 98, 203, 0.16); }
/* 按下效果 */ select:active, button:active { transform: translateY(1px); background: rgba(17, 28, 43, 0.9); }
/* 状态栏 显示当前加载状态 */ .status { height: 26px;
flex: 0 0 auto;
display: flex; align-items: center; justify-content: space-between;
gap: 8px;
color: rgba(202, 219, 241, 0.74);
font-size: 11px; line-height: 16px;
overflow: hidden;
position: relative; z-index: 1;
padding: 0 8px;
border-radius: 10px; border: 1px solid rgba(141, 183, 239, 0.13);
background: rgba(7, 12, 19, 0.24); }
/* 状态文本 */ .statusText { min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 状态圆点 */ .pulse { width: 7px; height: 7px;
flex: 0 0 auto;
border-radius: 50%;
background: #4ed9a7;
box-shadow: 0 0 12px rgba(78, 217, 167, 0.8); }
/* 加载状态 */ .pulse.loading { background: #73adff;
box-shadow: 0 0 12px rgba(115, 173, 255, 0.86);
animation: breathe 900ms ease-in-out infinite alternate; }
/* 错误状态 */ .pulse.error { background: #ff6f7d;
box-shadow: 0 0 12px rgba(255, 111, 125, 0.72); }
/* 呼吸动画 */ @keyframes breathe { from { opacity: 0.42; transform: scale(0.86); }
to { opacity: 1; transform: scale(1.08); } }
/* 文章列表区域 */ .list { position: relative; z-index: 1;
/* flex:1 占满剩余空间 */ flex: 1 1 auto;
min-height: 0;
overflow-y: auto; overflow-x: hidden;
padding: 1px 4px 18px 0;
/* CSS 计数器初始化 */ counter-reset: article; }
/* WebKit 滚动条宽度 */ .list::-webkit-scrollbar { width: 6px; }
/* 滚动条轨道 */ .list::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.04); border-radius: 999px; }
/* 滚动条滑块 */ .list::-webkit-scrollbar-thumb { background: rgba(143, 173, 213, 0.36); border-radius: 999px; }
/* 单篇文章按钮 */ .article { position: relative;
min-height: 40px; width: 100%;
border: 1px solid transparent; border-radius: 12px;
/* 左边留空间给编号 */ padding: 8px 8px 8px 34px;
display: grid;
/* 左边标题 右边标签 */ grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 9px;
background: transparent;
color: inherit; text-align: left;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0);
/* CSS 计数器递增 */ counter-increment: article;
transition: border-color 160ms ease, background 160ms ease, transform 120ms ease, box-shadow 160ms ease; }
/* 左侧自动编号 */ .article::before { /* decimal-leading-zero: 01 02 03 */ content: counter(article, decimal-leading-zero);
position: absolute;
left: 8px; top: 50%;
width: 18px; height: 18px;
transform: translateY(-50%);
border-radius: 7px;
display: flex; align-items: center; justify-content: center;
color: rgba(224, 239, 255, 0.82);
background: rgba(126, 174, 238, 0.12);
border: 1px solid rgba(151, 195, 255, 0.16);
font-size: 9px; line-height: 18px; }
/* hover 高亮 */ .article:hover { border-color: rgba(148, 196, 255, 0.22);
background: linear-gradient( 90deg, rgba(90, 162, 255, 0.16), rgba(84, 213, 184, 0.06) 64%, transparent ), rgba(255, 255, 255, 0.035);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); }
/* 点击按下效果 */ .article:active { transform: translateY(1px); background: rgba(118, 171, 236, 0.16); }
/* 文章标题 */ .articleTitle { min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-size: 13.5px; line-height: 19px; font-weight: 600;
color: rgba(248, 251, 255, 0.94); }
/* 标题浮层提示框 鼠标悬停时显示完整标题 */ .titlePopover { position: fixed; z-index: 20;
max-width: min(360px, calc(100vw - 28px));
padding: 9px 11px;
border-radius: 11px; border: 1px solid rgba(154, 202, 255, 0.32);
background: linear-gradient( 180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04) ), rgba(10, 15, 23, 0.96);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 16px 38px rgba(0, 0, 0, 0.36);
color: rgba(248, 251, 255, 0.96);
font-size: 12px; line-height: 18px;
word-break: break-word;
/* 初始隐藏 */ opacity: 0; transform: translateY(4px);
pointer-events: none;
transition: opacity 120ms ease, transform 120ms ease; }
/* 浮层显示状态 */ .titlePopover.visible { opacity: 1; transform: translateY(0); }
/* 标签容器 */ .tags { display: flex; align-items: center; justify-content: flex-end;
gap: 4px;
width: 142px; max-width: 142px;
overflow: hidden; }
/* 单个标签 */ .tag { max-width: 72px; height: 20px;
padding: 0 7px;
border-radius: 999px;
display: inline-flex; align-items: center; justify-content: center;
color: #fff;
font-size: 10px; line-height: 20px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 6px 18px rgba(0, 0, 0, 0.18); }
/* 空状态 没有文章时显示 */ .empty { height: 100%; min-height: 160px;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: 8px;
color: rgba(214, 227, 244, 0.78);
text-align: center; }
.empty strong { font-size: 14px; color: rgba(248, 251, 255, 0.94); }
.empty span { max-width: 300px;
font-size: 12px; line-height: 18px;
color: rgba(189, 207, 232, 0.68); }
/* 小屏适配 */ @media (max-width: 520px) {
.card { padding: 14px; }
.topbar { min-height: 44px; }
.title { grid-template-columns: 34px minmax(0, 1fr); }
.mark { width: 34px; height: 34px; border-radius: 11px; }
.title strong { font-size: 17px; line-height: 20px; }
select { max-width: 108px; }
.article { min-height: 38px; padding-left: 32px; }
.articleTitle { font-size: 13px; line-height: 18px; font-weight: 600; }
.tags { width: 112px; max-width: 112px; }
.tag { max-width: 54px; padding: 0 6px; } }
/* 超小屏适配 */ @media (max-width: 360px), (max-height: 260px) {
.card { padding: 10px; gap: 7px; }
.topbar { min-height: 38px; }
.title { grid-template-columns: 30px minmax(0, 1fr); column-gap: 8px; }
.mark { width: 30px; height: 30px;
border-radius: 10px; padding: 5px; }
.title strong { font-size: 15px; line-height: 18px; }
.title span, .status { font-size: 10px; }
select { max-width: 88px; }
.tags { max-width: 92px; }
.tag { max-width: 44px; } } </style></head>
<body>
<!-- 主卡片容器 --> <main class="card">
<!-- 顶部栏 --> <header class="topbar">
<!-- 标题区域 --> <div class="title">
<!-- Logo --> <span class="mark epdff-fixed-highlight">
<!-- base64 内嵌图片 优点: 1. 单文件 2. 无需外部资源 3. 适合组件化 --> <img alt="掘金" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAOSSURBVFhH7VdJaBRBFB33HZeDC7jiijDdkwwxxngQRMGLihIQRVG8uB1cENGuHho0MVHwoGLiRVG8aBQ9KYpeVNST28Wo4HpQISqauHZ1vu/3/On0TGZioplAIA8Kqn79/37V/7+quiPd6LIoc6ivaenVMUuvih+nPiLuHMQcGmYot8KwdGNU6Qb098UraahM5xcxRVMNpWvhXEctl7j5faXPFjo0RdTyh6jytsHZr5TzoLEMc6KWPxgHaZCpvB1RS9c3L0DXs4znRK3jMM+h3o5DPWXog8dIw2LDcp8i/3Xcz6bDtjL8NxiKJiG/1VFbr81W6QUOzeQmwwCsayq9DmmpYQ4Rtw8xm0qxu3uSX670iuh+Gi7TOcE6UeVW+icEtszBXDLdNiRD7FnI7c9UnrnSEeoLfApErQVMh6ZhsRfDJ4Q5DMvbk5mivwL562/Y3iYQvG8m83d030i484moh6hGuB9LuAuw84dhXSzmHTaykblENTegNLjsHPWSoY/WiE3b27DoMPXjlnWhlvsgc6EMLkz2JcMkDJuKEbbLINpacogGiDhAttCi/x2FdhRpOcb9kDxnqpjbtLztmL9iODTbF2KwHu1FkoDzpauLHBrtT4aQWVyi35RsMm6lWOPlNAbzNeyDddkn+8bN5lZC+DVMCifXcd/HxDYAHy8+kjB826wvTek3hq3XZDuu2G0hbG5kLPYLLzZFuhJOX4YJMfkMIV6WWRcM06K5fLwCXcu9m+2osa2R0Mvh+Hk6N3Zv6xVpi/XrQLm3woow/IQq3t2iaABEaCJ2cQZkpwssmiDiAKVVNISPMuY/hznh4yZqapaopaPIonF4408iGr8DA/RBcornRC0A3/1xhwbKMABk43lhYR4sFo+VPoF0jBW17GBSvGq7ePeBMS9EubeDym0FMYvmoK7uhG3h+COisbPNjxXnLqb0UjitCxNhV69yfQGxjL+Q4Ox12AbtSTShl7T7JmQU2GQiEtfSqhcnxrTdvfxVJGrJLyTbLYfzhpBeE8ZX8ZFiiNq/obicRvGFA7IfKXLkli+b2riiyfwVhPF5lgXOfV19BCkbKTT/B77LcfVugdMPgZNke4Q0PU6X4UpW3ma2EfOOAd/ppnIXstN0h6GGN4Pfjsz7v0OBIzYD4b6EMHspx374+a1QNF3U8osSh0Yg9AeQkm9w3oiirGKZTHcOUj8m3Lgv4m50NUQifwDPxUa7RSCE4AAAAABJRU5ErkJggg=="> </span>
<!-- 主标题 --> <strong>掘金精选</strong>
<!-- 副标题 --> <span id="subtitle">前端 · 推荐文章流</span> </div>
<!-- 操作区域 --> <div class="actions">
<!-- 分类下拉 --> <select id="category" aria-label="文章分类"> </select>
<!-- 刷新按钮 --> <button id="refresh" type="button" aria-label="刷新文章" title="刷新文章"> ↻ </button> </div> </header>
<!-- 状态栏 --> <section class="status">
<!-- 状态文本 --> <span id="statusText" class="statusText"> 正在准备浮岛组件... </span>
<!-- 状态圆点 --> <span id="pulse" class="pulse loading"> </span> </section>
<!-- 文章列表 --> <section id="list" class="list" aria-label="掘金文章列表"> </section>
<!-- 标题浮层 --> <div id="titlePopover" class="titlePopover" aria-hidden="true"> </div> </main>
<script> /* IIFE: Immediately Invoked Function Expression 立即执行函数
作用: 1. 避免全局变量污染 2. 形成私有作用域 */ (function () {
/* 本地状态存储 key */ var STATE_KEY = 'juejinSettings';
/* 掘金推荐 API */ var API_URL = 'https://api.juejin.cn/recommend_api/v1/article/recommend_cate_feed';
/* 每篇文章最多显示几个标签 */ var MAX_TAGS = 2;
/* 分类配置 label = 显示文本 value = 掘金分类 ID */ var categories = [ { label: '前端', value: '6809637767543259144' }, { label: '后端', value: '6809637769959178254' }, { label: '安卓', value: '6809635626879549454' }, { label: 'iOS', value: '6809635626661445640' }, { label: '人工智能', value: '6809637773935378440' }, { label: '开发工具', value: '6809637771511070734' }, { label: '代码人生', value: '6809637776263217160' }, { label: '阅读', value: '6809637772874219534' } ];
/* 兜底文章
当: 1. API 未返回 2. 宿主未连接 3. 页面刚启动
会先显示这些内容 */ var fallbackArticles = [ { article_info: { article_id: '7316421427778940943', title: '浮岛加载中:已先展示本地兜底文章' },
tags: [ { tag_name: '掘金', color: '#1e80ff' },
{ tag_name: '推荐', color: '#2f9d75' } ] },
{ article_info: { article_id: '7316421427778940943', title: '宿主就绪后会自动读取分类并刷新' },
tags: [ { tag_name: 'WebView2', color: '#7267f0' } ] } ];
/* 当前设置 */ var settings = { cate: categories[0].value };
/* 浮岛宿主是否已连接 */ var fudaoReady = false;
/* 等待次数计数 */ var fudaoWaitCount = 0;
/* 缓存 DOM 元素 避免频繁 getElementById */ var els = { category: document.getElementById('category'), refresh: document.getElementById('refresh'), subtitle: document.getElementById('subtitle'), statusText: document.getElementById('statusText'), pulse: document.getElementById('pulse'), list: document.getElementById('list'), popover: document.getElementById('titlePopover') };
/* 获取当前分类对象 */ function currentCategory () {
return categories.find(function (item) {
return item.value === settings.cate;
}) || categories[0]; }
/* 更新状态栏 */ function setStatus (text, mode) {
/* 设置状态文本 */ els.statusText.textContent = text;
/* 切换圆点样式
mode: loading error 空字符串 */ els.pulse.className = 'pulse' + (mode ? ' ' + mode : ''); }
/* 渲染分类下拉框 */ function renderCategories () {
/* map 生成 option HTML */ els.category.innerHTML = categories.map(function (item) {
return '<option value="' + escapeHTML(item.value) + '">' + escapeHTML(item.label) + '</option>';
}).join('');
/* 设置当前选中项 */ els.category.value = settings.cate;
/* 更新副标题 */ updateSubtitle(); }
/* 更新副标题 */ function updateSubtitle () {
els.subtitle.textContent = currentCategory().label + ' · 推荐文章流'; }
/* 渲染文章列表 */ function renderArticles (articles) {
/* 确保 articles 为数组 */ var list = Array.isArray(articles) ? articles : [];
/* 空状态 */ if (!list.length) {
els.list.innerHTML = '<div class="empty">' + '<strong>暂时没有文章</strong>' + '<span>可以切换分类,或稍后刷新掘金推荐流。</span>' + '</div>';
return; }
/* 渲染文章 HTML */ els.list.innerHTML = list.map(function (article) {
/* 文章信息 */ var info = article.article_info || {};
/* 截取标签数量 */ var tags = Array.isArray(article.tags) ? article.tags.slice(0, MAX_TAGS) : [];
/* 渲染标签 HTML */ var tagHTML = tags.map(function (tag) {
/* 规范化颜色 */ var color = normalizeColor(tag.color);
return '<span class="tag" style="background:' + color + '">' + escapeHTML(tag.tag_name || '标签') + '</span>';
}).join('');
/* 返回单篇文章 HTML */ return [ '<button class="article" type="button" data-id="' + escapeHTML(info.article_id || '') + '">',
'<span class="articleTitle epdff-fixed-highlight">' + escapeHTML(info.title || '未命名文章') + '</span>',
'<span class="tags">' + tagHTML + '</span>',
'</button>' ].join('');
}).join(''); }
/* 显示标题浮层 */ function showTitlePopover (target) {
/* 获取标题文字 */ var text = (target.textContent || '').trim();
if (!text) return;
/* 设置浮层内容 */ els.popover.textContent = text;
/* 添加 visible 类 */ els.popover.classList.add('visible');
/* ARIA 无障碍属性 */ els.popover.setAttribute( 'aria-hidden', 'false' );
/* 定位浮层 */ positionTitlePopover(target); }
/* 计算浮层位置 */ function positionTitlePopover (target) {
/* 浮层未显示直接返回 */ if ( !els.popover.classList.contains('visible') ) return;
/* 获取目标元素位置 */ var rect = target.getBoundingClientRect();
/* 获取浮层尺寸 */ var popRect = els.popover.getBoundingClientRect();
var margin = 10;
/* 默认位置: 标题下方 */ var left = rect.left; var top = rect.bottom + 8;
/* 防止超出右边界 */ if ( left + popRect.width > window.innerWidth - margin ) {
left = window.innerWidth - popRect.width - margin; }
/* 防止超出左边界 */ if (left < margin) { left = margin; }
/* 防止超出底部 自动改到上方显示 */ if ( top + popRect.height > window.innerHeight - margin ) {
top = rect.top - popRect.height - 8; }
/* 防止超出顶部 */ if (top < margin) { top = margin; }
/* 应用位置 */ els.popover.style.left = left + 'px';
els.popover.style.top = top + 'px'; }
/* 隐藏浮层 */ function hideTitlePopover () {
els.popover.classList.remove('visible');
els.popover.setAttribute( 'aria-hidden', 'true' ); }
/* 获取当前鼠标悬停的标题元素 */ function getHoveredTitle (event) {
/* 优先直接命中标题 */ var title = event.target.closest( '.articleTitle.epdff-fixed-highlight' );
if (title) return title;
/* 获取文章元素 */ var article = event.target.closest('.article');
if (!article) return null;
/* 从文章内查找标题 */ title = article.querySelector( '.articleTitle.epdff-fixed-highlight' );
if (!title) return null;
/* 获取标题区域 */ var rect = title.getBoundingClientRect();
/* 当前鼠标位置 */ var x = event.clientX; var y = event.clientY;
/* 判断是否位于标题区域 */ if ( x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom ) { return title; }
return null; }
/* 校验颜色值 */ function normalizeColor (value) {
/* 非字符串返回默认蓝色 */ if (typeof value !== 'string') { return '#1e80ff'; }
var text = value.trim();
/* #RGB #RRGGBB #RRGGBBAA */ if (/^#[0-9a-fA-F]{3,8}$/.test(text)) { return text; }
/* 纯 6 位 hex 自动补 # */ if (/^[0-9a-fA-F]{6}$/.test(text)) { return '#' + text; }
/* 默认颜色 */ return '#1e80ff'; }
/* HTML 转义 防止 XSS 注入 */ function escapeHTML (value) {
return String(value == null ? '' : value)
/* 转义 & */ .replace(/&/g, '&')
/* 转义 < */ .replace(/</g, '<')
/* 转义 > */ .replace(/>/g, '>')
/* 转义 " */ .replace(/"/g, '"')
/* 转义 ' */ .replace(/'/g, '''); }
/* 调用浮岛宿主 API */ function invoke (method, args) {
return window.fudao.invoke( method, args || {} ); }
/* 加载本地设置 */ function loadSettings () {
/* 宿主未连接 直接跳过 */ if (!fudaoReady) { return Promise.resolve(); }
/* 从宿主读取状态 */ return invoke('state.read', {
key: STATE_KEY,
defaultValue: JSON.stringify(settings)
}).then(function (res) {
/* 兼容字符串/对象 */ var raw = typeof res === 'string' ? res : JSON.stringify(res || {});
var saved = {};
/* JSON 解析保护 */ try {
saved = JSON.parse(raw || '{}') || {};
} catch (err) {
saved = {}; }
/* 校验分类合法性 */ if ( saved.cate && categories.some(function (item) { return item.value === saved.cate; }) ) {
settings.cate = saved.cate; }
/* 更新 UI */ els.category.value = settings.cate;
updateSubtitle(); }); }
/* 保存设置 */ function saveSettings () {
if (!fudaoReady) { return Promise.resolve(); }
/* 写入宿主状态 */ return invoke('state.write', {
key: STATE_KEY,
value: JSON.stringify(settings)
}).catch(function () {
/* 忽略保存错误 */ }); }
/* 请求文章 */ function requestArticles () {
/* 更新状态栏 */ setStatus( '正在刷新 ' + currentCategory().label + ' 推荐...', 'loading' );
/* 禁用刷新按钮 防止重复点击 */ els.refresh.disabled = true;
/* 请求体 */ var body = JSON.stringify({
/* 文章类型 */ id_type: 2,
/* 分类 ID */ cate_id: settings.cate,
/* 排序类型 */ sort_type: 300,
/* 分页游标 */ cursor: '0',
/* 每次加载数量 */ limit: 20 });
var request;
/* 浮岛宿主环境 */ if (fudaoReady) {
/* 使用宿主 http.post */ request = invoke('http.post', {
url: API_URL,
headers: { 'Content-Type': 'application/json; encoding=utf-8' },
body: body,
/* 10 秒超时 */ timeoutMs: 10000
}).then(function (result) {
/* HTTP 状态校验 */ if (!result || !result.ok) {
throw new Error( 'HTTP ' + ( result && result.status ? result.status : '请求失败' ) ); }
/* 解析 JSON */ return JSON.parse( result.text || '{}' ); });
/* 普通浏览器环境 */ } else if (window.fetch) {
/* 使用 fetch */ request = fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json; encoding=utf-8' },
body: body
}).then(function (res) {
if (!res.ok) {
throw new Error( 'HTTP ' + res.status ); }
return res.json(); });
} else {
/* fetch 不支持 */ request = Promise.reject( new Error('浮岛宿主尚未就绪') ); }
/* 请求成功 */ return request.then(function (json) {
/* 提取文章数据 */ var articles = Array.isArray(json.data) ? json.data : [];
/* 渲染列表 */ renderArticles(articles);
/* 更新状态栏 */ setStatus( '已更新 ' + articles.length + ' 篇 · ' + formatTime(new Date()), '' );
}).catch(function (err) {
/* 请求失败 */ setStatus( ( err && err.message ? err.message : '刷新失败' ) + ',已保留当前内容', 'error' );
}).then(function () {
/* 恢复刷新按钮 */ els.refresh.disabled = false; }); }
/* 格式化时间 输出 HH:mm */ function formatTime (date) {
/* padStart: 不足两位补 0 */ var h = String(date.getHours()) .padStart(2, '0');
var m = String(date.getMinutes()) .padStart(2, '0');
return h + ':' + m; }
/* 打开文章 */ function openArticle (articleId) {
if (!articleId) return;
/* 拼接文章 URL */ var url = 'https://juejin.cn/post/' + articleId;
/* 浮岛宿主环境 */ if (fudaoReady) {
/* 调用宿主打开链接 */ invoke('url.open', {
url: url,
/* 打开后关闭浮岛 */ closeAfterOpen: true
}).catch(function () {
/* 忽略错误 */ });
} else {
/* 浏览器环境提示 */ setStatus( '需要浮岛宿主打开文章链接', 'error' ); } }
/* 绑定事件 */ function bindEvents () {
/* 分类切换 */ els.category.addEventListener( 'change', function () {
/* 更新分类 */ settings.cate = els.category.value;
/* 更新副标题 */ updateSubtitle();
/* 保存设置 然后刷新文章 */ saveSettings() .then(requestArticles); } );
/* 刷新按钮 */ els.refresh.addEventListener( 'click', function () {
requestArticles(); } );
/* 点击文章 */ els.list.addEventListener( 'click', function (event) {
var item = event.target.closest('.article');
if (item) {
openArticle( item.getAttribute('data-id') ); } } );
/* pointer 系列事件: 现代指针事件 同时支持鼠标/触摸笔/触控 */
/* 鼠标移入 */ els.list.addEventListener( 'pointerover', function (event) {
var title = getHoveredTitle(event);
if (title) {
showTitlePopover(title); } } );
/* 鼠标移动 */ els.list.addEventListener( 'pointermove', function (event) {
var title = getHoveredTitle(event);
if (title) {
showTitlePopover(title);
} else {
hideTitlePopover(); } } );
/* 鼠标移出 */ els.list.addEventListener( 'pointerout', function (event) {
var title = getHoveredTitle(event);
/* relatedTarget: 当前移向的元素 */ if ( title && !title.contains(event.relatedTarget) ) {
hideTitlePopover(); } } );
/* mouse 系列事件 用于兼容旧环境 */
els.list.addEventListener( 'mouseover', function (event) {
var title = getHoveredTitle(event);
if (title) {
showTitlePopover(title); } } );
els.list.addEventListener( 'mousemove', function (event) {
var title = getHoveredTitle(event);
if (title) {
showTitlePopover(title);
} else {
hideTitlePopover(); } } );
els.list.addEventListener( 'mouseout', function (event) {
var title = getHoveredTitle(event);
if ( title && !title.contains(event.relatedTarget) ) {
hideTitlePopover(); } } );
/* 窗口大小变化 隐藏浮层 */ window.addEventListener( 'resize', hideTitlePopover );
/* 滚动列表时隐藏浮层 */ els.list.addEventListener( 'scroll', hideTitlePopover ); }
/* 等待浮岛宿主注入 */ function waitForFudao () {
/* 检测宿主 API 是否存在 */ if ( window.fudao && typeof window.fudao.invoke === 'function' ) {
/* 标记宿主已连接 */ fudaoReady = true;
setStatus( '浮岛已连接,正在读取偏好...', 'loading' );
/* 加载设置 然后刷新文章 */ loadSettings()
.then(requestArticles)
.catch(function () {
/* 设置读取失败 依然刷新文章 */ requestArticles(); });
return; }
/* 增加等待计数 */ fudaoWaitCount += 1;
/* 最多等待 80 次 */ if (fudaoWaitCount < 80) {
/* 120ms 后继续轮询 */ setTimeout( waitForFudao, 120 );
return; }
/* 超时后进入浏览器兜底模式 */ setStatus( '浮岛宿主未连接,使用浏览器兜底刷新', 'loading' );
requestArticles(); }
/* 初始化流程 */
/* 渲染分类 */ renderCategories();
/* 先显示兜底文章 */ renderArticles(fallbackArticles);
/* 绑定事件 */ bindEvents();
/* 延迟检测宿主 */ setTimeout( waitForFudao, 80 );
})(); </script></body></html>