Skip to content

Commit 3418905

Browse files
committed
refactor: 重构image
1 parent 228a7c9 commit 3418905

9 files changed

Lines changed: 219 additions & 203 deletions

File tree

package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@
3737
"typecheck": "vue-tsc --noEmit -p ./tsconfig.json --composite false"
3838
},
3939
"dependencies": {
40-
"@delta-comic/core": "^1.3.1",
41-
"@delta-comic/db": "^1.3.1",
42-
"@delta-comic/model": "^1.3.1",
43-
"@delta-comic/plugin": "^1.3.1",
44-
"@delta-comic/request": "^1.3.1",
45-
"@delta-comic/ui": "^1.3.1",
46-
"@delta-comic/utils": "^1.3.1",
40+
"@delta-comic/core": "^1.3.2",
41+
"@delta-comic/db": "^1.3.2",
42+
"@delta-comic/model": "^1.3.2",
43+
"@delta-comic/plugin": "^1.3.2",
44+
"@delta-comic/request": "^1.3.2",
45+
"@delta-comic/ui": "^1.3.2",
46+
"@delta-comic/utils": "^1.3.2",
4747
"@pinia/colada": "^1.0.0",
4848
"@tauri-apps/api": "^2.10.1",
4949
"@vicons/antd": "^0.13.0",
@@ -69,7 +69,7 @@
6969
"@commitlint/types": "^20.5.0",
7070
"@cspell/dict-npm": "^5.2.38",
7171
"@cspell/dict-rust": "^4.1.2",
72-
"@delta-comic/vite": "^1.3.1",
72+
"@delta-comic/vite": "^1.3.2",
7373
"@semantic-release/changelog": "^6.0.3",
7474
"@semantic-release/git": "^10.0.1",
7575
"@semantic-release/github": "^12.0.6",

pnpm-lock.yaml

Lines changed: 112 additions & 100 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/layout/Default.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ defineSlots<{
2323
description(args: LayoutInject.ContentProps): any
2424
recommend(args: LayoutInject.ContentProps): any
2525
tab(args: LayoutInject.TabProps): any
26-
view(): any
26+
view(args: { item?: uni.item.Item }): any
2727
}>()
2828
2929
const $props = defineProps<{ page: uni.content.ContentPage; isR18g?: boolean }>()
@@ -76,7 +76,7 @@ const { data: shortId } = useQuery({
7676
>
7777
<ViewBox :isScrolled :scrollbar>
7878
<template #view>
79-
<slot name="view" />
79+
<slot :item="union" name="view" />
8080
</template>
8181
</ViewBox>
8282
<VanTabs

src/layout/default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export enum QueryKey {
2222
Recommends = 'layout::default::recommends',
2323
ShortId = 'layout::default::shortId'
2424
}
25-
export const createPageQueryKey = (page: uni.content.ContentPage) => ({
25+
export const createPageQueryKey = (page: uni.content.ContentPage | uni.item.Item) => ({
2626
id: page.id,
2727
ct: uni.content.ContentPage.contentPages.key.toString(page.contentType)
2828
})

src/layout/default/Actions.vue

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,18 @@
11
<script setup lang="ts">
22
import { uni } from '@delta-comic/model'
33
import { Inject } from '@delta-comic/plugin'
4-
import { useMutation, useQueryCache } from '@pinia/colada'
54
import { LikeFilled } from '@vicons/antd'
65
import { FolderOutlined, ReportGmailerrorredRound } from '@vicons/material'
7-
import { computed } from 'vue'
86
97
import FavouriteSelect from '@/components/FavouriteSelect.vue'
10-
11-
import * as LayoutInject from '../default'
8+
import { useLike } from '@/utils/content'
129
1310
const $props = defineProps<{
1411
union?: uni.item.Item
1512
page: uni.content.ContentPage
1613
}>()
1714
18-
const queryKey = computed(() => [
19-
LayoutInject.QueryKey.Detail,
20-
LayoutInject.createPageQueryKey($props.page)
21-
])
22-
23-
const queryCache = useQueryCache()
24-
const { mutate: likeItem } = useMutation({
25-
mutation: async () => (await $props.union?.like()) as void,
26-
onMutate() {
27-
const key = queryKey.value
28-
const oldItem = queryCache.getQueryData<uni.item.Item>(key)!
29-
const newItem = uni.item.Item.create({
30-
...oldItem,
31-
isLiked: !oldItem.isLiked,
32-
likeNumber: oldItem.isLiked ? (oldItem.likeNumber ?? 0) - 1 : (oldItem.likeNumber ?? 0) + 1
33-
})
34-
35-
// update the cache with the new contact
36-
queryCache.setQueryData(key, newItem)
37-
// we cancel (without refetching) all queries that depend on the contact
38-
queryCache.cancelQueries({ key })
39-
40-
// pass the old and new contact to the other hooks
41-
return { oldItem, newItem }
42-
},
43-
44-
// on both error and success
45-
onSettled(_data, _error, _vars, { newItem }) {
46-
if (newItem) {
47-
queryCache.invalidateQueries({ key: queryKey.value })
48-
}
49-
},
50-
51-
onError(err, _, { newItem, oldItem }) {
52-
const key = queryKey.value
53-
if (newItem === queryCache.getQueryData(key)) {
54-
queryCache.setQueryData(key, oldItem)
55-
}
56-
57-
window.$message.error(err.message)
58-
}
59-
})
15+
const { mutate: likeItem } = useLike()
6016
</script>
6117

6218
<template>
@@ -65,7 +21,7 @@ const { mutate: likeItem } = useMutation({
6521
padding
6622
size="27px"
6723
:modelValue="union.isLiked"
68-
@click="likeItem()"
24+
@click="likeItem(union)"
6925
:icon="LikeFilled"
7026
>
7127
{{ (union.likeNumber ?? 0) || '喜欢' }}

src/model/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { uni } from '@delta-comic/model'
2-
import type { UseQueryReturn } from '@pinia/colada'
32
import type { AudioSrc, MediaSrc, TextTrackInit } from 'vidstack'
43

54
export abstract class ContentImagePage extends uni.content.ContentPage {
6-
public abstract fetchImages: () => UseQueryReturn<uni.image.Image[]>
5+
public abstract fetchImages: (signal?: AbortSignal) => Promise<uni.image.Image[]>
76
}
87

98
export type VideoConfig = { textTrack?: TextTrackInit[] } & Exclude<MediaSrc, string | AudioSrc>[]
109
export abstract class ContentVideoPage extends uni.content.ContentPage {
11-
public abstract fetchVideo: () => UseQueryReturn<VideoConfig>
10+
public abstract fetchVideo: (signal?: AbortSignal) => Promise<VideoConfig>
1211
}

src/utils/content.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { uni } from '@delta-comic/model'
2+
import { defineMutation, useMutation, useQueryCache } from '@pinia/colada'
3+
4+
import * as LayoutInject from '../layout/default'
5+
6+
export const useLike = defineMutation(() => {
7+
const queryCache = useQueryCache()
8+
const createQueryKey = (item: uni.item.Item) => [
9+
LayoutInject.QueryKey.Detail,
10+
LayoutInject.createPageQueryKey(item)
11+
]
12+
13+
const { mutateAsync: likeItem, ...mutation } = useMutation({
14+
mutation: async (item: uni.item.Item) => await item.like(),
15+
onMutate(item) {
16+
const key = createQueryKey(item)
17+
const oldItem = queryCache.getQueryData<uni.item.Item>(key)!
18+
const newItem = uni.item.Item.create({
19+
...oldItem,
20+
isLiked: !oldItem.isLiked,
21+
likeNumber: oldItem.isLiked ? (oldItem.likeNumber ?? 0) - 1 : (oldItem.likeNumber ?? 0) + 1
22+
})
23+
24+
// update the cache with the new contact
25+
queryCache.setQueryData(key, newItem)
26+
// we cancel (without refetching) all queries that depend on the contact
27+
queryCache.cancelQueries({ key })
28+
29+
// pass the old and new contact to the other hooks
30+
return { oldItem, newItem }
31+
},
32+
33+
// on both error and success
34+
onSettled(_data, _error, item, { newItem }) {
35+
const key = createQueryKey(item)
36+
if (newItem) {
37+
queryCache.invalidateQueries({ key })
38+
}
39+
},
40+
41+
onError(err, item, { newItem, oldItem }) {
42+
const key = createQueryKey(item)
43+
if (newItem === queryCache.getQueryData(key)) {
44+
queryCache.setQueryData(key, oldItem)
45+
}
46+
47+
window.$message.error(err.message)
48+
}
49+
})
50+
return { ...mutation, likeItem }
51+
})

src/view/Image.vue

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import 'swiper/css/zoom'
66
import 'swiper/css/free-mode'
77
import { uni } from '@delta-comic/model'
88
import { Inject, useConfig } from '@delta-comic/plugin'
9-
import { SmartAbortController } from '@delta-comic/request'
10-
import { useQuery } from '@pinia/colada'
9+
import { useInfiniteQuery, useQuery } from '@pinia/colada'
1110
import { LikeOutlined } from '@vicons/antd'
1211
import { ArrowBackIosNewRound, FullscreenExitRound } from '@vicons/material'
1312
import { computedWithControl, useResizeObserver } from '@vueuse/core'
@@ -24,20 +23,27 @@ import { useRoute } from 'vue-router'
2423
import ButtonPopup from '@/components/ButtonPopup.vue'
2524
import Settings from '@/components/Settings.vue'
2625
import { imageViewConfig } from '@/config'
26+
import { createPageQueryKey } from '@/layout/default'
2727
import type { ContentImagePage } from '@/model'
28+
import { useLike } from '@/utils/content'
2829
import { useSwipeDbClick } from '@/utils/ui'
2930
30-
import type * as ImageViewInject from './image'
31-
const $props = defineProps<{ page: ContentImagePage }>()
31+
import * as LayoutInject from '../layout/default'
32+
import * as ImageViewInject from './image'
33+
34+
const $props = defineProps<{ page: ContentImagePage; union?: uni.item.Item }>()
3235
3336
const config = useConfig().$load(imageViewConfig)
3437
3538
const swiper = shallowRef<SwiperClass>()
3639
3740
const { isFullscreen } = useFullscreen()
3841
39-
const images = useQuery({})
40-
// const images = computed(() => $props.page.images.content.data.value ?? [])
42+
const imagesQuery = useQuery({
43+
key: () => [ImageViewInject.QueryKey.Images, createPageQueryKey($props.page)],
44+
query: async ({ signal }) => await $props.page.fetchImages(signal)
45+
})
46+
const images = computed(() => imagesQuery.data.value ?? [])
4147
4248
const pageOnIndex = shallowRef(0)
4349
const selectPage = shallowRef(pageOnIndex.value)
@@ -59,22 +65,26 @@ const goToSlide = (offset: 1 | -1) => {
5965
offset < 0 ? swiper.value?.slidePrev() : swiper.value?.slideNext()
6066
}
6167
}
62-
const goPrev = () => goToSlide(-1)
63-
const goNext = () => goToSlide(1)
6468
6569
const isShowMenu = shallowRef(true)
6670
6771
const { handleTouchend, handleTouchmove, handleTouchstart, handleDbTap } = useSwipeDbClick(() => {
6872
isShowMenu.value = !isShowMenu.value
6973
})
7074
71-
const nowEp = computed(() =>
72-
$props.page.eps.content.data.value?.find(v => v.index === $props.page.ep)
73-
)
75+
const { likeItem } = useLike()
7476
77+
const queryEps = useInfiniteQuery({
78+
key: () => [LayoutInject.QueryKey.Ep, LayoutInject.createPageQueryKey($props.page)],
79+
query: async ({ signal, pageParam }) => await $props.page.fetchEps(pageParam, signal),
80+
initialPageParam: $props.page.fetchEps.initialPageParam,
81+
getNextPageParam: lp => lp.nextPage
82+
})
83+
const eps = computed(() => queryEps.data.value?.pages.flat() ?? [])
84+
const nowEp = computed(() => eps.value?.find(v => v.id === $props.page.ep))
7585
const $route = useRoute()
7686
const nowEpId = $route.params.ep.toString()
77-
const handleEpSelect = (preload: uni.item.RawItem) =>
87+
const routeToContent = (preload: uni.item.RawItem) =>
7888
SharedFunction.call(
7989
'routeToContent',
8090
preload.contentType,
@@ -83,23 +93,6 @@ const handleEpSelect = (preload: uni.item.RawItem) =>
8393
uni.item.Item.create(preload)
8494
)
8595
86-
const { data: detail } = useQuery({
87-
query: ({ signal }) => $props.page.fetchDetail(signal),
88-
key: () => [LayoutInject.QueryKey.Detail, LayoutInject.createPageQueryKey($props.page)]
89-
})
90-
const union = computed(() => detail.value ?? $props.page.preload)
91-
92-
const isLiked = shallowRef(union.value?.isLiked ?? false)
93-
const likeSignal = new SmartAbortController()
94-
const handleLike = async () => {
95-
likeSignal.abort()
96-
try {
97-
union.value.like(likeSignal.signal).then(v => (isLiked.value = v))
98-
} catch (error) {
99-
console.error('liked fail')
100-
}
101-
}
102-
10396
const slides = shallowReactive<InstanceType<typeof SwiperSlide>[]>([])
10497
const freeModeHeightCache = shallowReactive(new Array<number>())
10598
const virtualShowIndex = computedWithControl<number[]>([swiper], () =>
@@ -205,14 +198,14 @@ defineSlots<{
205198
ref="imgIns"
206199
class="absolute top-0 size-full"
207200
fit="contain"
208-
:src="comic.$cover"
201+
:src="union?.$cover"
209202
v-if="isEmpty(images)"
210203
/>
211204
<div
212205
class="pointer-events-none absolute top-0 left-0 z-2 h-full w-full *:pointer-events-auto *:absolute *:top-0 *:h-full *:w-10"
213206
>
214-
<div class="left-0" @click.stop="goPrev" />
215-
<div class="right-0" @click.stop="goNext" />
207+
<div class="left-0" @click.stop="goToSlide(-1)" />
208+
<div class="right-0" @click.stop="goToSlide(1)" />
216209
</div>
217210
<AnimatePresence>
218211
<motion.div
@@ -229,7 +222,7 @@ defineSlots<{
229222
</NIcon>
230223
</NButton>
231224
<div class="flex w-1/2 flex-col text-nowrap">
232-
<span class="van-ellipsis text-[1rem]">{{ comic.title }}</span>
225+
<span class="van-ellipsis text-[1rem]">{{ union?.title }}</span>
233226
<span class="van-ellipsis ml-1 text-xs">{{ nowEp?.name }}</span>
234227
</div>
235228
<div class="flex h-full w-full items-center justify-around">
@@ -239,13 +232,14 @@ defineSlots<{
239232
:args="{ page, images, swiper, index: pageOnIndex }"
240233
/>
241234
<DcToggleIcon
235+
v-if="union"
242236
padding
243237
size="30px"
244-
v-model="isLiked"
245-
@click="handleLike"
238+
v-model="union.isLiked"
239+
@click="likeItem(union)"
246240
:icon="LikeOutlined"
247241
/>
248-
<FavouriteSelect :item="page.union.value" v-if="page.union.value" plain />
242+
<FavouriteSelect :item="union" v-if="union" plain />
249243
</div>
250244
</motion.div>
251245
<motion.div
@@ -299,7 +293,7 @@ defineSlots<{
299293
<Settings />
300294
</ButtonPopup>
301295
<ButtonPopup
302-
v-if="(page.eps.content.data.value?.length ?? 1) > 1"
296+
v-if="(eps.length ?? 1) > 1"
303297
class="flex h-[70vh] flex-col bg-black/50! backdrop-blur"
304298
>
305299
<template #button>
@@ -310,17 +304,17 @@ defineSlots<{
310304
</div>
311305
<DcList
312306
class="h-full w-full"
313-
:source="{ data: page.eps.content, isEnd: true }"
307+
:source="{ type: 'infinite', value: queryEps }"
314308
:itemHeight="40"
315309
v-slot="{ data: { item: ep, index }, height }"
316310
:data-processor="v => v.toReversed()"
317311
ref="epSelList"
318312
>
319313
<VanCell
320314
clickable
321-
@click="handleEpSelect({ ...page.union.value!.toJSON(), thisEp: ep.toJSON() })"
322-
:title="ep.name || `第${page.eps.content.data.value!.length - index}话`"
323-
:title-class="['text-white', nowEpId === ep.index && 'font-bold !text-(--p-color)']"
315+
@click="union && routeToContent({ ...union.toJSON(), thisEp: ep.toJSON() })"
316+
:title="ep.name || `第${eps.length - index}话`"
317+
:title-class="['text-white', nowEpId === ep.id && 'font-bold !text-(--p-color)']"
324318
class="flex w-full items-center bg-transparent!"
325319
:style="{ height: `${height}px !important` }"
326320
>

src/view/image.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ export interface ContentProps {
1616
image: uni.image.Image
1717
swiper: SwiperClass
1818
index: number
19-
}
19+
}
20+
export enum QueryKey {
21+
Images = 'layout::view::image'
22+
}
23+
export const createImageQueryKey = () => {}

0 commit comments

Comments
 (0)