webui: update search card.

backend: update api url and rss manage api route.
This commit is contained in:
EstrellaXD
2023-09-04 20:53:25 +08:00
parent 74eea85094
commit 20c3c42f8a
11 changed files with 143 additions and 70 deletions

View File

@@ -64,6 +64,23 @@ async def disable_rss(rss_id: int, current_user=Depends(get_current_user)):
)
@router.post(path="/disable/many", response_model=APIResponse)
async def disable_many_rss(rss_ids: list[int], current_user=Depends(get_current_user)):
if not current_user:
raise UNAUTHORIZED
with RSSEngine() as engine:
if engine.disable_list(rss_ids):
return JSONResponse(
status_code=200,
content={"msg_en": "Disable RSS successfully.", "msg_zh": "禁用 RSS 成功。"},
)
else:
return JSONResponse(
status_code=406,
content={"msg_en": "Disable RSS failed.", "msg_zh": "禁用 RSS 失败。"},
)
@router.patch(path="/update/{rss_id}", response_model=APIResponse)
async def update_rss(
rss_id: int, data: RSSUpdate, current_user=Depends(get_current_user)

View File

@@ -9,7 +9,7 @@ from module.models import Bangumi
router = APIRouter(prefix="/search", tags=["search"])
@router.get("/", response_model=list[Bangumi])
@router.get("/bangumi", response_model=list[Bangumi])
async def search_torrents(
site: str = "mikan",
keywords: str = Query(None),

View File

@@ -59,6 +59,10 @@ class RSSEngine(Database):
msg_zh="RSS 添加失败。",
)
def disable_list(self, rss_id_list: list[int]):
for rss_id in rss_id_list:
self.rss.disable(rss_id)
def pull_rss(self, rss_item: RSSItem) -> list[Torrent]:
torrents = self._get_torrents(rss_item)
new_torrents = self.torrent.check_new(torrents)

View File

@@ -1,4 +1,4 @@
import type { BangumiAPI, BangumiRule, BangumiUpdate } from '#/bangumi';
import type { BangumiAPI, BangumiRule } from '#/bangumi';
import type { ApiSuccess } from '#/api';

View File

@@ -18,11 +18,21 @@ export const apiRSS = {
return data!;
},
async deleteMany(rss_list: number[]) {
const { data } = await axios.post<ApiSuccess>(`api/v1/rss/delete/many`, rss_list);
return data!;
},
async disable(rss_id: number) {
const { data } = await axios.patch<ApiSuccess>(`api/v1/rss/disable/${rss_id}`);
return data!;
},
async disableMany(rss_list: number[]) {
const { data } = await axios.post<ApiSuccess>(`api/v1/rss/disable/many`, rss_list);
return data!;
},
async update(rss_id: number, rss: RSS) {
const { data } = await axios.patch<ApiSuccess>(`api/v1/rss/update/${rss_id}`, rss);
return data!;

View File

@@ -55,7 +55,7 @@ export const apiSearch = {
get(keyword: string, site = 'mikan'): Observable<BangumiRule> {
const bangumiInfo$ = new Observable<BangumiRule>(observer => {
const eventSource = new EventSource(
`api/v1/search?site=${site}&keyword=${encodeURIComponent(keyword)}`,
`api/v1/search/bangumi?site=${site}&keywords=${encodeURIComponent(keyword)}`,
{ withCredentials: true },
);

View File

@@ -1,74 +1,32 @@
<script lang="ts" setup>
import {Search} from '@icon-park/vue-next';
import {Down, Search} from '@icon-park/vue-next';
import {ref} from 'vue';
import {Down, Up} from '@icon-park/vue-next';
import {isString} from "lodash";
import type {SelectItem} from "#/components";
import {apiSearch} from "@/api/search";
import {
Subject,
tap,
map,
switchMap,
debounceTime,
} from "rxjs";
import type {BangumiRule} from "#/bangumi";
const props = withDefaults(
defineProps<{
value?: string;
provider: string[];
placeholder?: string;
}>(),
{
value: '',
provider: ['Mikan', 'Dmhy', 'Nyaa'],
placeholder: '',
}
);
const emit = defineEmits(['update:value', 'click-search']);
const { site, providers, bangumiInfo$} = storeToRefs(useSearchStore());
const { getProviders, onInput } = useSearchStore();
const selected = ref<SelectItem | string>(
(props.provider?.[0] ?? '')
);
onMounted(() => {
getProviders();
});
const selectedProvider = computed(() => {
if (isString(selected.value)) {
return selected.value;
} else {
return selected.value.label ?? selected.value.value;
}
return site.value || '';
});
const onSelect = ref(false);
const input$ = new Subject<string>();
const onInput = (value: string) => {
input$.next(value);
};
const bangumiInfo$ = apiSearch.get('魔女之旅')
input$.pipe(
debounceTime(500),
tap((input: string) => {
console.log(input);
}),
switchMap((input: string) => {
return apiSearch.get(input, site);
}),
tap((bangumi: BangumiRule) => console.log(bangumi)),
tap((bangumi: BangumiRule) => {
console.log('bangumi', bangumi)
// set bangumi info to Search Result List
}),
).subscribe({
complete() {
}
});
function onSearch() {
emit('click-search', props.value);
}
@@ -102,7 +60,8 @@ function onSearch() {
:value="value"
:placeholder="placeholder"
input-reset
@keyup.enter="onSearch"
@keyup.enter="onInput"
@input="onInput"
/>
<div
h-full
@@ -111,10 +70,10 @@ function onSearch() {
px-12px
w-100px
is-btn
@click="() => onSelect = !onSelect"
class="provider-select"
@click="() => onSelect = !onSelect"
>
<div text-h3>
<div text-h3 truncate>
{{ selectedProvider }}
</div>
<div class="provider-select">
@@ -124,12 +83,12 @@ function onSearch() {
</div>
<div v-show="onSelect" abs top-84px left-540px w-100px rounded-12px shadow bg-white z-99 overflow-hidden>
<div
v-for="i in provider"
v-for="i in providers"
:key="i"
hover:bg-theme-row
is-btn
@click="() => {
selected = i;
site = i;
onSelect = false;
}"
@@ -139,11 +98,21 @@ function onSearch() {
text-primary
hover:text-white
p-12px
truncate
>
{{ i }}
</div>
</div>
</div>
<div abs top-84px left-200px z-98>
<ab-bangumi-card
name="name"
season=1
poster=""
group="Lilith-Raws"
type="search"
/>
</div>
</template>
<style lang="scss" scoped>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
const {rss} = storeToRefs(useRSSStore());
const {getAll, updateRSS, deleteRSS} = useRSSStore();
const {rss, selectedRSS} = storeToRefs(useRSSStore());
const {getAll, deleteSelected, disableSelected, appendSelected} = useRSSStore();
onActivated(() => {
getAll();
@@ -39,11 +39,14 @@ definePage({
:aggregate="i.aggregate">
</ab-rss-item>
</div>
<!-- <div line my-12px></div> -->
<!-- <div flex="~ justify-end" space-x-10px> -->
<!-- <ab-button @click="updateRSS">{{ $t('rss.disable') }}</ab-button> -->
<!-- <ab-button class="type-warn" @click="deleteRSS">{{ $t('rss.delete') }}</ab-button> -->
<!-- </div> -->
<div line my-12px></div>
<div text-h2>
{{ selectedRSS }}
</div>
<div flex="~ justify-end" space-x-10px>
<ab-button @click="disableSelected">{{ $t('rss.disable') }}</ab-button>
<ab-button class="type-warn" @click="deleteSelected">{{ $t('rss.delete') }}</ab-button>
</div>
</ab-container>
</div>
</template>

View File

@@ -4,6 +4,7 @@ import type { ApiSuccess } from '#/api';
export const useRSSStore = defineStore('rss', () => {
const message = useMessage();
const rss = ref<RSS[]>();
const selectedRSS= ref<number[]>([]);
const { execute: getAll, onResult: onRSSResult } = useApi(
apiRSS.get
@@ -12,10 +13,10 @@ export const useRSSStore = defineStore('rss', () => {
apiRSS.update
);
const { execute: disableRSS, onResult: onDisableRSSResult} = useApi(
apiRSS.disable
apiRSS.disableMany
);
const { execute: deleteRSS, onResult: onDeleteRSSResult } = useApi(
apiRSS.delete
apiRSS.deleteMany
);
@@ -34,11 +35,23 @@ export const useRSSStore = defineStore('rss', () => {
getAll();
}
function disableSelected() {
disableRSS(selectedRSS.value);
}
function deleteSelected() {
deleteRSS(selectedRSS.value);
}
function actionSuccess(apiRes: ApiSuccess) {
message.success(apiRes.msg_en);
refresh();
}
function appendSelected(id: number) {
selectedRSS.value.push(id);
}
onUpdateRSSResult(actionSuccess);
onDeleteRSSResult(actionSuccess);
onDisableRSSResult(actionSuccess)
@@ -47,8 +60,9 @@ export const useRSSStore = defineStore('rss', () => {
rss,
getAll,
refresh,
updateRSS,
disableRSS,
deleteRSS,
selectedRSS,
disableSelected,
deleteSelected,
appendSelected,
};
});

View File

@@ -1,2 +1,57 @@
import {
Subject,
tap,
map,
switchMap,
debounceTime,
} from "rxjs";
import type {BangumiRule} from "#/bangumi";
import type {ApiSuccess} from "#/api";
export const useSearchStore = defineStore('search', () => {
const input$ = new Subject<string>();
const onInput = (e: Event) => input$.next(e.target);
const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
const site = ref<string>('mikan');
const bangumiInfo$ = apiSearch.get('魔女之旅');
const {execute: getProviders, onResult: onGetProvidersResult} = useApi(
apiSearch.getProvider
);
onGetProvidersResult((res) => {
providers.value = res;
});
input$.pipe(
debounceTime(1000),
tap((input: string) => {
console.log('input', input)
// clear Search Result List
}),
switchMap((input: string) => apiSearch.get(input, site.value)),
tap((bangumi: BangumiRule) => console.log(bangumi)),
tap((bangumi: BangumiRule) => {
console.log('bangumi', bangumi)
// set bangumi info to Search Result List
}),
).subscribe({
complete() {
// end of stream, stop loading animation
}
}, (err) => {
console.error(err);
});
return {
onInput,
bangumiInfo$,
site,
getProviders,
providers,
};
});

View File

@@ -245,6 +245,7 @@ declare global {
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']