Tim's Blog

Tim's Blog

为【微浸】主题适配友链自助提交插件

2025-07-11
为【微浸】主题适配友链自助提交插件

由于目前主题未适配 友链自助提交插件 故自己动手适配一下,通过代码注入减少后期修改

JS

通过拦截重写 .link-main-banner-btn.mt-10 按钮逻辑实现。其他页面位置需要自己查找替换 e.target.closest('.link-main-banner-btn.mt-10') 按钮类,同时下面代码做了判断只处理 /links 页面

微浸主题提供 自定义JavaScript代码 直接复制下面代码。也可以用Halo官方的接口:设置 -> 代码注入 需要使用 <script></script> 包裹。

// 动态添加友链对话框
function insertCustomModal() {
    if (document.getElementById('link-submit-modal')) return; // 避免重复添加

    //添加提交窗口
    const modalHTML = `
        <div id="link-submit-modal">
            <div>
                <h2>🔗提交友链</h2>
                <div class="modal-tip">
                    <p style="margin: 0;">⚠️<b>重要提示:</b> 在您申请之前,希望您已仔细阅读过<b>友链规则</b>,这将加快审核流程!</p>
                </div>

                <div id="link-submit-form-scroll-area">
                    <form id="link-submit-form" onsubmit="submitLinkForm(event)">                     
                        <div class="input-group">
                            <h3>基础信息</h3>
                            <input name="displayName" placeholder="博客名称" required><br>
                            <input name="url" placeholder="博客链接 (以 http:// 或 https:// 开头)" required><br>
                            <input name="logo" placeholder="Logo 图片链接" required><br>
                             <select name="groupName" id="link-groups">
                                <option value="">加载友链分组中...</option>
                            </select><br>
                        </div>

                        <div class="input-group">
                            <h3>联系与描述</h3>
                            <input name="email" placeholder="您的邮箱 (可选,用于审核联系)" type="email"><br>
                            <input name="rssUrl" placeholder="RSS 订阅链接 (选填)" type="url"><br>
                            <textarea name="description" placeholder="一句话描述您的博客 (不超过50字)"></textarea><br>
                        </div>
                    
                    </form>
                </div>
                
                <div class="modal-buttons">
                    <div class="radio-group">
                        <label>
                            <input type="radio" name="type" value="add" checked> 添加
                        </label>
                        <label>
                            <input type="radio" name="type" value="update"> 更新
                        </label>
                    </div>
                    <div class="action-buttons">
                        <button type="button" id="link-submit-cancel" onclick="closeCustomModal()">取消</button>
                        <button type="submit" form="link-submit-form">提交</button> 
                    </div>
                </div>
            </div>
        </div>`;
    document.body.insertAdjacentHTML('beforeend', modalHTML);

    //添加toast
    const toastHTML = `<div id="custom-toast"></div>`;
    document.body.insertAdjacentHTML('beforeend', toastHTML);
}

/*
* 显示toast
* type success , error
*/
function showToast(message, type = 'info', duration = 3000) {
    const toast = document.getElementById('custom-toast');
    if (!toast) return; // 如果元素不存在则不执行

    toast.textContent = message; // 设置消息文本
    toast.className = ''; // 重置所有类名,清除之前的状态
    toast.classList.add(type); // 添加消息类型类('success', 'error', 'info')
    toast.classList.add('show'); // 添加 'show' 类以显示并触发动画

    // 在指定时间后隐藏toast
    setTimeout(() => {
        toast.classList.remove('show');
    }, duration);
}

// 打开对话框
function openCustomModal() {
    insertCustomModal();
    const modal = document.getElementById('link-submit-modal');
    modal.style.display = 'flex';
    loadLinkGroups();
}

// 关闭对话框 
function closeCustomModal() {
    const modal = document.getElementById('link-submit-modal');
    modal.style.display = 'none';
}

// 加载友链分组 
function loadLinkGroups() {
    fetch('/apis/anonymous.link.submit.kunkunyu.com/v1alpha1/linkgroups')
        .then(res => res.json())
        .then(data => {
            const select = document.getElementById('link-groups');
            select.innerHTML = '';
            data.forEach(group => {
                const opt = document.createElement('option');
                opt.value = group.groupName;
                opt.textContent = group.displayName;
                select.appendChild(opt);
            });
        })
        .catch(err => {
            console.error('加载分组失败', err);
            document.getElementById('link-groups').innerHTML = '<option>加载失败</option>';
        });
}

// 提交友链 
function submitLinkForm(e) {
    e.preventDefault();

    const form = e.target;
    const formData = new FormData(form);
    const json = {};
    formData.forEach((value, key) => json[key] = value);

    // 手动获取 type 单选框的选中值
    const selectedTypeRadio = document.querySelector('input[name="type"]:checked');
    if (selectedTypeRadio) 
        json.type = selectedTypeRadio.value;
    else
        json.type = 'add';//默认

    fetch('/apis/anonymous.link.submit.kunkunyu.com/v1alpha1/linksubmits/-/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(json)
    })
        .then(res => res.json())
        .then(result => {
            if (result.status) {
                showToast('⚠️ ' + result.detail, 'error');
                return;
            }
            showToast('🎉 提交成功,请等待审核!', 'success');
            closeCustomModal();
        })
        .catch(err => {
            console.error('提交失败', err);
            showToast('⚠️ 提交失败,请稍后再试', 'error');
        });
}

// 拦截主题原有的申请按钮逻辑
if (/^\/links(\/|$)/.test(window.location.pathname)) {
    document.addEventListener('click', function (e) {
        if (e.target.closest('.link-main-banner-btn.mt-10')) {
            e.stopImmediatePropagation();
            e.preventDefault();
            openCustomModal();
        }
    }, true);
}
else {
    console.log('🚫 非 /links 页面,不绑定');
}

CSS

微浸主题提供 自定义css代码 直接复制下面代码即可。也可以用Halo官方的接口:设置 -> 代码注入 需要使用 <style></style> 包裹。

/*************************Toast********************************/
#custom-toast {
    position: fixed; /* 固定定位在视口中 */
    top: 20px; /* 距离顶部 20px */
    left: 50%; /* 水平居中 */
    transform: translateX(-50%); /* 精确水平居中 */
    background-color: #333;
    color: #fff;
    padding: 10px 16px;
    border-radius: 6px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
    z-index: 10000; /* 确保在模态框之上 */
    opacity: 0; /* 默认隐藏 */
    transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; /* 淡入淡出动画 */
    pointer-events: none; /* 允许点击穿透,当隐藏时不阻挡下方元素 */
    min-width: 200px;
    text-align: center;
}

#custom-toast.show {
    opacity: 1; /* 显示时完全不透明 */
    transform: translateX(-50%) translateY(0); /* 保持居中并取消Y轴位移(如果有) */
}

#custom-toast.success {
    background-color: #4caf50; /* 成功消息的背景色 (绿色) */
}

#custom-toast.error {
    background-color: #EF5350; /* 错误消息的背景色 (红色) */
}

/*************************友链申请CSS********************************/

/*原插件自带按钮*/
button[title="提交友链"] {
  display: none !important;
}

 /* 模态框背景遮罩 */
#link-submit-modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.6);
    z-index: 9999;
    justify-content: center;
    align-items: center;
}

/* 模态框内容容器 - 负责整体布局 */
#link-submit-modal>div {
    background: #fff;
    padding: 20px;
    /* 整体内边距 */
    border-radius: 8px;
    max-width: 450px;
    width: 90%;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
    position: relative;
    display: flex;
    /* 使用 Flexbox 进行内部布局 */
    flex-direction: column;
    /* 垂直排列子元素 */
    max-height: 85vh;
    /* 限制整体高度 */
    box-sizing: border-box;
}

/* 标题 */
#link-submit-modal h2 {
    margin-top: 0;
    margin-bottom: 20px;
    /* 增加底部间距,让标题更突出 */
    font-size: 22px;
    /* 略微增大字体 */
    color: #333;
    text-align: center;
    /* 确保居中 */
}

/* 可滚动表单内容区 */
#link-submit-form-scroll-area {
    flex-grow: 1;
    /* 占据剩余空间 */
    /* 垂直滚动条 - 针对不同浏览器做兼容性处理 */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* 针对iOS平滑滚动 */
    scrollbar-width: thin;
    /* Firefox 滚动条样式 */
    scrollbar-color: #ccc #f0f0f0;
    /* Firefox 滚动条颜色 */

    /* Webkit (Chrome, Safari) 滚动条样式 */
    &::-webkit-scrollbar {
        width: 8px;
    }

    &::-webkit-scrollbar-track {
        background: #f0f0f0;
        border-radius: 10px;
    }

    &::-webkit-scrollbar-thumb {
        background-color: #ccc;
        border-radius: 10px;
        border: 2px solid #f0f0f0;
    }

    &::-webkit-scrollbar-thumb:hover {
        background-color: #999;
    }

    padding-right: 10px;
    /* 为滚动条留出空间,避免内容被遮挡 */
    margin-right: -10px;
    /* 抵消 padding-right,让内容对齐 */
}

/* 表单输入框及选择框 */
#link-submit-form input,
#link-submit-form textarea,
#link-submit-form select {
    width: calc(100% - 16px);
    /* 适应内边距 */
    margin-bottom: 10px;
    padding: 8px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
    box-sizing: border-box;
}

#link-submit-form input:focus,
#link-submit-form textarea:focus,
#link-submit-form select:focus {
    border-color: #358bff;
    outline: none;
}

#link-submit-form textarea {
    resize: vertical;
    min-height: 60px;
}

/* 按钮容器 (现在独立于滚动区) */
.modal-buttons {
    display: flex;
    justify-content: space-between;
    /* 单选框组在左,按钮在右 */
    gap: 8px;
    margin-top: 15px;
    /* 与上方内容保持距离 */
    flex-shrink: 0;
    /* 防止按钮区域收缩 */
}

/* 单选框组的容器 */
.radio-group {
    display: flex;
    align-items: center;
    gap: 15px;
    /* 单选框之间的间距 */
}

.radio-group label {
    display: flex;
    align-items: center;
    font-size: 14px;
    color: #555;
    cursor: pointer;
}

.radio-group input[type="radio"] {
    margin-right: 5px;
    /* 单选框和文本之间的间距 */
    width: auto;
    /* 恢复默认宽度,不被100%覆盖 */
    margin-bottom: 0;
    /* 消除底部间距 */
}

/* 按钮的通用样式 */
.modal-buttons button {
    padding: 8px 15px;
    border: none;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
    background: #eee;
    /* 默认一个浅色背景 */
    color: #333;
    /* 默认一个深色文本 */
}

/* 提交按钮的特定样式 */
.modal-buttons button[type="submit"] {
    background: #358bff;
    color: #fff;
}

.modal-buttons button[type="submit"]:hover {
    background: #2a72e8;
}

/* 取消按钮的特定样式 */
#link-submit-cancel {
    margin-right:5px;
    background: #eee;
    color: #333;
}

#link-submit-cancel:hover {
    background: #ddd;
}

/* 提示样式 */
.modal-tip {
    background: #e6f7ff;
    border-left: 4px solid #358bff;
    padding: 10px 15px;
    margin-bottom: 20px;
    font-size: 13px;
    color: #333;
    border-radius: 4px;
    line-height: 1.5;
    flex-shrink: 0;
}

/* 输入分组样式 */
.input-group {
    border: 1px solid #eee;
    padding: 15px;
    margin-bottom: 20px;
    border-radius: 6px;
    background-color: #fcfcfc;
}

.input-group h3 {
    margin-top: 0;
    margin-bottom: 10px;
    font-size: 16px;
    color: #358bff;
    border-bottom: 1px dashed #eee;
    padding-bottom: 5px;
}

.input-group input,
.input-group textarea,
.input-group select {
    margin-bottom: 8px;
}

效果

可以看到能正常添加更新

doc_links1.png

doc_links2.png