From 3c32cb18adc33a9e1dca0ccc143c4141b3460008 Mon Sep 17 00:00:00 2001 From: czy0729 <402731062@qq.com> Date: Thu, 6 Jul 2023 07:48:28 +0800 Subject: [PATCH] =?UTF-8?q?-=20[=E5=B8=96=E5=AD=90]=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=86=E6=9D=A1=E7=9B=AE=E5=AA=92=E4=BD=93=E5=9D=97=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=95=B0=E6=8D=AE=E5=92=8C=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/render-html/a/index.tsx | 95 ++++++----- .../render-html/a/subject/index.tsx | 78 +++++++++ src/components/render-html/a/utils.tsx | 150 ++++++------------ src/components/render-html/utils.tsx | 17 +- src/stores/subject/action.ts | 68 +++++++- src/stores/subject/computed.ts | 4 +- src/stores/subject/fetch.ts | 25 ++- src/stores/subject/init.ts | 2 +- src/stores/subject/types.ts | 16 ++ src/stores/subject/utils.ts | 27 ++++ 10 files changed, 330 insertions(+), 152 deletions(-) create mode 100644 src/components/render-html/a/subject/index.tsx diff --git a/src/components/render-html/a/index.tsx b/src/components/render-html/a/index.tsx index e55c1d19d..d222f8d8c 100644 --- a/src/components/render-html/a/index.tsx +++ b/src/components/render-html/a/index.tsx @@ -2,10 +2,10 @@ * @Author: czy0729 * @Date: 2022-05-13 05:12:53 * @Last Modified by: czy0729 - * @Last Modified time: 2023-03-02 18:36:49 + * @Last Modified time: 2023-07-06 07:33:47 */ -import React from 'react' -import { observer } from 'mobx-react' +import React, { useEffect, useState } from 'react' +import { useObserver } from 'mobx-react' import { rakuenStore } from '@stores' import { matchBgmLink } from '@utils' import { Text } from '../../text' @@ -16,42 +16,59 @@ function A({ style, attrs = {}, passProps, children, onPress, ...other }: Props) const { matchLink, acSearch } = rakuenStore.setting const { href } = attrs const { route, params = {}, app } = matchBgmLink(href) || {} - const onLinkPress = () => onPress(null, href) - - let el: JSX.Element - const args = { - style, - passProps, - params, - href, - onPress, - onLinkPress - } - - if (app && route === 'Subject') { - if (acSearch) el = getACSearch(args) - } else if (matchLink) { - if (route === 'Subject') { - el = getSubject(args) - } else if (route === 'Topic') { - if (params?.topicId !== 'group/350677') el = getTopic(args) - } else if (route === 'Mono') { - el = getMono(args) + + const [el, setEl] = useState(null) + useEffect(() => { + const onLinkPress = () => onPress(null, href) + const args = { + style, + passProps, + params, + href, + onPress, + onLinkPress } - } - if (el) return el - - return ( - - {filterChildren(children)} - - ) + + ;(async () => { + if (app && route === 'Subject') { + if (acSearch) setEl(getACSearch(args)) + return + } + + if (matchLink) { + if (route === 'Subject') { + setEl(await getSubject(args, setEl)) + return + } + + if (route === 'Topic') { + if (params?.topicId !== 'group/350677') setEl(await getTopic(args)) + return + } + + if (route === 'Mono') { + setEl(await getMono(args)) + } + } + })() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return useObserver(() => { + if (el) return el + + return ( + onPress(null, href)} + > + {filterChildren(children)} + + ) + }) } -export default observer(A) +export default A diff --git a/src/components/render-html/a/subject/index.tsx b/src/components/render-html/a/subject/index.tsx new file mode 100644 index 000000000..c50b555d1 --- /dev/null +++ b/src/components/render-html/a/subject/index.tsx @@ -0,0 +1,78 @@ +/* + * @Author: czy0729 + * @Date: 2023-07-06 07:36:09 + * @Last Modified by: czy0729 + * @Last Modified time: 2023-07-06 07:45:32 + */ +import React from 'react' +import { View } from 'react-native' +import { observer } from 'mobx-react' +import { _, systemStore } from '@stores' +import { HTMLDecode } from '@utils' +import { Touchable } from '../../../touchable' +import { Flex } from '../../../flex' +import { Text } from '../../../text' +import { Iconfont } from '../../../iconfont' +import { Cover } from '../../../cover' +import Rank from '../rank' +import { memoStyles } from '../styles' + +function Subject({ + text, + href, + image, + name, + name_cn, + rating, + rank, + air_date, + onLinkPress +}) { + const styles = memoStyles() + const top = HTMLDecode(name_cn || name || text || '') + const bottom = HTMLDecode( + text !== top && text !== href ? text : name || name_cn || '' + ) + const showScore = !systemStore.setting.hideScore && !!rating?.score + const showBottom = bottom && bottom !== top + return ( + + + + + + + {top} + {!!air_date && air_date !== '0000-00-00' && ( + + {' '} + {String(air_date).slice(0, 7)} + + )} + + {(showScore || showBottom) && ( + + {showScore && ( + + + + + {rating?.score} + + {!!rating?.total && ( + + ({rating?.total}) + + )} + + )} + + )} + + + + + ) +} + +export default observer(Subject) diff --git a/src/components/render-html/a/utils.tsx b/src/components/render-html/a/utils.tsx index f9b067c50..c8fe1c9bf 100644 --- a/src/components/render-html/a/utils.tsx +++ b/src/components/render-html/a/utils.tsx @@ -2,33 +2,32 @@ * @Author: czy0729 * @Date: 2022-05-13 05:32:07 * @Last Modified by: czy0729 - * @Last Modified time: 2023-04-11 12:58:33 + * @Last Modified time: 2023-07-06 07:43:18 */ import React from 'react' import { View } from 'react-native' -import { _, systemStore, subjectStore, rakuenStore, userStore } from '@stores' -import { runAfter, HTMLDecode, navigationReference } from '@utils' -import { IOS, API_COVER, API_AVATAR } from '@constants' -import { ReactNode } from '@types' +import { _, subjectStore, rakuenStore } from '@stores' +import { runAfter, navigationReference } from '@utils' +import { IOS, API_AVATAR } from '@constants' +import { Fn, ReactNode } from '@types' import { Touchable } from '../../touchable' import { Flex } from '../../flex' import { Text } from '../../text' -import { Iconfont } from '../../iconfont' import { Cover } from '../../cover' import { Avatar } from '../../avatar' import { fetchMediaQueue } from '../utils' import ACText from './ac-text' -import Rank from './rank' +import Subject from './subject' import { memoStyles } from './styles' -/** @todo 待优化, 安卓Text中一定要过滤非文字节点 */ +/** @todo 待优化, 安卓 Text 中一定要过滤非文字节点 */ export function filterChildren( children: ReactNode | ReactNode[] ): ReactNode | ReactNode[] { if (IOS) return children const childrens = React.Children.toArray(children) - const data = React.Children.toArray(children).filter( + const data = childrens.filter( item => // @ts-expect-error item?.type?.displayName === 'Text' @@ -45,7 +44,7 @@ export function filterChildren( } /** 获取 html 根节点文字 */ -export function getRawChildrenText(passProps) { +export function getRawChildrenText(passProps: any) { try { const text = passProps?.rawChildren?.[0]?.data if (text) return text @@ -85,103 +84,54 @@ export function getACSearch({ style, passProps, params, onPress }) { } /** 条目媒体块 */ -export function getSubject({ passProps, params, href, onLinkPress }) { - const text = getRawChildrenText(passProps) - if (text) { +export async function getSubject( + { passProps, params, href, onLinkPress }, + render?: Fn +) { + try { + const text = getRawChildrenText(passProps) + if (!text) return + const { subjectId } = params - const subject = subjectStore.subject(subjectId) - const { - images = { - common: undefined - }, - name, - name_cn, - rating = { - score: undefined, - total: undefined - }, - rank, - _loaded - } = subject - let { air_date } = subject - if (!_loaded) { + const subject = await subjectStore.getSubjectSnapshot(subjectId) + + // 等待列队请求媒体信息 + if (!subject?._loaded) { setTimeout(() => { - runAfter(() => fetchMediaQueue('subject', subjectId)) + fetchMediaQueue('subject', subjectId, async result => { + // 主动渲染组件 + if (result && typeof render === 'function') { + render(await getSubject({ passProps, params, href, onLinkPress })) + } + }) }, 2000) - } else { - const { score } = rating - const image = images.common - if (image) { - const styles = memoStyles() - const top = HTMLDecode(name_cn || name || text || '') - const bottom = HTMLDecode( - text !== top && text !== href ? text : name || name_cn || '' - ) - const showScore = !systemStore.setting.hideScore && !!score - const showBottom = bottom && bottom !== top - if (air_date === '0000-00-00') air_date = '' - return ( - - - - - - - {top}{' '} - {!!air_date && ( - - {String(air_date).slice(0, 7)} - - )} - - {(showScore || showBottom) && ( - - {showScore && ( - - - - - {score} - - {!!rating.total && ( - - ({rating.total}) - - )} - - )} - {/* {showBottom && ( - - {showScore && '· '} - {bottom} - - )} */} - - )} - - - - - ) - } + return } + + const { images, name, name_cn, rating, rank, air_date } = subject + const image = images?.common + if (!image) return + + return ( + + ) + } catch (error) { + console.error('render-html', 'a', 'utils', 'getSubject', error) } } /** 帖子媒体块 */ -export function getTopic({ passProps, params, onLinkPress }) { +export async function getTopic({ passProps, params, onLinkPress }) { const text = getRawChildrenText(passProps) if (text) { const { topicId } = params @@ -239,7 +189,7 @@ export function getTopic({ passProps, params, onLinkPress }) { } /** 人物媒体块 */ -export function getMono({ passProps, params, onLinkPress }) { +export async function getMono({ passProps, params, onLinkPress }) { const text = getRawChildrenText(passProps) if (text) { const { monoId } = params diff --git a/src/components/render-html/utils.tsx b/src/components/render-html/utils.tsx index c4dc9b2ed..8a9d80854 100644 --- a/src/components/render-html/utils.tsx +++ b/src/components/render-html/utils.tsx @@ -3,7 +3,7 @@ * @Author: czy0729 * @Date: 2021-09-14 20:53:38 * @Last Modified by: czy0729 - * @Last Modified time: 2023-04-20 11:16:07 + * @Last Modified time: 2023-07-06 07:24:31 */ import { _, systemStore, subjectStore, rakuenStore } from '@stores' import { sleep, HTMLDecode } from '@utils' @@ -216,7 +216,11 @@ const LOADED_IDS = [] let loading = false /** 列队请求媒体信息 */ -export async function fetchMediaQueue(type?: string, id?: unknown) { +export async function fetchMediaQueue( + type?: string, + id?: unknown, + onLoaded?: (result?: boolean) => void +) { if (type && id) { if ( IDS.length <= 16 && @@ -236,13 +240,12 @@ export async function fetchMediaQueue(type?: string, id?: unknown) { LOADED_IDS.push(item) try { - if (DEV) { - // console.info('fetchMediaQueue', IDS, item) - } - loading = true + if (DEV) console.info('fetchMediaQueue', IDS, item) + loading = true if (item.type === 'subject') { - await subjectStore.fetchSubject(item.id) + const result = await subjectStore.fetchSubjectSnapshot(item.id) + onLoaded(result) } else if (item.type === 'topic') { await rakuenStore.fetchTopic({ topicId: item.id diff --git a/src/stores/subject/action.ts b/src/stores/subject/action.ts index d6cae612a..eb1ddbe05 100644 --- a/src/stores/subject/action.ts +++ b/src/stores/subject/action.ts @@ -2,16 +2,80 @@ * @Author: czy0729 * @Date: 2023-04-16 13:38:53 * @Last Modified by: czy0729 - * @Last Modified time: 2023-04-16 13:40:48 + * @Last Modified time: 2023-07-06 07:32:28 */ import CryptoJS from 'crypto-js' import { put, read } from '@utils/db' import { APP_ID } from '@constants' -import { Actions, Origin } from '@types' +import { Actions, Origin, SubjectId } from '@types' import UserStore from '../user' import Fetch from './fetch' +import { SubjectSnapshot } from './types' +import { getInt, getSubjectSnapshot } from './utils' export default class Action extends Fetch { + /** 获取条目信息快照, 尽可能在其他数据中组装条目信息 */ + getSubjectSnapshot = async ( + subjectId: SubjectId + ): Promise => { + let subjectSnapShot: SubjectSnapshot + let flag = false + if (!flag) { + await this.initSubjectV2([subjectId]) + const subjectV2 = this.subjectV2(subjectId) + if (subjectV2._loaded && subjectV2.jp) { + flag = true + subjectSnapShot = getSubjectSnapshot( + subjectV2.date, + subjectV2.image, + subjectV2.jp, + subjectV2.cn, + subjectV2.rating.score, + subjectV2.rating.total, + subjectV2.rank + ) + } + } + + if (!flag) { + const last = getInt(subjectId) + const key = `subject${last}` as const + await this.init(key) + const subject = this.subject(subjectId) + if (subject._loaded && subject.name) { + flag = true + subjectSnapShot = getSubjectSnapshot( + subject.air_date, + subject.images.common, + subject.name, + subject.name_cn, + subject.rating.score, + subject.rating.total, + subject.rank + ) + } + } + + if (!flag) { + await this.init('subjectFromOSS') + const subjectFromOSS = this.subjectFromOSS(subjectId) + if (subjectFromOSS._loaded && subjectFromOSS.name) { + flag = true + subjectSnapShot = getSubjectSnapshot( + subjectFromOSS.air_date, + subjectFromOSS.images.common, + subjectFromOSS.name, + subjectFromOSS.name_cn, + subjectFromOSS.rating.score, + subjectFromOSS.rating.total, + subjectFromOSS.rank + ) + } + } + + return subjectSnapShot + } + /** 更新源头数据 */ updateOrigin = (data: Origin) => { const key = 'origin' diff --git a/src/stores/subject/computed.ts b/src/stores/subject/computed.ts index e0e2dcc3a..c25c74a92 100644 --- a/src/stores/subject/computed.ts +++ b/src/stores/subject/computed.ts @@ -2,7 +2,7 @@ * @Author: czy0729 * @Date: 2023-04-16 13:15:43 * @Last Modified by: czy0729 - * @Last Modified time: 2023-04-16 13:33:32 + * @Last Modified time: 2023-07-06 07:47:03 */ import { computed } from 'mobx' import { x18 } from '@utils' @@ -90,7 +90,7 @@ export default class Computed extends State implements StoreConstructor(() => { return this.state.subjectFormCDN[subjectId] || INIT_SUBJECT_FROM_CDN_ITEM diff --git a/src/stores/subject/fetch.ts b/src/stores/subject/fetch.ts index 3664de9db..ae9ae00a5 100644 --- a/src/stores/subject/fetch.ts +++ b/src/stores/subject/fetch.ts @@ -14,6 +14,7 @@ import { API_SUBJECT_EP, CDN_MONO, CDN_SUBJECT, + DEV, HTML_EP, HTML_MONO_VOICES, HTML_MONO_WORKS, @@ -218,7 +219,7 @@ export default class Fetch extends Computed { return false } - /** CDN 获取条目信息 */ + /** @deprecated CDN 获取条目信息 */ fetchSubjectFormCDN = async (subjectId: SubjectId) => { try { const { _response } = await xhrCustom({ @@ -241,6 +242,28 @@ export default class Fetch extends Computed { } } + /** 自动判断尽快条目快照数据 */ + fetchSubjectSnapshot = async (subjectId: SubjectId) => { + let result = false + if (!result) { + if (DEV) console.info('fetchSubjectSnapshot.oss', subjectId) + result = await this.fetchSubjectFromOSS(subjectId) + } + + if (!result) { + if (DEV) console.info('fetchSubjectSnapshot.v2', subjectId) + result = await this.fetchSubjectV2(subjectId) + } + + if (!result) { + if (DEV) console.info('fetchSubjectSnapshot.subject', subjectId) + const data = await this.fetchSubject(subjectId) + result = !!(data._loaded && data.name) + } + + return result + } + /** @deprecated 章节数据 */ fetchSubjectEp = (subjectId: SubjectId) => { return this.fetch( diff --git a/src/stores/subject/init.ts b/src/stores/subject/init.ts index 1386645d2..3e0b109a4 100755 --- a/src/stores/subject/init.ts +++ b/src/stores/subject/init.ts @@ -218,7 +218,7 @@ const STATE = { 0: INIT_SUBJECT }, - /** 条目 (CDN) */ + /** @deprecated 条目 (CDN) */ subjectFormCDN: { 0: INIT_SUBJECT_FROM_CDN_ITEM }, diff --git a/src/stores/subject/types.ts b/src/stores/subject/types.ts index 9192c317e..5d7eb4925 100644 --- a/src/stores/subject/types.ts +++ b/src/stores/subject/types.ts @@ -299,6 +299,22 @@ export type SubjectFormCDN = DeepPartial<{ _loaded: Loaded }> +/** 条目信息快照 */ +export type SubjectSnapshot = { + air_date: string + images: { + common: string + } + name: string + name_cn: string + rating: { + score: number + total: number + } + rank: number | '' + _loaded: number +} + /** 包含条目的目录 */ export type SubjectCatalogs = ListEmpty< Partial<{ diff --git a/src/stores/subject/utils.ts b/src/stores/subject/utils.ts index 5d8dd56fc..a5233d42e 100644 --- a/src/stores/subject/utils.ts +++ b/src/stores/subject/utils.ts @@ -5,8 +5,35 @@ * @Last Modified time: 2023-04-16 13:26:54 */ import { SubjectId } from '@types' +import { SubjectSnapshot } from './types' export function getInt(subjectId: SubjectId) { const str = String(subjectId) return Number(str.slice(str.length - 3, str.length)) } + +export function getSubjectSnapshot( + air_date = '', + common = '', + name = '', + name_cn = '', + score = 0, + total = 0, + rank: number | '' = '' +) { + const subjectSnapShot: SubjectSnapshot = { + air_date, + images: { + common + }, + name, + name_cn, + rating: { + score, + total + }, + rank, + _loaded: 1 + } + return subjectSnapShot +}