mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-04-04 02:59:18 +08:00
2
webui/.vscode/settings.json
vendored
2
webui/.vscode/settings.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": ["src/i18n"],
|
||||
"commentTranslate.targetLanguage": "zh-CN",
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.sourceLanguage": "zh-CN",
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"i18n-ally.keystyle": "nested"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b",
|
||||
"scripts": {
|
||||
"prepare": "cd .. && husky install ./webui/.husky",
|
||||
"test:build": "vue-tsc --noEmit",
|
||||
@@ -19,53 +20,51 @@
|
||||
"generate-pwa-assets": "pwa-assets-generator --preset minimal public/images/logo.svg"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.13",
|
||||
"@vueuse/components": "^10.4.1",
|
||||
"@vueuse/core": "^8.9.4",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@vueuse/components": "^10.11.1",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"axios": "^0.27.2",
|
||||
"naive-ui": "^2.34.4",
|
||||
"pinia": "^2.1.3",
|
||||
"rxjs": "^7.8.1",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-inline-svg": "^3.1.2",
|
||||
"vue-router": "^4.2.1"
|
||||
"naive-ui": "^2.39.0",
|
||||
"pinia": "^2.2.2",
|
||||
"vue": "^3.5.8",
|
||||
"vue-i18n": "^9.14.0",
|
||||
"vue-inline-svg": "^3.1.4",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.38.6",
|
||||
"@icon-park/vue-next": "^1.4.2",
|
||||
"@intlify/unplugin-vue-i18n": "^0.11.0",
|
||||
"@storybook/addon-essentials": "^7.0.12",
|
||||
"@storybook/addon-interactions": "^7.0.12",
|
||||
"@storybook/addon-links": "^7.0.12",
|
||||
"@storybook/blocks": "^7.0.12",
|
||||
"@storybook/addon-essentials": "^7.6.20",
|
||||
"@storybook/addon-interactions": "^7.6.20",
|
||||
"@storybook/addon-links": "^7.6.20",
|
||||
"@storybook/blocks": "^7.6.20",
|
||||
"@storybook/testing-library": "0.0.14-next.2",
|
||||
"@storybook/vue3": "^7.0.12",
|
||||
"@storybook/vue3-vite": "^7.0.12",
|
||||
"@types/node": "^18.16.14",
|
||||
"@unocss/preset-attributify": "^0.55.3",
|
||||
"@storybook/vue3": "^7.6.20",
|
||||
"@storybook/vue3-vite": "^7.6.20",
|
||||
"@types/node": "^18.19.50",
|
||||
"@unocss/preset-attributify": "^0.55.7",
|
||||
"@unocss/preset-rem-to-px": "^0.51.13",
|
||||
"@unocss/reset": "^0.51.13",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue": "^4.6.2",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/runtime-dom": "^3.3.4",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-storybook": "^0.6.12",
|
||||
"@vue/runtime-dom": "^3.5.8",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-storybook": "^0.6.15",
|
||||
"husky": "^8.0.3",
|
||||
"prettier": "^2.8.8",
|
||||
"radash": "^12.1.0",
|
||||
"sass": "^1.62.1",
|
||||
"storybook": "^7.0.12",
|
||||
"sass-embedded": "^1.79.3",
|
||||
"storybook": "^7.6.20",
|
||||
"typescript": "^4.9.5",
|
||||
"unocss": "^0.51.13",
|
||||
"unplugin-auto-import": "^0.10.3",
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
"unplugin-vue-router": "^0.6.4",
|
||||
"vite": "^4.3.5",
|
||||
"vite-plugin-pwa": "^0.16.4",
|
||||
"vite": "^4.5.5",
|
||||
"vite-plugin-pwa": "^0.16.7",
|
||||
"vitest": "^0.30.1",
|
||||
"vue-tsc": "^1.6.4"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0"
|
||||
"vue-tsc": "^2.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
10597
webui/pnpm-lock.yaml
generated
10597
webui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,57 +1,60 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import type { Ref } from 'vue';
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
|
||||
import type { BangumiAPI, BangumiRule } from '#/bangumi';
|
||||
type EventSourceStatus = 'OPEN' | 'CONNECTING' | 'CLOSED';
|
||||
|
||||
export const apiSearch = {
|
||||
/**
|
||||
* 番剧搜索接口是 Server Send 流式数据,每条是一个 Bangumi JSON 字符串,
|
||||
* 使用接口方式是监听连接消息后,转为 Observable 配合外层调用时 switchMap 订阅使用
|
||||
*/
|
||||
get(keyword: string, site = 'mikan'): Observable<BangumiRule> {
|
||||
const bangumiInfo$ = new Observable<BangumiRule>((observer) => {
|
||||
const eventSource = new EventSource(
|
||||
`api/v1/search/bangumi?site=${site}&keywords=${encodeURIComponent(
|
||||
keyword
|
||||
)}`,
|
||||
{ withCredentials: true }
|
||||
);
|
||||
get() {
|
||||
const eventSource = ref(null) as Ref<EventSource | null>;
|
||||
const status = ref<EventSourceStatus>('CLOSED');
|
||||
const data = ref<BangumiRule[]>([]);
|
||||
|
||||
eventSource.onmessage = (ev) => {
|
||||
try {
|
||||
const apiData: BangumiAPI = JSON.parse(ev.data);
|
||||
const data: BangumiRule = {
|
||||
...apiData,
|
||||
filter: apiData.filter.split(','),
|
||||
rss_link: apiData.rss_link.split(','),
|
||||
};
|
||||
observer.next(data);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'[/search/bangumi] Parse Error |',
|
||||
{ keyword },
|
||||
'response:',
|
||||
ev.data
|
||||
);
|
||||
}
|
||||
const keyword = ref('');
|
||||
const provider = ref('');
|
||||
|
||||
const close = () => {
|
||||
if (eventSource.value) {
|
||||
eventSource.value.close();
|
||||
eventSource.value = null;
|
||||
status.value = 'CLOSED';
|
||||
}
|
||||
};
|
||||
|
||||
const _init = () => {
|
||||
status.value = 'CONNECTING';
|
||||
|
||||
const url = `api/v1/search/bangumi?site=${
|
||||
provider.value
|
||||
}&keywords=${encodeURIComponent(keyword.value)}`;
|
||||
|
||||
const es = new EventSource(url, { withCredentials: true });
|
||||
eventSource.value = es;
|
||||
es.onopen = () => {
|
||||
status.value = 'OPEN';
|
||||
};
|
||||
|
||||
eventSource.onerror = (ev) => {
|
||||
console.error(
|
||||
'[/search/bangumi] Server Error |',
|
||||
{ keyword },
|
||||
'error:',
|
||||
ev
|
||||
);
|
||||
// 目前后端搜索完成关闭连接时会触发 error 事件,前端手动调用 close 不再自动重连
|
||||
eventSource.close();
|
||||
es.onmessage = (e) => {
|
||||
const newData = JSON.parse(e.data) as BangumiRule;
|
||||
data.value = [...data.value, newData];
|
||||
};
|
||||
|
||||
return () => {
|
||||
eventSource.close();
|
||||
es.onerror = (err) => {
|
||||
console.error('EventSource error:', err);
|
||||
close();
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return bangumiInfo$;
|
||||
const open = () => {
|
||||
data.value = [];
|
||||
_init();
|
||||
};
|
||||
|
||||
return {
|
||||
keyword,
|
||||
provider,
|
||||
status,
|
||||
data,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
},
|
||||
|
||||
async getProvider() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { ErrorPicture, Write } from '@icon-park/vue-next';
|
||||
import { Write } from '@icon-park/vue-next';
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
type?: 'primary' | 'search' | 'mobile';
|
||||
type?: 'primary' | 'search';
|
||||
bangumi: BangumiRule;
|
||||
}>(),
|
||||
{
|
||||
@@ -16,89 +16,77 @@ defineEmits(['click']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="type === 'primary'" w-150 is-btn @click="() => $emit('click')">
|
||||
<div rounded-4 overflow-hidden poster-shandow rel>
|
||||
<div w-full h-210>
|
||||
<template v-if="bangumi.poster_link">
|
||||
<img :src="bangumi.poster_link" alt="poster" wh-full />
|
||||
</template>
|
||||
<template v-if="type === 'primary'">
|
||||
<div w="full pc:150" is-btn @click="() => $emit('click')">
|
||||
<div rounded-4 overflow-hidden poster-shandow rel>
|
||||
<ab-image
|
||||
:src="bangumi.poster_link"
|
||||
:aspect-ratio="1 / 1.5"
|
||||
w-full
|
||||
></ab-image>
|
||||
|
||||
<template v-else>
|
||||
<div wh-full f-cer border="1 white">
|
||||
<ErrorPicture theme="outline" size="24" fill="#333" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
abs
|
||||
f-cer
|
||||
z-1
|
||||
inset-0
|
||||
opacity-0
|
||||
transition="all duration-300"
|
||||
hover="backdrop-blur-2 bg-white bg-opacity-30 opacity-100"
|
||||
active="duration-0 bg-opacity-60"
|
||||
class="group"
|
||||
>
|
||||
<div
|
||||
text-white
|
||||
rounded="1/2"
|
||||
wh-44
|
||||
abs
|
||||
f-cer
|
||||
bg-theme-row
|
||||
group-active="poster-pen-active"
|
||||
z-1
|
||||
inset-0
|
||||
opacity-0
|
||||
transition="all duration-300"
|
||||
hover="backdrop-blur-2 bg-white bg-opacity-30 opacity-100"
|
||||
active="duration-0 bg-opacity-60"
|
||||
class="group"
|
||||
>
|
||||
<Write size="20" />
|
||||
<div
|
||||
text-white
|
||||
rounded="1/2"
|
||||
wh-44
|
||||
f-cer
|
||||
bg-theme-row
|
||||
group-active="poster-pen-active"
|
||||
>
|
||||
<Write size="20" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div py-4>
|
||||
<div text-h3 truncate>{{ bangumi.official_title }}</div>
|
||||
<div py-4>
|
||||
<div text-h3 truncate>{{ bangumi.official_title }}</div>
|
||||
|
||||
<div space-x-5>
|
||||
<ab-tag :title="`Season ${bangumi.season}`" type="primary" />
|
||||
<ab-tag
|
||||
v-if="bangumi.group_name"
|
||||
:title="bangumi.group_name"
|
||||
type="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="type === 'search'"
|
||||
w-480
|
||||
rounded-12
|
||||
p-4
|
||||
shadow
|
||||
bg="#eee5f4"
|
||||
transition="opacity ease-in-out duration-300"
|
||||
>
|
||||
<div bg-white rounded-8 p-12 fx-cer justify-between gap-x-16>
|
||||
<div w-400 gap-x-16 fx-cer>
|
||||
<div h-44 w-72 rounded-6 overflow-hidden>
|
||||
<template v-if="bangumi.poster_link">
|
||||
<img
|
||||
:src="bangumi.poster_link"
|
||||
alt="poster"
|
||||
w-full
|
||||
translate-y="-25%"
|
||||
<div flex="~ wrap col" pc:flex-row gap-5>
|
||||
<template v-for="i in ['season', 'group_name']" :key="i">
|
||||
<ab-tag
|
||||
v-if="bangumi[i]"
|
||||
:title="i === 'season' ? `Season ${bangumi[i]}` : bangumi[i]"
|
||||
type="primary"
|
||||
pc:max-w="1/2"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div wh-full f-cer border="1 white">
|
||||
<ErrorPicture theme="outline" size="24" fill="#333" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div flex="~ col gap-y-4">
|
||||
<div w-300 text="h3 primary" truncate>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="type === 'search'">
|
||||
<div
|
||||
w-480
|
||||
max-w-90vw
|
||||
rounded-12
|
||||
p-4
|
||||
shadow
|
||||
bg="#eee5f4"
|
||||
transition="opacity ease-in-out duration-300"
|
||||
>
|
||||
<div w-full bg-white rounded-8 p-12 flex gap-x-14>
|
||||
<div w-72 rounded-6 overflow-hidden>
|
||||
<ab-image :src="bangumi.poster_link" w-full></ab-image>
|
||||
</div>
|
||||
|
||||
<div flex="~ col 1 gap-y-4 justify-between">
|
||||
<div text="h3 primary">
|
||||
{{ bangumi.official_title }}
|
||||
</div>
|
||||
<div flex="~ gap-x-8">
|
||||
|
||||
<div flex="~ wrap gap-8">
|
||||
<template
|
||||
v-for="i in ['season', 'group_name', 'subtitle']"
|
||||
:key="i"
|
||||
@@ -111,8 +99,14 @@ defineEmits(['click']);
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ab-add
|
||||
my-auto
|
||||
:round="true"
|
||||
type="medium"
|
||||
@click="() => $emit('click')"
|
||||
/>
|
||||
</div>
|
||||
<ab-add :round="true" type="medium" @click="() => $emit('click')" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -15,7 +15,7 @@ withDefaults(
|
||||
bg-theme-row
|
||||
w-full
|
||||
text-white
|
||||
px-20
|
||||
px="10 pc:20"
|
||||
h="38 pc:45"
|
||||
fx-cer
|
||||
justify-between
|
||||
|
||||
@@ -20,7 +20,7 @@ withDefaults(
|
||||
w-full
|
||||
text-white
|
||||
fx-cer
|
||||
px-20
|
||||
px="10 pc:20"
|
||||
h="38 pc:45"
|
||||
justify-between
|
||||
>
|
||||
|
||||
46
webui/src/components/ab-image.vue
Normal file
46
webui/src/components/ab-image.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import { ErrorPicture } from '@icon-park/vue-next';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
src?: string | null;
|
||||
aspectRatio?: number;
|
||||
objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
|
||||
}>(),
|
||||
{
|
||||
objectFit: 'cover',
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div rel>
|
||||
<template v-if="aspectRatio">
|
||||
<div
|
||||
w-full
|
||||
:style="{ paddingBottom: `calc(${1 / aspectRatio} * 100%)` }"
|
||||
></div>
|
||||
|
||||
<img
|
||||
v-if="src"
|
||||
:src="src"
|
||||
alt="poster"
|
||||
abs
|
||||
top-0
|
||||
left-0
|
||||
:style="{ objectFit }"
|
||||
wh-full
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<img v-if="src" :src="src" alt="poster" :style="{ objectFit }" wh-full />
|
||||
|
||||
<div v-else wh-full f-cer border="1 white">
|
||||
<ErrorPicture theme="outline" size="24" fill="#333" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scope></style>
|
||||
@@ -1,73 +1,102 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
Popover,
|
||||
PopoverButton,
|
||||
PopoverOverlay,
|
||||
PopoverPanel,
|
||||
} from '@headlessui/vue';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { Search } from '@icon-park/vue-next';
|
||||
import type { BangumiRule } from '#/bangumi';
|
||||
|
||||
defineEmits<{
|
||||
(e: 'add-bangumi', bangumiRule: BangumiRule): void;
|
||||
}>();
|
||||
|
||||
const showProvider = ref(false);
|
||||
const { providers, provider, loading, inputValue, bangumiList } = storeToRefs(
|
||||
const { providers, provider, loading, keyword, searchData } = storeToRefs(
|
||||
useSearchStore()
|
||||
);
|
||||
const { getProviders, onSearch, clearSearch } = useSearchStore();
|
||||
const { getProviders, clearSearch, openSearch } = useSearchStore();
|
||||
|
||||
onMounted(() => {
|
||||
getProviders();
|
||||
});
|
||||
|
||||
function onSelect(site: string) {
|
||||
provider.value = site;
|
||||
showProvider.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ab-search
|
||||
v-model:inputValue="inputValue"
|
||||
:provider="provider"
|
||||
:loading="loading"
|
||||
@search="onSearch"
|
||||
@select="() => (showProvider = !showProvider)"
|
||||
/>
|
||||
<Popover v-bind="$attrs">
|
||||
<transition name="fade">
|
||||
<PopoverOverlay
|
||||
class="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 z-5"
|
||||
/>
|
||||
</transition>
|
||||
|
||||
<div
|
||||
v-show="showProvider"
|
||||
v-on-click-outside="() => (showProvider = false)"
|
||||
abs
|
||||
top-84
|
||||
left-540
|
||||
w-100
|
||||
rounded-12
|
||||
shadow
|
||||
bg-white
|
||||
z-99
|
||||
overflow-hidden
|
||||
>
|
||||
<div
|
||||
v-for="site in providers"
|
||||
:key="site"
|
||||
hover:bg-theme-row
|
||||
is-btn
|
||||
@click="() => onSelect(site)"
|
||||
<PopoverButton bg-transparent text="pc:24 20" is-btn btn-click>
|
||||
<Search size="1em" fill="#fff" />
|
||||
</PopoverButton>
|
||||
|
||||
<transition
|
||||
enter-active-class="transition duration-200 ease-out"
|
||||
enter-from-class="translate-y--20 opacity-0"
|
||||
enter-to-class="translate-y-0 opacity-100"
|
||||
leave-active-class="transition duration-150 ease-in"
|
||||
leave-from-class="translate-y-0 opacity-100"
|
||||
leave-to-class="translate-y--20 opacity-0"
|
||||
>
|
||||
<div text="h3 primary" hover="text-white" p-12 truncate>
|
||||
{{ site }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-on-click-outside="clearSearch" abs top-84 left-192 z-8>
|
||||
<transition-group name="fade-list" tag="ul" space-y-12>
|
||||
<li v-for="bangumi in bangumiList" :key="bangumi.order">
|
||||
<ab-bangumi-card
|
||||
:bangumi="bangumi.value"
|
||||
type="search"
|
||||
@click="() => $emit('add-bangumi', bangumi.value)"
|
||||
<PopoverPanel
|
||||
v-on-click-outside="clearSearch"
|
||||
class="search-panel"
|
||||
fixed
|
||||
left-0
|
||||
right-0
|
||||
m-auto
|
||||
w-max
|
||||
z-5
|
||||
>
|
||||
<ab-search
|
||||
v-model:input-value="keyword"
|
||||
v-model:provider="provider"
|
||||
:providers="providers"
|
||||
:loading="loading"
|
||||
@search="openSearch"
|
||||
/>
|
||||
</li>
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
<div class="search-list" space-y-10 overflow-auto>
|
||||
<transition-group name="fade-list">
|
||||
<template v-for="i in searchData" :key="i.rss_link">
|
||||
<ab-bangumi-card
|
||||
:bangumi="i"
|
||||
type="search"
|
||||
@click="() => $emit('add-bangumi', i)"
|
||||
/>
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</transition>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.search-panel {
|
||||
--_offset-top: 80px;
|
||||
--_offset-bottom: 40px;
|
||||
--_search-input-height: 36px;
|
||||
--_search-list-offset: 20px;
|
||||
|
||||
@include forMobile {
|
||||
--_offset-top: 65px;
|
||||
--_search-list-offset: 10px;
|
||||
}
|
||||
|
||||
top: var(--_offset-top);
|
||||
|
||||
.search-list {
|
||||
margin-top: var(--_search-list-offset);
|
||||
max-height: calc(
|
||||
100vh - var(--_offset-top) - var(--_offset-bottom) -
|
||||
var(--_search-input-height) - var(--_search-list-offset)
|
||||
);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import { Down, Search } from '@icon-park/vue-next';
|
||||
import { NSpin } from 'naive-ui';
|
||||
import { watch } from 'vue';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
provider: string;
|
||||
providers: string[];
|
||||
loading: boolean;
|
||||
}>(),
|
||||
{
|
||||
provider: '',
|
||||
loading: false,
|
||||
}
|
||||
);
|
||||
|
||||
defineEmits(['select', 'search']);
|
||||
defineEmits(['search']);
|
||||
|
||||
const provider = defineModel<string>('provider');
|
||||
const inputValue = defineModel<string>('inputValue');
|
||||
|
||||
watch(inputValue, (val) => {
|
||||
console.log(val);
|
||||
});
|
||||
const showProvider = ref(false);
|
||||
|
||||
function onSelect(site: string) {
|
||||
provider.value = site;
|
||||
showProvider.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -33,7 +35,7 @@ watch(inputValue, (val) => {
|
||||
pl-12
|
||||
gap-x-12
|
||||
w-400
|
||||
overflow-hidden
|
||||
max-w-90vw
|
||||
shadow-inner
|
||||
>
|
||||
<Search
|
||||
@@ -54,22 +56,45 @@ watch(inputValue, (val) => {
|
||||
input-reset
|
||||
@keyup.enter="$emit('search')"
|
||||
/>
|
||||
<div
|
||||
h-full
|
||||
f-cer
|
||||
justify-between
|
||||
px-12
|
||||
w-100
|
||||
class="provider"
|
||||
is-btn
|
||||
@click="$emit('select')"
|
||||
>
|
||||
<div text-h3 truncate>
|
||||
{{ provider }}
|
||||
</div>
|
||||
<div class="provider">
|
||||
|
||||
<div rel w-100 h-full px-12 rounded-inherit class="provider" is-btn>
|
||||
<div
|
||||
fx-cer
|
||||
wh-full
|
||||
justify-between
|
||||
@click="() => (showProvider = !showProvider)"
|
||||
>
|
||||
<div text-h3 truncate>
|
||||
{{ provider }}
|
||||
</div>
|
||||
|
||||
<Down />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-show="showProvider"
|
||||
abs
|
||||
top="100%"
|
||||
left-0
|
||||
w-100
|
||||
rounded-12
|
||||
shadow
|
||||
bg-white
|
||||
z-1
|
||||
overflow-hidden
|
||||
>
|
||||
<div
|
||||
v-for="site in providers"
|
||||
:key="site"
|
||||
hover:bg-theme-row
|
||||
is-btn
|
||||
@click="() => onSelect(site)"
|
||||
>
|
||||
<div text="h3 primary" hover="text-white" p-12 truncate>
|
||||
{{ site }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,8 +16,8 @@ const InnerStyle = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div p-1 rounded-16 inline-flex :class="type">
|
||||
<div bg-white rounded-12 px-8 text-10 truncate max-w-72 :class="InnerStyle">
|
||||
<div p-1 rounded-16 inline-flex w-max :class="type">
|
||||
<div w-full bg-white rounded-12 px-8 text-10 truncate :class="InnerStyle">
|
||||
{{ title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -114,13 +114,11 @@ onUnmounted(() => {
|
||||
pc:top-2
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div hidden pc:block>
|
||||
<ab-search-bar @add-bangumi="addSearchResult" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ml-auto>
|
||||
<div ml-auto fx-cer>
|
||||
<ab-search-bar mr="pc:16 10" fx-cer @add-bangumi="addSearchResult" />
|
||||
|
||||
<ab-status-bar
|
||||
:items="items"
|
||||
:running="running"
|
||||
@@ -128,10 +126,8 @@ onUnmounted(() => {
|
||||
@change-lang="changeLocale"
|
||||
/>
|
||||
</div>
|
||||
<ab-change-account v-model:show="showAccount"></ab-change-account>
|
||||
<ab-add-rss
|
||||
v-model:show="showAddRSS"
|
||||
v-model:rule="searchRule"
|
||||
></ab-add-rss>
|
||||
</div>
|
||||
|
||||
<ab-change-account v-model:show="showAccount"></ab-change-account>
|
||||
<ab-add-rss v-model:show="showAddRSS" v-model:rule="searchRule"></ab-add-rss>
|
||||
</template>
|
||||
|
||||
@@ -7,7 +7,6 @@ const { getSettingGroup } = useConfigStore();
|
||||
|
||||
const parser = getSettingGroup('rss_parser');
|
||||
|
||||
/** @ts-expect-error Incorrect order */
|
||||
const langs: RssParserLang = ['zh', 'en', 'jp'];
|
||||
|
||||
const items: SettingItem<RssParser>[] = [
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { createSharedComposable, useIntervalFn } from '@vueuse/core';
|
||||
|
||||
export const useAppInfo = createSharedComposable(() => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
const running = ref<boolean>(false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createSharedComposable, useLocalStorage } from '@vueuse/core';
|
||||
import type { User } from '#/auth';
|
||||
import type { ApiError } from '#/api';
|
||||
import { router } from '@/router';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createDiscreteApi } from 'naive-ui';
|
||||
import { createSharedComposable } from '@vueuse/core';
|
||||
|
||||
export const useMessage = createSharedComposable(() => {
|
||||
const { message } = createDiscreteApi(['message']);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import { createSharedComposable, useLocalStorage } from '@vueuse/core';
|
||||
import enUS from '@/i18n/en.json';
|
||||
import zhCN from '@/i18n/zh-CN.json';
|
||||
import type { ApiSuccess } from '#/api';
|
||||
@@ -24,7 +25,7 @@ export const useMyI18n = createSharedComposable(() => {
|
||||
});
|
||||
|
||||
watch(lang, (val) => {
|
||||
i18n.global.locale.value = val;
|
||||
i18n.global.locale.value = val as unknown as Languages;
|
||||
});
|
||||
|
||||
function changeLocale() {
|
||||
|
||||
@@ -3,6 +3,8 @@ definePage({
|
||||
name: 'Index',
|
||||
redirect: '/bangumi',
|
||||
});
|
||||
|
||||
const title = computed(() => useRoute().name);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -13,7 +15,7 @@ definePage({
|
||||
<ab-sidebar />
|
||||
|
||||
<div class="layout-content">
|
||||
<ab-page-title :title="$route.name"></ab-page-title>
|
||||
<ab-page-title :title="title"></ab-page-title>
|
||||
|
||||
<RouterView v-slot="{ Component }">
|
||||
<KeepAlive>
|
||||
@@ -27,6 +29,10 @@ definePage({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -45,7 +51,6 @@ definePage({
|
||||
|
||||
@include forMobile {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,17 @@ onActivated(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div overflow-auto mt-12 flex-grow>
|
||||
<div overflow-auto pr-10 mt-12 flex-grow>
|
||||
<div>
|
||||
<transition-group
|
||||
name="bangumi"
|
||||
tag="div"
|
||||
flex="~ wrap"
|
||||
gap="20"
|
||||
:class="{ 'justify-center': isMobile }"
|
||||
gap="10"
|
||||
pc:gap="20"
|
||||
:class="[
|
||||
{ 'justify-center': isMobile },
|
||||
isMobile ? 'grid grid-cols-3' : 'flex flex-wrap',
|
||||
]"
|
||||
>
|
||||
<ab-bangumi-card
|
||||
v-for="i in bangumi"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { watchOnce } from '@vueuse/core';
|
||||
|
||||
definePage({
|
||||
name: 'Log',
|
||||
});
|
||||
@@ -52,11 +54,14 @@ onActivated(() => {
|
||||
if (log.value) {
|
||||
backToBottom();
|
||||
} else {
|
||||
watchOnce(log, () => {
|
||||
nextTick(() => {
|
||||
backToBottom();
|
||||
});
|
||||
});
|
||||
watchOnce(
|
||||
() => log.value,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
backToBottom();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ const { url } = storeToRefs(usePlayerStore());
|
||||
|
||||
<template>
|
||||
<div overflow-auto mt-12 flex-grow>
|
||||
<template v-if="url === ''">
|
||||
<template v-if="url.length === 0">
|
||||
<div wh-full f-cer text-h1 text-primary>
|
||||
<RouterLink to="/config" hover:underline>{{
|
||||
$t('player.hit')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useClipboard, useIntervalFn } from '@vueuse/core';
|
||||
|
||||
export const useLogStore = defineStore('log', () => {
|
||||
const message = useMessage();
|
||||
const { isLoggedIn } = useAuth();
|
||||
@@ -26,8 +28,12 @@ export const useLogStore = defineStore('log', () => {
|
||||
});
|
||||
|
||||
function copy() {
|
||||
const { copy: copyLog, isSupported } = useClipboard({ source: log });
|
||||
if (isSupported) {
|
||||
const { copy: copyLog, isSupported } = useClipboard({
|
||||
source: log.value,
|
||||
legacy: true,
|
||||
});
|
||||
|
||||
if (isSupported.value) {
|
||||
copyLog();
|
||||
message.success(t('notify.copy_success'));
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
|
||||
type MediaPlayerType = 'jump' | 'iframe';
|
||||
|
||||
export const usePlayerStore = defineStore('player', () => {
|
||||
const types = ref<MediaPlayerType[]>(['jump', 'iframe']);
|
||||
const type = useLocalStorage<MediaPlayerType>('media-player-type', 'jump');
|
||||
const url = useLocalStorage<string>('media-player-url', '');
|
||||
const url = useLocalStorage('media-player-url', '');
|
||||
|
||||
return {
|
||||
types,
|
||||
|
||||
@@ -1,74 +1,38 @@
|
||||
import { ref } from 'vue';
|
||||
import { EMPTY, Subject, debounceTime, switchMap, tap } from 'rxjs';
|
||||
import type { BangumiRule, SearchResult } from '#/bangumi';
|
||||
|
||||
export const useSearchStore = defineStore('search', () => {
|
||||
const bangumiList = ref<SearchResult[]>([]);
|
||||
const inputValue = ref<string>('');
|
||||
|
||||
const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
|
||||
const provider = ref<string>(providers.value[0]);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const {
|
||||
keyword,
|
||||
provider,
|
||||
open: openSearch,
|
||||
close: closeSearch,
|
||||
data: searchData,
|
||||
status,
|
||||
} = apiSearch.get();
|
||||
|
||||
const input$ = new Subject<string>();
|
||||
provider.value = providers.value[0];
|
||||
|
||||
watch(inputValue, (input) => {
|
||||
input$.next(input);
|
||||
loading.value = !!input;
|
||||
});
|
||||
const loading = computed(() => status.value !== 'CLOSED');
|
||||
|
||||
function getProviders() {
|
||||
apiSearch.getProvider().then((res) => {
|
||||
providers.value = res;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* - 输入中 debounce 600ms 后触发搜索
|
||||
* - 按回车或点击搜索 icon 按钮后触发搜索
|
||||
* - 切换 provider 源站时触发搜索
|
||||
*/
|
||||
|
||||
const bangumiInfo$ = input$
|
||||
.pipe(
|
||||
debounceTime(600),
|
||||
// switchMap 把输入 keyword 查询为 bangumiInfo$ 流,多次输入自动取消并停止前一次查询
|
||||
switchMap((input: string) => {
|
||||
// 有输入更新后清理之前的搜索结果
|
||||
bangumiList.value = [];
|
||||
return input ? apiSearch.get(input, provider.value) : EMPTY;
|
||||
}),
|
||||
tap((bangumi: BangumiRule) => {
|
||||
const result: SearchResult = {
|
||||
order: bangumiList.value.length + 1,
|
||||
value: bangumi,
|
||||
};
|
||||
bangumiList.value.push(result);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
function onSearch() {
|
||||
input$.next(inputValue.value);
|
||||
async function getProviders() {
|
||||
providers.value = await apiSearch.getProvider();
|
||||
provider.value = providers.value[0];
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
inputValue.value = '';
|
||||
bangumiList.value = [];
|
||||
keyword.value = '';
|
||||
}
|
||||
|
||||
return {
|
||||
input$,
|
||||
bangumiInfo$,
|
||||
inputValue,
|
||||
keyword,
|
||||
loading,
|
||||
provider,
|
||||
providers,
|
||||
bangumiList,
|
||||
searchData,
|
||||
|
||||
onSearch,
|
||||
clearSearch,
|
||||
getProviders,
|
||||
openSearch,
|
||||
closeSearch,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,62 +1,91 @@
|
||||
import type { UnionToTuple } from '#/utils';
|
||||
import type { TupleToUnion } from './utils';
|
||||
|
||||
/** 下载方式 */
|
||||
export type DownloaderType = ['qbittorrent'];
|
||||
/** rss parser 源 */
|
||||
export type RssParserType = ['mikan'];
|
||||
/** rss parser 方法 */
|
||||
export type RssParserMethodType = ['tmdb', 'mikan', 'parser'];
|
||||
/** rss parser 语言 */
|
||||
export type RssParserLang = ['zh', 'en', 'jp'];
|
||||
/** 重命名方式 */
|
||||
export type RenameMethod = ['normal', 'pn', 'advance', 'none'];
|
||||
/** 代理类型 */
|
||||
export type ProxyType = ['http', 'https', 'socks5'];
|
||||
/** 通知类型 */
|
||||
export type NotificationType = ['telegram', 'server-chan', 'bark', 'wecom'];
|
||||
/** OpenAI Model List */
|
||||
export type OpenAIModel = ['gpt-3.5-turbo'];
|
||||
/** OpenAI API Type */
|
||||
export type OpenAIType = ['openai', 'azure'];
|
||||
|
||||
export interface Program {
|
||||
rss_time: number;
|
||||
rename_time: number;
|
||||
webui_port: number;
|
||||
}
|
||||
|
||||
export interface Downloader {
|
||||
type: TupleToUnion<DownloaderType>;
|
||||
host: string;
|
||||
username: string;
|
||||
password: string;
|
||||
path: string;
|
||||
ssl: boolean;
|
||||
}
|
||||
export interface RssParser {
|
||||
enable: boolean;
|
||||
type: TupleToUnion<RssParserType>;
|
||||
token: string;
|
||||
custom_url: string;
|
||||
filter: Array<string>;
|
||||
language: TupleToUnion<RssParserLang>;
|
||||
parser_type: TupleToUnion<RssParserMethodType>;
|
||||
}
|
||||
export interface BangumiManage {
|
||||
enable: boolean;
|
||||
eps_complete: boolean;
|
||||
rename_method: TupleToUnion<RenameMethod>;
|
||||
group_tag: boolean;
|
||||
remove_bad_torrent: boolean;
|
||||
}
|
||||
export interface Log {
|
||||
debug_enable: boolean;
|
||||
}
|
||||
export interface Proxy {
|
||||
enable: boolean;
|
||||
type: TupleToUnion<ProxyType>;
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
export interface Notification {
|
||||
enable: boolean;
|
||||
type: 'telegram' | 'server-chan' | 'bark' | 'wecom';
|
||||
token: string;
|
||||
chat_id: string;
|
||||
}
|
||||
export interface ExperimentalOpenAI {
|
||||
enable: boolean;
|
||||
api_key: string;
|
||||
api_base: string;
|
||||
model: TupleToUnion<OpenAIModel>;
|
||||
// azure
|
||||
api_type: TupleToUnion<OpenAIType>;
|
||||
api_version?: string;
|
||||
deployment_id?: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
program: {
|
||||
rss_time: number;
|
||||
rename_time: number;
|
||||
webui_port: number;
|
||||
};
|
||||
downloader: {
|
||||
type: 'qbittorrent';
|
||||
host: string;
|
||||
username: string;
|
||||
password: string;
|
||||
path: string;
|
||||
ssl: boolean;
|
||||
};
|
||||
rss_parser: {
|
||||
enable: boolean;
|
||||
type: 'mikan';
|
||||
token: string;
|
||||
custom_url: string;
|
||||
filter: Array<string>;
|
||||
language: 'zh' | 'en' | 'jp';
|
||||
parser_type: 'tmdb' | 'mikan' | 'parser';
|
||||
};
|
||||
bangumi_manage: {
|
||||
enable: boolean;
|
||||
eps_complete: boolean;
|
||||
rename_method: 'normal' | 'pn' | 'advance' | 'none';
|
||||
group_tag: boolean;
|
||||
remove_bad_torrent: boolean;
|
||||
};
|
||||
log: {
|
||||
debug_enable: boolean;
|
||||
};
|
||||
proxy: {
|
||||
enable: boolean;
|
||||
type: 'http' | 'https' | 'socks5';
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
notification: {
|
||||
enable: boolean;
|
||||
type: 'telegram' | 'server-chan' | 'bark' | 'wecom';
|
||||
token: string;
|
||||
chat_id: string;
|
||||
};
|
||||
experimental_openai: {
|
||||
enable: boolean;
|
||||
api_key: string;
|
||||
api_base: string;
|
||||
model: 'gpt-3.5-turbo';
|
||||
// azure
|
||||
api_type: 'openai' | 'azure';
|
||||
api_version?: string;
|
||||
deployment_id?: string;
|
||||
};
|
||||
program: Program;
|
||||
downloader: Downloader;
|
||||
rss_parser: RssParser;
|
||||
bangumi_manage: BangumiManage;
|
||||
log: Log;
|
||||
proxy: Proxy;
|
||||
notification: Notification;
|
||||
experimental_openai: ExperimentalOpenAI;
|
||||
}
|
||||
|
||||
export const initConfig: Config = {
|
||||
@@ -117,33 +146,3 @@ export const initConfig: Config = {
|
||||
deployment_id: '',
|
||||
},
|
||||
};
|
||||
|
||||
type getItem<T extends keyof Config> = Pick<Config, T>[T];
|
||||
|
||||
export type Program = getItem<'program'>;
|
||||
export type Downloader = getItem<'downloader'>;
|
||||
export type RssParser = getItem<'rss_parser'>;
|
||||
export type BangumiManage = getItem<'bangumi_manage'>;
|
||||
export type Log = getItem<'log'>;
|
||||
export type Proxy = getItem<'proxy'>;
|
||||
export type Notification = getItem<'notification'>;
|
||||
export type ExperimentalOpenAI = getItem<'experimental_openai'>;
|
||||
|
||||
/** 下载方式 */
|
||||
export type DownloaderType = UnionToTuple<Downloader['type']>;
|
||||
/** rss parser 源 */
|
||||
export type RssParserType = UnionToTuple<RssParser['type']>;
|
||||
/** rss parser 方法 */
|
||||
export type RssParserMethodType = UnionToTuple<RssParser['parser_type']>;
|
||||
/** rss parser 语言 */
|
||||
export type RssParserLang = UnionToTuple<RssParser['language']>;
|
||||
/** 重命名方式 */
|
||||
export type RenameMethod = UnionToTuple<BangumiManage['rename_method']>;
|
||||
/** 代理类型 */
|
||||
export type ProxyType = UnionToTuple<Proxy['type']>;
|
||||
/** 通知类型 */
|
||||
export type NotificationType = UnionToTuple<Notification['type']>;
|
||||
/** OpenAI Model List */
|
||||
export type OpenAIModel = UnionToTuple<ExperimentalOpenAI['model']>;
|
||||
/** OpenAI API Type */
|
||||
export type OpenAIType = UnionToTuple<ExperimentalOpenAI['api_type']>;
|
||||
|
||||
193
webui/types/dts/auto-imports.d.ts
vendored
193
webui/types/dts/auto-imports.d.ts
vendored
@@ -15,56 +15,32 @@ declare global {
|
||||
const apiRSS: typeof import('../../src/api/rss')['apiRSS']
|
||||
const apiSearch: typeof import('../../src/api/search')['apiSearch']
|
||||
const assert: typeof import('vitest')['assert']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const axios: typeof import('../../src/utils/axios')['axios']
|
||||
const beforeAll: typeof import('vitest')['beforeAll']
|
||||
const beforeEach: typeof import('vitest')['beforeEach']
|
||||
const chai: typeof import('vitest')['chai']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||
const createPinia: typeof import('pinia')['createPinia']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const defineLoader: typeof import('vue-router/auto')['defineLoader']
|
||||
const definePage: typeof import('unplugin-vue-router/runtime')['_definePage']
|
||||
const defineStore: typeof import('pinia')['defineStore']
|
||||
const describe: typeof import('vitest')['describe']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const expect: typeof import('vitest')['expect']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const it: typeof import('vitest')['it']
|
||||
const logicAnd: typeof import('@vueuse/core')['logicAnd']
|
||||
const logicNot: typeof import('@vueuse/core')['logicNot']
|
||||
const logicOr: typeof import('@vueuse/core')['logicOr']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
const mapState: typeof import('pinia')['mapState']
|
||||
@@ -78,37 +54,20 @@ declare global {
|
||||
const onBeforeRouteUpdate: typeof import('vue-router/auto')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
||||
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
@@ -116,188 +75,36 @@ declare global {
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||
const suite: typeof import('vitest')['suite']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const test: typeof import('vitest')['test']
|
||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useApi: typeof import('../../src/hooks/useApi')['useApi']
|
||||
const useAppInfo: typeof import('../../src/hooks/useAppInfo')['useAppInfo']
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useAuth: typeof import('../../src/hooks/useAuth')['useAuth']
|
||||
const useBangumiStore: typeof import('../../src/store/bangumi')['useBangumiStore']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||
const useBreakpointQuery: typeof import('../../src/hooks/useBreakpointQuery')['useBreakpointQuery']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useClamp: typeof import('@vueuse/core')['useClamp']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfigStore: typeof import('../../src/store/config')['useConfigStore']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useImage: typeof import('@vueuse/core')['useImage']
|
||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useLogStore: typeof import('../../src/store/log')['useLogStore']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||
const useMessage: typeof import('../../src/hooks/useMessage')['useMessage']
|
||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useMyI18n: typeof import('../../src/hooks/useMyI18n')['useMyI18n']
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
||||
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||
const usePlayerStore: typeof import('../../src/store/player')['usePlayerStore']
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const useProgramStore: typeof import('../../src/store/program')['useProgramStore']
|
||||
const useRSSStore: typeof import('../../src/store/rss')['useRSSStore']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRoute: typeof import('vue-router/auto')['useRoute']
|
||||
const useRouter: typeof import('vue-router/auto')['useRouter']
|
||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||
const useSearchStore: typeof import('../../src/store/search')['useSearchStore']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
const vi: typeof import('vitest')['vi']
|
||||
const vitest: typeof import('vitest')['vitest']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchArray: typeof import('@vueuse/core')['watchArray']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
||||
|
||||
1
webui/types/dts/components.d.ts
vendored
1
webui/types/dts/components.d.ts
vendored
@@ -19,6 +19,7 @@ declare module '@vue/runtime-core' {
|
||||
AbContainer: typeof import('./../../src/components/ab-container.vue')['default']
|
||||
AbEditRule: typeof import('./../../src/components/ab-edit-rule.vue')['default']
|
||||
AbFoldPanel: typeof import('./../../src/components/ab-fold-panel.vue')['default']
|
||||
AbImage: typeof import('./../../src/components/ab-image.vue')['default']
|
||||
AbLabel: typeof import('./../../src/components/ab-label.vue')['default']
|
||||
AbPageTitle: typeof import('./../../src/components/basic/ab-page-title.vue')['default']
|
||||
AbPopup: typeof import('./../../src/components/ab-popup.vue')['default']
|
||||
|
||||
@@ -1,39 +1 @@
|
||||
/**
|
||||
* 将联合类型转为对应的交叉函数类型
|
||||
* @template U 联合类型
|
||||
*/
|
||||
export type UnionToInterFunction<U> = (
|
||||
U extends any ? (k: () => U) => void : never
|
||||
) extends (k: infer I) => void
|
||||
? I
|
||||
: never;
|
||||
|
||||
/**
|
||||
* 获取联合类型中的最后一个类型
|
||||
* @template U 联合类型
|
||||
*/
|
||||
export type GetUnionLast<U> = UnionToInterFunction<U> extends { (): infer A }
|
||||
? A
|
||||
: never;
|
||||
|
||||
/**
|
||||
* 在元组类型中前置插入一个新的类型(元素);
|
||||
* @template Tuple 元组类型
|
||||
* @template E 新的类型
|
||||
*/
|
||||
export type Prepend<Tuple extends any[], E> = [E, ...Tuple];
|
||||
|
||||
/**
|
||||
* 联合类型转元组类型;
|
||||
* @template Union 联合类型
|
||||
* @template T 初始元组类型
|
||||
* @template Last 传入联合类型中的最后一个类型(元素),自动生成,内部使用
|
||||
*/
|
||||
export type UnionToTuple<
|
||||
Union,
|
||||
T extends any[] = [],
|
||||
Last = GetUnionLast<Union>
|
||||
> = {
|
||||
0: T;
|
||||
1: UnionToTuple<Exclude<Union, Last>, Prepend<T, Last>>;
|
||||
}[[Union] extends [never] ? 0 : 1];
|
||||
export type TupleToUnion<T extends any[]> = T[number];
|
||||
|
||||
@@ -25,14 +25,7 @@ export default defineConfig(({ mode }) => ({
|
||||
}),
|
||||
UnoCSS(),
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
'vitest',
|
||||
'pinia',
|
||||
'@vueuse/core',
|
||||
VueRouterAutoImports,
|
||||
'vue-i18n',
|
||||
],
|
||||
imports: ['vue', 'vitest', 'pinia', VueRouterAutoImports, 'vue-i18n'],
|
||||
dts: 'types/dts/auto-imports.d.ts',
|
||||
dirs: ['src/api', 'src/store', 'src/hooks', 'src/utils'],
|
||||
}),
|
||||
@@ -88,6 +81,7 @@ export default defineConfig(({ mode }) => ({
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
additionalData: '@import "./src/style/mixin.scss";',
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user