const{createApp}=Vue const{createVuetify,useGoTo,useDisplay}=Vuetify var data={alert:{show:false,color:'success',text:'',timeout:0,},theme:{dark:false,},nav:{showDrawer:false,showTOC:true,tocPanel:0,tab:'account',post:{discussionId:2975,currentPage:1,targetPage:1,total:8,anchor:0,goToOptions:{container:null,duration:0,easing:'easeInOutCubic',offset:-100,},worker:null,task:[],active:[],apiLock:[],originLike:new Map([]),},related:{block:1,}},search:{width:80,text:null,loading:false,},tags:[{id:50,url:'/t/32715a296a045c7260770f713010',name:'图床',color:'#66BB6A',icon:'mdi-tag-heart',},],posts:[{id:35550,num:0,uid:1681,content:'\u003Cp\u003E主要是用别人的图床老是提示一分钟上传限制\u003Cbr\u003E想想自己弄一个代码参照https://skyimg.de/\u003Cbr\u003E全部用chatgpt编写的\u003Cbr\u003E我有一台**Web Hosting\u003Cbr\u003E之前推荐的2o一年\u003Cbr\u003E\u003Cbr\u003E\u003Cimg src\u003D\"https://tu.ganba.de/uploads/ffc24e0b121d874dcab8c68b5cb61d03.webp\" alt\u003D\"image\"\u003E\u003C/p\u003E\u003Cp\u003E教程编写中。。。\u003C/p\u003E📜 粘贴 \u0026amp; 拖拽图片自动上传 🚀\u003Cp\u003E🔥 支持 Markdown / BBCode / HTML / URL 图片上传,搭配 \u003Ccode\u003EPHP 服务器后端\u003C/code\u003E,高效 \u0026amp; 可控!\u003C/p\u003E📌 功能特点\u003Cp\u003E✅ 粘贴 (\u003Ccode\u003ECtrl + V\u003C/code\u003E) 或拖拽上传\u003Cbr\u003E✅ 自动转换 \u003Ccode\u003EJPEG / PNG\u003C/code\u003E 为 \u003Ccode\u003EWebP\u003C/code\u003E(可选)\u003Cbr\u003E✅ 支持 \u003Ccode\u003EMarkdown / BBCode / URL / HTML\u003C/code\u003E 格式\u003Cbr\u003E✅ 上传成功自动复制图片链接\u003Cbr\u003E✅ \u003Ccode\u003ETampermonkey\u003C/code\u003E 菜单选择格式\u003Cbr\u003E✅ 支持 \u003Ccode\u003EPHP 服务器端\u003C/code\u003E,图片存储 \u003Ccode\u003E安全 \u0026amp; 可控\u003C/code\u003E\u003C/p\u003E📌 1️⃣ 安装 \u003Ccode\u003ETampermonkey\u003C/code\u003E 用户脚本📥 复制以下 \u003Ccode\u003EJ**aScript\u003C/code\u003E 代码,并粘贴到 \u003Ccode\u003ETampermonkey\u003C/code\u003E 编辑器\u003Ccode\u003E// \u003D\u003DUserScript\u003D\u003D// @name 粘贴 \u0026amp; 拖拽图片自动上传// @namespace https://ganba.de/// @version 1.0.0// @license MIT// @description 修复格式选择问题,支持 Markdown / BBCode / URL / HTML / 富文本// @match *://*/*// @grant GM_notification// @grant GM_xmlhttpRequest// @grant GM_getValue// @grant GM_setValue// @grant GM_registerMenuCommand// \u003D\u003D/UserScript\u003D\u003D(function() { \u0026#39;use strict\u0026#39;; let storedFormat \u003D GM_getValue(\u0026#39;uploadFormat\u0026#39;, \u0026#39;markdown\u0026#39;); let autoDetect \u003D GM_getValue(\u0026#39;autoDetect\u0026#39;, false); const config \u003D { `apiUrl: \u0026#39;https://yourdomain.com/upload.php\u0026#39;,` defaultFormat: storedFormat, autoDetectFormat: autoDetect, }; let lastActiveElement \u003D null; document.addEventListener(\u0026#39;focusin\u0026#39;, function(e) { lastActiveElement \u003D e.target; }); document.addEventListener(\u0026#39;paste\u0026#39;, function(e) { const clipboardData \u003D e.clipboardData || window.clipboardData; if (!clipboardData) return; const items \u003D clipboardData.items; let imageFile \u003D null; for (let item of items) { if (item.type.indexOf(\u0026#39;image\u0026#39;) !\u003D\u003D -1) { imageFile \u003D item.getAsFile(); break; } } if (!imageFile) return; e.preventDefault(); uploadImage(imageFile); }); document.addEventListener(\u0026#39;dragover\u0026#39;, (e) \u003D\u0026gt; e.preventDefault()); document.addEventListener(\u0026#39;drop\u0026#39;, function(e) { e.preventDefault(); const files \u003D e.dataTransfer.files; if (files.length \u0026gt; 0 \u0026amp;\u0026amp; files[0].type.startsWith(\u0026#39;image/\u0026#39;)) { uploadImage(files[0]); } }); function uploadImage(file) { showToast(\u0026#39;正在上传图片...\u0026#39;, \u0026#39;info\u0026#39;); const formData \u003D new FormData(); formData.append(\u0026#39;file\u0026#39;, file); GM_xmlhttpRequest({ method: \u0026#39;POST\u0026#39;, url: config.apiUrl, data: formData, responseType: \u0026#39;json\u0026#39;, onload: function(response) { try { const data \u003D JSON.parse(response.responseText.trim()); if (data.url) { const format \u003D config.autoDetectFormat ? detectEditorFormat() : config.defaultFormat; const formattedLink \u003D formatLink(data.url, format); insertText(formattedLink); copyToClipboard(formattedLink); showToast(`图片上传成功!格式:${format}`, \u0026#39;success\u0026#39;); } else { showToast(\u0026#39;上传失败:\u0026#39; + (data.error || \u0026#39;未知错误\u0026#39;), \u0026#39;error\u0026#39;); } } catch (error) { showToast(\u0026#39;服务器返回数据错误\u0026#39;, \u0026#39;error\u0026#39;); } }, onerror: function() { showToast(\u0026#39;图片上传失败,请检查服务器\u0026#39;, \u0026#39;error\u0026#39;); } }); } function detectEditorFormat() { if (!lastActiveElement) return config.defaultFormat; if (lastActiveElement.tagName \u003D\u003D\u003D \u0026#39;TEXTAREA\u0026#39; || lastActiveElement.tagName \u003D\u003D\u003D \u0026#39;INPUT\u0026#39;) return \u0026#39;url\u0026#39;; if (lastActiveElement.closest(\u0026#39;[contenteditable]\u0026#39;)) return \u0026#39;html\u0026#39;; return config.defaultFormat; } function formatLink(url, format) { switch (format) { case \u0026#39;bbcode\u0026#39;: return `[img]${url}[/img]`; case \u0026#39;html\u0026#39;: return `\u0026lt;img src\u003D\u0026#34;${url}\u0026#34; alt\u003D\u0026#34;image\u0026#34;\u0026gt;`; case \u0026#39;wysiwyg\u0026#39;: return `\u0026lt;figure\u0026gt;\u0026lt;img src\u003D\u0026#34;${url}\u0026#34;\u0026gt;\u0026lt;/figure\u0026gt;`; case \u0026#39;url\u0026#39;: return url; case \u0026#39;markdown\u0026#39;: default: return `![image](${url})`; } } function insertText(text) { const el \u003D lastActiveElement; if (!el) return; if (el.tagName \u003D\u003D\u003D \u0026#39;TEXTAREA\u0026#39; || el.tagName \u003D\u003D\u003D \u0026#39;INPUT\u0026#39;) { insertAtCursor(el, text); } else if (el.isContentEditable) { try { document.execCommand(\u0026#39;insertText\u0026#39;, false, text); } catch (e) { el.innerText +\u003D text; } } else { showToast(\u0026#39;无法自动插入图片链接,请手动粘贴\u0026#39;, \u0026#39;warning\u0026#39;); copyToClipboard(text); } } function insertAtCursor(el, text) { const startPos \u003D el.selectionStart || 0; const endPos \u003D el.selectionEnd || startPos; el.value \u003D el.value.substring(0, startPos) + text + el.value.substring(endPos); el.selectionStart \u003D el.selectionEnd \u003D startPos + text.length; el.focus(); } function copyToClipboard(text) { n**igator.clipboard.writeText(text).then(() \u003D\u0026gt; { showToast(`图片链接已复制到剪贴板!格式: ${config.defaultFormat}`, \u0026#39;success\u0026#39;); }).catch(() \u003D\u0026gt; { const textarea \u003D document.createElement(\u0026#39;textarea\u0026#39;); textarea.value \u003D text; document.body.appendChild(textarea); textarea.select(); document.execCommand(\u0026#39;copy\u0026#39;); document.body.removeChild(textarea); showToast(`图片链接已复制到剪贴板!格式: ${config.defaultFormat}`, \u0026#39;success\u0026#39;); }); } function showToast(message, type \u003D \u0026#39;info\u0026#39;) { GM_notification({ text: message, title: \u0026#39;图片上传\u0026#39;, timeout: 5000 }); } function updateFormat(format) { GM_setValue(\u0026#39;uploadFormat\u0026#39;, format); config.defaultFormat \u003D format; showToast(`已切换格式:${format}`, \u0026#39;success\u0026#39;); } GM_registerMenuCommand(\u0026#34;📜 选择: Markdown\u0026#34;, () \u003D\u0026gt; updateFormat(\u0026#39;markdown\u0026#39;)); GM_registerMenuCommand(\u0026#34;📜 选择: BBCode\u0026#34;, () \u003D\u0026gt; updateFormat(\u0026#39;bbcode\u0026#39;)); GM_registerMenuCommand(\u0026#34;📜 选择: HTML\u0026#34;, () \u003D\u0026gt; updateFormat(\u0026#39;html\u0026#39;)); GM_registerMenuCommand(\u0026#34;📜 选择: WYSIWYG\u0026#34;, () \u003D\u0026gt; updateFormat(\u0026#39;wysiwyg\u0026#39;)); GM_registerMenuCommand(\u0026#34;📜 选择: 纯 URL\u0026#34;, () \u003D\u0026gt; updateFormat(\u0026#39;url\u0026#39;)); GM_registerMenuCommand(\u0026#34;⚙️ 自动匹配 (开关)\u0026#34;, () \u003D\u0026gt; { const newState \u003D !GM_getValue(\u0026#39;autoDetect\u0026#39;, false); GM_setValue(\u0026#39;autoDetect\u0026#39;, newState); config.autoDetectFormat \u003D newState; showToast(`自动匹配: ${newState ? \u0026#39;开启\u0026#39; : \u0026#39;关闭\u0026#39;}`, \u0026#39;success\u0026#39;); });})();\u003C/code\u003E📌 2️⃣ 服务器端 (PHP)📥 复制以下 \u003Ccode\u003Eupload.php\u003C/code\u003E,上传到你的服务器\u003Ccode\u003E\u0026lt;?php// **彻底清理所有可能的无效输出**if (ob_get_length()) ob_end_clean();header(\u0026#39;Content-Type: application/json; charset\u003Dutf-8\u0026#39;);header(\u0026#39;Access-Control-Allow-Origin: *\u0026#39;);header(\u0026#39;Access-Control-Allow-Methods: POST, OPTIONS\u0026#39;);header(\u0026#39;Access-Control-Allow-Headers: Content-Type\u0026#39;);// **优化 PHP 运行**ini_set(\u0026#39;display_errors\u0026#39;, 0);ini_set(\u0026#39;log_errors\u0026#39;, 1);ini_set(\u0026#39;output_buffering\u0026#39;, \u0026#39;off\u0026#39;);ini_set(\u0026#39;zlib.output_compression\u0026#39;, 0);error_reporting(0);// **配置**$config \u003D [ \u0026#39;upload_dir\u0026#39; \u003D\u0026gt; \u0026#39;uploads/\u0026#39;, \u0026#39;allowed_types\u0026#39; \u003D\u0026gt; [\u0026#39;image/jpeg\u0026#39;, \u0026#39;image/png\u0026#39;, \u0026#39;image/webp\u0026#39;, \u0026#39;image/gif\u0026#39;], \u0026#39;max_size\u0026#39; \u003D\u0026gt; 50 * 1024 * 1024, // 50MB \u0026#39;enable_webp\u0026#39; \u003D\u0026gt; true, \u0026#39;base_url\u0026#39; \u003D\u0026gt; \u0026#39;https://yourdomain.come/uploads/\u0026#39;, \u0026#39;hash_algorithm\u0026#39; \u003D\u0026gt; \u0026#39;md5\u0026#39; // 可选:\u0026#39;md5\u0026#39; 或 \u0026#39;sha1\u0026#39;];// **处理 OPTIONS 请求**if ($_SERVER[\u0026#39;REQUEST_METHOD\u0026#39;] \u003D\u003D\u003D \u0026#39;OPTIONS\u0026#39;) exit;// **检查 POST 请求**if ($_SERVER[\u0026#39;REQUEST_METHOD\u0026#39;] !\u003D\u003D \u0026#39;POST\u0026#39; || !isset($_FILES[\u0026#39;file\u0026#39;])) { output_json([\u0026#39;error\u0026#39; \u003D\u0026gt; \u0026#39;No file uploaded\u0026#39;], 400);}// **获取文件信息**$file \u003D $_FILES[\u0026#39;file\u0026#39;];$fileType \u003D mime_content_type($file[\u0026#39;tmp_name\u0026#39;]);$fileSize \u003D $file[\u0026#39;size\u0026#39;];$fileTmp \u003D $file[\u0026#39;tmp_name\u0026#39;];if (!in_array($fileType, $config[\u0026#39;allowed_types\u0026#39;])) { output_json([\u0026#39;error\u0026#39; \u003D\u0026gt; \u0026#39;Unsupported file type\u0026#39;], 400);}if ($fileSize \u0026gt; $config[\u0026#39;max_size\u0026#39;]) { output_json([\u0026#39;error\u0026#39; \u003D\u0026gt; \u0026#39;File too large\u0026#39;], 400);}// **计算文件哈希(唯一文件名)**$fileHash \u003D ($config[\u0026#39;hash_algorithm\u0026#39;] \u003D\u003D\u003D \u0026#39;sha1\u0026#39;) ? sha1_file($fileTmp) : md5_file($fileTmp);$ext \u003D strtolower(pathinfo($file[\u0026#39;name\u0026#39;], PATHINFO_EXTENSION));$newFilename \u003D \u0026#34;{$fileHash}.{$ext}\u0026#34;;$uploadPath \u003D $config[\u0026#39;upload_dir\u0026#39;] . $newFilename;$fileUrl \u003D trim($config[\u0026#39;base_url\u0026#39;] . $newFilename);// **如果文件已存在,直接返回 URL**if (file_exists($uploadPath)) { output_json([\u0026#39;url\u0026#39; \u003D\u0026gt; $fileUrl]);}// **确保目录存在**if (!is_dir($config[\u0026#39;upload_dir\u0026#39;]) \u0026amp;\u0026amp; !mkdir($config[\u0026#39;upload_dir\u0026#39;], 0755, true)) { output_json([\u0026#39;error\u0026#39; \u003D\u0026gt; \u0026#39;Failed to create upload directory\u0026#39;, \u0026#39;debug\u0026#39; \u003D\u0026gt; error_get_last()], 500);}// **移动文件**if (!move_uploaded_file($fileTmp, $uploadPath)) { output_json([\u0026#39;error\u0026#39; \u003D\u0026gt; \u0026#39;Failed to s**e file\u0026#39;, \u0026#39;debug\u0026#39; \u003D\u0026gt; error_get_last()], 500);}// **WebP 转换逻辑**if ($config[\u0026#39;enable_webp\u0026#39;] \u0026amp;\u0026amp; in_array($fileType, [\u0026#39;image/jpeg\u0026#39;, \u0026#39;image/png\u0026#39;])) { $webpFilename \u003D \u0026#34;{$fileHash}.webp\u0026#34;; $webpPath \u003D $config[\u0026#39;upload_dir\u0026#39;] . $webpFilename; if (convertToWebp($uploadPath, $webpPath)) { unlink($uploadPath); // **删除原始文件** output_json([\u0026#39;url\u0026#39; \u003D\u0026gt; trim($config[\u0026#39;base_url\u0026#39;] . $webpFilename)]); }}// **最终返回上传 URL**output_json([\u0026#39;url\u0026#39; \u003D\u0026gt; $fileUrl]);/** * **快速 JSON 响应** */function output_json($data, $http_status \u003D 200) { http_response_code($http_status); header(\u0026#39;Content-Type: application/json; charset\u003Dutf-8\u0026#39;); echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); exit;}/** * **将图片转换为 WebP 格式** */function convertToWebp($sourcePath, $destinationPath) { $info \u003D getimagesize($sourcePath); $mime \u003D $info[\u0026#39;mime\u0026#39;]; if ($mime \u003D\u003D\u003D \u0026#39;image/jpeg\u0026#39;) { $image \u003D imagecreatefromjpeg($sourcePath); } elseif ($mime \u003D\u003D\u003D \u0026#39;image/png\u0026#39;) { $image \u003D imagecreatefrompng($sourcePath); } else { return false; // **不转换 gif 或 webp** } if (!$image) return false; $result \u003D imagewebp($image, $destinationPath, 80); imagedestroy($image); return $result;}\u003C/code\u003E\u003Cp\u003E\u003Cimg src\u003D\"https://tu.ganba.de/uploads/8bf44e11b883ac7c6d412f979ad51e23.webp\" alt\u003D\"image\"\u003E\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-13 14:27:10',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:35551,num:1,uid:1705,content:'\u003Cp\u003E前排学习\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-13 14:28:52',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:35552,num:2,uid:7829,content:'\u003Cp\u003E挂alist会不会封号啊\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-13 14:29:53',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:35553,num:3,uid:7696,content:'\u003Cp\u003E学习学习 \u003Cimg src\u003D\"/z/img/xhj/003.webp\"\u003E\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-13 14:31:27',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:35554,num:4,uid:1405,content:'\u003Cp\u003E1\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-13 14:45:51',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:35555,num:5,uid:1405,content:'\u003Cp\u003E\u003Cimg src\u003D\"https://cdn.skyimg.de/up/2025/3/13/mpw91t.webp\" alt\u003D\"image\"\u003E\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-13 14:47:14',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:35556,num:6,uid:5390,content:'\u003Cp\u003E看着真不错\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-13 22:55:39',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},{id:35557,num:7,uid:5551,content:'\u003Cp\u003E转webp的目的是?\u003C/p\u003E',ipRegion:'',updatedByUid:0,createdAt:'2025-03-14 22:54:21',updatedAt:'2025-03-16 16:00:21',mentionNum:0,mentionedBy:[],mentionUsers:[],likeUsers:[],},],usersMap:new Map([[5551,{uid:5551,url:'/u/1805410c6a045c72656f576f21271d73',avatar:'/a/1805410c6a045c72656f576f21271d73',username:'seekwho🤖',}],[5390,{uid:5390,url:'/u/310066076a045c726363566f23183803',avatar:'/a/310066076a045c726363566f23183803',username:'zjkal🤖',}],[1705,{uid:1705,url:'/u/02714b3a6a045c76676a536f365a2072',avatar:'/a/02714b3a6a045c76676a536f365a2072',username:'solaireh3🤖',}],[1681,{uid:1681,url:'/u/630545086a045c766662576f1625650a',avatar:'/a/630545086a045c766662576f1625650a',username:'622cc🤖',}],[1405,{uid:1405,url:'/u/6a1f75556a045c76646a536f421e040c',avatar:'/a/6a1f75556a045c76646a536f421e040c',username:'goo🤖',}],[7829,{uid:7829,url:'/u/1439651f6a045c7068685f6f2e3c201d',avatar:'/a/1439651f6a045c7068685f6f2e3c201d',username:'Tily🤖',}],[7696,{uid:7696,url:'/u/087d7e5c6a045c706663506f34503034',avatar:'/a/087d7e5c6a045c706663506f34503034',username:'西瓜123🤖',}],]),related:[{title:'兰空图床马上要涨价了!',url:'/d/2f78602b6a045c77606a567a445d6b6a1d2a3b0a',},{title:'过客图床测试',url:'/d/33105d076a045c77606a5675405a6b6a1a73623e',},{title:'有大佬会minio接入CloudFlare-ImgBed',url:'/d/091e5f206a045c77606a5675405b606a2e39113c',},{title:'大伙为什么做公益图床',url:'/d/092340196a045c77606a56744650626a580a6338',},{title:'love图床 欢迎使用',url:'/d/160274006a045c77606a56774f51626a2e1b071d',},{title:'测一下图床速度',url:'/d/117f772f6a045c77606a56774f5b6b6a3a140460',},{title:'有服务器、网盘、nas、公网IP怎么搞一套最好的图床',url:'/d/340a7e026a045c77606a5677435d666a01316315',},{title:'上次看到有人分享avif的图床,是哪个的?',url:'/d/3d2107556a045c77606a56774658626a140e6420',},{title:'兰空开源版,有办法上传avif吗?',url:'/d/0d0b00176a045c77606a5676465a606a2b7b2907',},{title:'不懂就问 蓝空图床的注册点了之后没反应是怎么回事?',url:'/d/157c0a276a045c77606a56714e58626a3d071d04',},{title:'论坛只能用自建图床发图片吗?',url:'/d/303f642c6a045c77606a56714159626a08022562',},{title:'屌大的来个免费稳定的图床!',url:'/d/1f2277036a045c77606a567145586a6a0f2d6b28',},{title:'自建图床测试NSFW',url:'/d/6e2864386a045c77606a5671475f616a22326a26',},{title:'请教大佬们图床问题',url:'/d/6d70631e6a045c77606a5671475a6a6a3e753131',},{title:'bing出问题了吗?简单图床登录界面抓取bing图片失败',url:'/d/6e394b256a045c77606a5670405a676a010d1507',},{title:'图床半挂?',url:'/d/192e7b226a045c77606a5670445b656a1c090935',},{title:'来花1H自建自己的图床吧',url:'/d/2a2b582a6a045c77606a56704651666a00070733',},{title:'Lsky 公益图床',url:'/d/3e22785c6a045c77606a5673465e666a25313067',},{title:'各位用公共图床会不会有不安全感',url:'/d/30227f076a045c77606a5673465d666a26082721',},{title:'收个二手蓝空图床正版认证',url:'/d/693941346a045c77606a56724e5e676a5f331825',},],} const App={setup(){const goTo=useGoTo() const{mdAndUp}=useDisplay() return{goTo,mdAndUp}},data(){return data;},mounted(){const themeDark=localStorage.getItem("themeDark") if(themeDark!==null){this.theme.dark=JSON.parse(themeDark)} if(this.nav.post.total>(this.nav.post.currentPage-1)*100+20){let moreLen=100 if(this.nav.post.total({id:null,num:(this.nav.post.currentPage-1)*100+v,uid:null,content:null,ipRegion:null,updatedByUid:null,createdAt:null,updatedAt:null,mentionNum:null,mentionedBy:null,mentionUsers:null,likeUsers:null,})) this.posts.push(...morePosts.slice(20))} this.workerStart() const hash=window.location.hash const match=hash.match(/#(\d+)/) if(match){const n=parseInt(match[1],10) if(n>=(this.nav.post.currentPage-1)*100&&n{this.jumpTo(n)})}} this.$nextTick(()=>{this.addHeadingIds() tocbot.init({tocSelector:'.toc',contentSelector:'#post-content-0',headingSelector:'h2, h3, h4',headingsOffset:100,scrollSmoothOffset:-100,scrollSmooth:true,collapseDepth:6,onClick:function(e){setTimeout(()=>{history.replaceState(null,'',window.location.pathname+window.location.search)},0)},}) tocbot.refresh()});},beforeUnmount(){this.workerStop() if(this.quill){this.quill.destroy() this.quill=null}},computed:{dposts(){return this.posts.slice(20);},},created(){},methods:{successAlert(msg){this.alert={show:true,color:'success',text:msg,timeout:1500,}},failureAlert(msg){this.alert={show:true,color:'error',text:msg,timeout:5000,}},flipThemeDark(){this.theme.dark=!this.theme.dark localStorage.setItem("themeDark",JSON.stringify(this.theme.dark))},toSearch(){if(!this.search.text){this.failureAlert('搜索词不能为空') return} let keywords=this.search.text.trim() if(keywords.length<1){this.failureAlert('搜索词不能为空') return} if(keywords.length>100){this.failureAlert('搜索词过长') return} this.doSearch(keywords)},toReg(){window.location.href="/reg"},toLogin(){window.location.href="/login"},toPage(){let url=window.location.href url=url.replace(/(\/\d+)?(#[0-9]+)?$/,this.nav.post.targetPage>1?`/${this.nav.post.targetPage}`:'') window.location.href=url},toLoadRelated({done}){if(this.my&&this.my.uid){this.apiLoadRelated({done})}else{done('ok')}},workerStart(){this.nav.post.worker=setInterval(()=>{this.workerLoad()},500);},workerStop(){if(this.nav.post.worker){clearInterval(this.nav.post.worker);this.nav.post.worker=null;}},async jumpTo(num){const page=Math.floor(num/100)+1 const i=num-(page-1)*100 if(page===this.nav.post.currentPage){this.goTo("#post-"+num,this.nav.post.goToOptions) if(!this.posts[i].id){const block=Math.floor(num/20)+1 this.nav.post.apiLock[block]=true await this.apiLoadPosts(block) this.$nextTick(()=>{this.goTo("#post-"+num,this.nav.post.goToOptions)})}}else{let url=window.location.href url=url.replace(/(\/\d+)?(#[0-9]+)?$/,page>1?`/${page}`:'') url=url+"#"+num window.location.href=url}},postIntersect(num){return(isIntersecting,entries,observer)=>{if(isIntersecting){this.nav.post.task.push(num) this.nav.post.active.push(num) this.nav.post.active=this.nav.post.active.filter(item=>Math.abs(item-num)<=5) this.nav.post.active.sort((a,b)=>a-b)}else{this.nav.post.active=this.nav.post.active.filter(item=>item!==num)} if(this.nav.post.active[0]){this.nav.post.anchor=this.nav.post.active[0]}else{this.nav.post.anchor=0}}},async apiLoadPosts(block){try{const response=await axios.post('/fapi/v1/post/block/'+block,{discussionId:this.nav.post.discussionId,}) if(response.data.code===0){response.data.data.posts.forEach(post=>{const i=post.num%100 Object.assign(this.posts[i],post)}) response.data.data.users.forEach(user=>{this.usersMap.set(user.uid,user)})}else{this.failureAlert('回帖数据加载失败: '+response.data.msg)}}catch(error){this.failureAlert('回帖数据加载失败: '+error)} this.nav.post.apiLock[block]=false},workerLoad(){while(this.nav.post.task.length){const num=this.nav.post.task.pop() const i=num-(this.nav.post.currentPage-1)*100 if(!this.posts[i].id){const block=Math.floor(num/20)+1 if(!this.nav.post.apiLock[block]){this.nav.post.apiLock[block]=true this.apiLoadPosts(block)}}}},getTimeInfo(t){if(!t){return ""} const now=new Date();const then=new Date(t);const diff=now-then;const minute=60*1000;const hour=minute*60;const day=hour*24;const month=day*30;const year=month*12;if(diffpost.num===num) if(!post){return "#"+num} const uid=post.uid const username=this.usersMap.get(uid)?.username if(!username){return "#"+num} return username},getUsernameByPostId(id){const post=this.posts.find(post=>post.id===id) if(!post){return "#"+this.getPostNumByPostId(id)} const uid=post.uid const username=this.usersMap.get(uid).username if(!username){return "#"+this.getPostNumByPostId(id)} return username},getPostNumByPostId(id){const post=this.posts.find(post=>post.id===id) return post.num},getPostById(id){const post=this.posts.find(post=>post.id===id) return post},getPostByNum(num){const post=this.posts.find(post=>post.num===num) return post},getAvatarByUid(uid){const avatar=this.usersMap.get(uid)?.avatar if(!avatar){return this.getRandomAvatar()} return avatar},getAvatarByPostNum(num){const post=this.posts.find(post=>post.num===num) if(!post){return this.getRandomAvatar()} const uid=post.uid return this.getAvatarByUid(uid)},getRandomAvatar(){const num=Math.floor(Math.random()*100) return "https://randomuser.me/api/portraits/men/"+num+".jpg"},getUrlByUid(uid){const url=this.usersMap.get(uid)?.url if(!url){return ""} return url},getTextByPostNum(num){const post=this.posts.find(post=>post.num===num) if(!post||!post.content){return '点击跳转到#'+num+'查看'} const parser=new DOMParser() const doc=parser.parseFromString(post.content,'text/html') const text=doc.body.textContent||'' return text.slice(0,100)},addHeadingIds(){const content=document.getElementById('post-content-0') if(!content){this.nav.showTOC=false return} const headings=content.querySelectorAll('h2, h3, h4') headings.forEach((heading,index)=>{if(!heading.id){heading.id=`toc-nav-${index}`}}) if(headings.length==0){this.nav.showTOC=false}},async doSearch(keywords){this.search.loading=true try{const response=await axios.post('/fapi/v1/search',{keywords:keywords,}) if(response.data.code===0){if(response.data.data.hash&&response.data.data.hash.length===32){window.location.href="/s/"+response.data.data.hash}else{this.failureAlert('搜索失败: 搜索服务异常')}}else{this.failureAlert('搜索失败: '+response.data.msg)}}catch(error){this.failureAlert('搜索失败: '+error)} this.search.loading=false},debounce(fn,delay){let timer=null return function(...args){if(timer)clearTimeout(timer) timer=setTimeout(()=>{fn.apply(this,args)},delay);};},},watch:{'nav.post.targetPage':{handler:async function(newV,oldV){this.toPage()},immediate:false},},} const vuetify=createVuetify({defaults:{global:{ripple:true,},},}) const app=createApp(App) app.use(vuetify).mount("#app")