200字
Html 浏览记录历史功能实现API
2026-04-10
2026-04-15
API

​第一步,放入记录脚本,(可以跟你的统计代码放在一起)

在你的网页插入这段代码

<script src="https://api.afmax.cn/so/history/index.php?writer.js"></script>

第2步:(可选)首页插入 Cookie 提示

<script src="https://api.afmax.cn/use/cookie-consent.js"></script>

第3步:使用插入浏览查看代码

<!-- ID1经常访问/访问频次排序 num12输出12条-->

<script src="https://api.afmax.cn/so/history/index.php?id=1&num=12.js"></script>

<!-- ID2最近访问 num12输出12条-->

<script src="https://api.afmax.cn/so/history/index.php?id=2&num=12.js"></script>

如果num0是输出500条

推荐方法1,实时更新

或者:下载浏览历史记录查看页HTML到你的服务器

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>浏览记录</title>
<script>
(function(){
    var __API_DOMAIN__ = 'api.afmax.cn';
    var __WRITER_URL__ = 'https://' + __API_DOMAIN__ + '/so/history/index.php?writer.js';
    var __TDK_API__ = 'https://' + __API_DOMAIN__ + '/so/tdk/index.php';
    var __ICO_API__ = 'https://' + __API_DOMAIN__ + '/so/ico/index.php';
    Object.defineProperty(window, 'API_DOMAIN', {value: __API_DOMAIN__, writable: false, configurable: false});
    Object.defineProperty(window, 'WRITER_URL', {value: __WRITER_URL__, writable: false, configurable: false});
    Object.defineProperty(window, 'TDK_API', {value: __TDK_API__, writable: false, configurable: false});
    Object.defineProperty(window, 'ICO_API', {value: __ICO_API__, writable: false, configurable: false});
})();
// =================================
</script>
<style>
*{margin:0;padding:0;box-sizing:border-box;}
:root{--bg:#f5f5f7;--card:#fff;--text:#1d1d1f;--text2:#86868b;--accent:#0071e3;--red:#ff3b30;--green:#34c759;--border:#e5e5ea;--radius:12px;--shadow:0 1px 3px rgba(0,0,0,.08);}
@media(prefers-color-scheme:dark){:root{--bg:#000;--card:#1c1c1e;--text:#f5f5f7;--text2:#98989d;--accent:#0a84ff;--red:#ff453a;--green:#30d158;--border:#38383a;--shadow:0 1px 3px rgba(0,0,0,.3);}}
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);-webkit-tap-highlight-color:transparent;overflow-x:hidden;}
.header{position:sticky;top:0;z-index:100;background:var(--bg);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);padding:12px 16px;border-bottom:1px solid var(--border);}
.header h1{font-size:20px;font-weight:700;letter-spacing:-.3px;}
.toolbar{display:flex;gap:8px;padding:8px 16px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;}
.toolbar::-webkit-scrollbar{display:none;}
.toolbar .search{flex:1;min-width:0;padding:8px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-size:14px;outline:none;}
.toolbar .search:focus{border-color:var(--accent);}
.toolbar .search::placeholder{color:var(--text2);}
.btn{padding:8px 14px;border:none;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap;transition:opacity .15s;}
.btn:active{opacity:.6;}
.btn-primary{background:var(--accent);color:#fff;}
.btn-danger{background:var(--red);color:#fff;}
.btn-outline{background:var(--card);color:var(--text);border:1px solid var(--border);}
.actions{display:flex;gap:6px;padding:0 16px 8px;flex-wrap:wrap;}
.section{padding:0 16px;margin-bottom:8px;}
.section-title{font-size:13px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.5px;padding:12px 0 6px;display:flex;align-items:center;gap:6px;}
.section-title .count{font-size:11px;background:var(--border);color:var(--text2);padding:1px 6px;border-radius:10px;}
.item{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--card);border-radius:var(--radius);margin-bottom:6px;box-shadow:var(--shadow);transition:transform .15s,opacity .3s;position:relative;overflow:hidden;cursor:pointer;}
.item:hover{transform:translateY(-1px);}
.item.removing{transform:translateX(100%);opacity:0;}
.item .ico{width:32px;height:32px;border-radius:8px;background:var(--border);flex-shrink:0;object-fit:contain;}
.item .info{flex:1;min-width:0;}
.item .title{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
.item .meta{font-size:12px;color:var(--text2);margin-top:2px;}
.item .freq{font-size:12px;color:var(--accent);font-weight:600;flex-shrink:0;}
.item .del-btn{width:28px;height:28px;border:none;background:transparent;color:var(--text2);font-size:18px;cursor:pointer;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s;}
.item .del-btn:hover{background:var(--red);color:#fff;}
.empty{text-align:center;padding:40px 20px;color:var(--text2);font-size:14px;}
.empty a{color:var(--accent);text-decoration:none;}
.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.4);z-index:200;display:none;align-items:center;justify-content:center;padding:20px;}
.modal-overlay.show{display:flex;}
.modal{background:var(--card);border-radius:16px;padding:24px;width:100%;max-width:320px;box-shadow:0 20px 60px rgba(0,0,0,.2);}
.modal h3{font-size:17px;font-weight:600;margin-bottom:12px;}
.modal p{font-size:14px;color:var(--text2);margin-bottom:20px;line-height:1.5;}
.modal .btn-row{display:flex;gap:8px;justify-content:flex-end;}
.cookie-bar{position:fixed;bottom:0;left:0;width:100%;background:var(--card);border-top:1px solid var(--border);padding:12px 16px;z-index:150;display:none;box-shadow:0 -2px 10px rgba(0,0,0,.1);}
.cookie-bar.show{display:block;}
.cookie-bar p{font-size:13px;color:var(--text2);margin-bottom:8px;line-height:1.5;}
.cookie-bar .btn-row{display:flex;gap:8px;justify-content:flex-end;}
.toast{position:fixed;top:60px;left:50%;transform:translateX(-50%) translateY(-100px);background:var(--card);color:var(--text);padding:10px 20px;border-radius:10px;font-size:14px;box-shadow:0 4px 20px rgba(0,0,0,.15);z-index:300;transition:transform .3s;white-space:nowrap;}
.toast.show{transform:translateX(-50%) translateY(0);}
.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;}
.toggle-row label{font-size:13px;color:var(--text2);}
.toggle{width:44px;height:26px;border-radius:13px;background:var(--border);position:relative;cursor:pointer;transition:background .2s;border:none;padding:0;}
.toggle.on{background:var(--green);}
.toggle::after{content:'';position:absolute;width:22px;height:22px;border-radius:50%;background:#fff;top:2px;left:2px;transition:transform .2s;box-shadow:0 1px 3px rgba(0,0,0,.2);}
.toggle.on::after{transform:translateX(18px);}
</style>
</head>
<body>

<div class="header">
  <h1>浏览记录</h1>
  <div class="toggle-row" style="margin-top:4px">
    <label>显示经常访问</label>
    <button class="toggle on" id="toggleFrequent" aria-label="切换经常访问"></button>
  </div>
</div>

<div class="toolbar">
  <input class="search" id="searchInput" type="text" placeholder="搜索记录...">
  <button class="btn btn-primary" id="exportBtn">导出</button>
</div>

<div class="actions">
  <button class="btn btn-outline" data-clear="today">清空今天</button>
  <button class="btn btn-outline" data-clear="week">清空本周</button>
  <button class="btn btn-outline" data-clear="month">清空本月</button>
  <button class="btn btn-danger" data-clear="all">清空全部</button>
</div>

<div id="content"></div>

<div class="modal-overlay" id="modal">
  <div class="modal">
    <h3 id="modalTitle">确认操作</h3>
    <p id="modalMsg">确定要执行此操作吗?</p>
    <div class="btn-row">
      <button class="btn btn-outline" id="modalCancel">取消</button>
      <button class="btn btn-danger" id="modalOk">确认</button>
    </div>
  </div>
</div>

<div class="cookie-bar" id="cookieBar">
  <p>本站使用浏览器本地存储记录浏览数据,不使用 Cookie。根据欧盟法规,我们需要您的同意。</p>
  <div class="btn-row">
    <button class="btn btn-outline" id="cookieDeny">拒绝</button>
    <button class="btn btn-primary" id="cookieAccept">同意</button>
  </div>
</div>

<div class="toast" id="toast"></div>

<script>
(function(){
'use strict';

var SK='__hist_records';
var COOK='__hist_cookie';
var FREQ='__hist_show_freq';
var DAY=864e5;
var WEEK=7*DAY;
var MONTH=30*DAY;

function validateApiDomain(url){
    try{
        var parsed=new URL(url);
        return parsed.hostname===API_DOMAIN;
    }catch(e){return false;}
}

// ===== 加载 writer.js =====
(function(){
    if(!validateApiDomain(WRITER_URL)){
        console.error('API域名验证失败,已锁定为: '+API_DOMAIN);
        return;
    }
    var s=document.createElement('script');
    s.src=WRITER_URL;
    s.async=true;
    s.onerror=function(){console.error('writer.js 加载失败');};
    document.head.appendChild(s);
})();

// ===== 工具函数 =====
function $(id){return document.getElementById(id);}
function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML;}
function fmtTime(ts){
    var d=new Date(ts),now=new Date();
    var pad=function(n){return n<10?'0'+n:n;};
    if(d.toDateString()===now.toDateString()) return '今天 '+pad(d.getHours())+':'+pad(d.getMinutes());
    var yd=new Date(now);yd.setDate(yd.getDate()-1);
    if(d.toDateString()===yd.toDateString()) return '昨天 '+pad(d.getHours())+':'+pad(d.getMinutes());
    return d.getFullYear()+'/'+pad(d.getMonth()+1)+'/'+pad(d.getDate())+' '+pad(d.getHours())+':'+pad(d.getMinutes());
}
function toast(msg){var t=$('toast');t.textContent=msg;t.classList.add('show');setTimeout(function(){t.classList.remove('show');},2500);}

// ===== localStorage 操作 =====
function getLocal(){
    try{return JSON.parse(localStorage.getItem(SK)||'[]');}catch(e){return [];}
}
function setLocal(d){
    try{localStorage.setItem(SK,JSON.stringify(d));}catch(e){}
}

// ===== 分类 =====
function categorize(records){
    var now=Date.now();
    var showFreq=(localStorage.getItem(FREQ)||'1')==='1';
    var freqThreshold=Math.max(3,Math.ceil(records.length*0.05));
    var categories=[
        {key:'frequent',title:'经常访问',icon:'\u{1F525}',items:[]},
        {key:'today',title:'今天',icon:'\u{1F4C5}',items:[]},
        {key:'week',title:'本周',icon:'\u{1F4C6}',items:[]},
        {key:'month',title:'本月',icon:'\u{1F5D3}',items:[]},
        {key:'older',title:'更久以前',icon:'\u{23F3}',items:[]}
    ];
    var freqUrls={};
    records.forEach(function(r){
        // 先判断是否属于"经常访问"
        if(showFreq && (r.frequency||1)>=freqThreshold && (now-r.time)>=DAY){
            freqUrls[r.url]=true;
            categories[0].items.push(r);
            // 添加到"经常访问"后,不再添加到其他时间分类
        }else{
            // 非经常访问的记录,按时间分类
            if(now-r.time<DAY) categories[1].items.push(r);
            else if(now-r.time<WEEK) categories[2].items.push(r);
            else if(now-r.time<MONTH) categories[3].items.push(r);
            else categories[4].items.push(r);
        }
    });
    if(!showFreq) categories[0].items=[];
    return categories;
}

// ===== TDK 缓存 =====
var tdkCache={};
var tdkQueue=[];
var tdkRunning=0;
var TDK_MAX=3;
var TDK_DELAY=200;

function fetchTdk(url,callback){
    if(tdkCache[url]){callback(tdkCache[url]);return;}
    tdkQueue.push({url:url,callback:callback});
    processTdkQueue();
}

function processTdkQueue(){
    while(tdkRunning<TDK_MAX && tdkQueue.length>0){
        var item=tdkQueue.shift();
        tdkRunning++;
        (function(it){
            var xhr=new XMLHttpRequest();
            xhr.open('GET',TDK_API+'?r='+encodeURIComponent(it.url),true);
            xhr.timeout=5000;
            xhr.onload=function(){
                try{
                    var d=JSON.parse(xhr.responseText);
                    var title=d.title||new URL(it.url).hostname;
                    tdkCache[it.url]={title:title};
                    it.callback(tdkCache[it.url]);
                }catch(e){
                    tdkCache[it.url]={title:new URL(it.url).hostname};
                    it.callback(tdkCache[it.url]);
                }
            };
            xhr.onerror=xhr.ontimeout=function(){
                try{tdkCache[it.url]={title:new URL(it.url).hostname};}catch(e){tdkCache[it.url]={title:it.url};}
                it.callback(tdkCache[it.url]);
            };
            xhr.send();
        })(item);
        if(tdkQueue.length>0 && tdkRunning>=TDK_MAX) setTimeout(processTdkQueue,TDK_DELAY);
    }
}

// ===== 渲染 =====
function render(){
    var filter=($('searchInput').value||'').toLowerCase();
    var records=getLocal();
    if(filter){
        records=records.filter(function(r){
            return r.url.toLowerCase().indexOf(filter)!==-1;
        });
    }
    var cats=categorize(records);
    var html='';
    cats.forEach(function(cat){
        if(cat.items.length===0) return;
        html+='<div class="section"><div class="section-title">'+cat.icon+' '+esc(cat.title)+' <span class="count">'+cat.items.length+'</span></div>';
        cat.items.forEach(function(r){
            var hostname='';
            try{hostname=new URL(r.url).hostname;}catch(e){hostname=r.url;}
            var icoUrl=ICO_API+'?r='+encodeURIComponent(r.url);
            html+='<div class="item" data-url="'+esc(r.url)+'">';
            html+='<img class="ico" src="'+esc(icoUrl)+'" alt="" loading="lazy" onerror="this.style.display=\'none\'">';
            html+='<div class="info"><div class="title" data-tdk="'+esc(r.url)+'">'+esc(hostname)+'</div><div class="meta">'+esc(fmtTime(r.time))+'</div></div>';
            html+='<span class="freq">'+(r.frequency||1)+'次</span>';
            html+='<button class="del-btn" data-del="'+esc(r.url)+'" aria-label="删除">×</button>';
            html+='</div>';
        });
        html+='</div>';
    });
    if(!html){
        html='<div class="empty">暂无浏览记录<br>在其他页面引用 writer.js 后访问即可记录<br><br>API: '+API_DOMAIN+'</div>';
    }
    $('content').innerHTML=html;

    // 异步加载 TDK 标题
    document.querySelectorAll('[data-tdk]').forEach(function(el){
        var url=el.getAttribute('data-tdk');
        fetchTdk(url,function(info){
            if(info.title && el.parentNode){
                el.textContent=info.title;
            }
        });
    });

    bindEvents();
}

// ===== 事件 =====
var deleteUrl='';

function bindEvents(){
    // 点击访问
    document.querySelectorAll('.item').forEach(function(item){
        item.addEventListener('click',function(e){
            if(e.target.closest('.del-btn')) return;
            var url=item.getAttribute('data-url');
            if(url) window.open(url,'_blank');
        });
    });
    // 删除按钮
    document.querySelectorAll('.del-btn').forEach(function(btn){
        btn.addEventListener('click',function(e){
            e.stopPropagation();
            var url=btn.getAttribute('data-del');
            if(url) confirmDelete(url);
        });
    });
}

function confirmDelete(url){
    deleteUrl=url;
    var hostname='';
    try{hostname=new URL(url).hostname;}catch(e){hostname=url;}
    $('modalTitle').textContent='删除记录';
    $('modalMsg').textContent='确定要删除 '+hostname+' 的浏览记录吗?';
    $('modal').classList.add('show');
}

function doDelete(url){
    var local=getLocal().filter(function(r){return r.url!==url;});
    setLocal(local);
    if(window.HistAPI) HistAPI.remove(url);
    var item=document.querySelector('.item[data-url="'+CSS.escape(url)+'"]');
    if(item){
        item.classList.add('removing');
        setTimeout(render,300);
    }else{
        render();
    }
    toast('已删除');
}

function doClear(range){
    var now=Date.now();
    var local=getLocal();
    var keep=[];

    function shouldRemove(r){
        if(range==='all') return true;
        var age=now-r.time;
        if(range==='today') return age<DAY;
        if(range==='week') return age<WEEK;
        if(range==='month') return age<MONTH;
        return false;
    }

    local.forEach(function(r){if(!shouldRemove(r)) keep.push(r);});
    setLocal(keep);
    if(range==='all' && window.HistAPI) HistAPI.clear();
    render();
    var rangeText={today:'今天',week:'本周',month:'本月',all:'全部'}[range]||range;
    toast('已清空'+rangeText+'记录');
}

// ===== 导出 =====
function doExport(){
    var records=getLocal();
    var cats=categorize(records);
    var lines=['浏览记录导出 - '+new Date().toLocaleString(),'',''];
    cats.forEach(function(cat){
        if(!cat.items.length) return;
        lines.push('=== '+cat.title+' ===');
        cat.items.forEach(function(r){
            var hostname='';
            try{hostname=new URL(r.url).hostname;}catch(e){hostname=r.url;}
            lines.push(hostname+' | 访问'+(r.frequency||1)+'次 | '+fmtTime(r.time));
            lines.push('  '+r.url);
        });
        lines.push('');
    });
    var blob=new Blob([lines.join('\n')],{type:'text/plain;charset=utf-8'});
    var a=document.createElement('a');
    a.href=URL.createObjectURL(blob);
    var d=new Date();
    var pad=function(n){return n<10?'0'+n:n;};
    a.download='浏览记录_'+d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate())+'.txt';
    a.click();
    URL.revokeObjectURL(a.href);
    toast('导出成功');
}

// ===== GDPR Cookie 提示 =====
function checkGdpr(){
    var consent=localStorage.getItem(COOK);
    if(consent) return;
    try{
        var tz=Intl.DateTimeFormat().resolvedOptions().timeZone||'';
        if(tz.indexOf('Europe/')===0){
            $('cookieBar').classList.add('show');
        }
    }catch(e){}
}

// ===== 初始化 =====
function init(){
    // 经常访问开关 - 初始化状态
    var toggle=$('toggleFrequent');
    var savedFreq=localStorage.getItem(FREQ);
    if(savedFreq==='0'){
        toggle.classList.remove('on');
    }else{
        toggle.classList.add('on');
    }
    toggle.addEventListener('click',function(){
        var isOn=toggle.classList.toggle('on');
        localStorage.setItem(FREQ,isOn?'1':'0');
        render();
    });

    // 搜索防抖
    var timer;
    $('searchInput').addEventListener('input',function(){
        clearTimeout(timer);
        timer=setTimeout(render,300);
    });

    // 导出
    $('exportBtn').addEventListener('click',doExport);

    // 清空按钮
    document.querySelectorAll('[data-clear]').forEach(function(btn){
        btn.addEventListener('click',function(){
            var range=btn.getAttribute('data-clear');
            var rangeText={today:'今天',week:'本周',month:'本月',all:'全部'}[range]||range;
            $('modalTitle').textContent='清空记录';
            $('modalMsg').textContent='确定要清空'+rangeText+'的浏览记录吗?此操作不可撤销。';
            deleteUrl='__clear__'+range;
            $('modal').classList.add('show');
        });
    });

    // 模态框
    $('modalCancel').addEventListener('click',function(){$('modal').classList.remove('show');deleteUrl='';});
    $('modalOk').addEventListener('click',function(){
        $('modal').classList.remove('show');
        if(deleteUrl){
            if(deleteUrl.indexOf('__clear__')===0){
                doClear(deleteUrl.substring(9));
            }else{
                doDelete(deleteUrl);
            }
            deleteUrl='';
        }
    });

    // Cookie 同意
    $('cookieAccept').addEventListener('click',function(){
        localStorage.setItem(COOK,'1');
        $('cookieBar').classList.remove('show');
        toast('感谢您的同意');
    });
    $('cookieDeny').addEventListener('click',function(){
        localStorage.setItem(COOK,'0');
        $('cookieBar').classList.remove('show');
        toast('部分功能可能受限');
    });

    render();
    checkGdpr();
}

if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',init);
else init();

})();
</script>
</body>
</html>


Q1: 为什么看不到记录?

检查清单:

✅ writer.js 是否正确加载?(查看控制台)

✅ writer.js 和展示页面是否在同一域名下?

✅ localStorage 是否有数据?(控制台查看)

调试方法:

console.log(localStorage.getItem('__hist_records'));

Q2: 如何验证数据隔离?

测试步骤:

在 site-a.com 引入 writer.js 并访问页面

在 site-a.com 查看记录 → 应该能看到

访问 api.afmax.cn/so/history/index.php → 看不到 site-a.com 的记录

在 api.afmax.cn 上访问页面并查看记录 → 只能看到 api.afmax.cn 的记录