@lppx/lanshare 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -0
- package/dist/scripts/gitsync.js +100 -0
- package/dist/src/app.js +46 -0
- package/dist/src/cli/cli.js +131 -0
- package/dist/src/cli/index.js +30 -0
- package/dist/src/cli/network.js +29 -0
- package/dist/src/cli.js +19 -0
- package/dist/src/index.js +26 -0
- package/dist/src/routers/files.js +142 -0
- package/dist/src/routers/upload.js +47 -0
- package/dist/src/socket/index.js +65 -0
- package/dist/src/socket/types.js +2 -0
- package/dist/src/utils/logger.js +48 -0
- package/package.json +45 -0
- package/public/css/base.css +44 -0
- package/public/css/device-list.css +76 -0
- package/public/css/drop-zone.css +39 -0
- package/public/css/file-list.css +167 -0
- package/public/css/header.css +48 -0
- package/public/css/responsive.css +9 -0
- package/public/index.html +92 -0
- package/public/js/api.js +54 -0
- package/public/js/device-list.js +23 -0
- package/public/js/file-list.js +62 -0
- package/public/js/main.js +18 -0
- package/public/js/socket.js +63 -0
- package/public/js/upload.js +49 -0
package/public/js/api.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// #region API 调用
|
|
2
|
+
async function loadFiles() {
|
|
3
|
+
try {
|
|
4
|
+
const response = await fetch('/files');
|
|
5
|
+
const files = await response.json();
|
|
6
|
+
renderFileList(files);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.error('加载文件列表失败:', error);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function loadDevices() {
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch('/devices');
|
|
15
|
+
const devices = await response.json();
|
|
16
|
+
renderDeviceList(devices);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('加载设备列表失败:', error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function loadServerInfo() {
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch('/info');
|
|
25
|
+
const info = await response.json();
|
|
26
|
+
if (info.ip) {
|
|
27
|
+
const ipText = document.getElementById('ipText');
|
|
28
|
+
ipText.textContent = info.ip;
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('加载服务器信息失败:', error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function downloadFile(filename) {
|
|
36
|
+
window.location.href = '/download/' + encodeURIComponent(filename);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function deleteFile(filename) {
|
|
40
|
+
if (!confirm('确定要删除文件 ' + filename + ' 吗?')) return;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch('/delete/' + encodeURIComponent(filename), { method: 'DELETE' });
|
|
44
|
+
if (response.ok) {
|
|
45
|
+
console.log('文件删除成功');
|
|
46
|
+
loadFiles();
|
|
47
|
+
} else {
|
|
48
|
+
console.error('文件删除失败');
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('删除出错:', error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// #endregion
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// #region 设备列表渲染
|
|
2
|
+
function getDeviceIcon(deviceName) {
|
|
3
|
+
if (deviceName.includes('Android') || deviceName.includes('Pixel')) return '📱';
|
|
4
|
+
if (deviceName.includes('Mac') || deviceName.includes('MacBook')) return '💻';
|
|
5
|
+
if (deviceName.includes('Windows') || deviceName.includes('PC')) return '🖥️';
|
|
6
|
+
return '📱';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function renderDeviceList(devices) {
|
|
10
|
+
const devicesList = document.getElementById('devicesList');
|
|
11
|
+
|
|
12
|
+
devicesList.innerHTML = devices.map(device => `
|
|
13
|
+
<div class="device-item">
|
|
14
|
+
<div class="device-icon">${getDeviceIcon(device.name)}</div>
|
|
15
|
+
<div class="device-info">
|
|
16
|
+
<div class="device-name">${device.name}</div>
|
|
17
|
+
<div class="device-ip">${device.ip}</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="device-status"></div>
|
|
20
|
+
</div>
|
|
21
|
+
`).join('');
|
|
22
|
+
}
|
|
23
|
+
// #endregion
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// #region 文件列表渲染
|
|
2
|
+
function getFileIcon(filename) {
|
|
3
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
4
|
+
if (ext === 'pdf') return { icon: '📄', class: 'pdf' };
|
|
5
|
+
if (['zip', 'rar', '7z'].includes(ext)) return { icon: '📦', class: 'zip' };
|
|
6
|
+
if (['mp4', 'avi', 'mov'].includes(ext)) return { icon: '🎬', class: 'video' };
|
|
7
|
+
if (['jpg', 'png', 'gif'].includes(ext)) return { icon: '🖼️', class: 'image' };
|
|
8
|
+
return { icon: '📄', class: 'file' };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatFileSize(bytes) {
|
|
12
|
+
if (bytes === 0) return '0 B';
|
|
13
|
+
const k = 1024;
|
|
14
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
15
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
16
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderFileList(files) {
|
|
20
|
+
const fileList = document.getElementById('fileList');
|
|
21
|
+
const fileCount = document.getElementById('fileCount');
|
|
22
|
+
|
|
23
|
+
fileCount.textContent = files.length + ' Files';
|
|
24
|
+
|
|
25
|
+
fileList.innerHTML = files.map(file => {
|
|
26
|
+
const fileIcon = getFileIcon(file.name);
|
|
27
|
+
const downloadPercent = file.downloads > 0 ? Math.min((file.downloads / 10) * 100, 100) : 0;
|
|
28
|
+
|
|
29
|
+
return `
|
|
30
|
+
<div class="file-row">
|
|
31
|
+
<div class="file-info">
|
|
32
|
+
<div class="file-icon ${fileIcon.class}">${fileIcon.icon}</div>
|
|
33
|
+
<div class="file-details">
|
|
34
|
+
<div class="file-name">${file.name}</div>
|
|
35
|
+
<div class="file-meta">Shared by ${file.sharedBy || 'Unknown'} · ${file.ip || '192.168.1.1'}</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="file-size">${formatFileSize(file.size)}</div>
|
|
39
|
+
<div class="file-downloads">
|
|
40
|
+
<div class="download-progress">
|
|
41
|
+
<div class="progress-bar" style="width: ${downloadPercent}%"></div>
|
|
42
|
+
</div>
|
|
43
|
+
<span class="download-count">${file.downloads || 0}</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="file-actions">
|
|
46
|
+
<button class="action-btn download" onclick="downloadFile('${file.name}')" title="下载">
|
|
47
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
48
|
+
<path d="M12 3V16M12 16L16 12M12 16L8 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
49
|
+
<path d="M3 17V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
<button class="action-btn delete" onclick="deleteFile('${file.name}')" title="删除">
|
|
53
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
54
|
+
<path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
55
|
+
</svg>
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
}).join('');
|
|
61
|
+
}
|
|
62
|
+
// #endregion
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// #region 初始化
|
|
2
|
+
function init() {
|
|
3
|
+
initSocket();
|
|
4
|
+
initUpload();
|
|
5
|
+
loadFiles();
|
|
6
|
+
loadServerInfo();
|
|
7
|
+
|
|
8
|
+
// 定期刷新文件列表
|
|
9
|
+
setInterval(loadFiles, 5000);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 页面加载完成后初始化
|
|
13
|
+
if (document.readyState === 'loading') {
|
|
14
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
15
|
+
} else {
|
|
16
|
+
init();
|
|
17
|
+
}
|
|
18
|
+
// #endregion
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// #region Socket.IO 连接和设备注册
|
|
2
|
+
let socket;
|
|
3
|
+
|
|
4
|
+
function initSocket() {
|
|
5
|
+
socket = io();
|
|
6
|
+
|
|
7
|
+
socket.on('connect', () => {
|
|
8
|
+
console.log('Socket.IO 已连接:', socket.id);
|
|
9
|
+
|
|
10
|
+
// 注册设备,使用持久化的设备ID
|
|
11
|
+
const deviceInfo = {
|
|
12
|
+
deviceId: getDeviceId(),
|
|
13
|
+
name: getDeviceName(),
|
|
14
|
+
platform: navigator.userAgentData?.platform || 'Unknown'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
socket.emit('register', deviceInfo);
|
|
18
|
+
console.log('设备已注册:', deviceInfo);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
socket.on('disconnect', () => {
|
|
22
|
+
console.log('Socket.IO 已断开');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
socket.on('devices-update', (devices) => {
|
|
26
|
+
console.log('设备列表更新:', devices);
|
|
27
|
+
renderDeviceList(devices);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 发送心跳
|
|
31
|
+
setInterval(() => {
|
|
32
|
+
if (socket.connected) {
|
|
33
|
+
socket.emit('heartbeat');
|
|
34
|
+
}
|
|
35
|
+
}, 30000); // 每30秒发送一次心跳
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getDeviceId() {
|
|
39
|
+
// 从 localStorage 获取或生成唯一设备ID
|
|
40
|
+
let deviceId = localStorage.getItem('lanshare-device-id');
|
|
41
|
+
if (!deviceId) {
|
|
42
|
+
deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
43
|
+
localStorage.setItem('lanshare-device-id', deviceId);
|
|
44
|
+
}
|
|
45
|
+
return deviceId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getDeviceName() {
|
|
49
|
+
const ua = navigator.userAgent;
|
|
50
|
+
|
|
51
|
+
if (/Android/i.test(ua)) {
|
|
52
|
+
const match = ua.match(/Android.*?;\s*([^)]+)/);
|
|
53
|
+
return match ? match[1].trim() : 'Android Device';
|
|
54
|
+
}
|
|
55
|
+
if (/iPhone/i.test(ua)) return 'iPhone';
|
|
56
|
+
if (/iPad/i.test(ua)) return 'iPad';
|
|
57
|
+
if (/Mac/i.test(ua)) return 'MacBook Pro';
|
|
58
|
+
if (/Windows/i.test(ua)) return 'Windows PC';
|
|
59
|
+
if (/Linux/i.test(ua)) return 'Linux PC';
|
|
60
|
+
|
|
61
|
+
return 'Unknown Device';
|
|
62
|
+
}
|
|
63
|
+
// #endregion
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// #region 文件上传处理
|
|
2
|
+
function initUpload() {
|
|
3
|
+
const fileInput = document.getElementById('fileInput');
|
|
4
|
+
const dropZone = document.getElementById('dropZone');
|
|
5
|
+
|
|
6
|
+
dropZone.addEventListener('click', () => {
|
|
7
|
+
fileInput.click();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
dropZone.addEventListener('dragover', (e) => {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
dropZone.classList.add('drag-over');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
dropZone.addEventListener('dragleave', () => {
|
|
16
|
+
dropZone.classList.remove('drag-over');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
dropZone.addEventListener('drop', async (e) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
dropZone.classList.remove('drag-over');
|
|
22
|
+
const files = e.dataTransfer.files;
|
|
23
|
+
await uploadFiles(files);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
fileInput.addEventListener('change', async (e) => {
|
|
27
|
+
await uploadFiles(e.target.files);
|
|
28
|
+
fileInput.value = '';
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function uploadFiles(files) {
|
|
33
|
+
for (let file of files) {
|
|
34
|
+
const formData = new FormData();
|
|
35
|
+
formData.append('file', file);
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch('/upload', { method: 'POST', body: formData });
|
|
38
|
+
if (response.ok) {
|
|
39
|
+
console.log('文件上传成功:', file.name);
|
|
40
|
+
loadFiles();
|
|
41
|
+
} else {
|
|
42
|
+
console.error('文件上传失败:', file.name);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('上传出错:', error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// #endregion
|