mirror of
https://github.com/fish2018/GoComicMosaic.git
synced 2025-11-25 03:15:02 +08:00
update
This commit is contained in:
@@ -157,6 +157,20 @@ server {
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
|
||||
location /pansou/ {
|
||||
rewrite ^/pansou/(.*)$ /$1 break; # 移除 /pansou 前缀
|
||||
proxy_pass https://pansou.252035.xyz;
|
||||
proxy_set_header Host pansou.252035.xyz;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
|
||||
proxy_connect_timeout 15s;
|
||||
proxy_read_timeout 45s;
|
||||
proxy_send_timeout 15s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# CORS代理端点 - 注意这与前端请求的路径完全匹配 /proxy
|
||||
location = /proxy {
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/</loc>
|
||||
<lastmod>2025-07-09</lastmod>
|
||||
<lastmod>2025-07-16</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/submit</loc>
|
||||
<lastmod>2025-07-09</lastmod>
|
||||
<lastmod>2025-07-16</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/about</loc>
|
||||
<lastmod>2025-07-09</lastmod>
|
||||
<lastmod>2025-07-16</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
@@ -55,14 +55,14 @@
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/82</loc>
|
||||
<lastmod>2025-07-07</lastmod>
|
||||
<loc>https://dm.xueximeng.com/resource/5</loc>
|
||||
<lastmod>2025-07-06</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/5</loc>
|
||||
<lastmod>2025-07-06</lastmod>
|
||||
<loc>https://dm.xueximeng.com/resource/82</loc>
|
||||
<lastmod>2025-07-07</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
@@ -78,12 +78,6 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/10</loc>
|
||||
<lastmod>2025-06-18</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/30</loc>
|
||||
<lastmod>2025-06-27</lastmod>
|
||||
@@ -91,20 +85,14 @@
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/26</loc>
|
||||
<lastmod>2025-06-19</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/31</loc>
|
||||
<loc>https://dm.xueximeng.com/resource/10</loc>
|
||||
<lastmod>2025-06-18</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/8</loc>
|
||||
<lastmod>2025-06-20</lastmod>
|
||||
<loc>https://dm.xueximeng.com/resource/26</loc>
|
||||
<lastmod>2025-06-19</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
@@ -120,6 +108,18 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/31</loc>
|
||||
<lastmod>2025-06-18</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/8</loc>
|
||||
<lastmod>2025-06-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/28</loc>
|
||||
<lastmod>2025-06-27</lastmod>
|
||||
@@ -156,6 +156,12 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/38</loc>
|
||||
<lastmod>2025-06-25</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/47</loc>
|
||||
<lastmod>2025-06-27</lastmod>
|
||||
@@ -198,12 +204,6 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/38</loc>
|
||||
<lastmod>2025-06-25</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/48</loc>
|
||||
<lastmod>2025-06-20</lastmod>
|
||||
@@ -222,6 +222,18 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/107</loc>
|
||||
<lastmod>2025-07-06</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/185</loc>
|
||||
<lastmod>2025-07-16</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/1</loc>
|
||||
<lastmod>2025-06-19</lastmod>
|
||||
@@ -234,6 +246,12 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/27</loc>
|
||||
<lastmod>2025-06-21</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/29</loc>
|
||||
<lastmod>2025-06-20</lastmod>
|
||||
@@ -306,12 +324,6 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/107</loc>
|
||||
<lastmod>2025-07-06</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/124</loc>
|
||||
<lastmod>2025-06-19</lastmod>
|
||||
@@ -332,7 +344,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/15</loc>
|
||||
<lastmod>2025-07-03</lastmod>
|
||||
<lastmod>2025-07-16</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
@@ -342,12 +354,6 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/27</loc>
|
||||
<lastmod>2025-06-21</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/33</loc>
|
||||
<lastmod>2025-06-04</lastmod>
|
||||
@@ -462,6 +468,18 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/180</loc>
|
||||
<lastmod>2025-07-09</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/184</loc>
|
||||
<lastmod>2025-07-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/17</loc>
|
||||
<lastmod>2025-06-15</lastmod>
|
||||
@@ -830,7 +848,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/161</loc>
|
||||
<lastmod>2025-06-26</lastmod>
|
||||
<lastmod>2025-07-09</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
@@ -854,7 +872,7 @@
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/166</loc>
|
||||
<lastmod>2025-07-05</lastmod>
|
||||
<lastmod>2025-07-10</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
@@ -936,4 +954,10 @@
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://dm.xueximeng.com/resource/181</loc>
|
||||
<lastmod>2025-07-09</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
216
frontend/public/test-settings-api.html
Normal file
216
frontend/public/test-settings-api.html
Normal file
@@ -0,0 +1,216 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>测试设置API</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 10px;
|
||||
font-family: monospace;
|
||||
resize: vertical;
|
||||
}
|
||||
button {
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
button:hover {
|
||||
background: #4338ca;
|
||||
}
|
||||
.result {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ddd;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
.success {
|
||||
background-color: #d1fae5;
|
||||
border-color: #10b981;
|
||||
}
|
||||
.error {
|
||||
background-color: #fee2e2;
|
||||
border-color: #ef4444;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>设置API测试工具</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>测试页脚设置API</h2>
|
||||
<p>这个工具可以帮助您测试页脚设置API,发送PUT请求到 <code>/api/settings/footer</code> 端点。</p>
|
||||
|
||||
<div>
|
||||
<label for="token"><strong>认证令牌 (Bearer Token)</strong></label>
|
||||
<input type="text" id="token" style="width: 100%; padding: 8px; margin: 5px 0 15px;" placeholder="粘贴您的认证令牌">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="payload"><strong>请求数据 (JSON)</strong></label>
|
||||
<textarea id="payload" placeholder="输入JSON格式的请求数据">{
|
||||
"setting_value": {
|
||||
"links": [
|
||||
{ "text": "关于我们", "url": "/about", "type": "internal" },
|
||||
{ "text": "GitHub", "url": "https://github.com/fish2018/GoComicMosaic", "icon": "bi bi-github", "type": "external", "title": "查看GitHub源码" }
|
||||
],
|
||||
"copyright": "© 2025 美漫资源共建. 保留所有权利",
|
||||
"show_visitor_count": true
|
||||
}
|
||||
}</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button id="sendRequest">发送PUT请求</button>
|
||||
<button id="checkToken" style="background: #059669;">检查令牌</button>
|
||||
</div>
|
||||
|
||||
<div id="result" class="result" style="display: none;">
|
||||
待发送请求...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 从localStorage中获取token
|
||||
try {
|
||||
const savedToken = localStorage.getItem('accessToken');
|
||||
if (savedToken) {
|
||||
document.getElementById('token').value = savedToken;
|
||||
console.log('已从localStorage加载令牌');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('读取localStorage失败:', e);
|
||||
}
|
||||
|
||||
// 发送请求按钮
|
||||
document.getElementById('sendRequest').addEventListener('click', async function() {
|
||||
const token = document.getElementById('token').value.trim();
|
||||
const payload = document.getElementById('payload').value.trim();
|
||||
const resultElement = document.getElementById('result');
|
||||
|
||||
resultElement.style.display = 'block';
|
||||
resultElement.className = 'result';
|
||||
resultElement.textContent = '发送请求中...';
|
||||
|
||||
if (!token) {
|
||||
resultElement.textContent = '错误: 请提供认证令牌';
|
||||
resultElement.className = 'result error';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let payloadObj = JSON.parse(payload);
|
||||
|
||||
// 创建一个XHR请求
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('PUT', '/api/settings/footer', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resultElement.textContent = `请求成功 (${xhr.status} ${xhr.statusText}):\n\n${xhr.responseText}`;
|
||||
resultElement.className = 'result success';
|
||||
} else {
|
||||
resultElement.textContent = `请求失败 (${xhr.status} ${xhr.statusText}):\n\n${xhr.responseText}`;
|
||||
resultElement.className = 'result error';
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
resultElement.textContent = '网络错误,请检查控制台';
|
||||
resultElement.className = 'result error';
|
||||
};
|
||||
|
||||
const requestStart = new Date();
|
||||
xhr.send(payload);
|
||||
console.log('已发送请求:', payload);
|
||||
console.log('请求头:', {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token.substring(0, 10)}...` // 只显示部分令牌
|
||||
});
|
||||
} catch (e) {
|
||||
resultElement.textContent = `请求错误: ${e.message}`;
|
||||
resultElement.className = 'result error';
|
||||
}
|
||||
});
|
||||
|
||||
// 检查令牌按钮
|
||||
document.getElementById('checkToken').addEventListener('click', function() {
|
||||
const token = document.getElementById('token').value.trim();
|
||||
const resultElement = document.getElementById('result');
|
||||
|
||||
resultElement.style.display = 'block';
|
||||
|
||||
if (!token) {
|
||||
resultElement.textContent = '错误: 请提供认证令牌';
|
||||
resultElement.className = 'result error';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试解码令牌
|
||||
const tokenParts = token.split('.');
|
||||
if (tokenParts.length !== 3) {
|
||||
resultElement.textContent = '无效的JWT令牌格式';
|
||||
resultElement.className = 'result error';
|
||||
return;
|
||||
}
|
||||
|
||||
// 解码payload部分
|
||||
const payload = JSON.parse(atob(tokenParts[1]));
|
||||
const expDate = new Date(payload.exp * 1000);
|
||||
const now = new Date();
|
||||
const isExpired = expDate < now;
|
||||
|
||||
resultElement.textContent = `令牌信息:\n\n`;
|
||||
resultElement.textContent += `有效期至: ${expDate.toLocaleString()}\n`;
|
||||
resultElement.textContent += `当前时间: ${now.toLocaleString()}\n`;
|
||||
resultElement.textContent += `是否过期: ${isExpired ? '已过期' : '有效'}\n\n`;
|
||||
resultElement.textContent += `载荷数据:\n${JSON.stringify(payload, null, 2)}`;
|
||||
|
||||
resultElement.className = isExpired ? 'result error' : 'result success';
|
||||
} catch (e) {
|
||||
resultElement.textContent = `令牌解析错误: ${e.message}\n可能不是有效的JWT格式`;
|
||||
resultElement.className = 'result error';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -385,7 +385,7 @@
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 1px solid rgba(124, 58, 237, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow: visible; /* 修改为visible,确保不会截断header-actions中的按钮 */
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -400,6 +400,7 @@
|
||||
height: 100%;
|
||||
background: linear-gradient(45deg, rgba(124, 58, 237, 0.07), transparent);
|
||||
opacity: 0.8;
|
||||
z-index: 1; /* 添加z-index,确保在按钮下层 */
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
@@ -410,6 +411,7 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 2; /* 确保标题在背景上层 */
|
||||
}
|
||||
|
||||
.card-header h3::before {
|
||||
@@ -1307,6 +1309,7 @@
|
||||
|
||||
.card-header {
|
||||
padding: 1rem;
|
||||
overflow: visible; /* 确保不会截断按钮 */
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
@@ -1335,7 +1338,10 @@
|
||||
}
|
||||
|
||||
.table-body {
|
||||
max-height: none;
|
||||
max-height: 250px; /* 设置合适的高度,而不是none */
|
||||
overflow-y: auto; /* 保留自动滚动 */
|
||||
scrollbar-width: thin; /* 保留细滚动条 */
|
||||
-webkit-overflow-scrolling: touch; /* 增加iOS流畅滚动 */
|
||||
}
|
||||
|
||||
.col-link, .col-password, .col-note {
|
||||
@@ -1581,6 +1587,7 @@
|
||||
|
||||
.card-header {
|
||||
padding: 0.75rem;
|
||||
overflow: visible; /* 确保不会截断按钮 */
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
@@ -1696,7 +1703,7 @@
|
||||
.link-item {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 17. 贴纸容器样式 ===== */
|
||||
.stickers-container {
|
||||
@@ -2138,6 +2145,24 @@
|
||||
|
||||
.links-list {
|
||||
margin-bottom: 1.5rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
/* 为链接列表添加滚动条样式 */
|
||||
.links-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.links-list::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.links-list::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-gradient);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.link-item {
|
||||
@@ -2238,6 +2263,12 @@
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.links-list {
|
||||
max-height: 350px; /* 移动端稍微减小高度 */
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch; /* 增强移动端滚动体验 */
|
||||
}
|
||||
|
||||
.link-inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -2294,4 +2325,141 @@
|
||||
.link-item {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 19. 网盘搜索功能样式 ===== */
|
||||
/* 标题栏中的操作按钮容器 */
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
z-index: 10; /* 确保按钮在最上层 */
|
||||
}
|
||||
|
||||
/* 网盘搜索按钮 */
|
||||
.pan-search-button {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 5px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
position: relative;
|
||||
z-index: 20; /* 确保按钮在最上层 */
|
||||
}
|
||||
|
||||
.pan-search-button:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
.pan-search-button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pan-search-button i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* 搜索结果指示器 */
|
||||
.search-results-indicator {
|
||||
background-color: #f8f9fa;
|
||||
border-left: 3px solid #007bff;
|
||||
padding: 8px 15px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* 异步更新指示器 */
|
||||
.async-update-indicator {
|
||||
font-size: 12px;
|
||||
color: #007bff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* 旋转动画 */
|
||||
.spin {
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
/* 空搜索结果提示 */
|
||||
.empty-search-results {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.empty-search-results i {
|
||||
font-size: 24px;
|
||||
margin-right: 10px;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
/* 搜索错误消息 */
|
||||
.search-error-message {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 4px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
color: #721c24;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.search-error-message i {
|
||||
font-size: 18px;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
/* 网盘搜索功能的响应式适配 */
|
||||
@media (max-width: 768px) {
|
||||
/* .header-actions {
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
} */
|
||||
|
||||
.pan-search-button, .stream-button {
|
||||
padding: 4px 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.links-list {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
/* .header-actions {
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
} */
|
||||
|
||||
.pan-search-button, .stream-button {
|
||||
width: auto; /* 不要占满整行 */
|
||||
justify-content: center;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
122
frontend/src/utils/pansouService.js
Normal file
122
frontend/src/utils/pansouService.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// 创建一个专用于网盘搜索的axios实例,避免全局配置影响
|
||||
const pansouAxios = axios.create({
|
||||
baseURL: '', // 显式设置为空,避免继承全局baseURL
|
||||
timeout: 10000 // 10秒超时
|
||||
});
|
||||
|
||||
/**
|
||||
* 搜索网盘资源
|
||||
* @param {string} keyword - 搜索关键词
|
||||
* @param {boolean} refresh - 是否刷新结果
|
||||
* @param {Function} onUpdate - 结果更新时的回调函数
|
||||
* @param {Function} onComplete - 异步更新完成后的回调函数
|
||||
* @returns {Promise<Object>} 搜索结果
|
||||
*/
|
||||
export const searchPanResources = async (keyword, refresh = false, onUpdate = null, onComplete = null) => {
|
||||
console.log('pansouService: 开始搜索关键词:', keyword);
|
||||
|
||||
if (!keyword || keyword.trim() === '') {
|
||||
console.error('搜索关键词为空');
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
// 确保直接调用/pansou/api/search而不是通过/app前缀
|
||||
const requestUrl = `/pansou/api/search?kw=${encodeURIComponent(keyword)}&refresh=${refresh}&res=merge&src=all`;
|
||||
console.log('pansouService: 发送API请求到:', requestUrl);
|
||||
console.log('pansouService: 完整URL应为:', window.location.origin + requestUrl);
|
||||
|
||||
const response = await pansouAxios.get(`/pansou/api/search`, {
|
||||
params: {
|
||||
kw: keyword,
|
||||
refresh: refresh,
|
||||
res: 'merge',
|
||||
src: 'all'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('pansouService: API响应:', response);
|
||||
|
||||
if (response.data.code === 0 && response.data.message === 'success') {
|
||||
const results = response.data.data.merged_by_type || {};
|
||||
console.log('pansouService: 搜索成功, 返回结果:', results);
|
||||
|
||||
// 如果是首次搜索且有回调函数,则异步再次请求更新结果
|
||||
if (!refresh && typeof onUpdate === 'function') {
|
||||
console.log('pansouService: 将在4秒后异步更新搜索结果');
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
console.log('pansouService: 开始异步更新搜索结果');
|
||||
const updatedResults = await searchPanResources(keyword, false);
|
||||
console.log('pansouService: 异步更新完成,新结果:', updatedResults);
|
||||
onUpdate(updatedResults);
|
||||
|
||||
// 调用完成回调
|
||||
if (typeof onComplete === 'function') {
|
||||
onComplete();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('pansouService: 异步更新失败:', error);
|
||||
// 即使失败也调用完成回调
|
||||
if (typeof onComplete === 'function') {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
}, 4000); // 4秒后再次请求
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
console.warn('pansouService: API返回错误码:', response.data);
|
||||
return {};
|
||||
} catch (error) {
|
||||
console.error('搜索网盘资源失败:', error);
|
||||
if (error.response) {
|
||||
// 服务器响应了,但状态码不在2xx范围内
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
} else if (error.request) {
|
||||
// 请求已发送,但没有收到响应
|
||||
console.error('请求已发送,但没有收到响应:', error.request);
|
||||
} else {
|
||||
// 在设置请求时发生了错误
|
||||
console.error('请求设置错误:', error.message);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// 将API返回的资源转换为与系统兼容的格式
|
||||
export const convertToResourceLinks = (panResources) => {
|
||||
console.log('pansouService: 开始转换资源格式, 输入:', panResources);
|
||||
|
||||
if (!panResources || typeof panResources !== 'object') {
|
||||
console.warn('pansouService: 无效的资源数据');
|
||||
return {};
|
||||
}
|
||||
|
||||
const result = {};
|
||||
|
||||
try {
|
||||
// 处理每种网盘类型 - 无需映射,直接使用
|
||||
Object.keys(panResources).forEach(type => {
|
||||
if (Array.isArray(panResources[type])) {
|
||||
// 转换每个链接
|
||||
result[type] = panResources[type].map(item => ({
|
||||
url: item.url || '',
|
||||
password: item.password || '',
|
||||
note: item.note || '',
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
console.log('pansouService: 转换完成, 输出:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('pansouService: 转换资源格式失败:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
@@ -535,17 +535,51 @@
|
||||
<div class="links-card" >
|
||||
<div class="card-header">
|
||||
<h3>资源链接</h3>
|
||||
<!-- 添加点播图标按钮 -->
|
||||
<button
|
||||
class="stream-button"
|
||||
title="点播此资源"
|
||||
@click="goToStreamPage"
|
||||
>
|
||||
<i class="bi bi-play-circle"></i>
|
||||
<span>点播</span>
|
||||
</button>
|
||||
<div class="header-actions">
|
||||
<!-- 更多资源按钮 -->
|
||||
<button
|
||||
class="pan-search-button"
|
||||
title="盘搜"
|
||||
@click="searchPanResource"
|
||||
:disabled="isSearching"
|
||||
style="position: relative; z-index: 20;"
|
||||
>
|
||||
<i :class="isShowingSearchResults ? 'bi bi-arrow-left' : 'bi bi-search-heart'"></i>
|
||||
<span>{{ resourceButtonText }}</span>
|
||||
</button>
|
||||
<!-- 点播按钮 -->
|
||||
<button
|
||||
class="stream-button"
|
||||
title="点播此资源"
|
||||
@click="goToStreamPage"
|
||||
>
|
||||
<i class="bi bi-play-circle"></i>
|
||||
<span>点播</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 搜索错误提示 -->
|
||||
<div v-if="searchError" class="search-error-message">
|
||||
<i class="bi bi-exclamation-triangle-fill"></i>
|
||||
{{ searchError }}
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果指示器 -->
|
||||
<div v-if="isShowingSearchResults" class="search-results-indicator">
|
||||
<i class="bi bi-info-circle"></i> 网盘搜索结果 - {{ resource.title }}
|
||||
<span v-if="isAsyncUpdating" class="async-update-indicator">
|
||||
<i class="bi bi-arrow-repeat spin"></i> 正在获取更多结果...
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 空结果处理 -->
|
||||
<div v-if="isShowingSearchResults && (!resource.links || Object.keys(resource.links).length === 0 || orderedVisibleCategories.length === 0)" class="empty-search-results">
|
||||
<i class="bi bi-exclamation-circle"></i>
|
||||
未找到相关网盘资源,请尝试其他关键词
|
||||
</div>
|
||||
|
||||
<!-- 原有的链接标签和内容 -->
|
||||
<div class="links-tabs">
|
||||
<button
|
||||
v-for="category in orderedVisibleCategories"
|
||||
@@ -673,7 +707,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
||||
import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { isAdmin, getCurrentUser } from '../utils/auth'
|
||||
@@ -682,6 +716,7 @@ import ShareResource from '@/components/ShareResource.vue'
|
||||
import draggable from 'vuedraggable' // 导入 vuedraggable 组件
|
||||
import EpisodeOverview from '@/components/EpisodeOverview.vue' // 导入剧集探索组件
|
||||
import TmdbStatusService from '../services/TmdbStatusService'
|
||||
import { searchPanResources, convertToResourceLinks } from '@/utils/pansouService'
|
||||
import StickerManager from '@/components/StickerManager.vue' // 导入贴纸管理组件
|
||||
import DraggableSticker from '@/components/DraggableSticker.vue' // 导入可拖拽贴纸组件
|
||||
|
||||
@@ -1676,11 +1711,199 @@ const dragEnd = () => {
|
||||
// 贴纸相关数据
|
||||
const stickerBeingEdited = ref(null) // 当前正在编辑的贴纸索引
|
||||
|
||||
// 网盘搜索相关状态
|
||||
const isSearching = ref(false)
|
||||
const isShowingSearchResults = ref(false)
|
||||
const originalLinks = ref(null)
|
||||
const searchResultLinks = ref(null)
|
||||
const searchError = ref(null) // 添加搜索错误状态
|
||||
const isAsyncUpdating = ref(false) // 新增:异步更新状态
|
||||
|
||||
// 计算属性:按钮文本
|
||||
const resourceButtonText = computed(() => {
|
||||
if (isSearching.value) return '搜索中...'
|
||||
return isShowingSearchResults.value ? '返回原始资源' : '盘搜'
|
||||
})
|
||||
|
||||
// 搜索网盘资源
|
||||
const searchPanResource = async () => {
|
||||
console.log('搜索按钮被点击');
|
||||
console.log('当前按钮状态: isSearching=', isSearching.value, 'isShowingSearchResults=', isShowingSearchResults.value);
|
||||
searchError.value = null; // 清除之前的错误
|
||||
|
||||
// 如果已经在显示搜索结果,则切换回原始链接
|
||||
if (isShowingSearchResults.value) {
|
||||
console.log('当前显示搜索结果,切换回原始链接');
|
||||
toggleBackToOriginal()
|
||||
return
|
||||
}
|
||||
|
||||
isSearching.value = true
|
||||
console.log('开始搜索,设置isSearching=true');
|
||||
|
||||
try {
|
||||
// 保存原始链接数据
|
||||
originalLinks.value = JSON.parse(JSON.stringify(resource.value.links || {}))
|
||||
console.log('已保存原始链接数据:', originalLinks.value);
|
||||
|
||||
// 搜索关键词优先使用中文标题,如果没有则使用英文标题
|
||||
// 处理标题中的各种分隔符,只取主要部分
|
||||
let searchKeyword = resource.value.title || resource.value.title_en;
|
||||
if (searchKeyword) {
|
||||
// 处理斜杠分隔的情况,取第一部分
|
||||
if (searchKeyword.includes('/')) {
|
||||
searchKeyword = searchKeyword.split('/')[0].trim();
|
||||
console.log('标题包含斜杠,只使用斜杠前部分:', searchKeyword);
|
||||
}
|
||||
|
||||
// 处理括号内容,移除括号及其内容
|
||||
searchKeyword = searchKeyword
|
||||
.replace(/([^)]*)/g, '') // 中文括号
|
||||
.replace(/\([^)]*\)/g, '') // 英文括号
|
||||
.replace(/【[^】]*】/g, '') // 中文方括号
|
||||
.replace(/\[[^\]]*\]/g, '') // 英文方括号
|
||||
.trim();
|
||||
|
||||
console.log('处理后的搜索关键词:', searchKeyword);
|
||||
}
|
||||
console.log('最终搜索关键词:', searchKeyword);
|
||||
|
||||
// 定义异步更新回调函数
|
||||
const handleAsyncUpdate = (updatedResults) => {
|
||||
console.log('收到异步更新结果:', updatedResults);
|
||||
|
||||
// 只有当用户仍在查看搜索结果时才更新
|
||||
if (isShowingSearchResults.value) {
|
||||
// 转换为系统兼容的格式
|
||||
const updatedLinks = convertToResourceLinks(updatedResults);
|
||||
console.log('转换后的更新链接:', updatedLinks);
|
||||
|
||||
// 合并新旧结果
|
||||
const mergedLinks = mergeSearchResults(resource.value.links, updatedLinks);
|
||||
|
||||
// 更新链接
|
||||
resource.value.links = mergedLinks;
|
||||
console.log('已更新资源链接');
|
||||
|
||||
// 如果有新的分类,可能需要更新活动分类
|
||||
nextTick(() => {
|
||||
if (orderedVisibleCategories.value.length > 0 &&
|
||||
!orderedVisibleCategories.value.includes(activeCategory.value)) {
|
||||
activeCategory.value = orderedVisibleCategories.value[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 定义异步更新完成回调函数
|
||||
const handleAsyncComplete = () => {
|
||||
console.log('异步更新完成,关闭更新状态指示器');
|
||||
isAsyncUpdating.value = false;
|
||||
};
|
||||
|
||||
// 调用搜索API,传入回调函数
|
||||
console.log('调用searchPanResources API...');
|
||||
console.log('注意:如果请求URL中包含/app/pansou,说明全局axios配置仍在影响请求');
|
||||
isAsyncUpdating.value = true; // 开始异步更新
|
||||
const results = await searchPanResources(searchKeyword, false, handleAsyncUpdate, handleAsyncComplete);
|
||||
console.log('API返回结果:', results);
|
||||
|
||||
// 检查结果是否为空对象
|
||||
if (Object.keys(results).length === 0) {
|
||||
searchError.value = `未找到"${searchKeyword}"的网盘资源`;
|
||||
console.warn('搜索结果为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 转换为系统兼容的格式
|
||||
searchResultLinks.value = convertToResourceLinks(results)
|
||||
console.log('转换后的链接:', searchResultLinks.value);
|
||||
|
||||
// 检查转换后的结果是否为空
|
||||
if (Object.keys(searchResultLinks.value).length === 0) {
|
||||
searchError.value = '搜索结果格式无效';
|
||||
console.warn('转换后的链接为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 替换链接
|
||||
resource.value.links = searchResultLinks.value
|
||||
console.log('已替换资源链接');
|
||||
|
||||
// 更新状态
|
||||
isShowingSearchResults.value = true
|
||||
console.log('设置isShowingSearchResults=true');
|
||||
|
||||
// 如果有结果,自动选择第一个有内容的分类
|
||||
nextTick(() => {
|
||||
if (orderedVisibleCategories.value.length > 0) {
|
||||
console.log('选择第一个可见分类:', orderedVisibleCategories.value[0]);
|
||||
activeCategory.value = orderedVisibleCategories.value[0]
|
||||
} else {
|
||||
console.log('没有可见的分类');
|
||||
searchError.value = '搜索结果中没有可用的链接';
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('搜索网盘资源失败:', error)
|
||||
searchError.value = `搜索失败: ${error.message || '未知错误'}`;
|
||||
isAsyncUpdating.value = false; // 出错时也关闭异步更新状态
|
||||
} finally {
|
||||
isSearching.value = false
|
||||
console.log('搜索完成,设置isSearching=false');
|
||||
}
|
||||
}
|
||||
|
||||
// 合并搜索结果
|
||||
const mergeSearchResults = (oldResults, newResults) => {
|
||||
const merged = { ...oldResults };
|
||||
|
||||
// 遍历新结果中的每个分类
|
||||
Object.keys(newResults).forEach(category => {
|
||||
if (!merged[category]) {
|
||||
// 如果旧结果中没有这个分类,直接添加
|
||||
merged[category] = newResults[category];
|
||||
} else {
|
||||
// 如果旧结果中有这个分类,合并链接
|
||||
const oldUrls = new Set(merged[category].map(link => link.url));
|
||||
|
||||
// 添加旧结果中不存在的新链接
|
||||
newResults[category].forEach(newLink => {
|
||||
if (!oldUrls.has(newLink.url)) {
|
||||
merged[category].push(newLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
// 切换回原始链接
|
||||
const toggleBackToOriginal = () => {
|
||||
if (originalLinks.value) {
|
||||
resource.value.links = originalLinks.value
|
||||
isShowingSearchResults.value = false
|
||||
|
||||
// 恢复原来选择的分类
|
||||
nextTick(() => {
|
||||
if (orderedVisibleCategories.value.length > 0) {
|
||||
// 如果原来选择的分类还存在,则继续选择它
|
||||
if (orderedVisibleCategories.value.includes(activeCategory.value)) {
|
||||
// 保持当前选择
|
||||
} else {
|
||||
// 否则选择第一个分类
|
||||
activeCategory.value = orderedVisibleCategories.value[0]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchResource()
|
||||
loadTMDBConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped src="@/styles/ResourceDetail.css"></style>
|
||||
|
||||
<style scoped src="@/styles/ResourceDetail.css"></style>
|
||||
@@ -60,6 +60,11 @@ export default defineConfig(({ command, mode }) => {
|
||||
console.log('请求代理:', req.method, req.url, '->', options.target + proxyReq.path);
|
||||
});
|
||||
}
|
||||
},
|
||||
'/pansou': {
|
||||
target: 'https://pansou.252035.xyz',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/pansou/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user