此文章仅适用于已经安装好Qexo并已经适配其说说界面的hexo主题,单独使用无效!
Qexo有其独有的说说配置,和众多hexo主题的说说功能并不兼容
由于美化主题自带的说说界面需要改动主题本身的文件,对以后的主题升级很不友好
而Qexo的说说可以通过引入静态资源文件进行美化
于是博主就写了一个仿UyoAhz大佬的(大佬的代码在github是加密的,可个性化性不高)
食用方法:
在source/talks/index.md中添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <head> <link rel="stylesheet" href="https://fastly.jsdelivr.net/gh/kuiyr0810/qexo-talks@main/suns/talk.min.css"> <script src="//fastly.jsdelivr.net/gh/kuiyr0810/qt@main/suns/talk.min.js"></script> </head> <body> <div id="my-shouts-container"></div> <script> myQexoShouts.init({ el: "#my-shouts-container", avatar: "https://img.kuiyr.de/file/1753932255231_image.png", name: "Sunshine.", limit: 10, baseURL: "https://admin.example.com" }).catch(function(error) { console.error("加载过程中出现问题:", error); }); </script> </body>
|
保存推送即可!
源码在kuiyr0810/qexo-talks: qexo简洁大方说说界面
主要引用了两个文件
一个js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| myQexoShouts = { talks: [], currentPage: 1, isLoading: false, config: {},
_formatTime: function(timestamp) { const ts = timestamp.toString().length === 10 ? Number(timestamp) * 1000 : Number(timestamp);
const now = new Date(); const past = new Date(ts); const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000);
const minute = 60; const hour = minute * 60; const day = hour * 24; const week = day * 7;
if (diffInSeconds < minute) { return '刚刚'; } else if (diffInSeconds < hour) { return `${Math.floor(diffInSeconds / minute)}分钟前`; } else if (diffInSeconds < day) { return `${Math.floor(diffInSeconds / hour)}小时前`; } else if (diffInSeconds < week) { return `${Math.floor(diffInSeconds / day)}天前`; } else { const year = past.getFullYear(); const month = (past.getMonth() + 1).toString().padStart(2, '0'); const date = past.getDate().toString().padStart(2, '0'); return `${year}-${month}-${date}`; } },
_createTalkItemHTML: function(talk) { const formattedTime = this._formatTime(talk.time); const isoTime = new Date(Number(talk.time)).toISOString(); const tagsHTML = talk.tags.map(tag => `<a class="qexot-tag-item">#${tag}</a>` ).join('');
const likedIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" fill="red"><path transform="scale(0.03,0.03)" d="M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 0 232.4 0 190.9L0 190.9z"/></svg>`; const unlikeIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path transform="scale(0.03,0.03)" d="M244 84L255.1 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 0 232.4 0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84C243.1 84 244 84.01 244 84L244 84zM255.1 163.9L210.1 117.1C188.4 96.28 157.6 86.4 127.3 91.44C81.55 99.07 48 138.7 48 185.1V190.9C48 219.1 59.71 246.1 80.34 265.3L256 429.3L431.7 265.3C452.3 246.1 464 219.1 464 190.9V185.1C464 138.7 430.4 99.07 384.7 91.44C354.4 86.4 323.6 96.28 301.9 117.1L255.1 163.9z"/></svg>`;
return ` <div class="qexot-item" id="qexot-item-${talk.id}"> <div class="qexot-header"> <img class="qexot-avatar" src="${this.config.avatar}" alt="${this.config.name}" onerror="this.style.display='none'"> <div class="qexot-user-info"> <span class="qexot-name">${this.config.name}</span> <time class="qexot-datatime" datetime="${isoTime}">${formattedTime}</time> </div> <div class="qexot-tags-container">${tagsHTML}</div> </div> <div class="qexot-content"> <div class="datacont">${talk.content}</div> </div> <div class="qexot-bottom"> <a class="qexot-like" onclick="myQexoShouts.likeTalk('${talk.id}')"> ${talk.liked ? likedIcon : unlikeIcon} <span class="qexot-like-count">${talk.like}</span> </a> </div> </div> `; }, _render: function(items, append = false) { }, _updateLoadMoreButton: function(totalCount) { }, loadMoreTalks: async function() { }, likeTalk: async function(id) { },
init: function(userConfig) { this.config = { el: null, baseURL: null, limit: 5, name: 'Qexo User', avatar: '', timeFormat: 'YYYY-mm-dd HH:MM:SS', }; if (!this.config.el || !this.config.baseURL) { console.error("Qexo Talks Error: 'el' and 'baseURL' are required."); return; } this.loadMoreTalks(); } };
myQexoShouts._render = function(items, append = false) {const container = document.querySelector(this.config.el);if (!container) return;const listContainer = container.querySelector('.qexot-list');if (!append && !listContainer) {container.innerHTML = `<section class="qexot"><div class="qexot-list"></div></section>`;}const targetList = container.querySelector('.qexot-list');const itemsHTML = items.map(talk => this._createTalkItemHTML(talk)).join('');targetList.insertAdjacentHTML('beforeend', itemsHTML);}; myQexoShouts._updateLoadMoreButton = function(totalCount) {const container = document.querySelector(this.config.el);let moreButton = document.getElementById("qexot-more");if (moreButton) moreButton.remove();if (this.talks.length < totalCount) {const buttonHTML = `<center id="qexot-more"><div class="qexot-more" onclick="myQexoShouts.loadMoreTalks()">加载更多</div></center>`;container.insertAdjacentHTML('beforeend', buttonHTML);}}; myQexoShouts.loadMoreTalks = async function() {if (this.isLoading) return;this.isLoading = true;const container = document.querySelector(this.config.el);const moreButton = document.getElementById("qexot-more");if (moreButton) moreButton.innerHTML = '加载中...';if (this.currentPage === 1) {container.innerHTML = '<div class="qexo_loading"><p style="text-align: center; display: block">说说加载中...</p></div>';}try {const url = new URL('/pub/talks/', this.config.baseURL);url.searchParams.append('page', this.currentPage);url.searchParams.append('limit', this.config.limit);const response = await fetch(url.href);if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);const res = await response.json();if (res.status) {this._render(res.data, this.currentPage > 1);this.talks = this.talks.concat(res.data);this._updateLoadMoreButton(res.count);this.currentPage++;} else {throw new Error(res.data.msg || "API returned an error.");}} catch (error) {console.error("Failed to fetch talks:", error);if (this.currentPage === 1) {container.innerHTML = '<blockquote>说说加载失败,请检查 API 地址或网络连接。<br>错误详情请查看 F12 控制台。</blockquote>';}} finally {this.isLoading = false;}}; myQexoShouts.likeTalk = async function(id) {const url = new URL('/pub/like_talk/', this.config.baseURL);const talk = this.talks.find(t => t.id === id);if (!talk) return;try {const response = await fetch(url.href, {method: 'POST',headers: { 'Content-Type': 'application/x-www-form-urlencoded' },body: `id=${id}`});if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);const res = await response.json();if (res.status) {talk.liked = res.action;talk.like += res.action ? 1 : -1;const talkElement = document.getElementById(`qexot-item-${id}`);if (talkElement) {const likeElement = talkElement.querySelector('.qexot-like');const likeCountElement = likeElement.querySelector('.qexot-like-count');const svgElement = likeElement.querySelector('svg');const newIcon = this._createTalkItemHTML(talk).match(/<a class="qexot-like".*?>([\s\S]*?)<\/a>/s)[1].trim().split(/<span/)[0];svgElement.outerHTML = newIcon;likeCountElement.textContent = talk.like;}} else {throw new Error(res.data.msg || "Like action failed.");}} catch (error) {console.error("Failed to like talk:", error);}};
|
一个css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
| <style>
#my-shouts-container *, #my-shouts-container *::before, #my-shouts-container *::after { box-sizing: border-box; }
#my-shouts-container .qexot-item { background-color: #fff; padding: 16px; border-radius: 8px; margin-bottom: 20px; overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06);
transition: transform 0.3s ease, box-shadow 0.3s ease; }
#my-shouts-container .qexot-item:hover { transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(0, 0, 0, 0.08); }
#my-shouts-container .qexot-header { display: flex !important; align-items: center !important; margin-bottom: 16px; }
@keyframes pulseBorder { 0% { border-color: #007bff; box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.5); } 70% { border-color: #66a5ff; box-shadow: 0 0 0 8px rgba(0, 123, 255, 0); } 100% { border-color: #007bff; box-shadow: 0 0 0 0 rgba(0, 123, 255, 0); } }
#my-shouts-container .qexot-avatar { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; flex-shrink: 0; margin: 0 12px 0 0 !important;
border: 2px solid #007bff; animation: pulseBorder 2s infinite alternate; }
#my-shouts-container .qexot-user-info { display: flex; flex-direction: column; }
#my-shouts-container .qexot-name { font-weight: 600; font-size: 16px; color: #333; margin-right: 0; }
#my-shouts-container .qexot-datatime { font-size: 12px; color: #999; margin-top: 2px; }
#my-shouts-container .qexot-content { font-size: 15px; line-height: 1; color: #383838; white-space: pre-wrap; font-family: "Kaiti SC", KaiTi, "FangSong", "Apple LiSung", "STKaiti", cursive; }
#my-shouts-container .qexot-tags-container { margin-left: auto; display: flex; flex-wrap: wrap; justify-content: flex-end; }
#my-shouts-container .qexot-tag-item { background-color: #f0f4ff; color: #4a6dff; font-size: 12px; padding: 4px 10px; border-radius: 12px; text-decoration: none; white-space: nowrap; margin-left: 6px; margin-bottom: 4px; }
#my-shouts-container .qexot-content .datacont { clear: both; }
#my-shouts-container .qexot-content img { display: block; max-width: 60%; border-radius: 6px; margin-top: 10px; }
#my-shouts-container .qexot-bottom { display: flex !important; justify-content: flex-end !important; margin-top: 16px; }
#my-shouts-container .qexot-like { display: flex; align-items: center; color: #666; cursor: pointer; }
#my-shouts-container .qexot-like svg { margin-right: 4px; }
#my-shouts-container .qexot-more { background-color: #f0f0f0; padding: 8px 20px; border-radius: 20px; cursor: pointer; display: inline-block; margin: 10px auto; color: #555; } </style>
|
注释很详细了可以二次开发,改成自己喜欢的样式即可!