This commit is contained in:
www.xueximeng.com
2025-07-16 22:26:38 +08:00
parent f98bf36e25
commit ccd3e81a87
7 changed files with 830 additions and 58 deletions

View File

@@ -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 {

View File

@@ -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>

View 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>

View File

@@ -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;
}
}

View 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 {};
}
};

View File

@@ -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>

View File

@@ -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/, '')
}
}
}