feat: modernize file list view
This commit is contained in:
@@ -10,10 +10,10 @@
|
|||||||
button,input{font:inherit}button{border:0;border-radius:9px;padding:9px 13px;cursor:pointer;background:#2563eb;color:#fff}button.ghost{background:#eef2f7;color:#334155}button.danger{background:#dc2626}
|
button,input{font:inherit}button{border:0;border-radius:9px;padding:9px 13px;cursor:pointer;background:#2563eb;color:#fff}button.ghost{background:#eef2f7;color:#334155}button.danger{background:#dc2626}
|
||||||
main{max-width:1180px;margin:26px auto;padding:0 18px}.toolbar{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:18px}.toolbar input{min-width:220px;flex:1;padding:10px 12px;border:1px solid #d9dfeb;border-radius:9px;background:#fff}
|
main{max-width:1180px;margin:26px auto;padding:0 18px}.toolbar{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:18px}.toolbar input{min-width:220px;flex:1;padding:10px 12px;border:1px solid #d9dfeb;border-radius:9px;background:#fff}
|
||||||
.card{background:#fff;border:1px solid #e4e8f0;border-radius:14px;overflow:hidden;box-shadow:0 4px 18px rgba(20,30,50,.04)}
|
.card{background:#fff;border:1px solid #e4e8f0;border-radius:14px;overflow:hidden;box-shadow:0 4px 18px rgba(20,30,50,.04)}
|
||||||
table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:13px 15px;border-bottom:1px solid #edf0f5}th{font-size:13px;color:#7b8798;background:#fafbfc}tr:last-child td{border-bottom:0}.name{display:flex;align-items:center;gap:10px}.name a{color:#172033;text-decoration:none;font-weight:550}.meta{color:#8792a5;font-size:13px}.actions{display:flex;gap:7px;justify-content:flex-end}.empty{padding:45px;text-align:center;color:#8a94a6}
|
table{width:100%;border-collapse:separate;border-spacing:0}th,td{text-align:left;padding:12px 15px;border-bottom:1px solid #edf0f5}th{font-size:12px;color:#7b8798;background:#fafbfc;font-weight:650}tr:last-child td{border-bottom:0}.name{display:flex;align-items:center;gap:10px;min-width:0}.name a{color:#172033;text-decoration:none;font-weight:550;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meta{color:#8792a5;font-size:13px}.actions{display:flex;gap:7px;justify-content:flex-end;opacity:.55;transition:opacity .15s}.file-row:hover .actions,.file-row.selected .actions{opacity:1}.file-row{transition:background .12s}.file-row:hover{background:var(--surface2)}.file-row.selected{background:color-mix(in srgb,var(--primary) 7%,var(--surface))}.file-row .file-kind{width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border-radius:8px;background:var(--surface2);color:var(--muted);flex:0 0 auto}.file-row .file-kind .ui-icon{width:18px;height:18px}.file-row .size-cell,.file-row .time-cell{white-space:nowrap;color:var(--muted);font-size:12px}.file-row .perm-cell{white-space:nowrap}.perm-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--border);margin-right:5px}.perm-dot.read{background:var(--muted)}.perm-dot.write{background:var(--primary)}.perm-dot.delete{background:var(--danger)}.empty{padding:45px;text-align:center;color:#8a94a6}
|
||||||
.modal{position:fixed;inset:0;background:rgba(15,23,42,.5);display:none;align-items:center;justify-content:center;padding:18px;z-index:4;backdrop-filter:blur(4px)}.modal.show{display:flex}.dialog{width:min(480px,100%);max-height:calc(100vh - 36px);overflow:auto;background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:22px;box-shadow:0 24px 80px rgba(0,0,0,.18)}.dialog.preview-dialog{width:min(1000px,100%);padding:0;overflow:hidden}.dialog h2{margin-top:0}.field{display:grid;gap:7px;margin:13px 0}.field input{padding:10px 11px;border:1px solid #d7ddea;border-radius:9px}.row{display:flex;gap:9px;justify-content:flex-end;margin-top:18px}.preview-head{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--border)}.preview-title{font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:auto}.preview-stage{min-height:180px;max-height:calc(100vh - 150px);overflow:auto;background:#0b0f14;padding:24px;display:flex;align-items:center;justify-content:center}.preview-stage img{max-width:100%;max-height:70vh;object-fit:contain}.preview-stage video{width:min(920px,100%);max-height:70vh;border-radius:10px}.preview-stage audio{width:min(720px,100%)}.preview-stage iframe{width:100%;height:70vh;border:0;background:#fff}.preview-text{width:min(920px,100%);max-height:70vh;overflow:auto;text-align:left;white-space:pre-wrap;background:#111821;color:#dce5f2;padding:18px;border-radius:10px;font:13px/1.6 ui-monospace,SFMono-Regular,monospace}.preview-empty{color:#aab5c5;text-align:center}.preview-foot{padding:12px 16px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px}
|
.modal{position:fixed;inset:0;background:rgba(15,23,42,.5);display:none;align-items:center;justify-content:center;padding:18px;z-index:4;backdrop-filter:blur(4px)}.modal.show{display:flex}.dialog{width:min(480px,100%);max-height:calc(100vh - 36px);overflow:auto;background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:22px;box-shadow:0 24px 80px rgba(0,0,0,.18)}.dialog.preview-dialog{width:min(1000px,100%);padding:0;overflow:hidden}.dialog h2{margin-top:0}.field{display:grid;gap:7px;margin:13px 0}.field input{padding:10px 11px;border:1px solid #d7ddea;border-radius:9px}.row{display:flex;gap:9px;justify-content:flex-end;margin-top:18px}.preview-head{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--border)}.preview-title{font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:auto}.preview-stage{min-height:180px;max-height:calc(100vh - 150px);overflow:auto;background:#0b0f14;padding:24px;display:flex;align-items:center;justify-content:center}.preview-stage img{max-width:100%;max-height:70vh;object-fit:contain}.preview-stage video{width:min(920px,100%);max-height:70vh;border-radius:10px}.preview-stage audio{width:min(720px,100%)}.preview-stage iframe{width:100%;height:70vh;border:0;background:#fff}.preview-text{width:min(920px,100%);max-height:70vh;overflow:auto;text-align:left;white-space:pre-wrap;background:#111821;color:#dce5f2;padding:18px;border-radius:10px;font:13px/1.6 ui-monospace,SFMono-Regular,monospace}.preview-empty{color:#aab5c5;text-align:center}.preview-foot{padding:12px 16px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px}
|
||||||
.login{max-width:390px;margin:10vh auto}.notice{padding:12px 14px;background:#eff6ff;border-radius:10px;color:#315b9b;margin-bottom:14px}
|
.login{max-width:390px;margin:10vh auto}.notice{padding:12px 14px;background:#eff6ff;border-radius:10px;color:#315b9b;margin-bottom:14px}
|
||||||
@media(max-width:700px){th:nth-child(2),td:nth-child(2),th:nth-child(3),td:nth-child(3){display:none}.top{padding:0 13px}.path{max-width:40vw}.actions button{padding:7px 9px}.toolbar input{min-width:160px}}
|
@media(max-width:700px){.size-col,.time-col,.perm-col,.size-cell,.time-cell,.perm-cell{display:none}.top{padding:0 13px}.path{max-width:40vw}.actions button{padding:7px 9px}.toolbar input{min-width:160px}.file-row td{padding:10px 9px}}
|
||||||
|
|
||||||
:root{--bg:#f5f7fb;--surface:#fff;--surface2:#f8fafc;--text:#172033;--muted:#7b8798;--border:#e5e9f2;--primary:#2563eb;--danger:#dc2626;--shadow:0 8px 30px rgba(20,30,50,.08)}
|
:root{--bg:#f5f7fb;--surface:#fff;--surface2:#f8fafc;--text:#172033;--muted:#7b8798;--border:#e5e9f2;--primary:#2563eb;--danger:#dc2626;--shadow:0 8px 30px rgba(20,30,50,.08)}
|
||||||
html.dark{--bg:#0f141c;--surface:#171e28;--surface2:#202936;--text:#e7edf7;--muted:#9aa7b8;--border:#2c3746;--primary:#60a5fa;--danger:#f87171;--shadow:0 12px 35px rgba(0,0,0,.3)}
|
html.dark{--bg:#0f141c;--surface:#171e28;--surface2:#202936;--text:#e7edf7;--muted:#9aa7b8;--border:#2c3746;--primary:#60a5fa;--danger:#f87171;--shadow:0 12px 35px rgba(0,0,0,.3)}
|
||||||
@@ -41,7 +41,7 @@ button.ghost{background:var(--surface2);color:var(--text)}button.danger{backgrou
|
|||||||
<input id="search" placeholder="搜索文件名,回车执行">
|
<input id="search" placeholder="搜索文件名,回车执行">
|
||||||
<button id="usersBtn" onclick="showUsers()" hidden>用户管理</button><button class="ghost" id="pwdBtn" onclick="changePassword()" hidden>修改密码</button><button class="iconbtn" id="themeBtn" onclick="toggleTheme()" title="主题"><span id="themeIcon"></span></button><button class="iconbtn viewbtn active" id="listBtn" onclick="setView("list")" title="列表"><span id="listIcon"></span></button><button class="iconbtn viewbtn" id="gridBtn" onclick="setView("grid")" title="网格"><span id="gridIcon"></span></button>
|
<button id="usersBtn" onclick="showUsers()" hidden>用户管理</button><button class="ghost" id="pwdBtn" onclick="changePassword()" hidden>修改密码</button><button class="iconbtn" id="themeBtn" onclick="toggleTheme()" title="主题"><span id="themeIcon"></span></button><button class="iconbtn viewbtn active" id="listBtn" onclick="setView("list")" title="列表"><span id="listIcon"></span></button><button class="iconbtn viewbtn" id="gridBtn" onclick="setView("grid")" title="网格"><span id="gridIcon"></span></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="bulk" class="bulk"><span id="selectedCount" class="count"></span><button class="ghost" onclick="bulkMove()">移动</button><button class="ghost" onclick="bulkCopy()">复制</button><button class="ghost" onclick="bulkDownload()">下载 ZIP</button><button class="danger" onclick="bulkDelete()">删除</button><button class="ghost" onclick="clearSelection()">取消选择</button></div><div class="card" id="fileCard"><table><thead><tr><th><input class="selectbox" type="checkbox" onchange="toggleAll(this.checked)"></th><th>名称</th><th>大小</th><th>修改时间</th><th></th></tr></thead><tbody id="files"></tbody></table><div id="grid" class="grid" hidden></div></div></section></div>
|
<div id="bulk" class="bulk"><span id="selectedCount" class="count"></span><button class="ghost" onclick="bulkMove()">移动</button><button class="ghost" onclick="bulkCopy()">复制</button><button class="ghost" onclick="bulkDownload()">下载 ZIP</button><button class="danger" onclick="bulkDelete()">删除</button><button class="ghost" onclick="clearSelection()">取消选择</button></div><div class="card" id="fileCard"><table><thead><tr><th class="check-col"><input class="selectbox" type="checkbox" onchange="toggleAll(this.checked)"></th><th>名称</th><th class="size-col">大小</th><th class="time-col">修改时间</th><th class="perm-col">权限</th><th class="action-col"></th></tr></thead><tbody id="files"></tbody></table><div id="grid" class="grid" hidden></div></div></section></div>
|
||||||
</main><div id="context" class="context"></div><div id="toastHost" class="toast-host"></div>
|
</main><div id="context" class="context"></div><div id="toastHost" class="toast-host"></div>
|
||||||
<div class="modal" id="modal"><div class="dialog" id="dialog"></div></div>
|
<div class="modal" id="modal"><div class="dialog" id="dialog"></div></div>
|
||||||
<script>
|
<script>
|
||||||
@@ -54,10 +54,10 @@ function updateUser(){$("who").textContent=me.username?me.username: "访客";$("
|
|||||||
async function refresh(){try{const q=encodeURIComponent(current);const data=await api("/chfs/files?filepath="+q);currentMask=data.currentMask||"";const writable=currentMask.includes("W");$("newFolderBtn").disabled=!writable;$("newTextBtn").disabled=!writable;$("uploadBtn").disabled=!writable;$("search").disabled=!data.currentMask?.includes("R");render(data.files||[]);loadSideRoots();renderBreadcrumb();$("notice").innerHTML=""}catch(e){$("notice").innerHTML='<div class="notice">'+esc(e.message)+"</div>"}}
|
async function refresh(){try{const q=encodeURIComponent(current);const data=await api("/chfs/files?filepath="+q);currentMask=data.currentMask||"";const writable=currentMask.includes("W");$("newFolderBtn").disabled=!writable;$("newTextBtn").disabled=!writable;$("uploadBtn").disabled=!writable;$("search").disabled=!data.currentMask?.includes("R");render(data.files||[]);loadSideRoots();renderBreadcrumb();$("notice").innerHTML=""}catch(e){$("notice").innerHTML='<div class="notice">'+esc(e.message)+"</div>"}}
|
||||||
function fileType(f){const n=f.name.toLowerCase();if(f.dir)return "folder";if(/\.(png|jpe?g|gif|webp|bmp|svg)$/.test(n))return "image";if(/\.(mp4|webm|mov|mkv)$/.test(n))return "video";if(/\.(mp3|wav|flac|m4a|ogg)$/.test(n))return "audio";if(/\.(pdf)$/.test(n))return "pdf";return "file"}
|
function fileType(f){const n=f.name.toLowerCase();if(f.dir)return "folder";if(/\.(png|jpe?g|gif|webp|bmp|svg)$/.test(n))return "image";if(/\.(mp4|webm|mov|mkv)$/.test(n))return "video";if(/\.(mp3|wav|flac|m4a|ogg)$/.test(n))return "audio";if(/\.(pdf)$/.test(n))return "pdf";return "file"}
|
||||||
function svgIcon(type){const p={folder:'<path d="M3 6.5h6l2 2H21v9.5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',image:'<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8" cy="9" r="1.4"/><path d="m4 17 5-5 4 4 2-2 5 5"/>',video:'<rect x="3" y="5" width="13" height="14" rx="2"/><path d="m16 10 5-3v10l-5-3z"/>',audio:'<path d="M9 18V6l9-2v12"/><circle cx="6" cy="18" r="3"/><circle cx="15" cy="16" r="3"/>',pdf:'<path d="M6 3h8l4 4v14H6z"/><path d="M14 3v5h5M8 16h8M8 12h6"/>',archive:'<path d="M7 3h10v18H7z"/><path d="M10 3v3h4V3M10 8h4v3h-4v3h4v3h-4"/>',document:'<path d="M6 3h8l4 4v14H6z"/><path d="M14 3v5h5M9 12h6M9 16h6"/>',code:'<path d="m9 7-5 5 5 5M15 7l5 5-5 5M13 4l-2 16"/>',spreadsheet:'<rect x="4" y="3" width="16" height="18" rx="2"/><path d="M4 8h16M10 8v13M15 8v13M4 13h16M4 17h16"/>',presentation:'<rect x="3" y="4" width="18" height="14" rx="2"/><path d="M12 18v3M8 21h8M7 14l3-4 3 2 4-5"/>',file:'<path d="M6 3h8l4 4v14H6z"/><path d="M14 3v5h5"/>'};return '<svg class="ui-icon" viewBox="0 0 24 24" aria-hidden="true">'+(p[type]||p.file)+'</svg>'}function iconFor(f){const n=f.name.toLowerCase();const t=fileType(f);if(t!=="file")return svgIcon(t);if(/\.(zip|7z|rar|tar|gz|bz2|xz)$/.test(n))return svgIcon("archive");if(/\.(xls|xlsx|ods)$/.test(n))return svgIcon("spreadsheet");if(/\.(ppt|pptx|odp)$/.test(n))return svgIcon("presentation");if(/\.(doc|docx|odt|rtf)$/.test(n))return svgIcon("document");if(/\.(js|ts|jsx|tsx|css|scss|html|htm|go|rs|py|java|c|cpp|h|hpp|sh|sql|json|yaml|yml|xml)$/.test(n))return svgIcon("code");return svgIcon("file")}
|
function svgIcon(type){const p={folder:'<path d="M3 6.5h6l2 2H21v9.5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',image:'<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8" cy="9" r="1.4"/><path d="m4 17 5-5 4 4 2-2 5 5"/>',video:'<rect x="3" y="5" width="13" height="14" rx="2"/><path d="m16 10 5-3v10l-5-3z"/>',audio:'<path d="M9 18V6l9-2v12"/><circle cx="6" cy="18" r="3"/><circle cx="15" cy="16" r="3"/>',pdf:'<path d="M6 3h8l4 4v14H6z"/><path d="M14 3v5h5M8 16h8M8 12h6"/>',archive:'<path d="M7 3h10v18H7z"/><path d="M10 3v3h4V3M10 8h4v3h-4v3h4v3h-4"/>',document:'<path d="M6 3h8l4 4v14H6z"/><path d="M14 3v5h5M9 12h6M9 16h6"/>',code:'<path d="m9 7-5 5 5 5M15 7l5 5-5 5M13 4l-2 16"/>',spreadsheet:'<rect x="4" y="3" width="16" height="18" rx="2"/><path d="M4 8h16M10 8v13M15 8v13M4 13h16M4 17h16"/>',presentation:'<rect x="3" y="4" width="18" height="14" rx="2"/><path d="M12 18v3M8 21h8M7 14l3-4 3 2 4-5"/>',file:'<path d="M6 3h8l4 4v14H6z"/><path d="M14 3v5h5"/>'};return '<svg class="ui-icon" viewBox="0 0 24 24" aria-hidden="true">'+(p[type]||p.file)+'</svg>'}function iconFor(f){const n=f.name.toLowerCase();const t=fileType(f);if(t!=="file")return svgIcon(t);if(/\.(zip|7z|rar|tar|gz|bz2|xz)$/.test(n))return svgIcon("archive");if(/\.(xls|xlsx|ods)$/.test(n))return svgIcon("spreadsheet");if(/\.(ppt|pptx|odp)$/.test(n))return svgIcon("presentation");if(/\.(doc|docx|odt|rtf)$/.test(n))return svgIcon("document");if(/\.(js|ts|jsx|tsx|css|scss|html|htm|go|rs|py|java|c|cpp|h|hpp|sh|sql|json|yaml|yml|xml)$/.test(n))return svgIcon("code");return svgIcon("file")}
|
||||||
function selectItem(p,checked){if(checked)selected.add(p);else selected.delete(p);updateSelection()}
|
function selectItem(p,checked){if(checked)selected.add(p);else selected.delete(p);updateSelection();renderList(lastItems)}
|
||||||
let selected=new Set(),lastItems=[];
|
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 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")?'<button class="ghost" onclick="shareItem('+JSON.stringify(p)+')">分享</button>':"";return '<tr><td><input class="selectbox" type="checkbox" '+(selected.has(p)?"checked":"")+' onchange="selectItem('+JSON.stringify(p)+',this.checked)"></td><td><div class="name"><span>'+iconFor(f)+'</span>'+(f.dir&&canRead?'<a href="#" onclick="openDir('+JSON.stringify(p)+');return false">'+esc(f.name)+'</a>':(canRead?'<a href="#" onclick="previewFile('+JSON.stringify(p)+');return false">'+esc(f.name)+'</a>':'<span>'+esc(f.name)+'</span>'))+'</div></td><td class="meta">'+(f.dir?"—":fmt(f.size))+'</td><td class="meta">'+esc(f.modified||"")+'</td><td><div class="actions"><button class="iconbtn" onclick="itemMenu(event,'+JSON.stringify(p)+')">⋮</button></div></td></tr>'}).join("")||'<tr><td colspan="5" class="empty">这里还没有文件</td></tr>'}
|
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"),selectedNow=selected.has(p);const perms='<span class="perm-dot '+(canRead?'read':'')+'"></span><span class="perm-dot '+(canWrite?'write':'')+'"></span><span class="perm-dot '+(canDelete?'delete':'')+'"></span>';return '<tr class="file-row '+(selectedNow?'selected':'')+'"><td><input class="selectbox" type="checkbox" '+(selectedNow?"checked":"")+' onchange="selectItem('+JSON.stringify(p)+',this.checked)"></td><td><div class="name"><span class="file-kind">'+iconFor(f)+'</span>'+(f.dir&&canRead?'<a href="#" onclick="openDir('+JSON.stringify(p)+');return false">'+esc(f.name)+'</a>':(canRead?'<a href="#" onclick="previewFile('+JSON.stringify(p)+');return false">'+esc(f.name)+'</a>':'<span>'+esc(f.name)+'</span>'))+'</div></td><td class="size-cell">'+(f.dir?"—":fmt(f.size))+'</td><td class="time-cell">'+esc(f.modified||"")+'</td><td class="perm-cell">'+perms+'</td><td><div class="actions"><button class="iconbtn" onclick="itemMenu(event,'+JSON.stringify(p)+')" title="更多操作"><svg class="ui-icon" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg></button></div></td></tr>'}).join("")||'<tr><td colspan="6" class="empty">这里还没有文件</td></tr>'}
|
||||||
function renderGrid(items){$("grid").innerHTML=items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),type=fileType(f),selectedNow=selected.has(p);let thumb=iconFor(f);if(type==="image"&&canRead)thumb='<img src="/chfs/preview?file='+encodeURIComponent(p)+'" loading="lazy" alt="">';const tileClass=(f.dir?'folder-tile ':'')+(['video','audio'].includes(type)?'media-tile':'');const meta=f.dir?'文件夹':fmt(f.size);return '<div class="tile '+tileClass+(selectedNow?' selected':'')+'" onclick="'+(f.dir&&canRead?'openDir('+JSON.stringify(p)+')':'toggleTile('+JSON.stringify(p)+')')+'" oncontextmenu="itemMenu(event,'+JSON.stringify(p)+');return false"><input class="selectbox tile-check" type="checkbox" '+(selectedNow?'checked':'')+' onclick="event.stopPropagation();selectItem('+JSON.stringify(p)+',this.checked)"><button class="iconbtn tile-action" onclick="event.stopPropagation();itemMenu(event,'+JSON.stringify(p)+')" title="更多操作"><svg class="ui-icon" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg></button><div class="thumb">'+thumb+'</div><div class="tile-title">'+iconFor(f)+'<div class="file-name">'+esc(f.name)+'</div></div><div class="tile-meta"><span>'+meta+'</span>'+(f.modified?'<span>'+esc(f.modified)+'</span>':'')+'</div></div>'}).join('')||'<div class="empty">这里还没有文件</div>'}
|
function renderGrid(items){$("grid").innerHTML=items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),type=fileType(f),selectedNow=selected.has(p);let thumb=iconFor(f);if(type==="image"&&canRead)thumb='<img src="/chfs/preview?file='+encodeURIComponent(p)+'" loading="lazy" alt="">';const tileClass=(f.dir?'folder-tile ':'')+(['video','audio'].includes(type)?'media-tile':'');const meta=f.dir?'文件夹':fmt(f.size);return '<div class="tile '+tileClass+(selectedNow?' selected':'')+'" onclick="'+(f.dir&&canRead?'openDir('+JSON.stringify(p)+')':'toggleTile('+JSON.stringify(p)+')')+'" oncontextmenu="itemMenu(event,'+JSON.stringify(p)+');return false"><input class="selectbox tile-check" type="checkbox" '+(selectedNow?'checked':'')+' onclick="event.stopPropagation();selectItem('+JSON.stringify(p)+',this.checked)"><button class="iconbtn tile-action" onclick="event.stopPropagation();itemMenu(event,'+JSON.stringify(p)+')" title="更多操作"><svg class="ui-icon" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg></button><div class="thumb">'+thumb+'</div><div class="tile-title">'+iconFor(f)+'<div class="file-name">'+esc(f.name)+'</div></div><div class="tile-meta"><span>'+meta+'</span>'+(f.modified?'<span>'+esc(f.modified)+'</span>':'')+'</div></div>'}).join('')||'<div class="empty">这里还没有文件</div>'}
|
||||||
function toggleTile(p){selectItem(p,!selected.has(p))}
|
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 toggleAll(v){lastItems.forEach(f=>{const p=current?current+"/"+f.name:f.name;if(v)selected.add(p);else selected.delete(p)});render(lastItems)}
|
||||||
|
|||||||
Reference in New Issue
Block a user