deploy: sync server CloudSearch 0.5.6
This commit is contained in:
@@ -3,16 +3,51 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="description" content="随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。" />
|
||||
<meta name="robots" content="index,follow" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="CloudSearch - 网盘资源搜索" />
|
||||
<meta property="og:description" content="随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。" />
|
||||
<meta property="og:url" content="https://zy.hk.timxx.cn/" />
|
||||
<meta property="og:image" content="https://zy.hk.timxx.cn/api/uploads/logo/site_logo.png" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="CloudSearch - 网盘资源搜索" />
|
||||
<meta name="twitter:description" content="随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。" />
|
||||
<meta name="twitter:image" content="https://zy.hk.timxx.cn/api/uploads/logo/site_logo.png" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<link rel="canonical" href="https://zy.hk.timxx.cn/" />
|
||||
<title>CloudSearch - 网盘资源搜索</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<script>
|
||||
(function() {
|
||||
fetch('/api/site-config').then(function(r){return r.json()}).then(function(cfg){
|
||||
if(cfg.site_name) document.title = cfg.site_name + ' - 网盘资源搜索';
|
||||
}).catch(function(){});
|
||||
function setMeta(selector, attr, value) {
|
||||
var el = document.querySelector(selector);
|
||||
if (!el) return;
|
||||
el.setAttribute(attr, value);
|
||||
}
|
||||
function sync(cfg) {
|
||||
var origin = window.location.origin;
|
||||
var siteName = cfg.site_name || 'CloudSearch';
|
||||
var title = siteName + ' - 网盘资源搜索';
|
||||
var description = cfg.site_disclaimer || '随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。';
|
||||
var image = cfg.site_logo ? new URL(cfg.site_logo, origin).href : origin + '/api/uploads/logo/site_logo.png';
|
||||
var canonical = origin + '/';
|
||||
document.title = title;
|
||||
setMeta('meta[name="description"]', 'content', description);
|
||||
setMeta('meta[name="robots"]', 'content', 'index,follow');
|
||||
setMeta('meta[property="og:title"]', 'content', title);
|
||||
setMeta('meta[property="og:description"]', 'content', description);
|
||||
setMeta('meta[property="og:url"]', 'content', canonical);
|
||||
setMeta('meta[property="og:image"]', 'content', image);
|
||||
setMeta('meta[name="twitter:title"]', 'content', title);
|
||||
setMeta('meta[name="twitter:description"]', 'content', description);
|
||||
setMeta('meta[name="twitter:image"]', 'content', image);
|
||||
setMeta('link[rel="canonical"]', 'href', canonical);
|
||||
}
|
||||
sync({});
|
||||
fetch('/api/site-config').then(function(r){return r.json()}).then(function(cfg){ sync(cfg || {}); }).catch(function(){});
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
@@ -20,4 +55,4 @@
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
+387
@@ -23,6 +23,7 @@
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^2.1.9",
|
||||
"vue-tsc": "^2.1.0"
|
||||
}
|
||||
},
|
||||
@@ -869,6 +870,121 @@
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
||||
"integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "2.1.9",
|
||||
"@vitest/utils": "2.1.9",
|
||||
"chai": "^5.1.2",
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
|
||||
"integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "2.1.9",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"msw": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
|
||||
"integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
|
||||
"integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/utils": "2.1.9",
|
||||
"pathe": "^1.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
|
||||
"integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "2.1.9",
|
||||
"magic-string": "^0.30.12",
|
||||
"pathe": "^1.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
|
||||
"integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tinyspy": "^3.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
|
||||
"integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "2.1.9",
|
||||
"loupe": "^3.1.2",
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz",
|
||||
@@ -1086,6 +1202,15 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
@@ -1121,6 +1246,15 @@
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -1141,6 +1275,31 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
||||
"integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"assertion-error": "^2.0.1",
|
||||
"check-error": "^2.1.1",
|
||||
"deep-eql": "^5.0.1",
|
||||
"loupe": "^3.1.0",
|
||||
"pathval": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
|
||||
"integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
@@ -1194,6 +1353,23 @@
|
||||
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
@@ -1202,6 +1378,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
|
||||
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -1294,6 +1479,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
@@ -1362,6 +1553,15 @@
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
|
||||
"integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
@@ -1568,6 +1768,12 @@
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
|
||||
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -1623,6 +1829,12 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
@@ -1698,6 +1910,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pathval": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
|
||||
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1845,6 +2072,12 @@
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -1853,6 +2086,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
@@ -1877,6 +2122,45 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
|
||||
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tinypool": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
||||
"integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
|
||||
"integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyspy": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
|
||||
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
@@ -1960,6 +2244,93 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-node": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz",
|
||||
"integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cac": "^6.7.14",
|
||||
"debug": "^4.3.7",
|
||||
"es-module-lexer": "^1.5.4",
|
||||
"pathe": "^1.1.2",
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite-node": "vite-node.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
|
||||
"integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "2.1.9",
|
||||
"@vitest/mocker": "2.1.9",
|
||||
"@vitest/pretty-format": "^2.1.9",
|
||||
"@vitest/runner": "2.1.9",
|
||||
"@vitest/snapshot": "2.1.9",
|
||||
"@vitest/spy": "2.1.9",
|
||||
"@vitest/utils": "2.1.9",
|
||||
"chai": "^5.1.2",
|
||||
"debug": "^4.3.7",
|
||||
"expect-type": "^1.1.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"pathe": "^1.1.2",
|
||||
"std-env": "^3.8.0",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^0.3.1",
|
||||
"tinypool": "^1.0.1",
|
||||
"tinyrainbow": "^1.2.0",
|
||||
"vite": "^5.0.0",
|
||||
"vite-node": "2.1.9",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"@vitest/browser": "2.1.9",
|
||||
"@vitest/ui": "2.1.9",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
@@ -2060,6 +2431,22 @@
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"test": "vitest run",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -24,6 +25,7 @@
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^2.1.9",
|
||||
"vue-tsc": "^2.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://zy.hk.timxx.cn/sitemap.xml
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/search</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/disclaimer/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.4</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/admin/login</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.2</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/user/login</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.2</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -3,6 +3,49 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { applySeo, resolveSeo } from './seo'
|
||||
|
||||
interface SiteConfig {
|
||||
site_name?: string
|
||||
site_logo?: string
|
||||
site_disclaimer?: string
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
let currentSiteName = 'CloudSearch'
|
||||
let currentSiteLogo = ''
|
||||
let currentSiteDisclaimer = ''
|
||||
|
||||
function refreshSeo() {
|
||||
const origin = typeof window !== 'undefined' ? window.location.origin : ''
|
||||
applySeo(resolveSeo(route.path, route.query as Record<string, unknown>, currentSiteName, origin, currentSiteLogo, currentSiteDisclaimer))
|
||||
}
|
||||
|
||||
async function loadSiteConfig() {
|
||||
try {
|
||||
const resp = await fetch('/api/site-config')
|
||||
if (!resp.ok) return
|
||||
const cfg = (await resp.json()) as SiteConfig
|
||||
if (cfg.site_name) currentSiteName = cfg.site_name
|
||||
if (cfg.site_logo) currentSiteLogo = cfg.site_logo
|
||||
if (cfg.site_disclaimer) currentSiteDisclaimer = cfg.site_disclaimer
|
||||
} catch {
|
||||
// keep defaults
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadSiteConfig()
|
||||
refreshSeo()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => refreshSeo(),
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<main class="disclaimer-page">
|
||||
<section class="disclaimer-card">
|
||||
<h1>免责声明</h1>
|
||||
<p>CloudSearch 仅聚合和展示公开网络资源线索,不存储、不上传、不分发任何网盘文件内容。</p>
|
||||
<p>搜索结果来自第三方公开页面或接口,资源有效性、合法性与安全性需由用户自行判断。</p>
|
||||
<p>请勿使用本站搜索、保存或传播侵权、违法、违规内容。如权利人认为相关信息侵犯权益,请联系站点管理员处理。</p>
|
||||
<router-link class="back-link" to="/">返回首页</router-link>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disclaimer-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
.disclaimer-card {
|
||||
max-width: 760px;
|
||||
padding: 32px;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08);
|
||||
color: #334155;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.disclaimer-card h1 {
|
||||
margin: 0 0 20px;
|
||||
color: #0f172a;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
margin-top: 20px;
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<div class="hero-section">
|
||||
<h1 class="sr-only">网盘资源搜索</h1>
|
||||
<template v-if="configLoaded">
|
||||
<img v-if="siteLogo" :src="siteLogo" :alt="siteName || 'CloudSearch'" class="logo-img" @error="(e: any) => { (e.target as HTMLElement).style.display='none'; siteLogo='' }" />
|
||||
<div v-else class="logo-text">{{ siteName || 'CloudSearch' }}</div>
|
||||
</template>
|
||||
<div class="search-box">
|
||||
<el-input v-model="query" placeholder="搜索网盘资源..." size="large" clearable @keyup.enter="handleSearch">
|
||||
<el-input v-model="query" aria-label="搜索网盘资源" name="q" placeholder="搜索网盘资源..." size="large" clearable @keyup.enter="handleSearch">
|
||||
<template #prefix><el-icon><Search /></el-icon></template>
|
||||
</el-input>
|
||||
<el-button type="primary" size="large" @click="handleSearch" class="search-btn">搜索</el-button>
|
||||
@@ -27,6 +28,7 @@
|
||||
<div class="footer-inner">{{ siteDisclaimer }}</div>
|
||||
<el-button class="footer-btn" size="small" @click="openDisclaimer">免责声明</el-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,6 +37,7 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { getCategorizedRankings, getSiteConfig } from '../api'
|
||||
import { createDisclaimerTarget, createSearchPath } from '../utils/home-helpers'
|
||||
|
||||
const router = useRouter()
|
||||
const query = ref('')
|
||||
@@ -50,9 +53,9 @@ const QS = ['学而时习之,不亦说乎。', '温故而知新,可以为师
|
||||
const CS = ['#e74c3c', '#e67e22', '#f39c12', '#27ae60', '#2980b9', '#8e44ad', '#2c3e50', '#16a085', '#d35400', '#c0392b', '#1abc9c', '#3498db', '#9b59b6', '#34495e', '#e91e63', '#009688', '#ff5722', '#795548', '#607d8b', '#673ab7']
|
||||
const SS = [48,42,38,34,30,28,26,24,22,20,18,17,16,15,14,13,12]
|
||||
|
||||
function handleSearch() { const q = query.value.trim(); if(q) router.push('/search?q='+encodeURIComponent(q)) }
|
||||
function searchTag(t:string) { router.push('/search?q='+encodeURIComponent(t)) }
|
||||
function openDisclaimer() { window.open('/disclaimer/', '_blank') }
|
||||
function handleSearch() { const path = createSearchPath(query.value); if(path) router.push(path) }
|
||||
function searchTag(t:string) { const path = createSearchPath(t); if(path) router.push(path) }
|
||||
function openDisclaimer() { window.location.assign(createDisclaimerTarget()) }
|
||||
|
||||
onMounted(async () => {
|
||||
currentQuote.value = QS[Math.floor(Math.random()*QS.length)]
|
||||
@@ -81,6 +84,7 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.home-page{min-height:100vh;background:#f5f7fa}
|
||||
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
||||
.hero-section{text-align:center;padding:56px 24px 36px;background:#f5f7fa}
|
||||
.logo-text{font-size:48px;font-weight:700;color:#1d2129;margin-bottom:24px}
|
||||
.logo-img{display:block;max-width:400px;max-height:100px;margin:0 auto 24px}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<main class="not-found-page">
|
||||
<section class="not-found-card">
|
||||
<p class="eyebrow">404</p>
|
||||
<h1>页面不存在</h1>
|
||||
<p class="description">你访问的页面不存在或已被移动。</p>
|
||||
<el-button type="primary" @click="goHome">返回首页</el-button>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.not-found-page{min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f5f7fa;padding:24px}
|
||||
.not-found-card{width:min(460px,100%);padding:40px 32px;text-align:center;background:#fff;border-radius:18px;box-shadow:0 12px 32px rgba(31,45,61,.08)}
|
||||
.eyebrow{margin:0 0 8px;color:#409eff;font-size:14px;font-weight:700;letter-spacing:4px}
|
||||
h1{margin:0 0 12px;font-size:28px;color:#1d2129}
|
||||
.description{margin:0 0 24px;color:#606266;line-height:1.7}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
export const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
@@ -16,6 +16,12 @@ const routes = [
|
||||
name: 'result-detail',
|
||||
component: () => import('./pages/ResultDetail.vue'),
|
||||
},
|
||||
{
|
||||
path: '/disclaimer',
|
||||
alias: '/disclaimer/',
|
||||
name: 'disclaimer',
|
||||
component: () => import('./pages/Disclaimer.vue'),
|
||||
},
|
||||
{
|
||||
path: '/admin/login',
|
||||
name: 'admin-login',
|
||||
@@ -71,10 +77,15 @@ const routes = [
|
||||
path: '/user',
|
||||
redirect: '/user/login',
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: () => import('./pages/NotFound.vue'),
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: typeof window === 'undefined' ? createMemoryHistory() : createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveSeo } from './seo'
|
||||
|
||||
describe('resolveSeo', () => {
|
||||
it('builds home page seo for the site root', () => {
|
||||
const seo = resolveSeo('/', {}, '随便下 | 资源小站', 'https://zy.hk.timxx.cn')
|
||||
expect(seo.title).toBe('随便下 | 资源小站 - 网盘资源搜索')
|
||||
expect(seo.description).toContain('网盘资源搜索')
|
||||
expect(seo.canonical).toBe('https://zy.hk.timxx.cn/')
|
||||
expect(seo.robots).toBe('index,follow')
|
||||
})
|
||||
|
||||
it('builds search page seo from the q parameter', () => {
|
||||
const seo = resolveSeo('/search', { q: '阿凡达' }, '随便下 | 资源小站', 'https://zy.hk.timxx.cn')
|
||||
expect(seo.title).toBe('阿凡达 - 搜索结果 | 随便下 | 资源小站')
|
||||
expect(seo.description).toContain('阿凡达')
|
||||
expect(seo.canonical).toBe('https://zy.hk.timxx.cn/search?q=%E9%98%BF%E5%87%A1%E8%BE%BE')
|
||||
})
|
||||
|
||||
it('marks admin pages as noindex', () => {
|
||||
const seo = resolveSeo('/admin/login', {}, '随便下 | 资源小站', 'https://zy.hk.timxx.cn')
|
||||
expect(seo.robots).toBe('noindex,nofollow')
|
||||
expect(seo.title).toContain('管理后台')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,125 @@
|
||||
export interface SeoState {
|
||||
title: string;
|
||||
description: string;
|
||||
canonical: string;
|
||||
image: string;
|
||||
robots: string;
|
||||
}
|
||||
|
||||
function getSearchQuery(query: Record<string, unknown>): string {
|
||||
const q = query.q;
|
||||
if (typeof q === 'string') return q.trim();
|
||||
const input = query.input;
|
||||
if (typeof input === 'string') return input.trim();
|
||||
return '';
|
||||
}
|
||||
|
||||
function normalizePath(pathname: string): string {
|
||||
if (!pathname) return '/';
|
||||
if (!pathname.startsWith('/')) return `/${pathname}`;
|
||||
return pathname;
|
||||
}
|
||||
|
||||
function buildCanonical(origin: string, pathname: string, query: Record<string, unknown>): string {
|
||||
const base = origin ? origin.replace(/\/+$/, '') : '';
|
||||
const normalizedPath = normalizePath(pathname);
|
||||
const params = new URLSearchParams();
|
||||
if (normalizedPath.startsWith('/search')) {
|
||||
const q = getSearchQuery(query);
|
||||
if (q) params.set('q', q);
|
||||
}
|
||||
const suffix = params.toString() ? `?${params.toString()}` : '';
|
||||
return `${base}${normalizedPath}${suffix}` || normalizedPath;
|
||||
}
|
||||
|
||||
export function resolveSeo(
|
||||
pathname: string,
|
||||
query: Record<string, unknown> = {},
|
||||
siteName = 'CloudSearch',
|
||||
origin = '',
|
||||
siteLogo = '',
|
||||
siteDisclaimer = '',
|
||||
): SeoState {
|
||||
const normalizedPath = normalizePath(pathname);
|
||||
const q = getSearchQuery(query);
|
||||
const titleSuffix = `${siteName} - 网盘资源搜索`;
|
||||
let title = titleSuffix;
|
||||
let description = `${siteName} 提供网盘资源搜索、热门榜单和内容聚合。`;
|
||||
let robots = 'index,follow';
|
||||
|
||||
if (normalizedPath.startsWith('/search')) {
|
||||
if (q) {
|
||||
title = `${q} - 搜索结果 | ${siteName}`;
|
||||
description = `搜索 ${q} 的网盘资源结果,查看链接、分类和保存记录。`;
|
||||
} else {
|
||||
title = `搜索结果 | ${siteName}`;
|
||||
description = `${siteName} 的网盘资源搜索结果页。`;
|
||||
}
|
||||
} else if (normalizedPath.startsWith('/result/')) {
|
||||
title = `资源详情 | ${siteName}`;
|
||||
description = `查看资源详情、链接信息和关联内容。`;
|
||||
} else if (normalizedPath.startsWith('/disclaimer')) {
|
||||
title = `免责声明 | ${siteName}`;
|
||||
description = siteDisclaimer || `查看 ${siteName} 的免责声明和使用说明。`;
|
||||
} else if (normalizedPath.startsWith('/admin')) {
|
||||
title = `管理后台 | ${siteName}`;
|
||||
description = `访问 ${siteName} 的管理后台。`;
|
||||
robots = 'noindex,nofollow';
|
||||
} else if (normalizedPath.startsWith('/user')) {
|
||||
title = `用户中心 | ${siteName}`;
|
||||
description = `访问 ${siteName} 的用户中心。`;
|
||||
robots = 'noindex,nofollow';
|
||||
}
|
||||
|
||||
const canonical = buildCanonical(origin, normalizedPath, query);
|
||||
const image = siteLogo
|
||||
? new URL(siteLogo, origin || 'https://zy.hk.timxx.cn').href
|
||||
: `${origin ? origin.replace(/\/+$/, '') : ''}/api/uploads/logo/site_logo.png` || '/api/uploads/logo/site_logo.png';
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
canonical,
|
||||
image,
|
||||
robots,
|
||||
};
|
||||
}
|
||||
|
||||
function setMeta(selector: string, attr: string, value: string): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
let el = document.querySelector(selector) as HTMLMetaElement | HTMLLinkElement | null;
|
||||
if (!el) {
|
||||
el = document.createElement(selector.startsWith('link') ? 'link' : 'meta');
|
||||
if (selector.startsWith('meta[name="')) {
|
||||
const name = selector.match(/meta\[name="([^"]+)"\]/)?.[1];
|
||||
if (name) (el as HTMLMetaElement).setAttribute('name', name);
|
||||
}
|
||||
if (selector.startsWith('meta[property="')) {
|
||||
const property = selector.match(/meta\[property="([^"]+)"\]/)?.[1];
|
||||
if (property) (el as HTMLMetaElement).setAttribute('property', property);
|
||||
}
|
||||
if (selector.startsWith('link[')) {
|
||||
const rel = selector.match(/link\[rel="([^"]+)"\]/)?.[1];
|
||||
if (rel) (el as HTMLLinkElement).setAttribute('rel', rel);
|
||||
}
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
el.setAttribute(attr, value);
|
||||
}
|
||||
|
||||
export function applySeo(seo: SeoState): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
document.title = seo.title;
|
||||
setMeta('meta[name="description"]', 'content', seo.description);
|
||||
setMeta('meta[name="robots"]', 'content', seo.robots);
|
||||
setMeta('meta[property="og:title"]', 'content', seo.title);
|
||||
setMeta('meta[property="og:description"]', 'content', seo.description);
|
||||
setMeta('meta[property="og:type"]', 'content', 'website');
|
||||
setMeta('meta[property="og:url"]', 'content', seo.canonical);
|
||||
setMeta('meta[property="og:image"]', 'content', seo.image);
|
||||
setMeta('meta[name="twitter:card"]', 'content', 'summary_large_image');
|
||||
setMeta('meta[name="twitter:title"]', 'content', seo.title);
|
||||
setMeta('meta[name="twitter:description"]', 'content', seo.description);
|
||||
setMeta('meta[name="twitter:image"]', 'content', seo.image);
|
||||
setMeta('link[rel="canonical"]', 'href', seo.canonical);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createDisclaimerTarget, createSearchPath, getHealthDisplay } from './home-helpers';
|
||||
|
||||
describe('home helpers', () => {
|
||||
it('builds an encoded search path from trimmed user input', () => {
|
||||
expect(createSearchPath(' 周杰伦 演唱会 ')).toBe('/search?q=%E5%91%A8%E6%9D%B0%E4%BC%A6%20%E6%BC%94%E5%94%B1%E4%BC%9A');
|
||||
expect(createSearchPath(' ')).toBeNull();
|
||||
});
|
||||
|
||||
it('uses same-tab navigation for the full disclaimer page', () => {
|
||||
expect(createDisclaimerTarget()).toBe('/disclaimer');
|
||||
});
|
||||
|
||||
it('marks optional video parser gaps as a test-environment note, not a core outage', () => {
|
||||
const display = getHealthDisplay('ok', {
|
||||
videoParser: 'unreachable',
|
||||
videoParserRequired: false,
|
||||
});
|
||||
|
||||
expect(display.isCoreHealthy).toBe(true);
|
||||
expect(display.label).toBe('核心服务正常');
|
||||
expect(display.note).toContain('视频解析');
|
||||
expect(display.note).toContain('测试环境');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
export interface HealthDisplayComponents {
|
||||
videoParser?: string;
|
||||
videoParserRequired?: boolean;
|
||||
}
|
||||
|
||||
export interface HealthDisplay {
|
||||
label: string;
|
||||
isCoreHealthy: boolean;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export function createSearchPath(query: string): string | null {
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed) return null;
|
||||
return `/search?q=${encodeURIComponent(trimmed)}`;
|
||||
}
|
||||
|
||||
export function createDisclaimerTarget(path = '/disclaimer'): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
export function getHealthDisplay(status: string, components: HealthDisplayComponents = {}): HealthDisplay {
|
||||
const isCoreHealthy = status === 'ok' || (!components.videoParserRequired && status === 'degraded');
|
||||
const label = isCoreHealthy ? '核心服务正常' : status === 'degraded' ? '部分服务降级' : '服务异常';
|
||||
|
||||
const videoParserOptionalGap = !components.videoParserRequired
|
||||
&& components.videoParser
|
||||
&& components.videoParser !== 'ok';
|
||||
|
||||
return {
|
||||
label,
|
||||
isCoreHealthy,
|
||||
note: videoParserOptionalGap ? '视频解析仍在开发中,当前测试环境未启用,不影响核心搜索服务。' : '',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import { routes } from '../router';
|
||||
|
||||
describe('application routes', () => {
|
||||
function makeRouter() {
|
||||
return createRouter({ history: createMemoryHistory(), routes });
|
||||
}
|
||||
|
||||
it('resolves public and admin routes before the catch-all not-found route', () => {
|
||||
const router = makeRouter();
|
||||
|
||||
expect(router.resolve('/').name).toBe('home');
|
||||
expect(router.resolve('/search?q=test').name).toBe('search');
|
||||
expect(router.resolve('/result/abc123').name).toBe('result-detail');
|
||||
expect(router.resolve('/admin/login').name).toBe('admin-login');
|
||||
expect(router.resolve('/admin/dashboard').matched.some(route => route.name === 'admin-dashboard')).toBe(true);
|
||||
});
|
||||
|
||||
it('has an explicit disclaimer route instead of relying on the not-found fallback', () => {
|
||||
const router = makeRouter();
|
||||
|
||||
expect(router.resolve('/disclaimer').name).toBe('disclaimer');
|
||||
expect(router.resolve('/disclaimer/').name).toBe('disclaimer');
|
||||
});
|
||||
|
||||
it('resolves unknown frontend routes to the not-found page', () => {
|
||||
const router = makeRouter();
|
||||
|
||||
expect(router.resolve('/nonexistent-ui-audit').name).toBe('not-found');
|
||||
expect(router.resolve('/unknown/path').name).toBe('not-found');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
const KNOWN_PUBLIC_ROUTE_PATTERNS = [
|
||||
/^\/$/,
|
||||
/^\/search\/?$/,
|
||||
/^\/result\/[^/]+\/?$/,
|
||||
/^\/admin\/login\/?$/,
|
||||
/^\/user\/login\/?$/,
|
||||
/^\/user\/dashboard\/?$/,
|
||||
/^\/user\/?$/,
|
||||
];
|
||||
|
||||
export function isKnownPublicRoute(path: string): boolean {
|
||||
const cleanPath = path.split('?')[0].split('#')[0] || '/';
|
||||
return KNOWN_PUBLIC_ROUTE_PATTERNS.some(pattern => pattern.test(cleanPath));
|
||||
}
|
||||
Reference in New Issue
Block a user