aardio 文档
aardio 范例: 扫码传文件 - v2.8 (导航优化版)
//扫码传文件 - v2.8 (导航优化版)
import fsys;
import fsys.info;
import fsys.config;
import fsys.dlg.dir;
import fsys.update.simpleMain;
import process;
import web.socket.server;
import win.ui.simpleWindow2;
import qrencode.bitmap;
import win.ui.menu;
import inet.url;
import string.html;
import fonts.fontAwesome;
import win.ui;
import win.clip;
import win.clip.viewer;
/*DSG{{*/
var winform = win.form(text="qrfs - 扫码快传 v2.8";right=798;bottom=491;bgcolor=0xFAFAFA;border="none";max=false)
winform.add(
bk={cls="bk";left=0;top=-5;right=799;bottom=29;bgcolor=0xF0F0F0;dl=1;dr=1;dt=1;forecolor=0xEAEAEA;linearGradient=0;z=10};
bkplus={cls="bkplus";text="qrfs - 扫码快传 v2.8 - 共享文件、电脑剪贴板、手机输入法( AI 自动优化 )";left=35;top=3;right=509;bottom=25;align="left";color=0x5A5A5A;dl=1;dt=1;z=11};
btnOpen={cls="plus";text='\uF115';left=430;top=44;right=456;bottom=68;align="right";border={bottom=1;color=0xFF808080};dr=1;dt=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=1};
btnOpenUpload={cls="plus";text="上传目录";left=473;top=40;right=614;bottom=69;align="left";dl=1;dt=1;font=LOGFONT(h=-14);iconStyle={align="left";font=LOGFONT(h=-15;name='FontAwesome');padding={left=8;top=2}};iconText='\uF07C';notify=1;textPadding={left=27;bottom=1};z=12};
btnSetting={cls="plus";left=737;top=455;right=768;bottom=484;bgcolor=0xFAFAFA;db=1;dl=1;font=LOGFONT(h=-16;name='FontAwesome');iconStyle={align="left";font=LOGFONT(h=-16;name='FontAwesome');padding={left=7;top=3}};iconText='\uF013';notify=1;z=23};
btnStart={cls="plus";text="重启服务";left=635;top=69;right=768;bottom=106;align="left";bgcolor=0xD9A100;border={radius=8};color=0xFFFFFF;dr=1;dt=1;font=LOGFONT(h=-15);iconStyle={align="left";font=LOGFONT(h=-19;name='FontAwesome');padding={left=16}};iconText='\uF233';notify=1;textPadding={left=45;bottom=2};z=6};
chkAiOpt={cls="checkbox";text="AI 优化";left=642;top=461;right=712;bottom=483;bgcolor=0xFAFAFA;z=22};
chkPublicAddress={cls="checkbox";text="公网 IP";left=486;top=109;right=556;bottom=131;bgcolor=0xFFFFFF;z=24};
editDocumentRoot={cls="plus";left=131;top=46;right=430;bottom=68;align="left";border={bottom=1;color=0xFFC0C0C0};clip=1;dl=1;dr=1;dt=1;editable="edit";textPadding={bottom=1};z=8};
editHost={cls="plus";left=131;top=75;right=456;bottom=97;align="left";border={bottom=1;color=0xFFC0C0C0};dr=1;dt=1;editable="edit";textPadding={bottom=1};z=19};
editPassword={cls="plus";left=131;top=107;right=456;bottom=129;align="left";border={bottom=1;color=0xFFC0C0C0};dr=1;dt=1;editable="edit";password=1;textPadding={right=24;bottom=1};z=14};
editPort={cls="plus";left=529;top=78;right=615;bottom=100;align="left";border={bottom=1;color=0xFFC0C0C0};dr=1;dt=1;editable=1;notify=1;textPadding={bottom=1};z=9};
plus={cls="plus";text="密码:";left=71;top=108;right=129;bottom=132;align="right";dl=1;dr=1;dt=1;font=LOGFONT(h=-14);transparent=1;z=13};
plus2={cls="plus";left=38;top=148;right=455;bottom=442;align="left";border={color=0xFFC0C0C0;radius=8;width=1};db=1;dl=1;dr=1;dt=1;font=LOGFONT(h=-14);textPadding={left=16};valign="top";z=2};
plus3={cls="plus";text="自定义网址:";left=9;top=77;right=129;bottom=101;align="right";dr=1;dt=1;font=LOGFONT(h=-14);transparent=1;z=20};
qr={cls="plus";left=483;top=148;right=768;bottom=433;border={radius=8};db=1;dr=1;dt=1;foreRepeat="scale";repeat="scale";z=7};
radioQrClipboard={cls="radiobutton";text="共享剪贴板";left=442;top=461;right=529;bottom=483;bgcolor=0xFAFAFA;db=1;dr=1;z=18};
radioQrInputMethod={cls="radiobutton";text="共享输入法";left=541;top=461;right=637;bottom=483;bgcolor=0xFAFAFA;db=1;dr=1;z=21};
radioQrRootDir={cls="radiobutton";text="根目录";left=259;top=461;right=346;bottom=483;bgcolor=0xFAFAFA;db=1;dr=1;z=16};
radioQrUploadDir={cls="radiobutton";text="上传文件";left=350;top=461;right=437;bottom=483;bgcolor=0xFAFAFA;db=1;dr=1;z=17};
static={cls="plus";text="端口:";left=467;top=77;right=530;bottom=101;align="right";dr=1;dt=1;font=LOGFONT(h=-14);transparent=1;z=4};
static2={cls="plus";text="网站根目录:";left=15;top=45;right=129;bottom=69;align="right";dl=1;dt=1;font=LOGFONT(h=-14);transparent=1;z=5};
syslink={cls="syslink";text='<a href="https://github.com/aardio/qrfs">开源项目</a>';left=43;top=463;right=176;bottom=483;bgcolor=0xFAFAFA;db=1;dl=1;z=15};
txtMessage={cls="richedit";left=43;top=151;right=451;bottom=436;autohscroll=false;bgcolor=0xFAFAFA;db=1;dl=1;dr=1;dt=1;link=1;multiline=1;vscroll=1;z=3}
)
/*}}*/
if( fsys.update.simpleMain(
"qrfs - 扫码快传",
"http://d.aardio.com/qrfs/update/",
io.appData("/aardio/std/qrfs/app/update"),
function(version,description,status){})){
return 0;
}
config = fsys.config(io.appData("aardio/std/qrfs"));
table.mix(config.aiOpt,{
key = '\0\1\96';
url = "https://ai.aardio.com/api/v1/";
model = "prompt";
systemPrompt = /*你是语音输入法优化助手。
# 任务
用户输入的文本是使用语音输入法输入的的语音识别结果。
你的任务是在评估与尊重用户原意的基础上对识别结果进行评估与修复,纠正语音识别错误。
在保持与尊重用户原意的基础上修正错误的文法与不通顺之处,并合理使用标点符号。
然后在在保持与尊重用户原意的基础上,你应当评估输入并根据相关的文化与背景知识对识别结果的格式进行整理与优化,使文本更为通顺自然。
输出格式为纯文本,不使用 Markdown 标记(除非识别结果本身包含 Markdown 标记)。
*/
});
import sessionHandler.default;
sessionHandler.default.root = io.appData("aardio/std/qrfs/session")
if( #config.winform.documentRoot && io.exist(config.winform.documentRoot) ){
winform.editDocumentRoot.text = config.winform.documentRoot;
}
else {
winform.editDocumentRoot.text = io.getSpecial(0x5/*_CSIDL_MYDOCUMENTS*/)
}
if(config.winform.qrDir=="upload"){
winform.radioQrUploadDir.checked = true;
}
elseif(config.winform.qrDir=="clipboard"){
winform.radioQrClipboard.checked = true;
}
elseif(config.winform.qrDir=="input"){
winform.radioQrInputMethod.checked = true;
}
else {
winform.radioQrRootDir.checked = true;
}
winform.editHost.text = config.winform.editHost;
var wsrv = web.socket.server();
var srvHttp = wsrv.httpServer;
srvHttp.documentRoot = winform.editDocumentRoot.text;
srvHttp.userToken = string.random(18);
winform.editPassword.text = srvHttp.userToken;
var cacheSysIcons = {}
var getSysIconIndex = function(path){
var sfi;
sfi = fsys.info.get(path, 0x100/*_SHGFI_ICON*/ | 0x4000/*_SHGFI_SYSICONINDEX*/|0x10/*_SHGFI_USEFILEATTRIBUTES*/,
..fsys.isDir(path)?0x10/*_FILE_ATTRIBUTE_DIRECTORY*/?0x80/*_FILE_ATTRIBUTE_NORMAL*/);
if( !(sfi.returnValue ) ) return;
if(!cacheSysIcons[sfi.iIcon]){
var bmp = ..gdip.bitmap(sfi.hIcon,1/*_IMAGE_ICON*/);
if(bmp){
cacheSysIcons[sfi.iIcon] = bmp.saveToBuffer(".png");
bmp.dispose();
}
}
if(sfi.hIcon)::DestroyIcon(sfi.hIcon);
return sfi.iIcon;
}
var formatSize = function(size){
if(size===null) return "";
if(size < 1024) return size + " B";
if(size < 1024*1024) return string.format("%.1f KB", size/1024);
if(size < 1024*1024*1024) return string.format("%.1f MB", size/(1024*1024));
return string.format("%.1f GB", size/(1024*1024*1024));
}
var cacheClientIps = {}
// 生成通用 HTML 头部
var getHtmlHead = function(activeTab){
return `
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>qrfs - 扫码快传</title>
<link href="https://lib.baomitu.com/filepond/4.28.2/filepond.min.css" rel="stylesheet">
<script src="https://lib.baomitu.com/filepond/4.28.2/filepond.min.js"></script>
<style>
:root {
--bg-color: #f7f9fc;
--card-bg: #ffffff;
--text-primary: #2c3e50;
--text-secondary: #5e6d82;
--border-color: #ebEEF5;
--accent-color: #0366d6;
--accent-hover: #024ea4;
--danger-color: #ff4757;
--upload-area-bg: #eef2f7;
--shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
--nav-height: 50px;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #121212;
--card-bg: #1e1e1e;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--border-color: #333;
--accent-color: #58a6ff;
--accent-hover: #79c0ff;
--danger-color: #ff6b6b;
--upload-area-bg: #2d2d2d;
--shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
}
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
padding: 0;
padding-top: calc(var(--nav-height) + max(10px, env(safe-area-inset-top)));
padding-bottom: max(20px, env(safe-area-inset-bottom));
background-color: var(--bg-color);
color: var(--text-primary);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
/* 顶部导航栏 */
.nav-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-color);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
padding-top: max(0px, env(safe-area-inset-top));
}
@media (prefers-color-scheme: dark) {
.nav-header { background: rgba(30, 30, 30, 0.95); }
}
.nav-tabs {
display: flex;
height: var(--nav-height);
padding: 0;
margin: 0;
list-style: none;
}
.nav-tabs li {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.nav-tabs a {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
text-decoration: none;
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 500;
transition: all 0.2s;
border-bottom: 2px solid transparent;
gap: 2px;
}
.nav-tabs a .nav-icon {
font-size: 1.2rem;
}
.nav-tabs a:hover {
color: var(--accent-color);
background: rgba(3, 102, 214, 0.05);
}
.nav-tabs a.active {
color: var(--accent-color);
border-bottom-color: var(--accent-color);
background: rgba(3, 102, 214, 0.08);
}
/* 上传区域样式 */
.upload-section {
padding: 15px;
margin-bottom: 10px;
}
.filepond--root { margin-bottom: 0; min-height: 120px !important; }
.filepond--panel-root { background-color: var(--card-bg); border: 2px dashed var(--border-color); border-radius: 12px; }
.filepond--drop-label { color: var(--text-secondary); font-size: 1rem; }
.native-upload {
display: block; width: 100%; padding: 15px;
background: var(--card-bg); border: 2px dashed var(--border-color);
border-radius: 12px; color: var(--text-secondary); text-align: center;
}
.page-title {
font-size: 1.1rem;
margin: 15px 15px 15px 15px;
color: var(--text-secondary);
font-weight: 600;
}
ul.file-list {
list-style: none; padding: 0 15px; margin: 0;
display: flex; flex-direction: column; gap: 10px;
}
ul.file-list li {
background: var(--card-bg); border-radius: 10px;
box-shadow: var(--shadow);
display: flex; align-items: center; overflow: hidden;
transition: transform 0.1s;
}
ul.file-list li:active { transform: scale(0.99); }
ul.file-list li a {
flex: 1; display: flex; align-items: center;
padding: 16px; text-decoration: none;
color: var(--text-primary); font-size: 1rem; overflow: hidden;
}
ul.file-list li img { width: 24px; height: 24px; margin-right: 12px; object-fit: contain; flex-shrink: 0; }
ul.file-list li span.fname { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; margin-right: 10px; }
ul.file-list li span.fsize { font-size: 0.8rem; color: var(--text-secondary); white-space: nowrap; }
.del-btn {
background-color: var(--danger-color); color: white;
border: none; padding: 0 16px; height: auto;
font-weight: 600; font-size: 0.9rem; cursor: pointer;
align-self: stretch; display: flex; align-items: center;
margin-left: 1px;
}
/* 功能容器 */
.func-container {
padding: 0 15px;
display: flex;
flex-direction: column;
height: calc(100vh - var(--nav-height) - max(10px, env(safe-area-inset-top)) - 50px - max(20px, env(safe-area-inset-bottom)));
height: calc(100dvh - var(--nav-height) - max(10px, env(safe-area-inset-top)) - 50px - max(20px, env(safe-area-inset-bottom)));
}
.func-content {
flex: 1; width: 100%; padding: 15px;
border: 1px solid var(--border-color); border-radius: 12px;
font-size: 1.1em; background-color: var(--card-bg); color: var(--text-primary);
resize: none; line-height: 1.6; outline: none; margin-bottom: 15px;
overflow: auto;
}
.func-content:focus { border-color: var(--accent-color); box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1); }
.btn-group { display: flex; gap: 10px; flex-wrap: wrap; }
.btn-main {
flex: 1; min-width: 120px; padding: 14px; border: none; border-radius: 10px;
font-size: 1rem; font-weight: 600; cursor: pointer;
color: white; text-align: center;
}
.btn-copy { background-color: var(--accent-color); }
.btn-send { background-color: #2ecc71; }
.btn-clear { background-color: #95a5a6; }
/* 提示卡片 */
.tip-card {
background: var(--upload-area-bg);
border-radius: 8px;
padding: 10px 12px;
margin-bottom: 10px;
color: var(--text-secondary);
font-size: 0.85rem;
line-height: 1.5;
}
.tip-card .tip-icon {
font-size: 1rem;
margin-right: 6px;
vertical-align: middle;
}
/* 链接卡片 */
.link-card {
display: flex;
align-items: center;
justify-content: center;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
margin: 15px;
text-decoration: none;
color: var(--accent-color);
font-weight: 600;
box-shadow: var(--shadow);
transition: all 0.2s;
}
.link-card:hover {
background: var(--accent-color);
color: white;
transform: translateY(-2px);
}
.link-card .link-icon {
margin-right: 10px;
font-size: 1.2rem;
}
.toast {
position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%);
background: rgba(0,0,0,0.8); color: #fff; padding: 12px 24px;
border-radius: 25px; font-size: 0.9em; z-index: 9999;
opacity: 0; transition: opacity 0.3s; pointer-events: none;
}
.toast.show { opacity: 1; }
.empty-tip { text-align: center; padding: 40px; color: var(--text-secondary); }
.success-msg {
text-align: center;
padding: 30px;
color: #2ecc71;
font-size: 1.1rem;
}
.success-msg .check-icon {
font-size: 3rem;
margin-bottom: 15px;
display: block;
}
</style>
</head>
<body>
<div id="toast" class="toast"></div>
<nav class="nav-header">
<ul class="nav-tabs">
<li><a href="/?t=`+srvHttp.userToken+`" class="`+(activeTab=="files"?"active":"")+`"><span class="nav-icon">📁</span><span>文件</span></a></li>
<li><a href="/%3Cupload%3E/?t=`+srvHttp.userToken+`" class="`+(activeTab=="upload"?"active":"")+`"><span class="nav-icon">📤</span><span>上传</span></a></li>
<li><a href="/%3Cclipboard%3E/?t=`+srvHttp.userToken+`" class="`+(activeTab=="clipboard"?"active":"")+`"><span class="nav-icon">📋</span><span>剪贴板</span></a></li>
<li><a href="/%3Cinput%3E/?t=`+srvHttp.userToken+`" class="`+(activeTab=="input"?"active":"")+`"><span class="nav-icon">⌨️</span><span>输入法</span></a></li>
</ul>
</nav>
<script>
let websocket;
let wsReconnectAttempts = 0;
const wsUrl = "`+wsrv.getUrl("ws",true)+`";
function connectWebSocket() {
websocket = new WebSocket(wsUrl);
websocket.onopen = function() { wsReconnectAttempts = 0; };
websocket.onmessage = function(evt) {
if (evt.data === "reload") {
`+(activeTab=="files"?"window.location.reload();":"")+`
} else {
const clipboardContent = document.getElementById('clipboardContent');
if (clipboardContent /*&& document.activeElement !== clipboardContent*/) {
if(evt.data!=clipboardContent.value ){
clipboardContent.value = evt.data;
clipboardContent.select();
}
}
}
};
websocket.onclose = function() {
if (wsReconnectAttempts < 5) {
const delay = Math.min(1000 * Math.pow(1.5, wsReconnectAttempts), 10000);
setTimeout(connectWebSocket, delay);
wsReconnectAttempts++;
}
};
}
connectWebSocket();
function showToast(msg) {
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2000);
}
function deleteFile(path, btn) {
if(event) { event.preventDefault(); event.stopPropagation(); }
if (!confirm('确定要删除此文件吗?')) return;
fetch('/%3Cupload%3E/?t=`+srvHttp.userToken+`', { method: 'DELETE', body: path })
.then(res => {
if (res.ok) {
const li = btn.closest('li');
li.style.opacity = '0';
setTimeout(() => {
li.remove();
if(document.querySelectorAll('ul.file-list li').length === 0) {
document.querySelector('ul.file-list').innerHTML = '<li class="empty-tip">📭 目录为空</li>';
}
}, 300);
showToast('已删除');
} else { showToast('删除失败'); }
}).catch(() => showToast('请求失败'));
}
</script>
`;
}
srvHttp.run(
function(response,request,session){
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
response.headers["Pragma"] = "no-cache";
response.headers["Expires"] = "0";
var token = request.get["t"] : session["token"];
if( #srvHttp.userToken && (token != srvHttp.userToken) ){
winform.txtMessage.printf("客户端:%s 连接被拒绝",request.remoteAddr);
response.errorStatus(401)
return;
}
session["token"] = token;
if(!cacheClientIps[request.remoteAddr]){
winform.txtMessage.printf("客户端:%s 已连接",request.remoteAddr);
cacheClientIps[request.remoteAddr] = true;
}
response.headers["Access-Control-Allow-Origin"] = "*";
response.headers["Access-Control-Allow-Headers"] = "*"
// 图标请求
if(request.path=="/main.aardio" && request.get["icon"]){
var iconIdx = tonumber(request.get["icon"]);
if(iconIdx!==null && cacheSysIcons[iconIdx]){
response.contentType = "image/png";
response.write(cacheSysIcons[iconIdx])
return;
}
response.errorStatus(404);
return;
}
// 上传处理
if(request.path=="/<upload>/main.aardio"){ //注意请求目录会自动附加默认文档路径 /main.aardio
if(request.method=="DELETE"){
var path = request.postData();
if(path && string.startsWith(path,"/upload/")){
if(string.find(path,"\.\.")) { response.errorStatus(403); return; }
path = ..io.joinpath(srvHttp.documentRoot,path)
if(io.exist(path)){
io.remove(path);
winform.txtMessage.print("已删除:" + path);
response.close();
return;
}
}
response.errorStatus(404);
return;
}
fileData = request.postFileData()
if(fileData){
io.createDir(..io.joinpath(srvHttp.documentRoot,"upload"))
var fileName = ..io.joinpath(srvHttp.documentRoot,"upload",fileData.filepond.filename)
var ok,err = fileData.filepond.save(fileName);
if(!ok){ response.error(err); }
winform.txtMessage.text = 'HTTP 服务端已启动: \n';
winform.txtMessage.print( srvHttp.getUrl(,true) + "/?t=" + srvHttp.userToken );
winform.txtMessage.print( "" );
winform.txtMessage.print( "上传成功:" + fileName );
response.contentType = "text/plain";
response.write("/upload/",fileData.filepond.filename)
return response.close()
}
}
// 确定当前页面类型
var pageType = "files";
if(request.path=="/<clipboard>/main.aardio") pageType = "clipboard";
elseif(request.path=="/<input>/main.aardio") pageType = "input";
elseif(request.path=="/<upload>/main.aardio") pageType = "upload";
elseif(string.startsWith(request.path,"/upload/")) pageType = "files";
// 文件浏览处理
if(pageType=="files"){
if(!fsys.isDir(request.path)) {
if( ..io.exist(request.path) && (!_STUDIO_INVOKED || request.path!="/main.aardio") )
return response.loadcode(request.path)
else {
request.path = fsys.getParentDir(request.path)
}
}
}
// 输出 HTML 头部
response.write(getHtmlHead(pageType));
// 上传页面
if(pageType=="upload"){
response.write(`
<h3 class="page-title">📤 上传文件</h3>
<div class="upload-section">
<input type="file" class="filepond" name="filepond" multiple>
</div>
<script>
if (typeof FilePond !== 'undefined') {
FilePond.create(document.querySelector('input[type="file"]'), {
//这里不能写 %3Eupload%3E,因为 FilePond 会再次进行 URL 编码
server: '/<upload>/?t=`+srvHttp.userToken+`',
labelIdle: '点击或拖拽文件到此处上传',
labelFileProcessingComplete: '上传成功',
credits: false,
onprocessfiles: function() {
showToast('上传完成');
}
});
} else {
const input = document.querySelector('input[type="file"]');
input.className = 'native-upload';
input.insertAdjacentHTML('afterend', '<p style="text-align:center;color:var(--text-secondary)">选择文件后自动上传</p>');
}
</script>
<a class="link-card" href="/upload/?t=`+srvHttp.userToken+`">
<span class="link-icon">📂</span>
<span>查看已上传的文件</span>
</a>
</body></html>`);
return;
}
// 剪贴板页面
if(pageType=="clipboard"){
var clipText = win.clip.read() : "";
response.write(`
<h3 class="page-title">📋 共享剪贴板</h3>
<div class="func-container">
<textarea id="clipboardContent" class="func-content" placeholder="修改这里的文本,将同步修改电脑剪贴板...">`
+string.html.escape(clipText)+`</textarea>
<div class="tip-card">
<span class="tip-icon">💡</span>
<span id="pasteTip"></span>
</div>
<div class="btn-group">
<button id="copyBtn" class="btn-main btn-copy">📄 复制到手机</button>
</div>
</div>
<script>
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
const isAndroid = /Android/i.test(navigator.userAgent);
const copyBtn = document.getElementById('copyBtn');
let pasteTip = '使用 Ctrl+V 粘贴内容到输入框';
if (isIOS) {
pasteTip = '长按输入框 → 选择"粘贴"';
} else if (isAndroid) {
pasteTip = '长按输入框 → 点击"粘贴"';
} else if (isMobile) {
pasteTip = '长按输入框进行粘贴';
}
else{
copyBtn.textContent = "📄 复制到本机"
}
document.getElementById('pasteTip').textContent = pasteTip;
const clipboardContent = document.getElementById('clipboardContent');
clipboardContent.addEventListener('input', (e) => {
if (websocket && websocket.readyState === WebSocket.OPEN) {
var data = JSON.stringify({
event:"clipboard",
content:e.target.value
});
websocket.send(data);
}
});
copyBtn.addEventListener('click', async function() {
try {
await navigator.clipboard.writeText(clipboardContent.value);
showToast('✓ 已复制到剪贴板');
} catch (e) {
clipboardContent.select();
document.execCommand('copy');
showToast('✓ 已尝试复制');
}
});
clipboardContent.select();
</script>
</body></html>`);
return;
}
// 输入法页面
if(pageType=="input"){
response.write(`
<h3 class="page-title">⌨️ 共享输入法</h3>
<div class="func-container">
<textarea id="inputContent" class="func-content" placeholder="在此输入文字,点击发送后将输入到电脑当前焦点位置..."></textarea>
<div class="btn-group">
<button id="clearBtn" class="btn-main btn-clear">🗑️ 清空</button>
<button id="sendBtn" class="btn-main btn-send">📨 发送到电脑</button>
</div>
</div>
<script>
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
if (!isMobile) {
document.getElementById('sendBtn').textContent = '📨 发送到服务端';
}
const inputContent = document.getElementById('inputContent');
document.getElementById('sendBtn').addEventListener('click', async function() {
if(!inputContent.value.trim()) {
showToast('请先输入内容');
return;
}
try {
var data = JSON.stringify({
event:"input",
content:inputContent.value
});
websocket.send(data);
showToast('✓ 已发送到电脑');
inputContent.value = "";
inputContent.focus();
} catch (e) {
showToast('发送失败: ' + e.message);
}
});
document.getElementById('clearBtn').addEventListener('click', function() {
inputContent.value = "";
inputContent.focus();
showToast('已清空');
});
inputContent.focus();
</script>
</body></html>`);
return;
}
// 文件列表页面
var pathDisplay = string.html.escape(request.path);
var isUploadDir = string.startsWith(request.path,"/upload");
if(request.path=="/") {
pathDisplay = "📁 全部文件";
}
elseif(request.path=="/upload/" || request.path=="/upload") {
pathDisplay = "📤 已上传的文件";
}
response.write(`<h3 class="page-title">` + pathDisplay + `</h3>`);
// 显示返回上级按钮(非根目录时)
if(request.path!="/") {
var parentPath = fsys.getParentDir(request.path);
if(!#parentPath) parentPath = "/";
response.write(`<a class="link-card" href="`+parentPath+`?t=`+srvHttp.userToken+`" style="margin-top:0;">
<span class="link-icon">⬆️</span>
<span>返回上一级</span>
</a>`);
}
response.write(`<ul class="file-list">`);
// 根目录显示上传目录入口
if(request.path=="/" && ..io.exist("/upload/")){
response.write('<li><a href="/upload/?t='+srvHttp.userToken+'"><img src="/?icon='++getSysIconIndex("/upload/")
++'"><span class="fname">📤 已上传的文件</span></a></li>\r\n');
}
if(!..fsys.isDir(request.path)){
return response.errorStatus(404);
}
var file,dir = fsys.list(request.path,,"*.*");
var hasContent = false;
for(i=1;#dir;1){
if(dir[i]==="upload" && request.path=="/") continue;
hasContent = true;
var iconIdx = getSysIconIndex(dir[dir[i]]);
response.write('<li><a href="'
,inet.url.append(request.path, inet.url.encode(dir[i]))
,'?t='+srvHttp.userToken+'"><img src="/?icon='++(iconIdx)++'"><span class="fname">',string.html.escape(dir[i]),'</span></a></li>\r\n');
}
for(i=1;#file;1){
hasContent = true;
var iconIdx = getSysIconIndex(file[file[i]]);
var encodedPath = inet.url.append(request.path, inet.url.encode(file[i]));
var filePath = string.replace(request.path + file[i], "//", "/");
var fileSize = io.getSize( io.joinpath(srvHttp.documentRoot, request.path, file[i]) );
response.write('<li><a href="'
,encodedPath,'?t='+srvHttp.userToken+'"><img src="/?icon='++(iconIdx)++'"><span class="fname">',string.html.escape(file[i]),'</span>');
response.write('<span class="fsize">' + formatSize(fileSize) + '</span></a>');
if(isUploadDir){
var jsPath = string.replace(filePath,"'","\\'");
response.write('<button class="del-btn" onclick="deleteFile(\''
,jsPath,'\', this)">删除</button>');
}
response.write('</li>\r\n');
}
if(!hasContent && request.path!="/"){
response.write('<li class="empty-tip">📭 目录为空</li>');
}
elseif(!hasContent && request.path=="/"){
response.write('<li class="empty-tip">📭 暂无文件,请通过"上传"标签页添加文件</li>');
}
response.write("</ul></body></html>")
}
);
var serverInfo = function(){
var ip,port = srvHttp.getLocalIp();
winform.editPort.text = port;
winform.editDocumentRoot.text = io.fullpath(srvHttp.documentRoot)
var home = "/";
if(winform.radioQrUploadDir.checked){
home = "/%3Cupload%3E/";
}
elseif(winform.radioQrClipboard.checked){
home = "/%3Cclipboard%3E/";
}
elseif(winform.radioQrInputMethod.checked){
home = "/%3Cinput%3E/";
}
var url = srvHttp.getUrl(home,true);
var customUrl = string.trim(winform.editHost.text);
if(#customUrl){
if( inet.url.is(customUrl,-1) ){
url = customUrl;
}
elseif(string.startsWith(customUrl,"natapp:",true)){
url = winform.natAppTunnelUrl;
}
else{
url = "http://"+customUrl;
}
}
elseif(winform.chkPublicAddress.checked){
var tUrl = inet.url.split(url);
winform.chkPublicAddress.disabled = true;
winform.btnStart.disabledText = ['\uF254','\uF251','\uF252','\uF253','\uF250',text='获取 IP']
var resp = thread.invokeAndWait(
function(){
import wsock.udp.stunClient;
var sutn = wsock.udp.stunClient();
return sutn.getPublicAddress();
}
)
winform.chkPublicAddress.disabled = false;
winform.btnStart.disabledText = null;
if(resp){
tUrl.host = resp.ip;
url = tostring(tUrl);
}
}
var tUrl = inet.url.split(url);
if(!tUrl){
return winform.msgboxErr("指定了错误的网址");
}
url = tostring( tUrl );
if(#srvHttp.userToken){
url = inet.url.appendExtraInfo(url,{
t = srvHttp.userToken;
})
}
winform.txtMessage.text = 'HTTP 服务端已启动: \n';
winform.txtMessage.print( url );
var qrBmp = qrencode.bitmap( url );
winform.qr.setForeground(qrBmp.copyBitmap(winform.qr.width));
if(winform.chkPublicAddress.checked){
winform.txtMessage.print(
"
手机无线连接电脑局域网。
扫码打开网页即可互传文件,共享电脑剪贴板、手机输入法。
拖动文件或目录到窗口网页自动刷新。
基于纯 aardio 代码实现的开源单线程异步 HTTP / WebSocket 双服务端,体积仅数十 KB。"
);
}
else{
winform.txtMessage.print(
"
手机无线连接电脑局域网。
扫码打开网页即可互传文件,共享电脑剪贴板、手机输入法。
拖动文件或目录到窗口网页自动刷新。
本工具只走内网,不走公网!
端口与密钥动态改变,双重安全保障。
基于纯 aardio 代码实现的开源单线程异步 HTTP / WebSocket 双服务端,体积仅数十 KB。"
);
}
}
winform.btnStart.oncommand = function(id,event){
winform.txtMessage.text = "";
winform.btnStart.disabledText = {'\uF254';'\uF251';'\uF252';'\uF253';'\uF250'}
thread.delay(500);
var customUrl = string.trim(winform.editHost.text);
if(#customUrl){
if(string.startsWith(customUrl,"natapp:",true)){
if(..natapp){
..natapp.terminate()
}
var token = string.right(customUrl,-8);
import process.natapp;
..natapp = process.natapp(token,winform.edi,"INFO")
if(natapp[["tunnelUrl"]]){
winform.natAppTunnelUrl = natapp.tunnelUrl;
winform.editPort.text = 80;
}
else{
winform.btnStart.disabledText = null;
return winform.msgboxErr("错误的 NATAPP 令牌");
}
}
config.winform.editHost = customUrl;
}
else{
config.winform.editHost = null;
}
config.saveAll();
var port = tonumber(winform.editPort.text);
srvHttp.documentRoot = fsys.isDir(winform.editDocumentRoot.text) ? winform.editDocumentRoot.text;
srvHttp.userToken = winform.editPassword.text;
srvHttp.start("0.0.0.0",port);
serverInfo();
winform.btnStart.disabledText = null;
}
winform.txtMessage.enablePopMenu();
winform.txtMessage.onHyperlink = function(message,href){
if( message = 0x202/*_WM_LBUTTONUP*/ ) {
process.openUrl(href);
}
}
winform.onDropFiles = function(files){
var path = files[1]
if(!fsys.isDir(path)){
path = fsys.getParentDir(path)
}
winform.editDocumentRoot.text = path;
srvHttp.documentRoot = path;
config.winform.documentRoot = path;
config.winform.save();
wsrv.publish("reload");
}
winform.btnOpen.oncommand = function(id,event){
var dir = fsys.dlg.dir(winform.editDocumentRoot.text,winform)
if(dir){
winform.editDocumentRoot.text = dir;
srvHttp.documentRoot = dir;
config.winform.documentRoot = dir;
config.winform.save();
wsrv.publish("reload");
}
}
winform.btnOpenUpload.oncommand = function(id,event){
var path = io.joinpath(winform.editDocumentRoot.text,"upload")
if(io.createDir(path)){
process.explore(path)
}
}
var clipViewer = win.clip.viewer(winform);
clipViewer.onDrawClipboard=function(){
var str = win.clip.read();
//if(str!=wsrv.lastReceivedClipboardData ){
if(#str) wsrv.publish(str)
//wsrv.lastReceivedClipboardData = null;
//}
}
import key;
wsrv.onMessage = function(hSocket,msg){
var data = JSON.tryParse(msg.data)
if(!data) return;
if(data.event=="clipboard"){
wsrv.lastReceivedClipboardData = data.content;
win.clip.write(data.content);
}
elseif(data.event=="input"){
if(!winform.chkAiOpt.checked){
key.sendString(data.content)
}
else{
thread.invoke(
function(winform,aiOpt,text){
import key;
import web.rest.aiChat;
var aiClient = web.rest.aiChat(aiOpt)
var msg = web.rest.aiChat.message();
msg.system(aiOpt.systemPrompt)
msg.prompt( "请修复语音识别结果: " + text );
import winex.loading;
var loading = winex.loading("正在优化",hFocus)
var resp,err = aiClient.messages(msg,
function(deltaText,reasoning){
if(reasoning){
return loading.thinking(reasoning);
}
if(loading.isCanceled()){
return false;
}
key.sendString(deltaText)
}
);
},winform,config.aiOpt,data.content
)
}
}
}
var updateHomeDir = function(){
var home = "/";
if(winform.radioQrUploadDir.checked){
home = "/%3Cupload%3E/";
}
elseif(winform.radioQrClipboard.checked){
home = "/%3Cclipboard%3E/";
}
elseif(winform.radioQrInputMethod.checked){
home = "/%3Cinput%3E/";
}
var url = srvHttp.getUrl(home,true);
if(#srvHttp.userToken){
url = url ++ "?t=" + srvHttp.userToken;
}
var qrBmp = qrencode.bitmap( url );
winform.qr.setBackground(qrBmp.copyBitmap(winform.qr.width));
}
winform.radioQrRootDir.oncommand = function(id,event){
config.winform.qrDir = "root";
updateHomeDir();
}
winform.radioQrUploadDir.oncommand = function(id,event){
config.winform.qrDir = "upload";
updateHomeDir();
}
winform.radioQrClipboard.oncommand = function(id,event){
config.winform.qrDir = "clipboard";
updateHomeDir();
}
winform.radioQrInputMethod.oncommand = function(id,event){
config.winform.qrDir = "input";
updateHomeDir();
}
winform.show();
win.ui.simpleWindow2(winform);
var revealIcon = winform.editPassword.addCtrl(
cls="plus";
marginRight=0;marginBottom=2;
width=24;
iconText = '\uF023';
iconStyle={
align="right";font=LOGFONT(h=-15;name='FontAwesome');padding={top=3}
}
)
revealIcon.skin({
color = {
default = 0xC0000000;
hover = 0xFFFF0000;
active = 0xFF00FF00;
};
checked = {
iconText = '\uF06E';
}
})
revealIcon.onMouseClick = function(){
winform.editPassword.editBox.passwordChar = !owner.checked ? "*" : null;
}
winform.btnSetting.oncommand = function(id,event){
..table.assign(config.aiOpt,{
title = "扫码快传 - 设置 AI 优化助手";
});
import web.rest.aiChat.settingForm;
var frmSetting = web.rest.aiChat.settingForm(this,config.aiOpt);
if( frmSetting.doModal(this) ){
..table.assign(config.aiOpt,config.itemData[config.selItem] );
}
}
winform.chkAiOpt.checked = config.winform.checkAiOpt;
winform.onDestroy = function(){
config.winform.checkAiOpt = winform.chkAiOpt.checked;
}
winform.editHost.editBox.setCueBannerText("自定义访问域名")
winform.chkPublicAddress.oncommand = function(id,event){
if(winform.chkPublicAddress.checked){
import win.version;
if(win.version.isServer){
import win.ts;
if( win.ts.session.isRemote() ){
import wsock.tcp.server;
if( wsock.tcp.server.isFreePort(80)) winform.editPort.text = 80;
}
}
}
winform.btnStart.oncommand();
}
import gdip.imageAttributes;
var imgAttr = gdip.imageAttributes()
//替换前景图像(二维码)颜色
imgAttr.setRemapTable({
0xFF000000; 0xFF00A1D9; // 仅将二维码中的黑色(0xFF000000)替换为绿色(0xFF008000),不替换白色
0xFFFFFFFF; 0xFFFAFAFA;
});
winform.qr.imageAttributes = imgAttr
winform.btnStart.skin( {
background={
default=0xFF00A1D9;
hover=0xFFFF5C65;
active=0xFF1295C7;
disabled=0xFFCCCCCC;
}
})
winform.btnOpen.skin( {
background={
default=0;
hover=0xFF928BB3;
disabled=0xFFCCCCCC;
}
})
winform.btnOpenUpload.skin( {
background={
default=0;
hover=0xFF928BB3;
disabled=0xFFCCCCCC;
}
})
winform.btnSetting.skin({
color={
active=0xFF00FF00;
default=0xFF3C3C3C;
disabled=0xFF999999;
hover=0xFFFF0000
}
})
serverInfo();
win.loopMessage();
Markdown 格式