const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = 8081;
const DIRECTORY = '/data/ai-output';
const server = http.createServer((req, res) => {
// 添加CORS头部支持
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
let pathname = req.url.split('?')[0];
try {
pathname = decodeURIComponent(pathname);
} catch (e) {
console.error('URL解码失败:', e.message);
}
const filePath = path.join(DIRECTORY, pathname);
console.log(`[${new Date().toLocaleString('zh-CN')}] ${req.method} ${pathname}`);
if (!fs.existsSync(filePath)) {
res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
res.end('<h1>404 - 文件未找到</h1><p><a href="/">返回首页</a></p>');
return;
}
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
const files = fs.readdirSync(filePath).map(f => {
const fstat = fs.statSync(path.join(filePath, f));
const icon = fstat.isDirectory() ? '📁' : '📄';
const size = fstat.isFile() ? `${(fstat.size / 1024).toFixed(1)} KB` : '';
const encodedName = encodeURIComponent(f);
const link = path.join(pathname, encodedName);
const ext = path.extname(f).toLowerCase();
const isPreviewable = ['.md', '.txt', '.pdf', '.xlsx', '.xls', '.csv', '.doc', '.docx', '.ppt', '.pptx'].includes(ext);
// 格式化日期
const formatDate = (date) => {
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
const createdDate = formatDate(fstat.birthtime);
const modifiedDate = formatDate(fstat.mtime);
return `
<div style="padding: 15px; margin: 10px 0; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div>
<span style="font-size: 24px;">${icon}</span>
<a href="${link}" style="color: #667eea; text-decoration: none; font-size: 16px; margin-left: 10px; font-weight: 500;">${f}</a>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="color: #999; font-size: 14px;">${size}</span>
${isPreviewable ? `<button onclick="copyShareLink('${link}')" style="padding: 8px 16px; background: #00C853; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;">📋 复制链接</button>` : ''}
</div>
</div>
<div style="display: flex; gap: 20px; font-size: 13px; color: #7f8c8d;">
<span>📅 创建: ${createdDate}</span>
<span>✏️ 修改: ${modifiedDate}</span>
</div>
</div>
`;
}).join('');
const html = getHtmlTemplate('文件列表', `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px;">
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<h2 style="color: #2c3e50; margin: 0;">📂 当前目录</h2>
<button onclick="copyShareLink(window.location.href)" style="padding: 12px 24px; background: #00C853; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">📋 分享此目录</button>
</div>
${files}
</div>
`, pathname);
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
return;
}
const ext = path.extname(filePath).toLowerCase();
const filename = path.basename(filePath);
// 格式化日期
const formatDate = (date) => {
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
const createdDate = formatDate(stat.birthtime);
const modifiedDate = formatDate(stat.mtime);
// 检查是否是下载请求
const isDownload = req.url.includes('?download=1');
if (ext === '.md') {
const content = fs.readFileSync(filePath, 'utf-8');
const html = getHtmlTemplate(filename, `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px;">
<div style="margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="copyShareLink(window.location.href)" style="padding: 12px 24px; background: #00C853; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">📋 复制分享链接</button>
<button onclick="downloadFile('${filename}')" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">⬇️ 下载文件</button>
</div>
<div class="markdown-body" id="content"></div>
</div>
`, pathname, content, filename);
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else if (['.xlsx', '.xls'].includes(ext)) {
// Excel文件
if (isDownload) {
// 返回原始二进制文件
const content = fs.readFileSync(filePath);
res.writeHead(200, {
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Length': content.length,
'Content-Disposition': `attachment; filename="${encodeURIComponent(filename)}"`
});
res.end(content);
return;
}
// Excel文件预览 - 使用Handsontable
const html = getHtmlTemplate(filename, `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px;">
<div style="margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="copyShareLink(window.location.href)" style="padding: 12px 24px; background: #00C853; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">📋 复制分享链接</button>
<a href="${pathname}?download=1" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500; text-decoration: none; display: inline-block;">⬇️ 下载文件</a>
</div>
<div style="margin-top: 20px;">
<div id="sheet-tabs" style="margin-bottom: 10px; display: flex; gap: 10px; flex-wrap: wrap;"></div>
<div style="width: 100%; overflow: auto; border: 1px solid #ddd; border-radius: 8px;">
<div id="excel-container" style="width: 100%; height: 600px;"></div>
</div>
</div>
</div>
`, pathname, null, filename, 'excel');
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else if (ext === '.csv') {
// CSV文件
if (isDownload) {
const content = fs.readFileSync(filePath, 'utf-8');
res.writeHead(200, {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Length': Buffer.byteLength(content),
'Content-Disposition': `attachment; filename="${encodeURIComponent(filename)}"`
});
res.end(content);
return;
}
// CSV文件预览
const html = getHtmlTemplate(filename, `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px;">
<div style="margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="copyShareLink(window.location.href)" style="padding: 12px 24px; background: #00C853; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">📋 复制分享链接</button>
<a href="${pathname}?download=1" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500; text-decoration: none; display: inline-block;">⬇️ 下载文件</a>
</div>
<div id="csv-viewer" style="margin-top: 20px;">
<div style="text-align: center; padding: 40px;">
<div style="font-size: 48px;">📋</div>
<p style="color: #7f8c8d; margin-top: 20px;">正在加载CSV文件...</p>
</div>
</div>
</div>
`, pathname, null, filename, 'csv');
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else if (ext === '.pdf') {
// PDF文件
if (isDownload) {
const content = fs.readFileSync(filePath);
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Length': content.length,
'Content-Disposition': `inline; filename="${encodeURIComponent(filename)}"`
});
res.end(content);
return;
}
// PDF文件预览
const html = getHtmlTemplate(filename, `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px;">
<div style="margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="copyShareLink(window.location.href)" style="padding: 12px 24px; background: #00C853; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">📋 复制分享链接</button>
<a href="${pathname}?download=1" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500; text-decoration: none; display: inline-block;">⬇️ 下载文件</a>
</div>
<div style="margin-top: 20px; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
<embed src="${pathname}?download=1" type="application/pdf" width="100%" height="800px" style="display: block;">
</div>
</div>
`, pathname, null, filename, 'pdf');
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else if (['.doc', '.docx'].includes(ext)) {
// Word文档
if (isDownload) {
const content = fs.readFileSync(filePath);
res.writeHead(200, {
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'Content-Length': content.length,
'Content-Disposition': `attachment; filename="${encodeURIComponent(filename)}"`
});
res.end(content);
return;
}
// Word文档预览 - 使用mammoth.js本地转换
const html = getHtmlTemplate(filename, `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px;">
<div style="margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="copyShareLink(window.location.href)" style="padding: 12px 24px; background: #00C853; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">📋 复制分享链接</button>
<a href="${pathname}?download=1" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500; text-decoration: none; display: inline-block;">⬇️ 下载文件</a>
</div>
<div id="word-viewer" style="margin-top: 20px;">
<div style="text-align: center; padding: 40px;">
<div style="font-size: 48px;">📄</div>
<p style="color: #7f8c8d; margin-top: 20px;">正在加载Word文档...</p>
</div>
</div>
</div>
`, pathname, null, filename, 'word');
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else if (['.ppt', '.pptx'].includes(ext)) {
// PPT文档
if (isDownload) {
const content = fs.readFileSync(filePath);
res.writeHead(200, {
'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'Content-Length': content.length,
'Content-Disposition': `attachment; filename="${encodeURIComponent(filename)}"`
});
res.end(content);
return;
}
// PPT预览 - 提供下载和说明
const html = getHtmlTemplate(filename, `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px; text-align: center;">
<div style="font-size: 64px; margin-bottom: 20px;">📊</div>
<h2 style="color: #2c3e50; margin-bottom: 20px;">${filename}</h2>
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 30px; text-align: left; max-width: 600px; margin-left: auto; margin-right: auto;">
<h3 style="color: #2c3e50; margin-bottom: 15px;">📋 文件信息</h3>
<p style="color: #7f8c8d; margin: 8px 0;"><strong>文件类型:</strong> PowerPoint演示文稿 (.pptx)</p>
<p style="color: #7f8c8d; margin: 8px 0;"><strong>创建时间:</strong> ${createdDate}</p>
<p style="color: #7f8c8d; margin: 8px 0;"><strong>修改时间:</strong> ${modifiedDate}</p>
<p style="color: #7f8c8d; margin: 8px 0;"><strong>文件大小:</strong> ${(stat.size / 1024).toFixed(2)} KB</p>
</div>
<p style="color: #7f8c8d; margin-bottom: 30px; line-height: 1.8;">PowerPoint在线预览功能开发中<br>请下载后使用 Microsoft PowerPoint、WPS 或在线查看器打开</p>
<div style="margin-top: 20px; display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
<button onclick="copyShareLink(window.location.href)" style="padding: 12px 24px; background: #00C853; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500;">📋 复制分享链接</button>
<a href="${pathname}?download=1" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500; text-decoration: none; display: inline-block;">⬇️ 下载文件</a>
</div>
</div>
`, pathname, null, filename, 'ppt');
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else {
const mimeTypes = {
'.txt': 'text/plain; charset=utf-8',
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.pdf': 'application/pdf',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.xls': 'application/vnd.ms-excel',
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'.csv': 'text/csv; charset=utf-8'
};
const mimeType = mimeTypes[ext] || 'application/octet-stream';
res.writeHead(200, {
'Content-Type': mimeType,
'Content-Length': stat.size
});
res.end(content);
}
});
function getHtmlTemplate(title, bodyContent, currentPath, markdownContent = null, filename = null, fileType = 'markdown') {
const currentUrl = `http://proxy.mossai.icu:8081${currentPath}`;
// 获取文件日期信息(如果可用)
let dateInfo = '';
if (typeof createdDate !== 'undefined' && typeof modifiedDate !== 'undefined') {
dateInfo = `
<div style="background: white; padding: 20px; border-radius: 8px; margin-top: 10px;">
<div style="display: flex; gap: 30px; font-size: 14px; color: #7f8c8d;">
<span>📅 创建时间: ${createdDate}</span>
<span>✏️ 修改时间: ${modifiedDate}</span>
</div>
</div>
`;
}
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github-dark.min.css">
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/python.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/bash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/json.min.js"></script>
<!-- SheetJS for Excel/CSV parsing -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<!-- Handsontable - Excel-like spreadsheet component -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css">
<script src="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js"></script>
<!-- Mammoth.js for Word document conversion -->
<script src="https://cdn.jsdelivr.net/npm/mammoth@1.6.0/mammoth.browser.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "Microsoft YaHei", sans-serif;
line-height: 1.8;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.header h1 {
color: #2c3e50;
font-size: 28px;
margin-bottom: 10px;
}
.breadcrumb {
color: #7f8c8d;
font-size: 14px;
margin-top: 10px;
}
.breadcrumb a {
color: #667eea;
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
.markdown-body h1 {
color: #2c3e50;
border-bottom: 3px solid #667eea;
padding-bottom: 15px;
margin: 30px 0 20px 0;
font-size: 32px;
}
.markdown-body h2 {
color: #34495e;
border-bottom: 2px solid #95a5a6;
padding-bottom: 10px;
margin: 25px 0 15px 0;
font-size: 26px;
}
.markdown-body h3 {
color: #7f8c8d;
margin: 20px 0 10px 0;
font-size: 22px;
}
.markdown-body h4 {
color: #95a5a6;
margin: 15px 0 10px 0;
font-size: 18px;
}
.markdown-body p {
margin: 15px 0;
line-height: 1.8;
}
.markdown-body table {
border-collapse: collapse;
width: 100%;
margin: 20px 0;
font-size: 14px;
}
.markdown-body th, .markdown-body td {
border: 1px solid #e0e0e0;
padding: 12px 15px;
text-align: left;
}
.markdown-body th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
}
.markdown-body tr:nth-child(even) { background-color: #f9f9f9; }
.markdown-body tr:hover { background-color: #f0f0ff; }
.markdown-body code {
background: #f4f4f4;
padding: 3px 8px;
border-radius: 4px;
font-size: 0.9em;
font-family: 'Courier New', Consolas, monospace;
color: #e74c3c;
}
.markdown-body pre {
background: #2c3e50;
color: #ecf0f1;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
margin: 20px 0;
}
.markdown-body pre code {
background: none;
padding: 0;
color: #ecf0f1;
font-size: 14px;
}
.markdown-body blockquote {
border-left: 5px solid #667eea;
padding: 15px 20px;
margin: 20px 0;
color: #7f8c8d;
background: #f9f9f9;
border-radius: 0 8px 8px 0;
}
.markdown-body a {
color: #667eea;
text-decoration: none;
border-bottom: 1px dashed #667eea;
}
.markdown-body a:hover {
color: #764ba2;
border-bottom-style: solid;
}
.markdown-body hr {
border: none;
height: 2px;
background: linear-gradient(to right, transparent, #667eea, transparent);
margin: 30px 0;
}
.markdown-body ul, .markdown-body ol {
padding-left: 30px;
margin: 15px 0;
}
.markdown-body li {
margin: 8px 0;
line-height: 1.6;
}
.markdown-body img {
max-width: 100%;
border-radius: 8px;
margin: 15px 0;
}
.footer {
text-align: center;
padding: 20px;
color: white;
font-size: 14px;
margin-top: 20px;
}
.toast {
position: fixed;
bottom: 20px;
right: 20px;
background: #00C853;
color: white;
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
display: none;
z-index: 1000;
font-size: 16px;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@media (max-width: 768px) {
.header h1 { font-size: 24px; }
.markdown-body h1 { font-size: 24px; }
.markdown-body h2 { font-size: 20px; }
.markdown-body h3 { font-size: 18px; }
body { padding: 10px; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📄 ${title}</h1>
<div class="breadcrumb">
<a href="/">🏠 首页</a> / ${currentPath.split('/').filter(p => p).map(p => `<a href="${p}">${p}</a>`).join(' / ')}
</div>
${dateInfo}
</div>
${bodyContent}
<div class="footer">
由增强版文件服务器生成 • ${new Date().toLocaleString('zh-CN')} • 支持分享、Markdown渲染、多格式预览
</div>
</div>
<div id="toast" class="toast">✅ 链接已复制到剪贴板!</div>
${markdownContent ? `
<script>
marked.setOptions({
breaks: true,
gfm: true,
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return code;
}
});
const content = ${JSON.stringify(markdownContent)};
document.getElementById('content').innerHTML = marked.parse(content);
</script>
` : ''}
${fileType === 'excel' ? `
<style>
.sheet-tab {
padding: 8px 16px;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px 4px 0 0;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.sheet-tab:hover {
background: #e0e0e0;
}
.sheet-tab.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.handsontable td {
font-size: 13px;
}
.handsontable th {
font-size: 13px;
font-weight: 600;
}
</style>
<script>
let workbook = null;
let currentSheet = null;
let hotInstance = null;
async function loadExcel() {
try {
const url = window.location.pathname + '?download=1';
console.log('Loading Excel from:', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const arrayBuffer = await response.arrayBuffer();
workbook = XLSX.read(arrayBuffer, {type: 'array'});
console.log('Workbook loaded, sheets:', workbook.SheetNames);
// 创建sheet标签
const tabsContainer = document.getElementById('sheet-tabs');
workbook.SheetNames.forEach((sheetName, index) => {
const tab = document.createElement('button');
tab.className = 'sheet-tab' + (index === 0 ? ' active' : '');
tab.textContent = sheetName;
tab.onclick = () => switchSheet(sheetName, tab);
tabsContainer.appendChild(tab);
});
// 加载第一个sheet
switchSheet(workbook.SheetNames[0]);
} catch (error) {
console.error('Excel load error:', error);
document.getElementById('excel-container').innerHTML =
'<div style="text-align: center; padding: 40px; color: #e74c3c;">Load Failed: ' + error.message + '<br><a href="' + window.location.pathname + '?download=1">Download File</a></div>';
}
}
function switchSheet(sheetName, tabElement) {
if (!workbook) return;
// 更新标签状态
document.querySelectorAll('.sheet-tab').forEach(t => t.classList.remove('active'));
if (tabElement) tabElement.classList.add('active');
currentSheet = workbook.Sheets[sheetName];
// 转换为数组
const data = XLSX.utils.sheet_to_json(currentSheet, {header: 1, defval: ''});
// 销毁旧实例
if (hotInstance) {
hotInstance.destroy();
}
// 创建Handsontable实例
const container = document.getElementById('excel-container');
hotInstance = new Handsontable(container, {
data: data,
rowHeaders: true,
colHeaders: true,
licenseKey: 'non-commercial-and-evaluation',
height: 600,
stretchH: 'last',
manualColumnResize: true,
manualRowResize: true,
contextMenu: true,
filters: true,
dropdownMenu: true,
copyPaste: true,
fillHandle: true,
readOnly: true,
preventOverflow: 'horizontal',
renderer: function(instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
if (row === 0) {
td.style.fontWeight = 'bold';
td.style.background = '#f0f0f0';
}
}
});
console.log('Sheet loaded:', sheetName);
}
// 页面加载完成后执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadExcel);
} else {
loadExcel();
}
</script>
` : ''}
${fileType === 'word' ? `
<style>
.word-content {
max-width: 850px;
margin: 0 auto;
padding: 60px 80px;
background: white;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
min-height: 1100px;
font-family: "Calibri", "Microsoft YaHei", -apple-system, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #000;
}
/* 页面效果 */
.word-content::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 100%;
background: #f0f0f0;
z-index: -1;
}
/* 标题样式 - 更接近Word */
.word-content h1 {
font-family: "Calibri Light", "Microsoft YaHei", sans-serif;
font-size: 24pt;
font-weight: normal;
color: #1F4E79;
margin-top: 24pt;
margin-bottom: 6pt;
line-height: 1.15;
page-break-after: avoid;
border-bottom: none;
}
.word-content h2 {
font-family: "Calibri Light", "Microsoft YaHei", sans-serif;
font-size: 18pt;
font-weight: normal;
color: #2E75B6;
margin-top: 18pt;
margin-bottom: 6pt;
line-height: 1.15;
page-break-after: avoid;
border-bottom: none;
}
.word-content h3 {
font-size: 14pt;
font-weight: bold;
color: #1F4E79;
margin-top: 14pt;
margin-bottom: 6pt;
line-height: 1.15;
page-break-after: avoid;
}
.word-content h4 {
font-size: 12pt;
font-weight: bold;
color: #000;
margin-top: 12pt;
margin-bottom: 6pt;
line-height: 1.15;
font-style: italic;
}
/* 段落样式 */
.word-content p {
margin-top: 0;
margin-bottom: 10pt;
line-height: 1.5;
text-align: justify;
}
/* 列表样式 */
.word-content ul {
margin-left: 0.5in;
margin-top: 0;
margin-bottom: 10pt;
}
.word-content ol {
margin-left: 0.5in;
margin-top: 0;
margin-bottom: 10pt;
}
.word-content li {
margin-bottom: 3pt;
}
/* 表格样式 */
.word-content table {
border-collapse: collapse;
width: 100%;
margin-top: 6pt;
margin-bottom: 6pt;
}
.word-content td, .word-content th {
border: 1pt solid #000;
padding: 5pt;
vertical-align: top;
}
.word-content th {
background: #D9E2F3;
font-weight: bold;
}
/* 强调样式 */
.word-content strong {
font-weight: bold;
}
.word-content em {
font-style: italic;
}
.word-content u {
text-decoration: underline;
}
/* 超链接 */
.word-content a {
color: #0563C1;
text-decoration: underline;
}
/* 引用块 */
.word-content blockquote {
margin-left: 0.5in;
margin-right: 0in;
margin-top: 6pt;
margin-bottom: 6pt;
padding-left: 12pt;
border-left: 3pt solid #ccc;
color: #666;
}
/* 代码块 */
.word-content pre {
background: #f5f5f5;
padding: 12pt;
border: 1pt solid #ddd;
border-radius: 4pt;
overflow-x: auto;
font-family: "Courier New", Consolas, monospace;
font-size: 10pt;
}
.word-content code {
font-family: "Courier New", Consolas, monospace;
background: #f0f0f0;
padding: 2pt 4pt;
border-radius: 2pt;
}
/* 页眉页脚效果 */
.word-content::after {
content: '';
display: block;
margin-top: 60pt;
border-top: 1pt solid #ccc;
padding-top: 12pt;
text-align: center;
color: #999;
font-size: 9pt;
}
</style>
<script>
async function loadWord() {
try {
const url = window.location.pathname + '?download=1';
console.log('Loading Word from:', url);
const response = await fetch(url);
console.log('Response status:', response.status);
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const arrayBuffer = await response.arrayBuffer();
console.log('ArrayBuffer size:', arrayBuffer.byteLength);
// 使用mammoth.js转换Word为HTML
const result = await mammoth.convertToHtml({arrayBuffer: arrayBuffer}, {
styleMap: [
"p[style-name='Heading 1'] => h1:fresh",
"p[style-name='Heading 2'] => h2:fresh",
"p[style-name='Heading 3'] => h3:fresh",
"p[style-name='Heading 4'] => h4:fresh"
]
});
console.log('Conversion complete');
const html = result.value;
const messages = result.messages;
if (messages.length > 0) {
console.log('Conversion messages:', messages);
}
// 显示转换后的HTML
document.getElementById('word-viewer').innerHTML = '<div class="word-content">' + html + '</div>';
console.log('Word preview rendered successfully');
} catch (error) {
console.error('Word load error:', error);
document.getElementById('word-viewer').innerHTML = '<div style="text-align: center; padding: 40px; color: #e74c3c;">Load Failed: ' + error.message + '<br><a href="' + window.location.pathname + '?download=1">Download File</a></div>';
}
}
// 页面加载完成后执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadWord);
} else {
loadWord();
}
</script>
` : ''}
${fileType === 'csv' ? `
<script>
async function loadCSV() {
try {
const url = window.location.pathname + '?download=1';
console.log('Loading CSV from:', url);
const response = await fetch(url);
console.log('Response status:', response.status);
if (!response.ok) {
throw new Error('HTTP ' + response.status + ': ' + response.statusText);
}
const csvText = await response.text();
console.log('CSV text length:', csvText.length);
const workbook = XLSX.read(csvText, {type: 'string'});
console.log('Workbook loaded, sheets:', workbook.SheetNames);
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
const html = XLSX.utils.sheet_to_html(worksheet, {
editable: false,
header: '<table style="width:100%; border-collapse: collapse; font-size: 14px;">',
footer: '</table>'
});
document.getElementById('csv-viewer').innerHTML = '<h3 style="margin-bottom: 15px; color: #2c3e50;">📋 CSV数据预览</h3>' + html;
// 美化表格
const tables = document.querySelectorAll('#csv-viewer table');
tables.forEach(table => {
table.style.border = '1px solid #ddd';
table.style.borderRadius = '8px';
table.style.overflow = 'hidden';
const cells = table.querySelectorAll('td, th');
cells.forEach(cell => {
cell.style.border = '1px solid #e0e0e0';
cell.style.padding = '10px';
cell.style.textAlign = 'left';
});
const headers = table.querySelectorAll('tr:first-child td, tr:first-child th');
headers.forEach(header => {
header.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
header.style.color = 'white';
header.style.fontWeight = '600';
});
});
console.log('CSV preview rendered successfully');
} catch (error) {
console.error('CSV load error:', error);
document.getElementById('csv-viewer').innerHTML = '<div style="text-align: center; padding: 40px;"><div style="font-size: 48px; margin-bottom: 20px;">❌</div><p style="color: #e74c3c; margin-bottom: 20px;">Load Failed</p><p style="color: #7f8c8d; font-size: 14px; margin-bottom: 20px;">Please use download button</p><a href="' + currentPath + '?download=1" style="padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 500; text-decoration: none; display: inline-block;">⬇️ Download</a></div>';
}
}
loadCSV();
</script>
` : ''}
<script>
function copyShareLink(url) {
const fullUrl = url.startsWith('http') ? url : 'http://proxy.mossai.icu:8081' + url;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(fullUrl).then(() => {
showToast('✅ 链接已复制到剪贴板!');
}).catch(() => {
fallbackCopy(fullUrl);
});
} else {
fallbackCopy(fullUrl);
}
}
function fallbackCopy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.left = '-999999px';
textarea.style.top = '-999999px';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
document.execCommand('copy');
showToast('✅ 链接已复制到剪贴板!');
} catch (err) {
showToast('❌ 复制失败,请手动复制');
}
document.body.removeChild(textarea);
}
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.style.display = 'block';
setTimeout(() => {
toast.style.display = 'none';
}, 3000);
}
function downloadFile(filename) {
const link = document.createElement('a');
link.href = window.location.pathname + '?download=1';
link.download = filename;
link.click();
}
</script>
</body>
</html>`;
}
server.listen(PORT, '0.0.0.0', () => {
console.log('✅ 增强版文件服务器运行中(带分享功能)');
console.log(`📁 服务目录: ${DIRECTORY}`);
console.log(`🌐 访问地址: http://proxy.mossai.icu:${PORT}`);
console.log('🎨 功能: Markdown渲染、分享按钮、多格式预览、UTF-8编码');
});