番剧订阅

This commit is contained in:
Rewrite0
2023-05-28 14:58:21 +08:00
parent 278f95421b
commit 3f70dd446c
13 changed files with 305 additions and 115 deletions

View File

@@ -1,12 +1,24 @@
<script setup lang="ts">
import { NMessageProvider } from 'naive-ui';
import {
NMessageProvider,
NConfigProvider,
type GlobalThemeOverrides,
} from 'naive-ui';
const theme: GlobalThemeOverrides = {
Spin: {
color: '#fff',
},
};
</script>
<template>
<Suspense>
<NMessageProvider>
<RouterView></RouterView>
</NMessageProvider>
<n-config-provider :theme-overrides="theme">
<n-message-provider>
<RouterView></RouterView>
</n-message-provider>
</n-config-provider>
</Suspense>
</template>

View File

@@ -1,7 +1,11 @@
import type { BangumiRule } from '#/bangumi';
interface Status {
status: 'Success' | 'Failed to parse link';
status: 'Success';
}
interface AnalysisError {
status: 'Failed to parse link';
}
export const apiDownload = {
@@ -10,10 +14,25 @@ export const apiDownload = {
* @param rss_link - RSS 链接
*/
async analysis(rss_link: string) {
const { data } = await axios.post<BangumiRule>('api/v1/download/analysis', {
rss_link,
});
return data;
const fetchResult = createEventHook<BangumiRule>();
const fetchError = createEventHook<AnalysisError>();
axios
.post<any>('api/v1/download/analysis', {
rss_link,
})
.then(({ data }) => {
if (data.status) {
fetchError.trigger(data as AnalysisError);
} else {
fetchResult.trigger(data as BangumiRule);
}
});
return {
onResult: fetchResult.on,
onError: fetchError.on,
};
},
/**

View File

@@ -1,14 +1,18 @@
<script lang="ts" setup>
import { NSpin } from 'naive-ui';
const props = withDefaults(
defineProps<{
type?: 'primary' | 'warn';
size?: 'big' | 'normal' | 'small';
link?: string | null;
loading?: boolean;
}>(),
{
type: 'primary',
size: 'normal',
link: null,
loading: false,
}
);
@@ -24,6 +28,17 @@ const buttonSize = computed(() => {
return 'rounded-6px w-86px h-28px text-main';
}
});
const loadingSize = computed(() => {
switch (props.size) {
case 'big':
return 'large';
case 'normal':
return 'small';
case 'small':
return 18;
}
});
</script>
<template>
@@ -36,7 +51,9 @@ const buttonSize = computed(() => {
:class="[`type-${type}`, buttonSize]"
@click="$emit('click')"
>
<slot></slot>
<n-spin :show="loading" :size="loadingSize">
<slot></slot>
</n-spin>
</Component>
</template>

2
src/components.d.ts vendored
View File

@@ -10,6 +10,7 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AbAdd: typeof import('./basic/ab-add.vue')['default']
AbAddBangumi: typeof import('./components/ab-add-bangumi.vue')['default']
AbBangumiCard: typeof import('./components/ab-bangumi-card.vue')['default']
AbButton: typeof import('./basic/ab-button.vue')['default']
AbChangeAccount: typeof import('./components/ab-change-account.vue')['default']
@@ -21,6 +22,7 @@ declare module '@vue/runtime-core' {
AbLabel: typeof import('./components/ab-label.vue')['default']
AbPageTitle: typeof import('./basic/ab-page-title.vue')['default']
AbPopup: typeof import('./components/ab-popup.vue')['default']
AbRule: typeof import('./components/ab-rule.vue')['default']
AbSearch: typeof import('./basic/ab-search.vue')['default']
AbSelect: typeof import('./basic/ab-select.vue')['default']
AbSetting: typeof import('./components/ab-setting.vue')['default']

View File

@@ -0,0 +1,126 @@
<script lang="ts" setup>
import { useMessage } from 'naive-ui';
import type { BangumiRule } from '#/bangumi';
const { getAll } = useBangumiStore();
const show = defineModel('show', { default: false });
const rss = ref('');
const message = useMessage();
const rule = ref<BangumiRule>();
const analysis = reactive({
loading: false,
next: false,
});
const loading = reactive({
collect: false,
subscribe: false,
});
watch(show, (val) => {
if (!val) {
rss.value = '';
setTimeout(() => {
analysis.next = false;
}, 300);
}
});
const analyser = async () => {
if (rss.value === '') {
message.error('Please enter the RSS link!');
} else {
try {
analysis.loading = true;
const { onError, onResult } = await apiDownload.analysis(rss.value);
onResult((data) => {
rule.value = data;
analysis.loading = false;
analysis.next = true;
console.log('rule', data);
});
onError((err) => {
message.error(err.status);
analysis.loading = false;
console.log('error', err);
});
} catch (error) {
message.error('Failed to analyser!');
}
}
};
const collect = async () => {
if (rule.value) {
try {
loading.collect = true;
const res = await apiDownload.collection(rule.value);
loading.collect = false;
if (res) {
message.success('Collect Success!');
getAll();
show.value = false;
} else {
message.error('Collect Failed!');
}
} catch (error) {
message.error('Collect Error!');
}
}
};
const subscribe = async () => {
if (rule.value) {
try {
loading.subscribe = true;
const res = await apiDownload.subscribe(rule.value);
loading.subscribe = false;
if (res) {
message.success('Subscribe Success!');
getAll();
show.value = false;
} else {
message.error('Subscribe Failed!');
}
} catch (error) {
message.error('Subscribe Error!');
}
}
};
</script>
<template>
<ab-popup v-model:show="show" title="Add Bangumi" css="w-360px">
<div space-y-12px v-if="!analysis.next">
<ab-setting
label="RSS Link"
type="input"
:prop="{
placeholder: 'Please enter the RSS link',
}"
:bottom-line="true"
v-model:data="rss"
></ab-setting>
<div flex="~ justify-end">
<ab-button size="small" :loading="analysis.loading" @click="analyser"
>Analyser</ab-button
>
</div>
</div>
<div v-else>
<ab-rule v-model:rule="rule"></ab-rule>
<div flex="~ justify-end" space-x-10px>
<ab-button size="small" :loading="loading.collect" @click="collect"
>Collect</ab-button
>
<ab-button size="small" :loading="loading.subscribe" @click="subscribe"
>Subscribe</ab-button
>
</div>
</div>
</ab-popup>
</template>

View File

@@ -1,23 +1,14 @@
<script lang="ts" setup>
import type { AbEditRuleItem, SettingItem } from '#/components';
import type { BangumiRule } from '#/bangumi';
const emit = defineEmits<{
delete: [{ id: number; deleteFile: boolean }];
apply: [item: AbEditRuleItem];
apply: [item: BangumiRule];
}>();
const show = defineModel('show', { default: false });
const item = defineModel<AbEditRuleItem>('item', {
default: () => {
return {
id: -1,
official_title: '',
year: '',
season: 1,
offset: 0,
filter: [],
};
},
const rule = defineModel<BangumiRule>('rule', {
required: true,
});
const deleteRuleDialog = ref(false);
@@ -29,69 +20,19 @@ watch(show, (val) => {
function emitDelete(deleteFile: boolean) {
emit('delete', {
id: item.value.id,
id: rule.value.id,
deleteFile,
});
}
function emitApply() {
emit('apply', item.value);
emit('apply', rule.value);
}
const items: SettingItem<AbEditRuleItem>[] = [
{
configKey: 'official_title',
label: 'Officical Ttile',
type: 'input',
prop: {
type: 'text',
},
},
{
configKey: 'year',
label: 'Year',
type: 'input',
css: 'w-72px',
prop: {
type: 'text',
},
},
{
configKey: 'season',
label: 'Season',
type: 'input',
css: 'w-72px',
prop: {
type: 'number',
},
bottomLine: true,
},
{
configKey: 'offset',
label: 'Offset',
type: 'input',
css: 'w-72px',
prop: {
type: 'number',
},
},
{
configKey: 'filter',
label: 'Exclude',
type: 'dynamic-tags',
bottomLine: true,
},
];
</script>
<template>
<ab-popup v-model:show="show" title="Edit Rule" css="w-380px">
<div space-y-12px>
<ab-setting
v-for="i in items"
:key="i.configKey"
v-bind="i"
v-model:data="item[i.configKey]"
></ab-setting>
<ab-rule v-model:rule="rule"></ab-rule>
<div fx-cer justify-end space-x-10px>
<ab-button
@@ -109,10 +50,10 @@ const items: SettingItem<AbEditRuleItem>[] = [
<div line my-8px></div>
<div fx-cer justify-center space-x-10px>
<ab-button size="small" type="warn" @click="emitDelete(true)"
<ab-button size="small" type="warn" @click="() => emitDelete(true)"
>Yes</ab-button
>
<ab-button size="small" @click="emitDelete(false)">No</ab-button>
<ab-button size="small" @click="() => emitDelete(false)">No</ab-button>
</div>
</ab-popup>
</ab-popup>

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import type { BangumiRule } from '#/bangumi';
import type { SettingItem } from '#/components';
const rule = defineModel<BangumiRule>('rule', {
required: true,
});
const items: SettingItem<BangumiRule>[] = [
{
configKey: 'official_title',
label: 'Officical Ttile',
type: 'input',
prop: {
type: 'text',
},
},
{
configKey: 'year',
label: 'Year',
type: 'input',
css: 'w-72px',
prop: {
type: 'text',
},
},
{
configKey: 'season',
label: 'Season',
type: 'input',
css: 'w-72px',
prop: {
type: 'number',
},
bottomLine: true,
},
{
configKey: 'offset',
label: 'Offset',
type: 'input',
css: 'w-72px',
prop: {
type: 'number',
},
},
{
configKey: 'filter',
label: 'Exclude',
type: 'dynamic-tags',
bottomLine: true,
},
];
</script>
<template>
<div space-y-12px>
<ab-setting
v-for="i in items"
:key="i.configKey"
v-bind="i"
v-model:data="rule[i.configKey]"
></ab-setting>
</div>
</template>

View File

@@ -16,13 +16,22 @@ withDefaults(
running: false,
}
);
defineEmits(['clickAdd']);
</script>
<template>
<Menu>
<div rel>
<div fx-cer space-x-16px>
<AddOne theme="outline" size="24" fill="#fff" is-btn btn-click />
<AddOne
theme="outline"
size="24"
fill="#fff"
is-btn
btn-click
@click="() => $emit('clickAdd')"
/>
<MenuButton bg-transparent is-btn btn-click>
<More theme="outline" size="24" fill="#fff" />

View File

@@ -1,35 +1,40 @@
<script lang="ts" setup>
import type { BangumiRule } from '#/bangumi';
import type { AbEditRuleItem } from '#/components';
const { data } = storeToRefs(useBangumiStore());
const { getAll, updateRule, removeRule } = useBangumiStore();
const editRule = reactive<{
show: boolean;
item: AbEditRuleItem;
item: BangumiRule;
}>({
show: false,
item: {
id: -1,
official_title: '',
year: '',
season: 1,
offset: 0,
added: false,
deleted: false,
dpi: '',
eps_collect: false,
filter: [],
group_name: '',
id: 0,
official_title: '',
offset: 0,
poster_link: '',
rss_link: [],
rule_name: '',
save_path: '',
season: 1,
season_raw: '',
source: null,
subtitle: '',
title_raw: '',
year: null,
},
});
function open(data: BangumiRule) {
async function open(data: BangumiRule) {
editRule.show = true;
editRule.item = {
id: data.id,
official_title: data.official_title,
year: data.year ?? '',
season: data.season,
offset: data.offset,
filter: data.filter,
};
editRule.item = data;
}
async function deleteRule({
@@ -46,11 +51,8 @@ async function deleteRule({
}
}
async function applyRule(newData: AbEditRuleItem) {
const id = newData.id;
const oldData = await apiBangumi.getRule(id);
const data = Object.assign(oldData, newData);
const res = await updateRule(data);
async function applyRule(newData: BangumiRule) {
const res = await updateRule(newData);
if (res) {
editRule.show = false;
getAll();
@@ -75,12 +77,12 @@ definePage({
:poster="i.poster_link ?? ''"
:name="i.official_title"
:season="i.season"
@click="open(i)"
@click="() => open(i)"
></ab-bangumi-card>
<AbEditRule
v-model:show="editRule.show"
:item="editRule.item"
v-model:rule="editRule.item"
@delete="deleteRule"
@apply="applyRule"
></AbEditRule>

View File

@@ -3,12 +3,12 @@
.fade {
&-enter-active,
&-leave-active {
position: absolute;
transition: opacity 0.2s ease;
}
&-enter-from,
&-leave-to {
position: absolute;
opacity: 0;
}
}

View File

@@ -7,7 +7,7 @@ import {
Logout,
MenuUnfold,
Play,
Setting,
SettingTwo,
} from '@icon-park/vue-next';
const props = withDefaults(
@@ -58,7 +58,7 @@ const items = [
},
{
id: 6,
icon: Setting,
icon: SettingTwo,
label: 'Config',
path: '/config',
},

View File

@@ -3,6 +3,7 @@ import { Me, Pause, PlayOne, Power, Refresh } from '@icon-park/vue-next';
const search = ref('');
const show = ref(false);
const showAdd = ref(false);
const { onUpdate, offUpdate, start, pause, shutdown, restart } =
useProgramStore();
@@ -59,9 +60,15 @@ onUnmounted(() => {
<ab-search v-model:value="search" />
<div ml-auto>
<ab-status-bar :items="items" :running="running"></ab-status-bar>
<ab-status-bar
:items="items"
:running="running"
@click-add="() => (showAdd = true)"
></ab-status-bar>
</div>
<ab-change-account v-model:show="show"></ab-change-account>
<ab-add-bangumi v-model:show="showAdd"></ab-add-bangumi>
</div>
</template>

View File

@@ -16,12 +16,3 @@ export interface AbSettingProps {
export type SettingItem<T> = AbSettingProps & {
configKey: keyof T;
};
export interface AbEditRuleItem {
id: number;
official_title: string;
year: string;
season: number;
offset: number;
filter: string[];
}