diff --git a/chfs-ng/internal/api/web/index.html b/chfs-ng/internal/api/web/index.html index 5e15966..e3a5195 100644 --- a/chfs-ng/internal/api/web/index.html +++ b/chfs-ng/internal/api/web/index.html @@ -21,7 +21,7 @@ body{background:var(--bg);color:var(--text);transition:background .2s,color .2s} button.ghost{background:var(--surface2);color:var(--text)}button.danger{background:var(--danger)}.meta,.path{color:var(--muted)} .card{box-shadow:var(--shadow)}th{background:var(--surface2);color:var(--muted)}th,td{border-color:var(--border)}.field input,.toolbar input,.field select{background:var(--surface);color:var(--text);border-color:var(--border)} .iconbtn{width:38px;height:38px;padding:0;background:var(--surface2);color:var(--text)}.viewbtn.active{background:var(--primary);color:#fff}.selectbox{width:18px;height:18px;accent-color:var(--primary)} -.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:12px;padding:14px}.tile{position:relative;border:1px solid var(--border);border-radius:12px;padding:14px;background:var(--surface);min-height:145px;cursor:pointer}.tile:hover{border-color:var(--primary)}.tile.selected{outline:2px solid var(--primary)}.tile .thumb{height:82px;display:flex;align-items:center;justify-content:center;font-size:42px;border-radius:9px;background:var(--surface2);overflow:hidden;margin-bottom:9px}.tile .thumb img{width:100%;height:100%;object-fit:cover}.tile .tilecheck{position:absolute;top:9px;left:9px}.tile .tilemenu{position:absolute;right:7px;top:7px}.file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bulk{display:none;align-items:center;gap:8px;margin-bottom:12px}.bulk.show{display:flex}.bulk .count{font-weight:650;margin-right:auto}.context{position:fixed;z-index:10;display:none;min-width:170px;background:var(--surface);border:1px solid var(--border);box-shadow:var(--shadow);border-radius:10px;padding:5px}.context button{display:block;width:100%;text-align:left;background:transparent;color:var(--text);border-radius:7px}.context button:hover{background:var(--surface2)} +.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:12px;padding:14px}.tile{position:relative;border:1px solid var(--border);border-radius:12px;padding:14px;background:var(--surface);min-height:145px;cursor:pointer}.tile:hover{border-color:var(--primary)}.tile.selected{outline:2px solid var(--primary)}.tile .thumb{height:82px;display:flex;align-items:center;justify-content:center;font-size:42px;border-radius:9px;background:var(--surface2);overflow:hidden;margin-bottom:9px}.tile .thumb img{width:100%;height:100%;object-fit:cover}.progress{height:8px;background:var(--surface2);border-radius:99px;overflow:hidden}.progress i{display:block;height:100%;width:0;background:var(--primary);transition:width .15s}.upload-row{display:grid;gap:7px;padding:10px 0;border-bottom:1px solid var(--border)}.tile .tilecheck{position:absolute;top:9px;left:9px}.tile .tilemenu{position:absolute;right:7px;top:7px}.file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bulk{display:none;align-items:center;gap:8px;margin-bottom:12px}.bulk.show{display:flex}.bulk .count{font-weight:650;margin-right:auto}.context{position:fixed;z-index:10;display:none;min-width:170px;background:var(--surface);border:1px solid var(--border);box-shadow:var(--shadow);border-radius:10px;padding:5px}.context button{display:block;width:100%;text-align:left;background:transparent;color:var(--text);border-radius:7px}.context button:hover{background:var(--surface2)} @@ -55,8 +55,8 @@ function iconFor(f){return {folder:"📁",image:"🖼️",video:"🎬",audio:" function selectItem(p,checked){if(checked)selected.add(p);else selected.delete(p);updateSelection()} let selected=new Set(),lastItems=[]; function render(items){lastItems=items;selected=new Set([...selected].filter(p=>items.some(f=>(current?current+"/":"")+f.name===p)));renderList(items);renderGrid(items);updateSelection()} -function renderList(items){const body=$("files");body.innerHTML=items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),canWrite=(f.mask||"").includes("W"),canDelete=(f.mask||"").includes("D"),share=f.guestMask&&f.guestMask.includes("R")?'':"";return '
'+iconFor(f)+''+(f.dir&&canRead?''+esc(f.name)+'':''+esc(f.name)+'')+'
'+(f.dir?"—":fmt(f.size))+''+esc(f.modified||"")+'
'}).join("")||'这里还没有文件'} -function renderGrid(items){$("grid").innerHTML=items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R");let thumb=iconFor(f);if(fileType(f)==="image"&&canRead)thumb='';return '
'+thumb+'
'+esc(f.name)+'
'+(f.dir?'文件夹':fmt(f.size))+'
'}).join('')||'
这里还没有文件
'} +function renderList(items){const body=$("files");body.innerHTML=items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),canWrite=(f.mask||"").includes("W"),canDelete=(f.mask||"").includes("D"),share=f.guestMask&&f.guestMask.includes("R")?'':"";return '
'+iconFor(f)+''+(f.dir&&canRead?''+esc(f.name)+'':(canRead?''+esc(f.name)+'':''+esc(f.name)+''))+'
'+(f.dir?"—":fmt(f.size))+''+esc(f.modified||"")+'
'}).join("")||'这里还没有文件'} +function renderGrid(items){$("grid").innerHTML=items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R");let thumb=iconFor(f);if(fileType(f)==="image"&&canRead)thumb='';return '
'+thumb+'
'+esc(f.name)+'
'+(f.dir?'文件夹':fmt(f.size))+'
'}).join('')||'
这里还没有文件
'} function toggleTile(p){selectItem(p,!selected.has(p))} function toggleAll(v){lastItems.forEach(f=>{const p=current?current+"/"+f.name:f.name;if(v)selected.add(p);else selected.delete(p)});render(lastItems)} function clearSelection(){selected.clear();updateSelection();renderGrid(lastItems);renderList(lastItems)} @@ -77,9 +77,11 @@ async function renameItem(p){const name=p.split("/").pop();const n=await ask(" async function removeItem(p){if(!await confirmBox("确认删除","确定删除 "+p+"?"))return;try{await api("/chfs/rmfiles",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:p})});refresh()}catch(e){alert(e.message)}} async function newFolder(){const n=await ask("新建文件夹","文件夹名称");if(!n)return;const p=current?current+"/"+n:n;try{await api("/chfs/newdir",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:p})});refresh()}catch(e){alert(e.message)}} async function newText(){const n=await ask("新建文本","文件名","new.txt");if(!n)return;const text=await ask("新建文本","文本内容");if(text===null)return;try{await api("/chfs/newtext",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:current,title:n,text:text||""})});refresh()}catch(e){alert(e.message)}} -$("upload").addEventListener("change",async e=>{const fd=new FormData();fd.append("folder",current);for(const f of e.target.files)fd.append("file",f);try{await api("/chfs/upload",{method:"POST",body:fd});refresh()}catch(x){alert(x.message)}e.target.value=""}) +$("upload").addEventListener("change",async e=>{const files=[...e.target.files];if(!files.length)return;show('

上传文件

');const box=$("uploadList");for(const file of files){const row=document.createElement("div");row.className="upload-row";row.innerHTML=''+esc(file.name)+''+fmt(file.size)+' · 准备上传
';box.appendChild(row);await uploadOne(file,row)}e.target.value="";refresh()}) +function uploadOne(file,row){return new Promise(resolve=>{const xhr=new XMLHttpRequest(),fd=new FormData();fd.append("folder",current);fd.append("file",file);const meta=row.querySelector('.meta'),bar=row.querySelector('i');xhr.open('POST','/chfs/upload');xhr.upload.onprogress=e=>{if(e.lengthComputable){const pct=Math.round(e.loaded/e.total*100);bar.style.width=pct+'%';meta.textContent=fmt(file.size)+' · '+pct+'%'}};xhr.onload=()=>{if(xhr.status>=200&&xhr.status<300){bar.style.width='100%';meta.textContent=fmt(file.size)+' · 完成'}else{meta.textContent='上传失败';bar.style.width='0'}};xhr.onerror=()=>{meta.textContent='网络错误';bar.style.width='0'};xhr.onloadend=resolve;xhr.send(fd)})} $("search").addEventListener("keydown",async e=>{if(e.key!=="Enter")return;const s=e.target.value.trim();if(!s){refresh();return}try{const d=await api("/chfs/search?str="+encodeURIComponent(s));renderSearch(d.files||[])}catch(x){alert(x.message)}}) function renderSearch(items){$("crumb").textContent="搜索结果";const body=$("files");body.innerHTML="";for(const f of items){const row=document.createElement("tr");row.innerHTML='
🔎 '+esc(f.path)+'
'+(f.dir?"—":fmt(f.size))+''+esc(f.modified||"")+'';body.appendChild(row)}} +function previewFile(p){const n=p.split('/').pop(),ext=n.toLowerCase().split('.').pop(),url='/chfs/shared/'+encodeURI(p),media=['mp4','webm','mov','mkv'].includes(ext),audio=['mp3','wav','flac','m4a','ogg'].includes(ext),image=['png','jpg','jpeg','gif','webp','bmp','svg'].includes(ext),pdf=ext==='pdf';let body='';if(image)body='';else if(media)body='';else if(audio)body='';else if(pdf)body='';else body='
暂不支持该文件类型的在线预览
';show('

'+esc(n)+'

'+body+'
')} function changePassword(){show('

修改密码

')} async function savePassword(){try{await api("/chfs/password",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({currentPassword:$("cp").value,password:$("npw").value})});alert("密码已更新");closeModal()}catch(e){alert(e.message)}} function openLogin(){show('

登录

')}