| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
- <meta name="color-scheme" content="light">
- <title>__TITLE__</title>
- <style>
- :root{
- --ink:#1B2559; --sub:#64748B; --line:#E2E8F0; --card:#FFFFFF;
- --bg:#F5F8FF; --accent:#5B5BD6; --accent-soft:#EEF0FF; --accent-line:#D5D9F5;
- --accent2:#7C6FF0; --grad:linear-gradient(135deg,#5B5BD6 0%,#7C6FF0 60%,#9BB8F5 100%);
- --ok:#1E7A46; --ok-soft:#EAF6EF; --warn:#8A6A1F; --warn-soft:#FAF4E6;
- --radius:14px;
- }
- *{box-sizing:border-box}
- html,body{margin:0;padding:0}
- body{
- background:var(--bg); color:var(--ink); min-height:100vh;
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
- line-height:1.55; -webkit-font-smoothing:antialiased;
- padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
- }
- .wrap{max-width:720px;margin:0 auto;padding:20px 16px 56px}
- header{padding:8px 0 4px}
- .eyebrow{color:var(--accent);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
- h1{font-size:22px;margin:6px 0 2px;line-height:1.3;word-break:break-word}
- .sub{color:var(--sub);font-size:13.5px;margin:0 0 4px}
- .card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:16px;margin-top:14px;box-shadow:0 1px 2px rgba(26,22,20,.03)}
- .limit{background:var(--warn-soft);border:1px solid #EFE1BE;border-radius:var(--radius);padding:14px 16px;margin-top:14px}
- .limit .big{font-weight:800;font-size:15px}
- .limit .channel{color:var(--accent);font-weight:700}
- .limit p{margin:6px 0 0;font-size:13px;color:var(--sub)}
- .notice{display:flex;gap:10px;align-items:flex-start;background:var(--accent-soft);border:1px solid var(--accent-line);border-radius:12px;padding:12px 14px;margin-top:12px;font-size:13px;color:#7A1220}
- .notice .ico{flex:0 0 auto;font-weight:800}
- #drop{
- margin-top:14px;border:2px dashed var(--accent-line);border-radius:var(--radius);
- background:linear-gradient(180deg,#fff, #FFF9F9); padding:30px 18px; text-align:center;
- cursor:pointer; transition:border-color .15s, background .15s;
- }
- #drop.hot{border-color:var(--accent);background:var(--accent-soft)}
- #drop .big{font-size:16px;font-weight:700;margin-bottom:6px}
- #drop .small{font-size:12.5px;color:var(--sub)}
- #drop .pick{display:inline-block;margin-top:12px;background:var(--accent);color:#fff;font-weight:700;
- border:0;border-radius:10px;padding:10px 20px;font-size:14.5px;cursor:pointer}
- #drop .pick:active{transform:translateY(1px)}
- .files{margin-top:16px;display:flex;flex-direction:column;gap:10px}
- .f{border:1px solid var(--line);border-radius:12px;padding:12px 14px;background:#fff}
- .f .row{display:flex;justify-content:space-between;gap:10px;align-items:baseline}
- .f .name{font-weight:600;font-size:13.5px;word-break:break-all}
- .f .meta{color:var(--sub);font-size:12px;white-space:nowrap}
- .f .slot{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--sub);margin-top:2px;word-break:break-all}
- .bar{height:7px;border-radius:99px;background:#EFEAE3;overflow:hidden;margin-top:9px}
- .bar > i{display:block;height:100%;width:0;background:var(--accent);transition:width .2s}
- .f.done .bar > i{background:var(--ok)}
- .f.done{border-color:#CDE7D7;background:var(--ok-soft)}
- .f.err .bar > i{background:#B8182A}
- .status{font-size:12px;margin-top:6px;font-weight:600}
- .status.ok{color:var(--ok)} .status.err{color:var(--accent)} .status.up{color:var(--sub)}
- footer{color:var(--sub);font-size:11.5px;margin-top:22px;text-align:center;line-height:1.7}
- code{background:#F2EDE6;border-radius:5px;padding:1px 5px;font-size:12px}
- .hide{display:none}
- </style>
- </head>
- <body>
- <div class="wrap">
- <header>
- <div class="brand" style="display:flex;align-items:center;gap:8px;margin-bottom:10px">
- <span style="width:26px;height:26px;border-radius:8px;background:var(--grad);display:inline-flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:14px">F</span>
- <span style="font-weight:800;font-size:13px;color:var(--accent)">未来飞马 FmodeAgent</span>
- <span style="font-size:11px;color:var(--sub)">大文件接收网关</span>
- </div>
- <h1>__TITLE__</h1>
- <p class="sub">拖入文件即直传对象存储,不占聊天通道 —— 多大都行。</p>
- </header>
- <div class="limit">
- <div class="big">当前通道限制:<span class="channel">__CHANNEL_NAME__</span></div>
- <div class="big" style="margin-top:2px">上限 __LIMIT_TEXT__</div>
- <p>__LIMIT_NOTE__</p>
- </div>
- <div class="notice"><span class="ico">!</span><span>这是通道限制,不是你操作的问题。用本页直传存储,原文件多大都可以。</span></div>
- <div id="drop">
- <div class="big">拖拽文件到此处,或点击选择</div>
- <div class="small">支持多文件同时上传 · 手机、电脑均可</div>
- <button class="pick" id="pick" type="button">选择文件</button>
- <input id="input" type="file" multiple class="hide">
- </div>
- <div class="files" id="files"></div>
- <footer>
- <div>上传地址由临时授权(STS)签发,约 __TTL_HOURS__ 小时内有效。</div>
- <div>本页不含任何密钥;每个文件写入独立槽位,互不影响。</div>
- <div id="slotsleft"></div>
- <div style="margin-top:12px;padding-top:10px;border-top:1px solid var(--line);color:#94A3B8;font-size:11px">由 <b style="color:var(--accent)">未来飞马 FmodeAgent</b> 提供技术支持 · fmode.cn</div>
- </footer>
- </div>
- <script id="gateway-config" type="application/json">__CONFIG_JSON__</script>
- <script>
- (function(){
- var CFG = JSON.parse(document.getElementById('gateway-config').textContent);
- var PUB_BASE = CFG.publicBase, SLOTS = CFG.slots, TTL = CFG.ttlHours;
- var drop = document.getElementById('drop'), input = document.getElementById('input'),
- pick = document.getElementById('pick'), filesEl = document.getElementById('files');
- var free = SLOTS.slice();
- function fmt(n){ if(n<1024) return n+' B'; var u=['KB','MB','GB'], i=-1; do{ n/=1024; i++; }while(n>=1024&&i<2); return n.toFixed(n<10?1:0)+' '+u[i]; }
- function esc(s){ return String(s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];}); }
- pick.addEventListener('click', function(e){ e.stopPropagation(); input.click(); });
- drop.addEventListener('click', function(){ input.click(); });
- input.addEventListener('change', function(){ enqueue(input.files); input.value=''; });
- ['dragenter','dragover'].forEach(function(ev){ drop.addEventListener(ev, function(e){ e.preventDefault(); drop.classList.add('hot'); }); });
- ['dragleave','drop'].forEach(function(ev){ drop.addEventListener(ev, function(e){ e.preventDefault(); drop.classList.remove('hot'); }); });
- drop.addEventListener('drop', function(e){ if(e.dataTransfer && e.dataTransfer.files) enqueue(e.dataTransfer.files); });
- function updateLeft(){ var el=document.getElementById('slotsleft'); el.textContent = '剩余可用槽位 '+free.length+' / '+SLOTS.length; }
- updateLeft();
- function enqueue(fl){
- for(var i=0;i<fl.length;i++){ (function(f){ uploadOne(f); })(fl[i]); }
- }
- function uploadOne(file){
- var slot = free.shift();
- var row = document.createElement('div');
- row.className='f';
- row.innerHTML = '<div class="row"><span class="name">'+esc(file.name)+'</span><span class="meta">'+fmt(file.size)+'</span></div>'
- + '<div class="slot">'+(slot? '槽位 '+slot.n+' · '+esc(slot.name):'槽位已用尽')+'</div>'
- + '<div class="bar"><i></i></div><div class="status up">'+ (slot? '等待上传…' : '无可用槽位,请重新生成页面') +'</div>';
- filesEl.appendChild(row);
- updateLeft();
- if(!slot){ row.classList.add('err'); row.querySelector('.status').className='status err'; return; }
- var bar = row.querySelector('.bar > i'), st = row.querySelector('.status');
- var xhr = new XMLHttpRequest();
- xhr.open('PUT', slot.url, true);
- xhr.setRequestHeader('Content-Type', slot.contentType || 'application/octet-stream');
- xhr.setRequestHeader('x-obs-acl', 'public-read');
- st.textContent='上传中…'; st.className='status up';
- xhr.upload.onprogress = function(e){ if(e.lengthComputable){ var p=Math.round(e.loaded/e.total*100); bar.style.width=p+'%'; st.textContent='上传中… '+p+'%'; } };
- xhr.onload = function(){
- if(xhr.status>=200 && xhr.status<300){
- bar.style.width='100%'; row.classList.add('done'); st.className='status ok';
- st.innerHTML = '上传成功 ✓ <a href="'+slot.publicUrl+'" target="_blank" rel="noopener">查看/下载</a>';
- } else {
- row.classList.add('err'); st.className='status err';
- st.textContent='上传失败(HTTP '+xhr.status+')。若本页已打开超过 '+TTL+' 小时,请让智能体重新生成。';
- }
- };
- xhr.onerror = function(){ row.classList.add('err'); st.className='status err'; st.textContent='网络中断,请重试或让智能体重新生成页面。'; };
- xhr.send(file);
- }
- })();
- </script>
- </body>
- </html>
|