mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-08 17:16:52 +08:00
feat: 番剧搜索
This commit is contained in:
@@ -3,17 +3,19 @@ import { computed, ref } from 'vue'
|
|||||||
import { SelectProps, useMessage } from 'naive-ui'
|
import { SelectProps, useMessage } from 'naive-ui'
|
||||||
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
|
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||||
import FloatLabelInput from '../../components/FloatLabelInput.vue'
|
import FloatLabelInput from '../../components/FloatLabelInput.vue'
|
||||||
import { commands, GetNormalInfoParams, SearchParams, SearchResult } from '../../bindings.ts'
|
import { commands, GetBangumiInfoParams, GetNormalInfoParams, SearchParams, SearchResult } from '../../bindings.ts'
|
||||||
import NormalSeasonPanel from './components/NormalSeasonPanel.vue'
|
import NormalSeasonPanel from './components/NormalSeasonPanel.vue'
|
||||||
import NormalSinglePanel from './components/NormalSinglePanel.vue'
|
import NormalSinglePanel from './components/NormalSinglePanel.vue'
|
||||||
import { extractBvid, extractAid } from '../../utils.tsx'
|
import { extractBvid, extractAid, extractEpId, extractSeasonId } from '../../utils.tsx'
|
||||||
import { useStore } from '../../store.ts'
|
import { useStore } from '../../store.ts'
|
||||||
|
import BangumiPanel from './components/BangumiPanel.vue'
|
||||||
|
|
||||||
export type SearchType = 'Auto' | 'Normal' | 'Bangumi' | 'Cheese'
|
export type SearchType = 'Auto' | 'Normal' | 'Bangumi'
|
||||||
|
|
||||||
const searchTypeOptions: SelectProps['options'] = [
|
const searchTypeOptions: SelectProps['options'] = [
|
||||||
{ label: '自动', value: 'Auto' },
|
{ label: '自动', value: 'Auto' },
|
||||||
{ label: '视频', value: 'Normal' },
|
{ label: '视频', value: 'Normal' },
|
||||||
|
{ label: '番剧', value: 'Bangumi' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
@@ -28,8 +30,10 @@ const searchResult = ref<SearchResult>()
|
|||||||
const searchLabel = computed(() => {
|
const searchLabel = computed(() => {
|
||||||
if (searchTypeSelected.value === 'Normal') {
|
if (searchTypeSelected.value === 'Normal') {
|
||||||
return '链接 / av... / BV...'
|
return '链接 / av... / BV...'
|
||||||
|
} else if (searchTypeSelected.value === 'Bangumi') {
|
||||||
|
return '链接 / ep... / ss...'
|
||||||
}
|
}
|
||||||
return '链接 / av... / BV... '
|
return '链接 / av... / BV... / ep... / ss...'
|
||||||
})
|
})
|
||||||
|
|
||||||
async function search(input: string, searchType: SearchType) {
|
async function search(input: string, searchType: SearchType) {
|
||||||
@@ -48,6 +52,8 @@ async function search(input: string, searchType: SearchType) {
|
|||||||
await searchAuto(input, isUrl)
|
await searchAuto(input, isUrl)
|
||||||
} else if (searchType === 'Normal') {
|
} else if (searchType === 'Normal') {
|
||||||
await searchNormal(input, isUrl)
|
await searchNormal(input, isUrl)
|
||||||
|
} else if (searchType === 'Bangumi') {
|
||||||
|
await searchBangumi(input, isUrl)
|
||||||
} else {
|
} else {
|
||||||
message.error('未知的搜索类型')
|
message.error('未知的搜索类型')
|
||||||
}
|
}
|
||||||
@@ -60,11 +66,17 @@ async function searchAuto(input: string, isUrl: boolean) {
|
|||||||
if (isUrl) {
|
if (isUrl) {
|
||||||
const bvid = extractBvid(input)
|
const bvid = extractBvid(input)
|
||||||
const aid = extractAid(input)
|
const aid = extractAid(input)
|
||||||
|
const epId = extractEpId(input)
|
||||||
|
const seasonId = extractSeasonId(input)
|
||||||
|
|
||||||
if (bvid !== undefined) {
|
if (bvid !== undefined) {
|
||||||
params = { Normal: { Bvid: bvid } }
|
params = { Normal: { Bvid: bvid } }
|
||||||
} else if (aid !== undefined) {
|
} else if (aid !== undefined) {
|
||||||
params = { Normal: { Aid: aid } }
|
params = { Normal: { Aid: aid } }
|
||||||
|
} else if (epId !== undefined) {
|
||||||
|
params = { Bangumi: { EpId: epId } }
|
||||||
|
} else if (seasonId !== undefined) {
|
||||||
|
params = { Bangumi: { SeasonId: seasonId } }
|
||||||
}
|
}
|
||||||
} else if (input.toLowerCase().startsWith('bv')) {
|
} else if (input.toLowerCase().startsWith('bv')) {
|
||||||
params = { Normal: { Bvid: input } }
|
params = { Normal: { Bvid: input } }
|
||||||
@@ -73,10 +85,20 @@ async function searchAuto(input: string, isUrl: boolean) {
|
|||||||
if (!isNaN(aid)) {
|
if (!isNaN(aid)) {
|
||||||
params = { Normal: { Aid: aid } }
|
params = { Normal: { Aid: aid } }
|
||||||
}
|
}
|
||||||
|
} else if (input.toLowerCase().startsWith('ep')) {
|
||||||
|
const epId = parseInt(input.substring(2), 10)
|
||||||
|
if (!isNaN(epId)) {
|
||||||
|
params = { Bangumi: { EpId: epId } }
|
||||||
|
}
|
||||||
|
} else if (input.toLowerCase().startsWith('ss')) {
|
||||||
|
const seasonId = parseInt(input.substring(2), 10)
|
||||||
|
if (!isNaN(seasonId)) {
|
||||||
|
params = { Bangumi: { SeasonId: seasonId } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params === undefined) {
|
if (params === undefined) {
|
||||||
message.error('解析输入失败,请输入正确的链接或ID(如 av... / BV...)')
|
message.error('解析输入失败,请输入正确的链接或ID(如 av... / BV... / ep... / ss...)')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +143,42 @@ async function searchNormal(input: string, isUrl: boolean) {
|
|||||||
searchResult.value = result.data
|
searchResult.value = result.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function searchBangumi(input: string, isUrl: boolean) {
|
||||||
|
let params: GetBangumiInfoParams | undefined
|
||||||
|
|
||||||
|
if (isUrl) {
|
||||||
|
const epId = extractEpId(input)
|
||||||
|
const seasonId = extractSeasonId(input)
|
||||||
|
if (epId !== undefined) {
|
||||||
|
params = { EpId: epId }
|
||||||
|
} else if (seasonId !== undefined) {
|
||||||
|
params = { SeasonId: seasonId }
|
||||||
|
}
|
||||||
|
} else if (input.toLowerCase().startsWith('ep')) {
|
||||||
|
const epId = parseInt(input.substring(2), 10)
|
||||||
|
if (!isNaN(epId)) {
|
||||||
|
params = { EpId: epId }
|
||||||
|
}
|
||||||
|
} else if (input.toLowerCase().startsWith('ss')) {
|
||||||
|
const seasonId = parseInt(input.substring(2), 10)
|
||||||
|
if (!isNaN(seasonId)) {
|
||||||
|
params = { SeasonId: seasonId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params === undefined) {
|
||||||
|
message.error('解析输入失败,请输入正确的链接或ID(如 ep... / ss...)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await commands.search({ Bangumi: params })
|
||||||
|
if (result.status === 'error') {
|
||||||
|
console.error(result.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
searchResult.value = result.data
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ search })
|
defineExpose({ search })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -158,6 +216,7 @@ defineExpose({ search })
|
|||||||
:normal-result="searchResult.Normal"
|
:normal-result="searchResult.Normal"
|
||||||
:ugc-season="searchResult.Normal.ugc_season" />
|
:ugc-season="searchResult.Normal.ugc_season" />
|
||||||
<NormalSinglePanel v-else-if="'Normal' in searchResult" :normal-result="searchResult.Normal" />
|
<NormalSinglePanel v-else-if="'Normal' in searchResult" :normal-result="searchResult.Normal" />
|
||||||
|
<BangumiPanel v-else-if="'Bangumi' in searchResult" :bangumi-result="searchResult.Bangumi" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
import { BangumiSearchResult, commands, EpInBangumi } from '../../../bindings.ts'
|
||||||
|
import { SelectionArea } from '@viselect/vue'
|
||||||
|
import { ref, nextTick, watch, computed } from 'vue'
|
||||||
|
import CollectionCard from './CollectionCard.vue'
|
||||||
|
import { useEpisodeCard, useEpisodeDropdown, useEpisodeSelection } from '../../../utils.tsx'
|
||||||
|
import EpisodeCard, { EpisodeInfo } from './EpisodeCard.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
bangumiResult: BangumiSearchResult
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const collectionCardShowing = ref<boolean>(false)
|
||||||
|
|
||||||
|
const { selectedIds, updateSelectedIds, unselectAll } = useEpisodeSelection()
|
||||||
|
const selectionAreaRef = ref<InstanceType<typeof SelectionArea>>()
|
||||||
|
const checkedIds = ref<Set<number>>(new Set())
|
||||||
|
|
||||||
|
const rootDivRef = ref<HTMLDivElement>()
|
||||||
|
const episodeCardRefs = ref<InstanceType<typeof EpisodeCard>[]>([])
|
||||||
|
const episodeCardRefsMap = computed<Map<number, InstanceType<typeof EpisodeCard>>>(() => {
|
||||||
|
const map = new Map<number, InstanceType<typeof EpisodeCard>>()
|
||||||
|
episodeCardRefs.value.forEach((card) => map.set(card.episodeInfo.aid, card))
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
const { dropdownX, dropdownY, dropdownShowing, dropdownOptions, showDropdown } = useEpisodeDropdown(
|
||||||
|
() => {
|
||||||
|
selectedIds.value.forEach((aid) => checkedIds.value.add(aid))
|
||||||
|
dropdownShowing.value = false
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
selectedIds.value.forEach((aid) => checkedIds.value.delete(aid))
|
||||||
|
dropdownShowing.value = false
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
showingEpisodes.value.forEach((ep) => selectedIds.value.add(ep.aid))
|
||||||
|
dropdownShowing.value = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const { downloadEpisode, checkboxChecked, handleCheckboxClick, handleContextMenu } = useEpisodeCard(
|
||||||
|
async (episodeInfo: EpisodeInfo) => {
|
||||||
|
if (episodeInfo.epId !== undefined && episodeInfo.epId !== 0) {
|
||||||
|
// 创建番剧下载任务
|
||||||
|
await downloadBangumiEpisode(episodeInfo.epId)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
await downloadNormalEpisode(episodeInfo.aid)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(episodeInfo: EpisodeInfo) => {
|
||||||
|
return checkedIds.value.has(episodeInfo.aid)
|
||||||
|
},
|
||||||
|
(episodeInfo: EpisodeInfo) => {
|
||||||
|
const checked = checkedIds.value.has(episodeInfo.aid)
|
||||||
|
if (checked) {
|
||||||
|
checkedIds.value.delete(episodeInfo.aid)
|
||||||
|
} else {
|
||||||
|
checkedIds.value.add(episodeInfo.aid)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(episodeInfo: EpisodeInfo) => {
|
||||||
|
if (selectedIds.value.has(episodeInfo.aid)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedIds.value.clear()
|
||||||
|
selectedIds.value.add(episodeInfo.aid)
|
||||||
|
const selection = selectionAreaRef.value?.selection
|
||||||
|
if (selection) {
|
||||||
|
selection.clearSelection()
|
||||||
|
selection.select(`[data-key="${episodeInfo.aid}"]`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentTabIndex = ref<number>(0)
|
||||||
|
|
||||||
|
const tabNames = computed<string[]>(() => {
|
||||||
|
const sections = props.bangumiResult.info.section
|
||||||
|
if (sections === null) {
|
||||||
|
return ['正片']
|
||||||
|
}
|
||||||
|
return ['正片', ...sections.map((section) => section.title)]
|
||||||
|
})
|
||||||
|
|
||||||
|
const showingEpisodes = computed<EpInBangumi[]>(() => {
|
||||||
|
if (currentTabIndex.value === 0) {
|
||||||
|
return props.bangumiResult.info.episodes
|
||||||
|
}
|
||||||
|
const sections = props.bangumiResult.info.section
|
||||||
|
if (sections === null || currentTabIndex.value - 1 >= sections.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return sections[currentTabIndex.value - 1].episodes
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(currentTabIndex, () => {
|
||||||
|
selectionAreaRef.value?.$el.scrollTo({ top: 0, behavior: 'instant' })
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.bangumiResult,
|
||||||
|
async () => {
|
||||||
|
const episode = props.bangumiResult.ep
|
||||||
|
if (episode === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const sections = props.bangumiResult.info.section
|
||||||
|
if (sections === null || props.bangumiResult.info.episodes.some((ep) => ep.aid === episode.aid)) {
|
||||||
|
currentTabIndex.value = 0
|
||||||
|
} else {
|
||||||
|
currentTabIndex.value = sections.findIndex((s) => s.episodes.some((ep) => ep.aid === episode.aid)) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedIds.value = new Set([episode.aid])
|
||||||
|
checkedIds.value = new Set([episode.aid])
|
||||||
|
const selection = selectionAreaRef.value?.selection
|
||||||
|
if (selection) {
|
||||||
|
selection.clearSelection()
|
||||||
|
selection.select(`[data-key="${episode.aid}"]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
if (rootDivRef.value === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetElement = rootDivRef.value.querySelector(`[data-key="${episode.aid}"]`)
|
||||||
|
if (targetElement !== null) {
|
||||||
|
targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
async function downloadCheckedEpisodes() {
|
||||||
|
for (const aid of checkedIds.value) {
|
||||||
|
const ep = showingEpisodes.value.find((ep) => ep.aid === aid)
|
||||||
|
if (ep === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (ep.link_type === null) {
|
||||||
|
await downloadBangumiEpisode(ep.ep_id)
|
||||||
|
playCardDownloadAnimation(ep.aid)
|
||||||
|
} else {
|
||||||
|
await downloadNormalEpisode(ep.aid)
|
||||||
|
playCardDownloadAnimation(ep.aid)
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadBangumiEpisode(epId: number) {
|
||||||
|
await commands.createDownloadTasks({ Bangumi: { ep_ids: [epId], info: props.bangumiResult.info } })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadNormalEpisode(aid: number) {
|
||||||
|
// 获取普通视频信息,用于创建下载任务
|
||||||
|
const getNormalInfoResult = await commands.getNormalInfo({ Aid: aid })
|
||||||
|
if (getNormalInfoResult.status === 'error') {
|
||||||
|
console.error(getNormalInfoResult.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 创建下载任务
|
||||||
|
await commands.createDownloadTasks({ Normal: { info: getNormalInfoResult.data, aid_cid_pairs: [[aid, null]] } })
|
||||||
|
}
|
||||||
|
|
||||||
|
function playCardDownloadAnimation(aid: number) {
|
||||||
|
const card = episodeCardRefsMap.value.get(aid)
|
||||||
|
if (card !== undefined) {
|
||||||
|
card.playDownloadAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-full select-none" ref="rootDivRef">
|
||||||
|
<SelectionArea
|
||||||
|
ref="selectionAreaRef"
|
||||||
|
class="selection-container flex flex-col flex-1 px-2 pt-0 overflow-auto"
|
||||||
|
:options="{ selectables: '.selectable', features: { deselectOnBlur: true } }"
|
||||||
|
@contextmenu="showDropdown"
|
||||||
|
@move="updateSelectedIds"
|
||||||
|
@start="unselectAll">
|
||||||
|
<div class="animate-pulse text-violet">左键拖动进行框选,右键打开菜单,滚轮可以滚动底部的标签</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<EpisodeCard
|
||||||
|
ref="episodeCardRefs"
|
||||||
|
v-for="ep in showingEpisodes"
|
||||||
|
:key="ep.aid"
|
||||||
|
:data-key="ep.aid"
|
||||||
|
:class="['selectable', selectedIds.has(ep.aid) ? 'selected shadow-md' : 'hover:bg-gray-1']"
|
||||||
|
:search-result="bangumiResult"
|
||||||
|
:episode="ep"
|
||||||
|
:episode-type="'Bangumi'"
|
||||||
|
:download-episode="downloadEpisode"
|
||||||
|
:checkbox-checked="checkboxChecked"
|
||||||
|
:handle-checkbox-click="handleCheckboxClick"
|
||||||
|
:handle-context-menu="handleContextMenu" />
|
||||||
|
</div>
|
||||||
|
</SelectionArea>
|
||||||
|
|
||||||
|
<n-tabs class="select-none mt-2" v-model:value="currentTabIndex" type="line" size="small" placement="bottom">
|
||||||
|
<n-tab v-for="(tabName, index) in tabNames" :key="index" :name="index" :tab="tabName" />
|
||||||
|
|
||||||
|
<template #suffix>
|
||||||
|
<n-button class="ml-auto mb-2" size="small" @click="collectionCardShowing = !collectionCardShowing">
|
||||||
|
{{ collectionCardShowing ? '隐藏合集' : '显示合集' }}
|
||||||
|
</n-button>
|
||||||
|
<n-button class="mx-2 mb-2" size="small" type="primary" @click="downloadCheckedEpisodes">下载勾选视频</n-button>
|
||||||
|
</template>
|
||||||
|
</n-tabs>
|
||||||
|
|
||||||
|
<n-collapse-transition :show="collectionCardShowing">
|
||||||
|
<CollectionCard
|
||||||
|
class="mt-0"
|
||||||
|
:title="bangumiResult.info.title"
|
||||||
|
:description="bangumiResult.info.evaluate"
|
||||||
|
:cover="bangumiResult.info.cover"
|
||||||
|
:up-name="bangumiResult.info.up_info?.uname"
|
||||||
|
:up-avatar="bangumiResult.info.up_info?.avatar"
|
||||||
|
:up-uid="bangumiResult.info.up_info?.mid" />
|
||||||
|
</n-collapse-transition>
|
||||||
|
|
||||||
|
<n-dropdown
|
||||||
|
placement="bottom-start"
|
||||||
|
trigger="manual"
|
||||||
|
:x="dropdownX"
|
||||||
|
:y="dropdownY"
|
||||||
|
:options="dropdownOptions"
|
||||||
|
:show="dropdownShowing"
|
||||||
|
:on-clickoutside="() => (dropdownShowing = false)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.selection-container .selected {
|
||||||
|
@apply bg-[rgb(204,232,255)];
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-tabs-nav__suffix) {
|
||||||
|
@apply important-border-0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { computed, inject, onMounted, onUpdated, ref } from 'vue'
|
import { computed, inject, onMounted, onUpdated, ref } from 'vue'
|
||||||
import { EpInNormal, NormalInfo, NormalSearchResult } from '../../../bindings.ts'
|
import { BangumiSearchResult, EpInBangumi, EpInNormal, NormalInfo, NormalSearchResult } from '../../../bindings.ts'
|
||||||
import SimpleCheckbox from '../../../components/SimpleCheckbox.vue'
|
import SimpleCheckbox from '../../../components/SimpleCheckbox.vue'
|
||||||
import { PhDownloadSimple, PhGoogleChromeLogo, PhQueue, PhMagnifyingGlass } from '@phosphor-icons/vue'
|
import { PhDownloadSimple, PhGoogleChromeLogo, PhQueue, PhMagnifyingGlass } from '@phosphor-icons/vue'
|
||||||
import { useDialog } from 'naive-ui'
|
import { useDialog } from 'naive-ui'
|
||||||
import PartsDialogContent from './PartsDialogContent.vue'
|
import PartsDialogContent from './PartsDialogContent.vue'
|
||||||
import { ensureHttps, isElementInViewport, playTaskToQueueAnimation } from '../../../utils.tsx'
|
import { ensureHttps, extractBvid, isElementInViewport, playTaskToQueueAnimation } from '../../../utils.tsx'
|
||||||
import { navDownloadButtonRefKey } from '../../../injection_keys.ts'
|
import { navDownloadButtonRefKey } from '../../../injection_keys.ts'
|
||||||
import { SearchType } from '../SearchPane.vue'
|
import { SearchType } from '../SearchPane.vue'
|
||||||
|
|
||||||
@@ -29,9 +29,9 @@ export type EpisodeInfo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
searchResult: NormalSearchResult
|
searchResult: NormalSearchResult | BangumiSearchResult
|
||||||
episode: NormalInfo | EpInNormal
|
episode: NormalInfo | EpInNormal | EpInBangumi
|
||||||
episodeType: 'NormalSingle' | 'NormalSeason'
|
episodeType: 'NormalSingle' | 'NormalSeason' | 'Bangumi'
|
||||||
downloadEpisode?: (episodeInfo: EpisodeInfo) => Promise<void>
|
downloadEpisode?: (episodeInfo: EpisodeInfo) => Promise<void>
|
||||||
checkboxChecked?: (episodeInfo: EpisodeInfo) => boolean
|
checkboxChecked?: (episodeInfo: EpisodeInfo) => boolean
|
||||||
handleCheckboxClick?: (episodeInfo: EpisodeInfo) => void
|
handleCheckboxClick?: (episodeInfo: EpisodeInfo) => void
|
||||||
@@ -70,6 +70,24 @@ const episodeInfo = computed<EpisodeInfo>(() => {
|
|||||||
upUid: episode.arc.author.mid,
|
upUid: episode.arc.author.mid,
|
||||||
pubTime: episode.arc.pubdate,
|
pubTime: episode.arc.pubdate,
|
||||||
}
|
}
|
||||||
|
} else if (props.episodeType === 'Bangumi') {
|
||||||
|
const episode = props.episode as EpInBangumi
|
||||||
|
const searchResult = props.searchResult as BangumiSearchResult
|
||||||
|
return {
|
||||||
|
episodeType: 'Bangumi',
|
||||||
|
aid: episode.aid,
|
||||||
|
bvid: episode.bvid ?? undefined,
|
||||||
|
epId: episode.ep_id,
|
||||||
|
href:
|
||||||
|
episode.link_type === null
|
||||||
|
? `https://www.bilibili.com/bangumi/play/ep${episode.ep_id}`
|
||||||
|
: `https://www.bilibili.com/video/${extractBvid(episode.link)}/`,
|
||||||
|
cover: episode.cover,
|
||||||
|
title: episode.show_title ?? episode.title,
|
||||||
|
upName: searchResult.info.up_info?.uname ?? '无',
|
||||||
|
upUid: searchResult.info.up_info?.mid ?? 0,
|
||||||
|
pubTime: episode.pub_time,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw new Error(`错误的 episodeType: ${props.episodeType}`)
|
throw new Error(`错误的 episodeType: ${props.episodeType}`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,6 +31,36 @@ export function extractBvid(url: string): string | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractEpId(url: string): number | undefined {
|
||||||
|
const parsedUrl = new URL(url)
|
||||||
|
const pathname = parsedUrl.pathname
|
||||||
|
const segments = pathname.split('/')
|
||||||
|
for (const segment of segments) {
|
||||||
|
if (segment.toLowerCase().startsWith('ep')) {
|
||||||
|
const epIdString = segment.substring(2)
|
||||||
|
const epId = parseInt(epIdString, 10)
|
||||||
|
if (!isNaN(epId)) {
|
||||||
|
return epId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractSeasonId(url: string): number | undefined {
|
||||||
|
const parsedUrl = new URL(url)
|
||||||
|
const pathname = parsedUrl.pathname
|
||||||
|
const segments = pathname.split('/')
|
||||||
|
for (const segment of segments) {
|
||||||
|
if (segment.toLowerCase().startsWith('ss')) {
|
||||||
|
const seasonIdString = segment.substring(2)
|
||||||
|
const seasonId = parseInt(seasonIdString, 10)
|
||||||
|
if (!isNaN(seasonId)) {
|
||||||
|
return seasonId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useEpisodeDropdown(onCheck: () => void, onUncheck: () => void, onSelectAll: () => void) {
|
export function useEpisodeDropdown(onCheck: () => void, onUncheck: () => void, onSelectAll: () => void) {
|
||||||
const dropdownX = ref<number>(0)
|
const dropdownX = ref<number>(0)
|
||||||
const dropdownY = ref<number>(0)
|
const dropdownY = ref<number>(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user