webui: add rss manage function.

This commit is contained in:
EstrellaXD
2023-09-05 00:37:36 +08:00
parent 20c3c42f8a
commit fdd67697bd
17 changed files with 185 additions and 107 deletions

View File

@@ -30,6 +30,15 @@ async def add_rss(rss: RSSItem, current_user=Depends(get_current_user)):
return u_response(result)
@router.post(path="/enable/many", response_model=APIResponse)
async def enable_many_rss(rss_ids: list[int], current_user=Depends(get_current_user)):
if not current_user:
raise UNAUTHORIZED
with RSSEngine() as engine:
result = engine.enable_list(rss_ids)
return u_response(result)
@router.delete(path="/delete/{rss_id}", response_model=APIResponse)
async def delete_rss(rss_id: int, current_user=Depends(get_current_user)):
if not current_user:
@@ -47,6 +56,15 @@ async def delete_rss(rss_id: int, current_user=Depends(get_current_user)):
)
@router.post(path="/delete/many", response_model=APIResponse)
async def delete_many_rss(rss_ids: list[int], current_user=Depends(get_current_user)):
if not current_user:
raise UNAUTHORIZED
with RSSEngine() as engine:
result = engine.delete_list(rss_ids)
return u_response(result)
@router.patch(path="/disable/{rss_id}", response_model=APIResponse)
async def disable_rss(rss_id: int, current_user=Depends(get_current_user)):
if not current_user:
@@ -69,16 +87,8 @@ async def disable_many_rss(rss_ids: list[int], current_user=Depends(get_current_
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 失败。"},
)
result = engine.disable_list(rss_ids)
return u_response(result)
@router.patch(path="/update/{rss_id}", response_model=APIResponse)

View File

@@ -44,6 +44,17 @@ class RSSDatabase:
self.session.refresh(db_data)
return True
def enable(self, _id: int):
statement = select(RSSItem).where(RSSItem.id == _id)
db_data = self.session.exec(statement).first()
if not db_data:
return False
db_data.enabled = True
self.session.add(db_data)
self.session.commit()
self.session.refresh(db_data)
return True
def disable(self, _id: int):
statement = select(RSSItem).where(RSSItem.id == _id)
db_data = self.session.exec(statement).first()

View File

@@ -62,6 +62,32 @@ class RSSEngine(Database):
def disable_list(self, rss_id_list: list[int]):
for rss_id in rss_id_list:
self.rss.disable(rss_id)
return ResponseModel(
status=True,
status_code=200,
msg_en="Disable RSS successfully.",
msg_zh="禁用 RSS 成功。",
)
def enable_list(self, rss_id_list: list[int]):
for rss_id in rss_id_list:
self.rss.enable(rss_id)
return ResponseModel(
status=True,
status_code=200,
msg_en="Enable RSS successfully.",
msg_zh="启用 RSS 成功。",
)
def delete_list(self, rss_id_list: list[int]):
for rss_id in rss_id_list:
self.rss.delete(rss_id)
return ResponseModel(
status=True,
status_code=200,
msg_en="Delete RSS successfully.",
msg_zh="删除 RSS 成功。",
)
def pull_rss(self, rss_item: RSSItem) -> list[Torrent]:
torrents = self._get_torrents(rss_item)

View File

@@ -38,6 +38,11 @@ export const apiRSS = {
return data!;
},
async enableMany(rss_list: number[]) {
const { data } = await axios.post<ApiSuccess>(`api/v1/rss/enable/many`, rss_list);
return data!;
},
async refreshAll() {
const { data } = await axios.get<ApiSuccess>('api/v1/rss/refresh/all');
return data!;

View File

@@ -65,6 +65,7 @@ defineEmits(['click']);
<div text-h3 truncate>{{ name }}</div>
<ab-tag
:title="`Season ${season}`"
type="primary"
/>
</div>
</div>
@@ -90,6 +91,7 @@ defineEmits(['click']);
<div flex space-x-8px>
<ab-tag
:title="`Season ${season}`"
type="primary"
/>
<ab-tag
v-if="group !== ''"
@@ -99,7 +101,7 @@ defineEmits(['click']);
</div>
</div>
</div>
<ab-add round="true" type="medium" @click="()=> $emit('click')"/>
<ab-add :round="true" type="medium" @click="()=> $emit('click')"/>
</div>
</div>
</template>

View File

@@ -10,11 +10,13 @@ withDefaults(
parser: string;
}>(),
{
enable: false,
aggregate: false,
}
);
const select = ref(false);
defineEmits(['on-select']);
const checked = ref(false);
</script>
<template>
@@ -22,8 +24,9 @@ const select = ref(false);
<div class="left-side" flex space-x-40px>
<ab-checkbox
small
:model-value="select"
@update:model-value="select = $event"
:model-value="checked"
@update:model-value="checked = $event"
@click="() => $emit('on-select')"
/>
<div w-200px text-h3 truncate>{{ name }}</div>
<div w-300px text-h3 truncate>{{ url }}</div>

View File

@@ -14,8 +14,8 @@ const props = withDefaults(
);
const emit = defineEmits(['update:value', 'click-search']);
const { site, providers, bangumiInfo$} = storeToRefs(useSearchStore());
const { getProviders, onInput } = useSearchStore();
const {site, providers } = storeToRefs(useSearchStore());
const {getProviders} = useSearchStore();
onMounted(() => {
getProviders();
@@ -60,8 +60,7 @@ function onSearch() {
:value="value"
:placeholder="placeholder"
input-reset
@keyup.enter="onInput"
@input="onInput"
@keyup.enter="onSearch"
/>
<div
h-full
@@ -104,15 +103,18 @@ function onSearch() {
</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>
<!-- <div -->
<!-- v-if="bangumiInfo$" -->
<!-- abs top-84px left-200px z-98> -->
<!-- <ab-bangumi-card-->
<!-- v-for="i in bangumiInfo$" -->
<!-- :key="i.id"-->
<!-- :poster="i.poster_link ?? ''" -->
<!-- :name="i.official_title" -->
<!-- :season="i.season" -->
<!-- :group="i.group_name" -->
<!-- /> -->
<!-- </div> -->
</template>
<style lang="scss" scoped>

View File

@@ -15,7 +15,6 @@ const props = withDefaults(
items: Array<SelectItem | string>;
}>(),
{
items: ['test1', 'test2'],
}
);

View File

@@ -58,6 +58,8 @@ const items = [
},
];
const onSearchFocus = ref(false);
onBeforeMount(() => {
onUpdate();
});
@@ -68,14 +70,14 @@ onUnmounted(() => {
</script>
<template>
<div h-60px bg-theme-row text-white rounded-12px fx-cer px-24px>
<div h-60px bg-theme-row text-white rounded-16px fx-cer px-24px>
<div flex space-x-16px>
<div fx-cer space-x-16px>
<img src="/images/logo-light.svg" alt="favicon" wh-24px/>
<img src="/images/AutoBangumi.svg" alt="AutoBangumi" h-24px rel top-2px/>
<img v-show="onSearchFocus === false" src="/images/AutoBangumi.svg" alt="AutoBangumi" h-24px rel top-2px/>
</div>
<ab-search v-model:value="search"/>
<ab-search/>
</div>
<div ml-auto>

View File

@@ -67,7 +67,8 @@
"url": "Url",
"status": "Status",
"delete": "Delete",
"disable": "Disable"
"disable": "Disable",
"enable": "Enable",
},
"player": {
"hit": "Please set up the media player"

View File

@@ -68,6 +68,7 @@
"status": "状态",
"delete": "删除",
"disable": "禁用",
"enable": "启用",
},
"player": {
"hit": "请设置媒体播放器地址"

View File

@@ -22,6 +22,7 @@ definePage({
:poster="i.poster_link ?? ''"
:name="i.official_title"
:season="i.season"
type="primary"
@click="() => openEditPopup(i)"
></ab-bangumi-card>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
const {rss, selectedRSS} = storeToRefs(useRSSStore());
const {getAll, deleteSelected, disableSelected, appendSelected} = useRSSStore();
const {getAll, deleteSelected, disableSelected, enableSelected, handleCheckboxClicked} = useRSSStore();
onActivated(() => {
getAll();
@@ -36,16 +36,18 @@ definePage({
:url="i.url"
:enable="i.enabled"
:parser="i.parser"
:aggregate="i.aggregate">
:aggregate="i.aggregate"
@on-select="handleCheckboxClicked(i.id)"
>
</ab-rss-item>
</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 v-if="selectedRSS.length > 0">
<div line my-12px></div>
<div flex="~ justify-end" space-x-10px>
<ab-button @click="enableSelected">{{ $t('rss.enable') }}</ab-button>
<ab-button @click="disableSelected">{{ $t('rss.disable') }}</ab-button>
<ab-button class="type-warn" @click="deleteSelected">{{ $t('rss.delete') }}</ab-button>
</div>
</div>
</ab-container>
</div>

View File

@@ -1,24 +1,28 @@
import type { RSS } from '#/rss';
import type { ApiSuccess } from '#/api';
import type {RSS} from '#/rss';
import type {ApiSuccess} from '#/api';
export const useRSSStore = defineStore('rss', () => {
const message = useMessage();
const rss = ref<RSS[]>();
const selectedRSS= ref<number[]>([]);
const selectedRSS = ref<number[]>([]);
const { execute: getAll, onResult: onRSSResult } = useApi(
const {execute: getAll, onResult: onRSSResult} = useApi(
apiRSS.get
);
const { execute: updateRSS, onResult: onUpdateRSSResult } = useApi(
const {execute: updateRSS, onResult: onUpdateRSSResult} = useApi(
apiRSS.update
);
const { execute: disableRSS, onResult: onDisableRSSResult} = useApi(
const {execute: disableRSS, onResult: onDisableRSSResult} = useApi(
apiRSS.disableMany
);
const { execute: deleteRSS, onResult: onDeleteRSSResult } = useApi(
const {execute: deleteRSS, onResult: onDeleteRSSResult} = useApi(
apiRSS.deleteMany
);
const {execute: enableRSS, onResult: onEnableRSSResult} = useApi(
apiRSS.enableMany
);
onRSSResult((res) => {
function sort(arr: RSS[]) {
@@ -43,18 +47,28 @@ export const useRSSStore = defineStore('rss', () => {
deleteRSS(selectedRSS.value);
}
function enableSelected() {
enableRSS(selectedRSS.value);
}
function actionSuccess(apiRes: ApiSuccess) {
message.success(apiRes.msg_en);
refresh();
}
function appendSelected(id: number) {
selectedRSS.value.push(id);
function handleCheckboxClicked(id: number) {
if (selectedRSS.value.includes(id)) {
// delete id in list
selectedRSS.value = selectedRSS.value.filter((e) => e !== id);
} else {
selectedRSS.value.push(id)
}
}
onUpdateRSSResult(actionSuccess);
onDeleteRSSResult(actionSuccess);
onDisableRSSResult(actionSuccess)
onDisableRSSResult(actionSuccess);
onEnableRSSResult(actionSuccess);
return {
rss,
@@ -63,6 +77,7 @@ export const useRSSStore = defineStore('rss', () => {
selectedRSS,
disableSelected,
deleteSelected,
appendSelected,
enableSelected,
handleCheckboxClicked,
};
});

View File

@@ -10,7 +10,7 @@ import type {BangumiRule} from "#/bangumi";
export const useSearchStore = defineStore('search', () => {
const input$ = new Subject<string>();
const onInput = (e: Event) => input$.next(e.target);
// const onInput = (e: Event) => input$.next(e.target);
const providers = ref<string[]>(['mikan', 'dmhy', 'nyaa']);
const site = ref<string>('mikan');
@@ -24,30 +24,29 @@ export const useSearchStore = defineStore('search', () => {
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);
});
// 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,

View File

@@ -18,7 +18,6 @@ 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']
AbInfoCard: typeof import('./../../src/components/ab-info-card.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']

View File

@@ -1,8 +1,8 @@
import {
defineConfig,
presetAttributify,
presetIcons,
presetUno,
defineConfig,
presetAttributify,
presetIcons,
presetUno,
} from 'unocss';
import presetRemToPx from '@unocss/preset-rem-to-px';
@@ -51,33 +51,33 @@ export default defineConfig({
shortcuts: [
[/^wh-(.*)$/, ([, t]) => `w-${t} h-${t}`],
[
'layout-container',
'wh-screen min-w-1024px min-h-768px p-16px space-y-12px flex flex-col bg-page',
],
[
'layout-main',
'flex space-x-20px overflow-hidden h-[calc(100vh_-_2_*_16px_-_60px_-_12px)]',
],
['layout-content', 'overflow-hidden h-full flex flex-col flex-1'],
[
'layout-container',
'wh-screen min-w-1024px min-h-768px p-16px space-y-12px flex flex-col bg-page',
],
[
'layout-main',
'flex space-x-20px overflow-hidden h-[calc(100vh_-_2_*_16px_-_60px_-_12px)]',
],
['layout-content', 'overflow-hidden h-full flex flex-col flex-1'],
['rel', 'relative'],
['abs', 'absolute'],
['fx-cer', 'flex items-center'],
['f-cer', 'fx-cer justify-center'],
['text-h1', 'text-24px'],
['text-h2', 'text-20px'],
['text-h3', 'text-16px'],
['text-main', 'text-12px'],
[
'ab-input',
'outline-none min-w-0 w-200px h-28px px-12px text-main text-right rounded-6px border-1 border-black shadow-inset hover:border-color-[#7A46AE]',
['rel', 'relative'],
['abs', 'absolute'],
['fx-cer', 'flex items-center'],
['f-cer', 'fx-cer justify-center'],
['text-h1', 'text-24px'],
['text-h2', 'text-20px'],
['text-h3', 'text-16px'],
['text-main', 'text-12px'],
[
'ab-input',
'outline-none min-w-0 w-200px h-28px px-12px text-main text-right rounded-6px border-1 border-black shadow-inset hover:border-color-[#7A46AE]',
],
['input-error', 'border-color-[#CA0E0E]'],
['is-btn', 'cursor-pointer select-none'],
['is-disabled', 'cursor-not-allowed select-none'],
['input-reset', 'bg-transparent min-w-0 flex-1 outline-none'],
['btn-click', 'hover:scale-110 active:scale-100'],
['line', 'w-full h-1px bg-[#DFE1EF]'],
],
['input-error', 'border-color-[#CA0E0E]'],
['is-btn', 'cursor-pointer select-none'],
['is-disabled', 'cursor-not-allowed select-none'],
['input-reset', 'bg-transparent min-w-0 flex-1 outline-none'],
['btn-click', 'hover:scale-110 active:scale-100'],
['line', 'w-full h-1px bg-[#DFE1EF]'],
],
});