前言
mclo.gs 是一个用于上传Minecraft日志的网站,并可以提供一定程度的自动分析。但总有人说看不懂英语,于是我利用了它的API写一个适合中国宝宝体质的日志分享网站。
API分析
mclo.gs的粘贴日志API使用
post
请求,请求体格式为application/x-www-form-urlencoded
,请求字段为content
,最大长度为10MiB
和25k
行。
代码示例
纯静态+bootstrap:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>日志上传,基于mclo.gs!</title> <link rel="stylesheet" href="./upload/bootstrap.css" /> <style> body { display: flex; flex-direction: column; min-height: 100vh; margin: 0; /* Remove default margin */ background-color: #f0f0f0; /* 浅灰色背景 */ padding: 1rem; /* 给 body 添加一些内外边距 */ } .container { flex: 1; padding-top: 2rem; /* Add some top padding */ padding-bottom: 2rem; /* Add some bottom padding */ background-color: #fff; /* 白色背景 */ border-radius: 10px; /* 圆角 */ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 可选:添加阴影效果 */ max-width: 800px; /* 限制最大宽度 */ width: 100%; /* 确保在小屏幕上占满宽度 */ box-sizing: border-box; /* 确保内边距不会增加元素的总宽度 */ margin: 0 auto; /* 水平居中 */ margin-bottom: 1rem; /* 与页脚之间的边距 */ } footer { flex-shrink: 0; padding: 1rem 0; text-align: center; width: 100%; /* 确保 footer 占满整个宽度 */ margin: 0 auto; /* 水平居中 */ max-width: 800px; /* 与 .container 保持一致的最大宽度 */ margin-top: 1rem; /* 与 .container 之间的边距 */ } </style> </head> <body> <div class="container"> <h1 class="text-center mt-5 mb-4">将你的日志上传到mclo.gs,分享给别人!</h1> <div class="row justify-content-center"> <div class="col-12 col-md-8"> <label for="fileInput">将日志粘贴到此</label> <div class="form-group mb-4"> <textarea id="logContent" class="form-control" rows="10" placeholder="将日志粘贴到此!"></textarea> </div> <button id="uploadButton" class="btn btn-primary w-100 mb-4" onclick="uploadLog()">上传粘贴内容</button> <div class="mt-3 mb-4"> <label for="fileInput">或上传文件</label> <div class="input-group mb-3"> <input type="file" class="form-control" id="fileInput" accept=".txt,.log,application/octet-stream"> </div> <button id="fileUploadButton" class="btn btn-primary w-100 mb-4" onclick="uploadFile()">上传文件</button> </div> <div class="mt-3 mb-4"> <label for="responseOutput">返回结果:</label> <pre id="responseOutput" class="bg-light p-3 mb-4"></pre> <button id="copyButton" class="btn btn-secondary w-100" onclick="copyToClipboard()">点击复制,分享给他人!</button> </div> </div> </div> </div> <footer> <div class="container"> <p>黑ICP备2024025211号-1</p> </div> </footer> <script> let requestCount = 0; let firstRequestTime = null; function uploadLog() { const logContent = document.getElementById('logContent').value; const formData = new FormData(); formData.append('content', logContent); const uploadButton = document.getElementById('uploadButton'); const copyButton = document.getElementById('copyButton'); const currentTime = new Date().getTime(); if (firstRequestTime === null) { firstRequestTime = currentTime; } if (currentTime - firstRequestTime < 60000) { if (requestCount >= 5) { alert('请求频率过高,请稍后再试。'); return; } } else { firstRequestTime = currentTime; requestCount = 0; } requestCount++; if (!logContent.trim()) { alert('上传失败,请粘贴日志!'); return; } uploadButton.innerHTML = ` <button class="btn btn-primary" type="button" disabled> <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <span class="visually-hidden">Loading...</span> </button> `; fetch('https://api.mclo.gs/1/log', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { document.getElementById('responseOutput').textContent = data.url ? data.url : JSON.stringify(data, null, 2); }) .catch(error => { console.error('Error:', error); document.getElementById('responseOutput').textContent = JSON.stringify(error, null, 2); }) .finally(() => { uploadButton.innerHTML = '上传'; copyButton.classList.remove('btn-success'); copyButton.classList.add('btn-secondary'); copyButton.textContent = '复制链接'; }); } function copyToClipboard() { const responseOutput = document.getElementById('responseOutput'); const text = responseOutput.textContent; const copyButton = document.getElementById('copyButton'); if (!text || text.includes('Error')) { copyButton.classList.remove('btn-secondary'); copyButton.classList.add('btn-danger'); copyButton.textContent = '复制失败,请确认上传成功后再点此按钮!'; return; } const tempInput = document.createElement('textarea'); tempInput.value = text; document.body.appendChild(tempInput); tempInput.select(); document.execCommand('copy'); document.body.removeChild(tempInput); copyButton.classList.remove('btn-secondary'); copyButton.classList.add('btn-success'); copyButton.textContent = '复制成功!'; } function uploadFile() { const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; if (!file) { alert('请选择要上传的文件!'); return; } const reader = new FileReader(); reader.onload = function(event) { const logContent = event.target.result; const formData = new FormData(); formData.append('content', logContent); const fileUploadButton = document.getElementById('fileUploadButton'); const copyButton = document.getElementById('copyButton'); fileUploadButton.innerHTML = ` <button class="btn btn-primary" type="button" disabled> <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <span class="visually-hidden">Loading...</span> </button> `; fetch('https://api.mclo.gs/1/log', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { document.getElementById('responseOutput').textContent = data.url ? data.url : JSON.stringify( data, null, 2); }) .catch(error => { console.error('Error:', error); document.getElementById('responseOutput').textContent = JSON.stringify(error, null, 2); }) .finally(() => { fileUploadButton.innerHTML = '上传文件'; copyButton.classList.remove('btn-success'); copyButton.classList.add('btn-secondary'); copyButton.textContent = '复制链接'; }); }; reader.readAsText(file); // 将文件读取为文本 } </script> </body> </html>
该代码使用
bootstrap
进行美化,其中的引入部分可自行下载文件到本地或使用CDN引入。同时该代码对粘贴内容做出一定判断,如果为空,则提示需要粘贴内容,并提供一个复制链接的功能。
vue+ElementPlus:
<template> <div id="app"> <el-container style="min-height: 100vh; background-color: #f0f0f0; padding: 1rem;"> <el-card class="header-card" shadow="always"> <el-row justify="space-between" align="middle"> <el-col :span="12"> <h1 style="color: #409EFF; margin: 0">将你的日志上传到mclo.gs,分享给别人!</h1> </el-col> <el-col :span="12" style="text-align: right"> <el-button type="primary" link @click="openBlog"> 返回博客主页 </el-button> </el-col> </el-row> </el-card> <el-main> <h1 class="text-center"></h1> <el-card class="container" shadow="always"> <el-steps :active="activeStep" align-center> <el-step title="选择上传方式" :icon="Edit" /> <el-step title="上传日志" /> <el-step title="获取链接" /> </el-steps> <div v-if="activeStep === 0" class="step-content"> <el-radio-group v-model="uploadMethod" size="large"> <el-radio-button label="paste" border>粘贴日志</el-radio-button> <el-radio-button label="file" border>上传文件</el-radio-button> </el-radio-group> <div class="button-group"> <el-button type="primary" @click="nextStep" :disabled="!uploadMethod">下一步</el-button> </div> </div> <div v-else-if="activeStep === 1" class="step-content"> <div v-if="uploadMethod === 'paste'"> <el-input type="textarea" :rows="10" placeholder="请将日志粘贴在此处!" v-model="logContent"> </el-input> </div> <div v-else-if="uploadMethod === 'file'"> <el-upload action="#" :auto-upload="false" :on-change="handleFileChange" accept=".txt,.log"> <el-button type="primary" >点击上传文件</el-button> </el-upload> </div> <div class="button-group"> <el-button type="primary" @click="uploadLogOrFile" v-loading.fullscreen.lock="fullscreenLoading">上传</el-button> <el-button type="default" @click="prevStep">上一步</el-button> </div> </div> <div v-else class="step-content"> <el-result icon="success" title="成功上传" :sub-title="`链接为:${responseOutput},您可通过复制链接按钮进行复制,分享给他人。`" > <template #extra> <el-button type="primary" @click="ResetStep">重新上传</el-button> <el-button type="secondary" @click="copyToClipboard">{{ copyButtonText }}</el-button> </template> </el-result> </div> </el-card> </el-main> <el-footer style="display: flex; justify-content: center; align-items: center;"> <el-card style="width: 100%; max-width: 800px; text-align: center;"> <p>黑ICP备2024025211号-1</p> </el-card> </el-footer> </el-container> </div> </template> <script> import { ref } from 'vue'; import { ElMessage,ElLoading } from 'element-plus'; export default { setup() { const fullscreenLoading = ref(false); const activeStep = ref(0); const uploadMethod = ref(''); const logContent = ref(''); const fileContent = ref(''); const responseOutput = ref(''); const copyButtonText = ref('复制链接'); const openBlog = () => { window.open('https://ideafox.top') }; const nextStep = () => { if (!uploadMethod.value) { ElMessage.error('请选择一种上传方式!'); return; } activeStep.value++; }; const prevStep = () => { activeStep.value--; }; const ResetStep = ()=>{ activeStep.value = 0; uploadMethod.value = ''; logContent.value = ''; fileContent.value = ''; responseOutput.value = ''; }; const handleFileChange = (file, fileList) => { const allowedExtensions = ['txt', 'log']; const fileName = file.raw.name; const extension = fileName.split('.').pop().toLowerCase(); if (!allowedExtensions.includes(extension)) { ElMessage.error('仅支持上传.txt和.log类型的文件'); return; // 阻止后续文件读取 } const reader = new FileReader(); reader.onload = e => fileContent.value = e.target.result; reader.readAsText(file.raw); }; const uploadLogOrFile = async () => { fullscreenLoading.value = true if ((uploadMethod.value === 'paste' && !logContent.value.trim()) || (uploadMethod.value === 'file' && !fileContent.value)) { ElMessage.error(uploadMethod.value === 'paste' ? '请先粘贴或输入日志内容!' : '请选择一个文件进行上传!'); fullscreenLoading.value = false; return; } const formData = new FormData(); let content = uploadMethod.value === 'paste' ? logContent.value : fileContent.value; formData.append('content', content); try { const res = await fetch('https://api.mclo.gs/1/log', { method: 'POST', body: formData, }); const data = await res.json(); if(data.success) { responseOutput.value = data.url; activeStep.value++; } else { ElMessage.error(`上传失败:${data.error}`); } } catch(error) { ElMessage.error('请求过程中出现错误,请稍后再试。'); } finally { fullscreenLoading.value = false; } }; const copyToClipboard = () => { if (!responseOutput.value) { ElMessage.error('没有可复制的链接!'); return; } navigator.clipboard.writeText(responseOutput.value).then(() => { ElMessage.success('链接已复制到剪贴板!'); }).catch(err => { ElMessage.error('无法复制链接,请手动复制。'); }); }; return { activeStep, uploadMethod, logContent, responseOutput, copyButtonText, nextStep, prevStep, handleFileChange, uploadLogOrFile, copyToClipboard, ResetStep, fullscreenLoading, openBlog, }; } } </script> <style scoped> .container { max-width: 800px; margin: 0 auto; padding: 2rem; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); background-color: #fff; } .text-center { text-align: center; } .el-steps { margin: 20px 0; } .button-group { display: flex; justify-content: space-between; margin-top: 20px; } .step-content { text-align: center; } .el-menu--horizontal > .el-menu-item:nth-child(1) { margin-right: auto; } </style>
需导入
Element-Plus
,可直接粘贴到APP.vue文件,并通过npm run build
命令导出为纯静态页面。
总结
虽然该代码比较简陋,但足以应对大部分需求,同时也更适合中国宝宝!