init commit

This commit is contained in:
xuliulei 2024-05-16 22:55:09 +08:00
commit 355f7e7736
53 changed files with 16708 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# Nuxt dev/build outputs
.output
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
shamefully-hoist=true
strict-peer-dependencies=false

60
README.md Normal file
View File

@ -0,0 +1,60 @@
# 爱盼-网盘资源搜索Web
爱盼-网盘资源搜索:是一个免费开源项目,感谢 [混合盘提供的API](https://docs.hunhepan.com/api-docs.html)
👉 [爱盼-网盘资源搜索](https://so.aicompasspro.com)
## 快速开始
### 在 Vercel 上部署
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/unilei/aipan-netdisk-search.git&project-name=aipan-netdisk-search&repository-name=aipan-netdisk-search)
### 在 Vercel 上手动部署 操作方法
```
1. fork 本项目
2. 在 [Vercel] 官网点击 [New Project]
3. 点击 [Import Git Repository] 并选择你 fork 的此项目并点击 [import]
4. 然后直接点 [Deploy] 接着等部署完成即可
```
### 1. 克隆项目
```bash
git clone https://github.com/unilei/aipan-netdisk-search.git
```
### 2. 安装依赖
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
```
### 3. 运行到浏览器
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
```
### 4. 在浏览器打开 [http://localhost:3001](http://localhost:3001)
![success_deploy.jpg](/assets/readme/screen-1.png)
![success_deploy.jpg](/assets/readme/screen-2.png)
#### 如何部署到自己服务器? NUXT.JS 打包部署文档
[部署文档](https://nuxt.com/docs/getting-started/deployment)
### 打赏
<img src="/assets/donation/wechat_pay.jpg" width=200 />
### 交流
<img src="/assets/readme/wechat.jpg" width=200 />

6
app.config.ts Normal file
View File

@ -0,0 +1,6 @@
export default defineAppConfig({
foo:'bar',
theme:{
primaryColor:'#ababab'
}
})

28
app.vue Normal file
View File

@ -0,0 +1,28 @@
<template>
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
</template>
<script setup>
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const nuxtApp = useNuxtApp()
for(const [key,component] of Object.entries(ElementPlusIconsVue)){
nuxtApp.vueApp.component(key,component)
}
</script>
<style>
body{
background-color: #fcfcfd;
padding: 0;
margin: 0 auto;
}
h1,h2,h3,h4,h5,h6{
margin: 0;
padding: 0;
}
</style>

BIN
assets/donation/alipay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1715865726218" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4382" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M235.8 67.9C153 87.4 87.4 153 67.9 235.8L235.8 67.9z" fill="#FF6968" p-id="4383"></path><path d="M301.7 62H287C163.3 62 62 163.3 62 287v14.7L301.7 62z" fill="#FF6867" p-id="4384"></path><path d="M361.7 62H287C163.3 62 62 163.3 62 287v74.7L361.7 62z" fill="#FF6866" p-id="4385"></path><path d="M421.6 62H287C163.3 62 62 163.3 62 287v134.6L421.6 62z" fill="#FF6765" p-id="4386"></path><path d="M481.6 62H287c-19.4 0-38.3 2.5-56.3 7.2L69.2 230.7c-4.7 18-7.2 36.9-7.2 56.3v194.6L481.6 62z" fill="#FF6764" p-id="4387"></path><path d="M62 541.6L541.6 62H297.9L62 297.9z" fill="#FF6763" p-id="4388"></path><path d="M62 601.6L601.6 62H357.8L62 357.8z" fill="#FF6662" p-id="4389"></path><path d="M62 417.8v243.7L661.5 62H417.8z" fill="#FF6661" p-id="4390"></path><path d="M62 477.8v243.7L721.5 62H477.8z" fill="#FF6560" p-id="4391"></path><path d="M737 62H537.8L62 537.8V737c0 13.9 1.3 27.5 3.7 40.8l712-712C764.5 63.3 750.9 62 737 62z" fill="#FF655F" p-id="4392"></path><path d="M737 62H597.7L62 597.7V737c0 30.8 6.3 60.1 17.6 86.9L823.9 79.6C797.1 68.3 767.8 62 737 62z" fill="#FF655E" p-id="4393"></path><path d="M862.8 100.7C826.8 76.3 783.5 62 737 62h-79.3L62 657.7V737c0 46.5 14.3 89.8 38.7 125.8l762.1-762.1z" fill="#FF645D" p-id="4394"></path><path d="M895.6 127.8C854.9 87.2 798.7 62 737 62h-19.3L62 717.7V737c0 61.7 25.2 117.9 65.8 158.6l767.8-767.8z" fill="#FF645C" p-id="4395"></path><path d="M65.2 774.5C75.6 835.8 111 889 160.6 922.8l762.3-762.3c-33.9-49.5-87-85-148.3-95.4L65.2 774.5z" fill="#FF635B" p-id="4396"></path><path d="M78.5 821.2c22.4 55.1 66.2 99.6 120.8 122.9l744.8-744.8c-23.3-54.7-67.8-98.4-122.9-120.8L78.5 821.2z" fill="#FF635A" p-id="4397"></path><path d="M99.2 860.5c33 49.9 85.3 86.1 146.1 97.6l712.8-712.8c-11.5-60.8-47.7-113.1-97.6-146.1L99.2 860.5z" fill="#FF635A" p-id="4398"></path><path d="M962 287c0-63-26.2-120.1-68.3-161.1L125.9 893.7C166.9 935.8 224 962 287 962h14.3L962 301.3V287z" fill="#FF6259" p-id="4399"></path><path d="M962 287c0-47.8-15.1-92.2-40.7-128.7l-763 763C194.8 946.9 239.2 962 287 962h74.3L962 361.3V287z" fill="#FF6258" p-id="4400"></path><path d="M962 287c0-32.1-6.8-62.7-19.1-90.4L196.6 942.9c27.7 12.3 58.3 19.1 90.4 19.1h134.3L962 421.3V287z" fill="#FF6157" p-id="4401"></path><path d="M287 962h194.2L962 481.2V287c0-15.4-1.6-30.4-4.5-44.9L242.1 957.5c14.5 2.9 29.5 4.5 44.9 4.5z" fill="#FF6156" p-id="4402"></path><path d="M541.2 962L962 541.2V297.5L297.5 962z" fill="#FF6155" p-id="4403"></path><path d="M601.2 962L962 601.2V357.5L357.5 962z" fill="#FF6054" p-id="4404"></path><path d="M661.2 962L962 661.2V417.4L417.4 962z" fill="#FF6053" p-id="4405"></path><path d="M721.1 962L962 721.1V477.4L477.4 962z" fill="#FF5F52" p-id="4406"></path><path d="M962 737V537.4L537.4 962H737c17.1 0 33.7-1.9 49.7-5.6l169.7-169.7c3.7-16 5.6-32.6 5.6-49.7z" fill="#FF5F51" p-id="4407"></path><path d="M962 597.4L597.4 962H737c123.8 0 225-101.3 225-225V597.4z" fill="#FF5F50" p-id="4408"></path><path d="M962 657.3L657.3 962H737c123.8 0 225-101.3 225-225v-79.7z" fill="#FF5E4F" p-id="4409"></path><path d="M962 717.3L717.3 962H737c123.8 0 225-101.3 225-225v-19.7z" fill="#FF5E4E" p-id="4410"></path><path d="M957.5 781.8L781.8 957.5c88-18 157.7-87.7 175.7-175.7z" fill="#FF5D4D" p-id="4411"></path><path d="M512 512m-243 0a243 243 0 1 0 486 0 243 243 0 1 0-486 0Z" fill="#FFF3F3" p-id="4412"></path><path d="M512 512m-207 0a207 207 0 1 0 414 0 207 207 0 1 0-414 0Z" fill="#FFCDCB" p-id="4413"></path><path d="M500.4 402.8h23.4v26.4H547c4.8-8 9.1-16.3 12.9-25l21.5 7.5c-3.1 6.9-6.5 12.7-10.1 17.6h40.2v49.1h-22v-30H434.7v30.2h-22.2v-49.4h41.7c-3.3-5.8-7.2-11.4-11.7-16.8l21.1-7.7c5.2 6.7 9.8 14.9 14 24.6h22.9v-26.5z m24.1 142.5c-3.3 26.7-10.2 44.3-20.8 52.9-11.5 11.1-39 18.2-82.4 21.5l-8.9-20.1c36.3-1.4 60.3-6.3 71.8-14.7 10.1-7.8 16.1-21.6 18-41.4l22.3 1.8z m62.7-28.7v64.8h-22v-45.2h-106V584h-22v-67.4h150z m-136.4-56.2h122.6v46.1H450.8v-46.1z m100.9 30.4v-14.5h-79.1v14.5h79.1z m-24.4 87.5c31 6.4 59 14.2 84 23.4l-12.6 19.4c-26.8-11.2-54.4-19.9-82.6-26l11.2-16.8z" fill="#FFFFFF" p-id="4414"></path></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
assets/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

3
assets/lang/en-US.json Normal file
View File

@ -0,0 +1,3 @@
{
}

3
assets/lang/zh-CN.json Normal file
View File

@ -0,0 +1,3 @@
{
}

BIN
assets/my-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
assets/netdisk/aliyun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/netdisk/baidu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/netdisk/quark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/netdisk/xunlei.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

BIN
assets/readme/screen-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
assets/readme/screen-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

BIN
assets/readme/wechat.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View File

@ -0,0 +1,85 @@
<script setup>
const searchSiteData = ref([
{
id:1,
name:'谷歌',
url:'https://www.google.com/search',
s_key:'q'
},
{
id:2,
name:'必应[国际]',
url:'https://www.bing.com/search',
s_key:'q'
},
{
id:3,
name:'必应[国内]',
url:'https://cn.bing.com/',
s_key:'q'
},
{
id:4,
name:'百度',
url:'https://www.baidu.com/s',
s_key:'q'
}
])
const searchSite = ref({
id:4,
name:'百度',
url:'https://www.baidu.com/s',
s_key:'wd'
})
const searchKeyword = ref('')
const search = ()=>{
let str = searchSite.value.url + '?'+ searchSite.value.s_key+'=' + searchKeyword.value
window.open(str, '_blank')
}
</script>
<template>
<div class="w-[100%] md:w-[800px] border border-slate-300 font-mono overflow-hidden rounded-[50px]">
<client-only>
<el-input class="h-[50px]" v-model="searchKeyword" placeholder="请输入关键词" @keydown.enter="search()" >
<template #prepend>
<el-select class="w-[40px] md:w-[100px] h-[50px]" placeholder="搜索引擎" value-key="id" v-model="searchSite">
<el-option class="h-[50px]" v-for="(item,i) in searchSiteData" :key="i" :label="item.name" :value="item"></el-option>
</el-select>
</template>
<template #append>
<el-button icon="search" @click="search()"></el-button>
</template>
</el-input>
</client-only>
</div>
</template>
<style scoped>
:deep(.el-input__inner) {
height: 48px;
}
:deep(.el-input__wrapper){
box-shadow: none;
}
:deep(.el-input-group__prepend){
box-shadow: none;
}
:deep(.el-input){
--el-input-focus-border:transparent;
--el-input-border-color:transparent;
--el-input-focus-border-color:transparent;
--el-input-hover-border-color:transparent;
}
:deep(.el-input-group--prepend .el-input-group__prepend .el-select .el-input.is-focus .el-input__wrapper){
box-shadow: none !important;
}
:deep(.el-input-group--prepend .el-input-group__prepend .el-select .el-input .el-input__inner){
text-align: center;
}
:deep(.el-select .el-input__wrapper.is-focus){
box-shadow: none !important;
}
</style>

View File

@ -0,0 +1,82 @@
<script setup lang="ts">
defineProps({
sources: {
type: Object,
default: () => {}
}
})
const emit = defineEmits(['openLink'])
const handleOpenSourceLink = (link: string) => {
emit('openLink', link)
}
const formatDiskType = (type: string) => {
switch (type) {
case 'ALY':
return '阿里云盘'
case 'BDY':
return '百度网盘'
case 'QUARK':
return '夸克网盘'
case 'XUNLEI':
return '迅雷网盘'
default:
return '未知类型'
}
}
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString()
}
</script>
<template>
<div
class="bg-white shadow p-[14px] rounded-[6px] cursor-pointer
hover:bg-[#f5f5f5] hover:shadow-lg transition duration-300 ease-in-out"
v-for="(item,i) in sources?.list" :key="i"
@click="handleOpenSourceLink(item.link)"
>
<div class="flex flex-row gap-2 items-center">
<img class="w-[20px]" v-if="item.disk_type === 'ALY'" src="@/assets/netdisk/aliyun.png" alt="aliyun">
<img class="w-[20px]" v-if="item.disk_type === 'QUARK'" src="@/assets/netdisk/quark.png" alt="quark">
<img class="w-[20px]" v-if="item.disk_type === 'BDY'" src="@/assets/netdisk/baidu.png" alt="baidu">
<img class="w-[20px]" v-if="item.disk_type === 'XUNLEI'" src="@/assets/netdisk/xunlei.png" alt="xunlei">
<p class="text-[14px] font-inter font-[600]" v-html="item.disk_name"></p>
</div>
<div class="py-[12px]">
<p class="text-[12px] text-slate-400 truncate-3-lines" v-html="item.files"></p>
</div>
<div class="text-[12px] text-slate-600 flex flex-row items-center justify-between">
<div class="flex flex-row items-center gap-2">
<span v-if="item.disk_type" class="bg-blue-500 text-white px-[6px] py-[2px] rounded">
{{ formatDiskType(item.disk_type) }}
</span>
<span v-if="item.disk_pass" class=" bg-purple-500 text-white px-[6px] py-[2px] rounded">
{{ item.disk_pass }}
</span>
</div>
<div>
<span v-if="item.update_time" class="text-slate-600 px-[6px] py-[2px] rounded">
{{ formatDate(item.update_time) }}
</span>
</div>
</div>
</div>
</template>
<style>
em {
color: blue;
margin: 0 2px;
}
</style>
<style scoped>
.truncate-3-lines {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<div class="flex flex-row items-center justify-center my-[20px]">
<p class="font-mono text-[12px] text-center text-gray-400">
爱盼-网盘资源搜索
</p>
</div>
</template>
<script setup>
</script>
<style scoped>
</style>

View File

@ -0,0 +1,24 @@
<template>
<el-affix>
<div class="shadow px-[20px] py-[10px]">
<div class="max-w-[1240px] mx-auto h-[40px] flex flex-row items-center justify-between">
<div class="flex flex-row items-center justify-center gap-1">
<img class="w-[40px] h-[40px] cursor-pointer" src="@/assets/my-logo.png" alt="logo" @click="goHome()">
<h1 class="text-[14px] font-serif font-bold cursor-pointer" @click="goHome()">爱盼-网盘资源搜索</h1>
</div>
</div>
</div>
</el-affix>
</template>
<script setup>
const router = useRouter()
const goHome = () => {
router.push('/')
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,87 @@
<script setup lang="ts">
const router = useRouter()
const goHome = () => {
router.push('/')
}
const props = defineProps({
keyword: {
type: String,
default: () => ''
}
})
const searchKeyword = ref(props.keyword)
const emit = defineEmits(['search'])
const search = () => {
emit('search',searchKeyword.value)
}
const goGithub = () => {
window.open('https://github.com/unilei/aipan-netdisk-search.git')
}
</script>
<template>
<el-affix>
<div class="bg-white shadow px-[20px] py-[10px]">
<div class="max-w-[1240px] mx-auto h-[40px] flex flex-row items-center gap-6 relative">
<div class="flex flex-row items-center gap-1">
<img class="w-[30px] h-[30px] md:w-[40px] md:h-[40px] cursor-pointer" src="@/assets/my-logo.png" alt="logo" @click="goHome()">
<h1 class="hidden md:block text-[14px] font-serif font-bold cursor-pointer" @click="goHome()" >爱盼-网盘资源搜索</h1>
</div>
<div class="w-[220px] md:w-[400px] border border-slate-300 font-mono overflow-hidden rounded-[50px]">
<client-only>
<el-input class="h-[30px]"
v-model="searchKeyword"
placeholder="请输入关键词搜索"
@keydown.enter="search()"
prefix-icon="Search"
>
</el-input>
</client-only>
</div>
<div class="absolute right-[20px]">
<el-button link @click="goGithub()">
<img class="w-[20px] h-[20px]" src="@/assets/github.png" alt="github">
</el-button>
</div>
</div>
</div>
</el-affix>
</template>
<style scoped lang="scss">
:deep(.el-input__inner) {
height: 48px;
}
:deep(.el-input__wrapper) {
box-shadow: none;
}
:deep(.el-input-group__prepend) {
box-shadow: none;
}
:deep(.el-input) {
--el-input-focus-border: transparent;
--el-input-border-color: transparent;
--el-input-focus-border-color: transparent;
--el-input-hover-border-color: transparent;
}
:deep(.el-input-group--prepend .el-input-group__prepend .el-select .el-input.is-focus .el-input__wrapper) {
box-shadow: none !important;
}
:deep(.el-input-group--prepend .el-input-group__prepend .el-select .el-input .el-input__inner) {
text-align: center;
}
:deep(.el-select .el-input__wrapper.is-focus) {
box-shadow: none !important;
}
</style>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
defineProps({
tagsData: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['change'])
const handleChange = (id) => {
emit('change',id)
}
</script>
<template>
<ul class="grid grid-cols-5 gap-3">
<li class="bg-white px-[14px] py-[6px]
cursor-pointer hover:scale-105 rounded-[6px]
transition duration-300 ease-in-out"
v-for="(item,i) in tagsData" :key="i"
@click="handleChange(item.id)"
>
<span class="text-[14px] font-inter font-[600]">{{ item.name }}</span>
</li>
</ul>
</template>
<style scoped>
</style>

10
layouts/custom.vue Normal file
View File

@ -0,0 +1,10 @@
<template>
<slot></slot>
</template>
<script setup>
</script>
<style scoped>
</style>

15
layouts/default.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<Header></Header>
<slot></slot>
<Footer></Footer>
</template>
<script setup>
import Footer from "~/components/layout/Footer.vue";
import Header from "~/components/layout/Header.vue";
</script>
<style scoped>
</style>

103
nuxt.config.ts Normal file
View File

@ -0,0 +1,103 @@
export default defineNuxtConfig({
devtools: { enabled: false },
app: {
// head
head: {
title: '爱盼-网盘资源搜索',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
name: 'referrer',
content: 'no-referrer'
},
{
name: 'referrer',
content: 'always'
},
{
name: 'referrer',
content: 'strict-origin-when-cross-origin'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
script: [
{
src: 'https://www.googletagmanager.com/gtag/js?id=G-7X3KPN3R02',
async: true
},
{
src: '/ga.js'
},
{
src: '/qrcode.min.js'
},
{
src: 'https://challenges.cloudflare.com/turnstile/v0/api.js'
},
{
src: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8210373406341452',
async: true,
crossorigin: 'anonymous'
}
]
}
},
// build modules
modules: [
'@element-plus/nuxt',
'@nuxtjs/tailwindcss',
'@nuxtjs/device',
'@nuxtjs/i18n',
'@nuxtjs/google-fonts'
],
tailwindcss: {
configPath: 'tailwind.config.js'
},
googleFonts: {
display: 'swap',
prefetch: false,
preconnect: false,
preload: true,
download: false,
base64: false,
families: {
'Inter': [100, 200, 300, 400, 500, 600, 700, 800, 900],
'Poetsen One': [100, 200, 300, 400, 500, 600, 700, 800, 900],
'Sedan SC': [100, 200, 300, 400, 500, 600, 700, 800, 900],
'Briem Hand': [100, 200, 300, 400, 500, 600, 700, 800, 900],
'Noto Sans Simplified Chinese': [100, 200, 300, 400, 500, 600, 700, 800, 900]
}
},
i18n: {
defaultLocale: 'cn',
langDir: './assets/lang/',
locales: [
{
code: 'en',
name: 'English',
iso: 'en-US',
file: 'en-US.json'
},
{
code: 'cn',
name: '中文',
iso: 'zh-CN',
file: 'zh-CN.json'
}
],
},
plugins: [
],
nitro: {
devProxy: {
}
},
runtimeConfig: {
openaiApiKey: '',
proxyUrl: ''
}
})

14270
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

38
package.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "nuxt-app",
"private": true,
"version": "1.0.0",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev --port 3001",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@element-plus/nuxt": "^1.0.7",
"@nuxt/config": "^2.17.1",
"@nuxt/devtools": "latest",
"@nuxt/types": "^2.17.1",
"@nuxt/typescript-build": "^3.0.1",
"@nuxtjs/device": "^3.1.1",
"@nuxtjs/google-fonts": "^3.2.0",
"@nuxtjs/i18n": "^8.0.0-rc.7",
"@types/node": "^18.16.19",
"nuxt": "^3.7.0",
"sass": "^1.77.1",
"typescript": "^5.1.6",
"vue-tsc": "^1.8.5"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@nuxtjs/tailwindcss": "^6.8.0",
"axios": "^1.6.2",
"bcryptjs": "^2.4.3",
"nuxt-icon": "^0.6.10",
"nuxt-proxy": "^0.0.8"
},
"overrides": {
"vue": "latest"
}
}

16
pages/donate.vue Normal file
View File

@ -0,0 +1,16 @@
<script setup lang="ts">
</script>
<template>
<div class="p-[20px]">
<h1 class=" text-3xl font-bold text-center">打赏</h1>
<div class="mt-10 flex justify-center">
<img style="width: 300px;margin-top: 40px;" src="@/assets/donation/wechat_pay.jpg" alt="打赏">
</div>
</div>
</template>
<style scoped>
</style>

100
pages/index.vue Normal file
View File

@ -0,0 +1,100 @@
<script setup>
definePageMeta({
layout: 'custom',
})
const searchKeyword = ref('')
const router = useRouter()
const search = (keyword) => {
router.push({path:'/search',query:{keyword:encodeURIComponent(keyword)}})
}
const donate = () => {
router.push({path:'/donate'})
}
const hotKeywords = ref(['庆余年2','歌手2024','庆余年','我的阿勒泰','新生'])
</script>
<template>
<div class="bg-[#ffffff] min-h-screen py-[60px]">
<div class="flex flex-row items-center justify-center gap-3 mt-[80px]">
<img class="w-[40px] h-[40px] sm:w-[60px] sm:h-[60px]" src="@/assets/my-logo.png" alt="logo">
<h1 class="text-[18px] sm:text-[24px] font-serif font-bold ">爱盼-网盘资源搜索</h1>
</div>
<div class="max-w-[1240px] mx-auto mt-[20px]">
<div class="w-[80%] md:w-[700px] mx-auto border border-slate-300 font-mono overflow-hidden rounded-[50px]">
<client-only>
<el-input class="h-[40px] sm:h-[50px]"
v-model="searchKeyword"
placeholder="请输入关键词搜索"
@keydown.enter="search(searchKeyword)"
prefix-icon="Search"
>
</el-input>
</client-only>
</div>
</div>
<div class="max-w-[1240px] mx-auto mt-[20px]">
<div class="flex flex-row flex-wrap gap-1 justify-center">
<el-tag class="mx-1 cursor-pointer"
v-for="keyword in hotKeywords"
:key="keyword"
type="info"
@click="search(keyword)"
>
{{ keyword }}
</el-tag>
</div>
</div>
<div class="fixed bottom-0 left-0 right-0 bg-white p-3">
<div class="flex flex-row items-center justify-center gap-3 my-3">
<a class="" href="https://github.com/unilei/aipan-netdisk-search">
<img class="w-[50px] h-[50px]" src="@/assets/github.png" alt="github">
</a>
<el-button color="#ffffff" @click="donate()">
<img class="w-[50px] h-[50px]" src="@/assets/donation/dashang.svg" alt="打赏">
</el-button>
</div>
<p class="text-center text-[8px] sm:text-[12px] text-slate-400">
声明本站不产生/存储任何数据也从未参与录制上传所有资源均来自网络
</p>
</div>
</div>
</template>
<style scoped>
:deep(.el-input__inner) {
height: 48px;
}
:deep(.el-input__wrapper) {
box-shadow: none;
}
:deep(.el-input-group__prepend) {
box-shadow: none;
}
:deep(.el-input) {
--el-input-focus-border: transparent;
--el-input-border-color: transparent;
--el-input-focus-border-color: transparent;
--el-input-hover-border-color: transparent;
}
:deep(.el-input-group--prepend .el-input-group__prepend .el-select .el-input.is-focus .el-input__wrapper) {
box-shadow: none !important;
}
:deep(.el-input-group--prepend .el-input-group__prepend .el-select .el-input .el-input__inner) {
text-align: center;
}
:deep(.el-select .el-input__wrapper.is-focus) {
box-shadow: none !important;
}
</style>

110
pages/latest-sources.vue Normal file
View File

@ -0,0 +1,110 @@
<script setup>
import Tags from "~/components/sources/tags.vue";
import SearchHeader from "~/components/search/SearchHeader.vue";
import DiskInfoList from "~/components/diskInfoList.vue";
definePageMeta({
layout: 'custom',
})
const tagsVisible = ref(false)
const tagsData = ref([])
const getTabsData = async () => {
let res = await $fetch('/api/sources/hh/tabs')
if (res.code === 200) {
tagsData.value = res.data
await getSourcesDataByTag(res.data[0].id)
}
}
const sourcesData = ref([])
const getSourcesDataByTag = async (id) => {
let res = await $fetch('/api/sources/hh/tabs-id', {
method: 'get',
query: {
id: id,
page: 1,
size: 20
}
})
sourcesData.value = res.data
}
const latestSourcesData = ref([])
const latestPage = ref(1)
const latestSize = ref(10)
const getLatestSourcesData = async (page, size) => {
const loading = ElLoading.service({
text: '加载中...',
background: 'transparent',
target: '#latest-sources-all',
})
let res = await $fetch('/api/sources/hh/latest-sources', {
method: 'get',
query: {
page: page,
size: size
}
})
if (res.code === 200) {
latestSourcesData.value = res.data
loading.close()
}
}
const handleLatestPageChange = (page) => {
latestPage.value = page
window.scroll(0, 0)
getLatestSourcesData(latestPage.value, latestSize.value)
}
const handleOpenSourceLink = (url) => {
window.open(url, '_blank')
}
// BDY, ALY, QUARK, XUNLEI
const keyword = ref('')
const router = useRouter()
const search = (keyword) => {
router.push({path: '/search', query: {keyword: encodeURIComponent(keyword)}})
}
onMounted(() => {
getTabsData()
getLatestSourcesData(latestPage.value, latestSize.value)
})
</script>
<template>
<div class="min-h-screen">
<search-header :keyword="keyword" @search="search"></search-header>
<div class="max-w-[1240px] mx-auto p-[20px] sm:py-[20px]">
<tags v-if="tagsVisible" :tags-data="tagsData" @change="getSourcesDataByTag"></tags>
<div class="max-w-[1240px] mx-auto grid grid-cols-1 md:grid-cols-[1fr_400px] gap-8">
<div class="min-h-[calc(100vh-90px)]" id="latest-sources-all">
<div class="text-xl font-bold">最新资源</div>
<div class="grid grid-cols-1 gap-3 mt-3">
<disk-info-list :sources="latestSourcesData" @open-link="handleOpenSourceLink"></disk-info-list>
</div>
<div class="mt-[20px] flex justify-center">
<client-only>
<el-pagination
layout="prev, pager, next"
@current-change="handleLatestPageChange"
:total="latestSourcesData?.total"
></el-pagination>
</client-only>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

236
pages/search.vue Normal file
View File

@ -0,0 +1,236 @@
<script setup>
import SearchHeader from "~/components/search/SearchHeader.vue";
import DiskInfoList from "~/components/diskInfoList.vue";
import aliImg from '@/assets/netdisk/aliyun.png'
import quarkImg from '@/assets/netdisk/quark.png'
import xunleiImg from '@/assets/netdisk/xunlei.png'
import bdyImg from '@/assets/netdisk/baidu.png'
definePageMeta({
layout: 'custom',
})
const router = useRouter()
const handleOpenSourceLink = (url) => {
window.open(url, '_blank')
}
const tabsOptions = [
{
label: '所有',
value: ''
},
{
label: '阿里',
value: 'ALY',
img: aliImg
},
{
label: '百度',
value: 'BDY',
img: bdyImg
},
{
label: '夸克',
value: 'QUARK',
img: quarkImg
},
{
label: '迅雷',
value: 'XUNLEI',
img: xunleiImg
}
]
const route = useRoute()
const keyword = ref(decodeURIComponent(route.query.keyword))
const currentTabValue = ref('')
const page = ref(1)
const exact = ref(false)
const sources = ref([])
const handleSearchByHunhe = async () => {
let res = await $fetch('/api/sources/hh/search', {
method: 'POST',
body: {
'engine': currentEngine.value,
"q": keyword.value,
"page": page.value,
"size": 10,
"time": "",
"type": currentTabValue.value,
"exact": exact.value
}
})
if (res.code === 200) {
sources.value = res.data
}
}
const search = (e) => {
keyword.value = e
handleSearchByHunhe()
}
const handleChangeTab = (e) => {
currentTabValue.value = e
handleSearchByHunhe()
}
const handleCurrentPageChange = (e) => {
page.value = e
window.scroll(0, 0)
handleSearchByHunhe()
}
const handleChangeExact = (e) => {
exact.value = !e
handleSearchByHunhe()
}
const handleEngineChange = (e) => {
currentEngine.value = e
handleSearchByHunhe()
}
const latestSourcesData = ref([])
const getLatestSourcesData = async (page, size) => {
const loading = ElLoading.service({
text: '加载中...',
background: 'transparent',
target: '#latest-sources',
})
let res = await $fetch('/api/sources/hh/latest-sources', {
method: 'get',
query: {
page: page,
size: size
}
})
if (res.code === 200) {
latestSourcesData.value = res.data
loading.close()
}
}
const handleGoToLatestSources = () => {
router.push({path: '/latest-sources'})
}
const currentEngine = ref(1)
const searchOptions = [
{
label: '默认引擎',
value: 1
},
{
label: '搜索引擎一',
value: 2
},
{
label: '搜索引擎二',
value: 3
},
{
label: '搜索引擎三',
value: 4
},
{
label: '搜索引擎四',
value: 5
},
{
label: '搜索引擎五',
value: 6
}
]
onMounted(() => {
handleSearchByHunhe()
getLatestSourcesData(1, 10)
})
</script>
<template>
<div>
<search-header :keyword="keyword" @search="search"></search-header>
<div class="max-w-[1240px] mx-auto grid grid-cols-1 md:grid-cols-[1fr_400px] gap-8">
<div class="grid grid-cols-1 gap-3 sm:mt-3 sm:pb-[60px] min-h-[500px] p-[20px] md:p-0">
<div class="py-3">
<ul class="flex flex-row gap-3 flex-wrap">
<li v-for="(item,i) in tabsOptions" :key="i">
<el-check-tag :checked="item.value === currentTabValue" @click="handleChangeTab(item.value)"
type="success">
<div class="flex flex-row items-center">
<span class="text-[10px] md:text-[14px]">{{ item.label }}</span>
</div>
</el-check-tag>
</li>
<li>
<el-check-tag :checked="exact" @click="handleChangeExact(exact)"
type="success">
<span class="text-[10px] md:text-[14px]">精确搜索</span>
</el-check-tag>
</li>
<li>
<el-select
v-model="currentEngine"
placeholder="Select"
style="width: 140px"
@change="handleEngineChange"
>
<el-option v-for="item in searchOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</li>
</ul>
</div>
<disk-info-list :sources="sources" @open-link="handleOpenSourceLink"></disk-info-list>
<div class="py-[40px] flex justify-center">
<client-only>
<el-pagination
layout="prev, pager, next"
@current-change="handleCurrentPageChange"
:total="sources?.total"
></el-pagination>
</client-only>
</div>
</div>
<div class="p-[20px] sm:py-[20px]">
<div class="bg-white shadow p-[14px] rounded-[6px]">
<div class="flex flex-row justify-between items-center">
<span class="text-[14px] font-bold">最近更新</span>
<div>
<el-button link icon="refresh" @click="getLatestSourcesData(1, 10)"></el-button>
<el-button link icon="more" @click="handleGoToLatestSources()"></el-button>
</div>
</div>
<div class="grid grid-cols-1 gap-3 mt-3 min-h-[500px]" id="latest-sources">
<div
class="bg-white shadow p-[14px] rounded-[6px] cursor-pointer
hover:bg-[#f5f5f5] hover:shadow-lg transition duration-300 ease-in-out"
v-for="(item,i) in latestSourcesData?.list" :key="i"
@click="handleOpenSourceLink(item.link)"
>
<div class="flex flex-row gap-2 items-center">
<img class="w-[20px]" v-if="item.disk_type === 'ALY'" src="@/assets/netdisk/aliyun.png" alt="aliyun">
<img class="w-[20px]" v-if="item.disk_type === 'QUARK'" src="@/assets/netdisk/quark.png" alt="quark">
<img class="w-[20px]" v-if="item.disk_type === 'BDY'" src="@/assets/netdisk/baidu.png" alt="baidu">
<img class="w-[20px]" v-if="item.disk_type === 'XUNLEI'" src="@/assets/netdisk/xunlei.png" alt="xunlei">
<span class="text-[14px] font-inter">{{ item.disk_name }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

BIN
public/favicon-edu.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

5
public/ga.js Normal file
View File

@ -0,0 +1,5 @@
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-7X3KPN3R02');

BIN
public/icon/kkpans.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
public/icon/openai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

1
public/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
export default defineEventHandler(async (event) => {
const query = await getQuery(event)
try{
let res = await $fetch('https://api.hunhepan.com/v1/raw_disk/latest_with_extab',{
method:'GET',
query:{
type: query.type,
page: query.page,
size: query.size
}
})
return res
}catch (e) {
console.log(e)
return {
code: 500,
msg:'error',
}
}
})

View File

@ -0,0 +1,38 @@
export default defineEventHandler(async (event) => {
// 定义多个 API 的请求配置
const apiEndpoints = [
{ url: 'https://alipanx.com/v1/search/disk', method: 'POST' },
{ url: 'https://xlpanso.com/v1/search/disk', method: 'POST' },
{ url: 'https://hunhepan.com/open/search/disk', method: 'POST' },
{ url: 'https://www.lzpanx.com/v1/search/disk', method: 'POST' },
{ url: 'https://qkpanso.com/v1/search/disk', method: 'POST' },
{ url: 'https://www.pansou.icu/v1/search/disk', method: 'POST' }
];
try {
const body = await readBody(event);
// 使用 Promise.all() 并行发起多个请求
const requests = apiEndpoints.map(async (endpoint) => {
const res = await $fetch(endpoint.url, {
method: 'POST',
body
});
return res;
});
// 等待所有请求完成
const results = await Promise.all(requests);
// 返回所有请求的结果数组
return results;
} catch (e) {
console.log(e);
return {
code: 500,
msg: 'error',
};
}
});

View File

@ -0,0 +1,32 @@
export default defineEventHandler(async (event) => {
try{
let body = await readBody(event)
let apiEndpoints = [
{ url: 'https://alipanx.com/v1/search/disk', engine: 2 },
{ url: 'https://xlpanso.com/v1/search/disk', engine: 3 },
{ url: 'https://hunhepan.com/open/search/disk', engine: 1 },
{ url: 'https://www.lzpanx.com/v1/search/disk', engine: 4 },
{ url: 'https://qkpanso.com/v1/search/disk', engine: 5 },
{ url: 'https://www.pansou.icu/v1/search/disk', engine: 6 }
];
let engineValue = body.engine
let index = apiEndpoints.findIndex((item) => item.engine === engineValue)
let res = await $fetch( apiEndpoints[index].url ,{
method:'POST',
body
})
return res
}catch (e) {
console.log(e)
return {
code: 500,
msg:'error',
}
}
})

View File

@ -0,0 +1,22 @@
export default defineEventHandler(async (event) => {
const query = await getQuery(event)
try{
let res = await $fetch('https://api.hunhepan.com/v1/extab/raw_disks/'+query.id,{
method:'GET',
query:{
page: query.page,
size: query.size
}
})
return res
}catch (e) {
console.log(e)
return {
code: 500,
msg:'error',
}
}
})

View File

@ -0,0 +1,16 @@
export default defineEventHandler(async (event) => {
try{
let res = await $fetch('https://api.hunhepan.com/v1/extab/list_all',{
method:'GET'
})
return res
}catch (e) {
console.log(e)
return {
code: 500,
msg:'error',
}
}
})

14
server/middleware/cors.ts Normal file
View File

@ -0,0 +1,14 @@
export default defineEventHandler((event) => {
setResponseHeaders(event, {
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Credentials': 'true',
"Access-Control-Allow-Headers": '*',
"Access-Control-Expose-Headers": '*'
})
if(event.method === 'OPTIONS'){
event.node.res.statusCode = 204
event.node.res.statusMessage = "No Content."
return 'OK'
}
})

2
server/routes/hello.ts Normal file
View File

@ -0,0 +1,2 @@
export default defineEventHandler(() => 'Hello World!')

11
tailwind.config.js Normal file
View File

@ -0,0 +1,11 @@
export const theme = {
extend: {
fontFamily: {
'inter': ['Inter', 'sans-serif'],
'poetsen-one': ['Poetsen One', 'sans-serif'],
'sedan-sc': ['Sedan SC', 'sans-serif'],
'breiem-hand': ['Briem Hand', 'sans-serif'],
'noto-sans-cn': ['Noto Sans Simplified Chinese', 'sans-serif']
}
}
};

4
tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

1180
utils/sensitiveWords.js Normal file

File diff suppressed because it is too large Load Diff

13
vercel.json Normal file
View File

@ -0,0 +1,13 @@
{
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Credentials", "value": "true" },
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Access-Control-Allow-Methods", "value": "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
{ "key": "Access-Control-Allow-Headers", "value": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }
]
}
]
}