过节有点时间捣鼓 Everything 的 HTTP 共享,发现对于自己家里用 Win10 NAS 来说,无论是按目录浏览还是快速查找,直接用 Evertything 的 Web 端都太顺手了。唯一不太满意的,就是这个自带的 WebUI 是为桌面浏览器设计的,在手机上用起来就不太舒服了。在网上找了一圈没找到现成的插件定制 UI,于是我突发奇想,索性就在 DeepSeek 免费的 Web Chat 里让它搓了一个 SearchUI.html 出来。
这个手搓的过程完全是在浏览器里通过:对话提出要求 ->复制代码到文件->测试->对话提出改进要求 的方式完成的,我刻意没用平常习惯的 vscode + kilocode,就想比较一下有什么区别。
-
每次测试修改,AI 都会重新生成完整的 html, 因为不是只修改部分代码,所以输出的速度显然比调用 API 要慢不少
-
有一次改崩了目录显示的逻辑,然后我无论怎么提示都恢复不回去,最后只能把旧的版本文件拖给它,让在这个基础上修改才算翻过这个坎儿
-
每次 AI 都会信誓旦旦地保证代码一定能正常工作,我开始还将信将疑,后面就完全无视了
-
我本来是打算全程一点代码都不写的,但是现实是我发现有些关键点,我手动修改一下,远比跟 AI 啰嗦半天效率要高得多。吐糟给 AI 吧,得到的回复居然说这才是人机合作开发的最高境界?!
最终结果,花了半天时间,经历了大概四十多轮对话,终于完美实现了我所需要的,适应手机的UI效果,挂到 Everything 的 HTTP 服务下面(不是替代默认页面),完美。
这是 Everything 自带的 HTTP 页面
这是我定制的页面
这个静态页面本质上就是通过 JS 解析 Eveything 的 HTML,技术说穿了就不值钱,分享出来供有需要的网友参考。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Everything</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #f5f5f5;
margin: 0;
padding: 16px;
}
.container {
max-width: 700px;
margin: 0 auto;
}
.search-box {
width: 100%;
padding: 14px 16px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 12px;
outline: none;
background: white;
margin-bottom: 12px;
}
.search-box:focus {
border-color: #007aff;
}
.drive-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 12px 0;
}
.drive-btn {
background: #e5e5ea;
color: #1c1c1e;
border: none;
padding: 8px 20px;
border-radius: 25px;
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
.drive-btn:active {
background: #c6c6c8;
}
.drive-btn.active {
background: #007aff;
color: white;
}
.breadcrumb {
background: white;
padding: 10px 14px;
border-radius: 12px;
margin-bottom: 12px;
font-size: 13px;
word-break: break-all;
white-space: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.breadcrumb span {
color: #007aff;
cursor: pointer;
}
.separator {
margin: 0 4px;
color: #888;
}
.results {
list-style: none;
padding: 0;
margin: 16px 0 0 0;
}
.file-item {
background: white;
border-radius: 12px;
margin-bottom: 8px;
cursor: pointer;
}
.file-item:active {
background: #e5e5ea;
}
.file-link {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
color: inherit;
padding: 12px;
width: 100%;
}
.file-icon,
.folder-icon {
font-size: 28px;
flex-shrink: 0;
}
.file-info {
flex: 1;
min-width: 0;
}
.file-name,
.folder-name {
font-weight: 500;
font-size: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-name {
color: #007aff;
}
.folder-name {
color: #ff9500;
}
.file-meta {
font-size: 12px;
color: #888;
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.folder-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
width: 100%;
}
.status {
padding: 20px;
text-align: center;
color: #888;
background: white;
border-radius: 12px;
}
.stats {
font-size: 13px;
color: #888;
margin-top: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>Everything v1.4.1.1032</h1>
<input type="text" id="searchInput" class="search-box" placeholder="输入文件名搜索..." autofocus>
<div class="drive-bar" id="driveBar"></div>
<div id="breadcrumb" class="breadcrumb" style="display:none;"></div>
<div id="resultsContainer"></div>
</div>
<script>
const searchInput = document.getElementById('searchInput');
const resultsContainer = document.getElementById('resultsContainer');
const breadcrumbDiv = document.getElementById('breadcrumb');
const driveBar = document.getElementById('driveBar');
let currentPath = '';
let searchDebounceTimer = null;
let driveList = [];
// 从 Everything 默认页面获取磁盘列表
async function fetchDriveList() {
try {
const response = await fetch(`/`);
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const table = doc.querySelector('table');
if (!table) return [];
const drives = [];
const rows = table.querySelectorAll('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].querySelectorAll('td');
if (cells.length >= 1) {
const link = cells[0]?.querySelector('a');
let name = link?.textContent || cells[0]?.textContent || '';
if (/^[A-Z]:$/i.test(name)) {
drives.push(`${name}\\`);
}
}
}
return drives;
} catch (err) {
console.error('获取磁盘列表失败', err);
return ['C:\\'];
}
}
async function init() {
const drives = await fetchDriveList();
if (drives.length > 0) {
driveList.length = 0;
driveList.push(...drives);
}
renderDriveButtons();
}
function getDriveRootFromPath(path) {
const match = path.match(/^[A-Z]:\\/i);
return match ? match[0] : null;
}
function updateDriveHighlights() {
const currentRoot = getDriveRootFromPath(currentPath);
document.querySelectorAll('.drive-btn').forEach(btn => {
const drive = btn.dataset.drive;
if (drive === currentRoot) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
}
function renderDriveButtons() {
driveBar.innerHTML = driveList.map(drive =>
`<button class="drive-btn" data-drive="${drive}">${drive.replace('\\', '')}</button>`
).join('');
document.querySelectorAll('.drive-btn').forEach(btn => {
btn.addEventListener('click', () => {
browseDirectory(btn.dataset.drive);
});
});
updateDriveHighlights();
}
function escapeAttr(str) {
if (!str) return '';
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\\/g, '\');
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, (m) => {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
function urlToLocalPath(url) {
try {
const urlObj = new URL(url);
let pathname = decodeURIComponent(urlObj.pathname);
if (pathname.startsWith('/')) pathname = pathname.substring(1);
return pathname.replace(/\//g, '\\');
} catch (e) {
return url;
}
}
// 提取路径的目录部分(去掉文件名)
function getDirectoryPath(fullPath) {
const lastSlash = fullPath.lastIndexOf('\\');
if (lastSlash > 0) {
return fullPath.substring(0, lastSlash + 1);
}
return fullPath;
}
// ==================== 搜索功能 ====================
async function performSearch(query) {
if (!query.trim()) {
resultsContainer.innerHTML = '<div class="status">输入关键词开始搜索</div>';
return;
}
resultsContainer.innerHTML = '<div class="status">搜索中...</div>';
try {
const response = await fetch(`/?search=${encodeURIComponent(query)}`);
const html = await response.text();
const results = parseSearchResults(html);
renderSearchResults(results, query);
} catch (err) {
resultsContainer.innerHTML = '<div class="status">❌ 搜索失败</div>';
}
}
function parseSearchResults(html) {
const results = [];
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const table = doc.querySelector('table');
if (!table) return results;
const rows = table.querySelectorAll('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].querySelectorAll('td');
if (cells.length >= 3) {
const link = cells[0]?.querySelector('a');
let name = link?.textContent || cells[0]?.textContent || '';
const nativeUrl = link?.href || '';
const idxSize = cells.length > 3 ? 2 : 1;
const idxDate = cells.length > 3 ? 3 : 2;
const size = cells[idxSize]?.textContent?.trim() || '';
const date = cells[idxDate]?.textContent?.trim() || '';
if (name === '名称' || name === 'Name' || name === 'name') continue;
if (name.includes('\\')) {
name = name.split('\\').pop();
}
if (!name) continue;
const isFolder = (size === '' && !/^[A-Z]:$/i.test(name));
let folderPath = '';
if (isFolder) {
folderPath = urlToLocalPath(nativeUrl);
}
// 获取文件所在目录路径
const localPath = urlToLocalPath(nativeUrl);
const directoryPath = isFolder ? localPath : getDirectoryPath(localPath);
results.push({ name, nativeUrl, size, date, isFolder, folderPath, directoryPath });
}
}
return results;
}
function renderSearchResults(results, query) {
if (!results.length) {
resultsContainer.innerHTML = `<div class="status">没有找到 "${escapeHtml(query)}" 相关文件</div>`;
return;
}
const folders = results.filter(r => r.isFolder);
const files = results.filter(r => !r.isFolder);
const html = `
<div class="stats">找到 ${results.length} 个结果(目录 ${folders.length},文件 ${files.length})</div>
<ul class="results">
${folders.map(folder => `
<li class="file-item" data-type="folder" data-path="${escapeAttr(folder.folderPath)}">
<div class="folder-item">
<div class="folder-icon">📂</div>
<div class="file-info">
<div class="folder-name">${escapeHtml(folder.name)}</div>
<div class="file-meta">${escapeHtml(folder.folderPath)}</div>
</div>
</div>
</li>
`).join('')}
${files.map(file => `
<li class="file-item">
<a href="${escapeAttr(file.nativeUrl)}" target="_blank" class="file-link">
<div class="file-icon">${getIcon(file.name)}</div>
<div class="file-info">
<div class="file-name">${escapeHtml(file.name)}</div>
<div class="file-meta">${file.size} • ${file.date} • ${escapeHtml(file.directoryPath)}</div>
</div>
</a>
</li>
`).join('')}
</ul>
`;
resultsContainer.innerHTML = html;
document.querySelectorAll('#resultsContainer .file-item[data-type="folder"]').forEach(el => {
el.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const path = el.dataset.path;
if (path) {
searchInput.value = '';
browseDirectory(path);
}
});
});
}
if (searchInput) {
searchInput.addEventListener('input', (e) => {
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
searchDebounceTimer = setTimeout(() => {
performSearch(e.target.value);
}, 300);
});
}
function getIcon(filename) {
const ext = filename.split('.').pop().toLowerCase();
const icons = {
'mp4': '🎬', 'mkv': '🎥', 'avi': '🎬', 'mov': '🎬', 'mp3': '🎵',
'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'pdf': '📄',
'doc': '📝', 'docx': '📝', 'txt': '📃', 'zip': '📦', 'rar': '📦', 'exe': '⚙️'
};
return icons[ext] || '📄';
}
async function browseDirectory(path) {
let normalizedPath = path.trim();
if (!normalizedPath.endsWith('\\')) {
normalizedPath += '\\';
}
currentPath = normalizedPath;
updateBreadcrumb(currentPath);
updateDriveHighlights();
resultsContainer.innerHTML = '<div class="status">加载中...</div>';
try {
const searchQuery = `parent:"${currentPath}"`;
const response = await fetch(`/?search=${encodeURIComponent(searchQuery)}`);
const html = await response.text();
const items = parseBrowseResults(html, currentPath);
renderBrowseResults(items);
} catch (err) {
resultsContainer.innerHTML = '<div class="status">❌ 读取目录失败</div>';
}
}
function parseBrowseResults(html, currentPath) {
const items = [];
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const table = doc.querySelector('table');
if (!table) return items;
const rows = table.querySelectorAll('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].querySelectorAll('td');
if (cells.length >= 3) {
const link = cells[0]?.querySelector('a');
let name = link?.textContent || cells[0]?.textContent || '';
const idxSize = cells.length > 3 ? 2 : 1;
const idxDate = cells.length > 3 ? 3 : 2;
let size = cells[idxSize]?.textContent?.trim() || '';
let date = cells[idxDate]?.textContent?.trim() || '';
const nativeUrl = link?.href || '';
if (name === '名称' || name === 'Name' || name === 'name') continue;
if (name.includes('\\')) {
name = name.split('\\').pop();
}
if (!name || name === '.' || name === '..') continue;
if (/^[A-Z]:$/i.test(name)) continue;
const isFolder = (size === '');
items.push({ name, size, date, isFolder, nativeUrl });
}
}
const parentPath = getParentPath(currentPath);
if (parentPath && parentPath !== currentPath) {
items.unshift({
name: '..',
isFolder: true,
isParent: true,
parentPath: parentPath
});
}
return items;
}
function getParentPath(path) {
let p = path.replace(/\\$/, '');
const lastSlash = p.lastIndexOf('\\');
if (lastSlash <= 2) {
return p.substring(0, lastSlash + 1);
}
if (lastSlash > 0) {
return p.substring(0, lastSlash + 1);
}
return null;
}
function updateBreadcrumb(path) {
const parts = path.split('\\').filter(p => p !== '');
if (parts.length === 0) {
breadcrumbDiv.style.display = 'none';
return;
}
let html = '';
let current = '';
for (let i = 0; i < parts.length; i++) {
current += parts[i] + '\\';
if (i === parts.length - 1) {
html += `<span>${escapeHtml(parts[i])}</span>`;
} else {
const jsPath = current.replace(/\\/g, '\\\\');
if (i === 0 && parts[i].includes(':')) {
html += `<span onclick="window.browseDirectory('${jsPath}')">${escapeHtml(parts[i])}</span><span class="separator">/</span>`;
} else {
html += `<span onclick="window.browseDirectory('${jsPath}')">${escapeHtml(parts[i])}</span><span class="separator">/</span>`;
}
}
}
breadcrumbDiv.innerHTML = `📁 ${html}`;
breadcrumbDiv.style.display = 'block';
}
function renderBrowseResults(items) {
if (!items.length) {
resultsContainer.innerHTML = '<div class="status">此目录为空</div>';
return;
}
const regularCount = items.filter(i => !i.isParent).length;
const html = `
<div class="stats">共 ${regularCount} 个项目</div>
<ul class="results">
${items.map(item => {
if (item.isParent) {
return `
<li class="file-item" data-type="parent" data-path="${escapeAttr(item.parentPath)}">
<div class="folder-item">
<div class="folder-icon">📂</div>
<div class="file-info">
<div class="folder-name">⬆️ 返回上级</div>
</div>
</div>
</li>
`;
}
if (item.isFolder) {
const folderPath = currentPath + item.name + '\\';
return `
<li class="file-item" data-type="folder" data-path="${escapeAttr(folderPath)}">
<div class="folder-item">
<div class="folder-icon">📂</div>
<div class="file-info">
<div class="folder-name">📁 ${escapeHtml(item.name)}</div>
<div class="file-meta">${escapeHtml(folderPath)}</div>
</div>
</div>
</li>
`;
}
return `
<li class="file-item">
<a href="${escapeAttr(item.nativeUrl)}" target="_blank" class="file-link">
<div class="file-icon">${getIcon(item.name)}</div>
<div class="file-info">
<div class="file-name">${escapeHtml(item.name)}</div>
<div class="file-meta">${item.size} • ${item.date} • ${escapeHtml(currentPath)}</div>
</div>
</a>
</li>
`;
}).join('')}
</ul>
`;
resultsContainer.innerHTML = html;
document.querySelectorAll('#resultsContainer .file-item[data-type="parent"], #resultsContainer .file-item[data-type="folder"]').forEach(el => {
el.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const path = el.dataset.path;
if (path) browseDirectory(path);
});
});
}
window.browseDirectory = browseDirectory;
init();
</script>
</body>
</html>
View Code