import { reactive, ref, ComputedRef, watchEffect, watch, nextTick, computed, onBeforeUnmount } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { HistoryVideoType } from '@/enums/app'; import indexDB from './indexDB'; import { showToast } from 'vant'; import AcgApi from '@/service/acg'; import useLoading from '@/hooks/loading'; import { useUserStore } from '@/stores/user'; import { freeSet } from './common'; interface ReadData { [key: string]: any; } export default ({ mediaId = '', isScrollTop = true, isGetInfo = true, isUpdate = true, isNeedHistory = true, isGetHistoryInit = false, pageSize = 30, dbType = HistoryVideoType.AcgImage, }) => { const userStore = useUserStore(); const { openLoading, closeLoading } = useLoading(); const route = useRoute(); const router = useRouter(); const { id, episodeNumber, title } = route.query; const state = reactive({ list: [] as Types.Acg.MediaContentListItem[], info: {} as Types.Acg.MediaContentInfo, validateItem: {} as Types.Acg.MediaContentListItem, episodeNumber: Number(episodeNumber || '1'), id: (mediaId || id) as string, title, gradingGroupIndex: 0, }); const { historyData, historyEpisodeNumber, getHistoryData, setHistoryData } = indexDB(dbType, true); const addHistory = (num: number) => { if (num === -1) { num = state.episodeNumber; } const data = { ...historyData.value, historyEpisodeNumber: num, historyReadData }; setHistoryData(data, dbType); }; const historyReadData = [] as Types.Acg.ReadData[]; const addHistoryReadData = (num: number, obj: ReadData, imgObj?: { url: string; width: number; height: number }) => { const listItem = state.list.find((item) => item.episodeNumber === num); if (!listItem) return; const index = (historyReadData || []).findIndex((item) => item.episodeNumber === num); const isHas = index > -1; const imgs = isHas ? historyReadData[index].imgs || {} : {}; if (imgObj?.url) { imgs[imgObj?.url] = { width: imgObj?.width, height: imgObj?.height, }; } let item = {}; if (isHas) { item = historyReadData.splice(index, 1)[0]; } const newItem = Object.assign({}, { ...item }, { ...listItem, ...obj, imgs }); historyReadData.push(newItem); }; if (isUpdate) { onBeforeUnmount(() => { nextTick(() => { addHistory(-1); }); }); } const currentItem = computed(() => { if (state.list.length === 0) return {}; return state.list.find((item) => item.episodeNumber === state.episodeNumber) || {}; }) as ComputedRef; let initScrollLeft = true; watch(currentItem, () => { if (initScrollLeft) { goSectionScrollLeft(); initScrollLeft = false; } if (['AcgTextDetail', 'AcgImageDetail'].includes((route.name as string) || '')) { return; } validateHasBuy(currentItem.value); }); watch( () => state.episodeNumber, () => { addHistory(state.episodeNumber); }, ); const getPageNumber = () => { return Math.ceil(state.episodeNumber / 30); }; const pageNumber = getPageNumber(); const params = { prevPageNumber: pageNumber, nextPageNumber: pageNumber, pageSize, }; const moreParams = reactive({ loading: true, refreshing: false, finished: false, isNoData: false, error: false, isHigehtMax: false, }); const moreParams_finished = { prev: false, next: false, }; const getList = async (type: 'prev' | 'next', pageNumber?: number) => { try { const tempParams = { mediaId: state.id, pageSize: params.pageSize, pageNumber: pageNumber ? pageNumber : type === 'prev' ? params.prevPageNumber : params.nextPageNumber, }; let res: any = await AcgApi.getMediaContentList(tempParams); if ((res.list || []).length === 0 && state.list.length === 0 && tempParams.pageNumber !== 1) { params.prevPageNumber = 1; params.nextPageNumber = 1; tempParams.pageNumber = 1; res = await AcgApi.getMediaContentList(tempParams); } historyData.value.currentEpisode = res.total; moreParams.loading = false; moreParams.refreshing = false; let list = res.list || []; if (list.length > 0) { const reList = (list || []).reduce( (acc: Types.Acg.MediaContentListItem[], current: Types.Acg.MediaContentListItem) => { const x = acc.find((item) => item.episodeNumber === current.episodeNumber); if (!x) { return acc.concat([current]); } else { return acc; } }, [], ); if (isInitGetList && res.hasNext && list.length !== reList.length) { list = reList; onLoad(); } } isInitGetList = false; if (state.list.length === 0) { state.list = list; } else { state.list = state.list.concat(list); } if (tempParams.pageNumber == 1 && state.list.length == 0) { moreParams.isNoData = true; } if (!res.hasNext) { moreParams_finished[type] = true; moreParams.finished = type === 'next' && sort.value; } return list; } catch (e) { moreParams.loading = false; moreParams.error = true; return []; } }; const onLoad = () => { moreParams.loading = true; if (sort.value) { params.nextPageNumber += 1; getList('next'); } else { if (params.prevPageNumber === 1) { moreParams.loading = false; return; } params.prevPageNumber -= 1; getList('prev'); } }; const onRefresh = () => { if (sort.value) { if (params.prevPageNumber == 1) { moreParams.refreshing = false; return; } params.prevPageNumber -= 1; moreParams.refreshing = true; moreParams.finished = false; getList('prev'); } else { if (moreParams_finished['next']) { moreParams.refreshing = false; return; } params.nextPageNumber += 1; moreParams.refreshing = true; getList('next'); } }; let isInitGetList = true; const init = () => { getList('next'); }; if (isNeedHistory) { getHistoryData(state.id).then(() => { historyReadData.push(...JSON.parse(JSON.stringify(historyData.value.historyReadData || '[]'))); if (isGetHistoryInit) { if (!episodeNumber) { state.episodeNumber = historyData.value.historyEpisodeNumber || state.episodeNumber; } const pageNumber = getPageNumber(); params.prevPageNumber = pageNumber; params.nextPageNumber = pageNumber; } init(); }); } else { init(); } const getContent = async () => { if (state.info.id) { openLoading(); } await AcgApi.getMediaContentInfo({ id: currentItem.value.id }).then((res) => { state.info = res; }); closeLoading(); }; if (isGetInfo) { watch( () => currentItem.value.id, (newId, oldId) => { if (!newId || newId === oldId) return; getContent(); }, ); } const statusText = computed(() => { switch (historyData.value.updateStatus) { case 1: return '连载中'; case 2: return '已完结'; default: return '已完结'; } }); //目录 const visibleCatalogue = ref(false); const sort = ref(true); const autoList = computed(() => { return (state.list || []).sort( sort.value ? (a, b) => a.episodeNumber - b.episodeNumber : (a, b) => b.episodeNumber - a.episodeNumber, ); }); const scrollToElement = (elementId: string) => { const element = document.getElementById(elementId) as HTMLElement; if (!element) return; const parent = element.closest('.list') as HTMLElement; const parentRect = parent.getBoundingClientRect(); const elementRect = element.getBoundingClientRect(); const offsetTop = elementRect.top - parentRect.top; const offsetBottom = elementRect.bottom - parentRect.bottom; if (offsetTop < 0 || offsetBottom > 0) { // parent.scrollTop = element.offsetTop - (parent.offsetHeight - element.offsetHeight) / 2; parent.scrollTop = element.offsetTop - parent.offsetHeight / 2; } }; const goSectionScrollTop = () => { if (!isScrollTop) return; nextTick(() => { scrollToElement(`section_${state.episodeNumber}`); }); }; let isInit = false; watchEffect(() => { if (!visibleCatalogue.value) return; if (isInit) { return; } isInit = true; goSectionScrollTop(); }); watch( () => sort.value, () => { moreParams.finished = moreParams_finished[sort.value ? 'next' : 'prev']; goSectionScrollTop(); }, ); //支付 const dvPayForWatch = ref(); const visiblePayForWatch = ref(false); const validatePermission = () => { // 购买优先 if (historyData.value.id && historyData.value.mediaStatus && historyData.value.mediaStatus.hasPaid) return true; // 会员 if (historyData.value.permission === 0 && userStore.isVip) return true; // 付费 if (historyData.value.permission === 1) { if (!historyData.value.price) { return true; } if (userStore.userInfo.vipLevel >= 2) { return true; } } // 免费 || 前几集免费 if (historyData.value.permission === 2 || historyData.value.freeEpisode >= state.validateItem.episodeNumber) return true; return false; }; const validateHasBuy = (item: Types.Acg.MediaContentListItem) => { state.validateItem = item; if (!validatePermission()) { visiblePayForWatch.value = true; // showToast('付费章节,购买后才能观看喔'); return true; } visiblePayForWatch.value = false; return false; }; const buySuccess = (type = 'one') => { state.info.hasBuy = true; if (state.validateItem.id) { state.validateItem.hasBuy = true; state.episodeNumber = state.validateItem.episodeNumber; } visiblePayForWatch.value = false; visibleCatalogue.value = false; if (type === 'all') { state.list.forEach((item) => { item.hasBuy = true; }); } }; // watch( // dvPayForWatch, // (el) => { // debugger; // if (!el) return; // validateHasBuy(currentItem.value); // }, // // { // // once: true, // // }, // ); const closePayForWatch = () => { if (validatePermission()) { visiblePayForWatch.value = false; return; } router.back(); }; const loadEpisodeList = async (num: number, type: 'prev' | 'next') => { const item = state.list.find((item) => item.episodeNumber === num); if (item) return item; openLoading(); await getList(type); closeLoading(); return state.list.find((item) => item.episodeNumber === num); }; const replaceRouter = (num: number) => { router.replace({ path: route.path, query: { ...route.query, episodeNumber: num, }, }); }; const switchEpisodeNumber = async (num: number, isValidate = true) => { if (num === -1) { if (state.episodeNumber !== 1) { const prevNum = state.episodeNumber - 1; const item = await loadEpisodeList(prevNum, 'prev'); if (isValidate) { if (validateHasBuy(item as Types.Acg.MediaContentListItem)) { return false; } } state.episodeNumber = prevNum; if (dbType === HistoryVideoType.AcgText) { addHistoryReadData(prevNum, { progress: 0, scrollTop: 0 }); addHistory(prevNum); } replaceRouter(prevNum); return true; } else { showToast('这是第一章了喔~'); return false; } } else { if (state.episodeNumber < historyData.value.currentEpisode) { const nextNum = state.episodeNumber + 1; const item = await loadEpisodeList(nextNum, 'next'); if (isValidate) { if (validateHasBuy(item as Types.Acg.MediaContentListItem)) { return false; } } state.episodeNumber = nextNum; if (dbType === HistoryVideoType.AcgText) { addHistoryReadData(nextNum, { progress: 0, scrollTop: 0 }); addHistory(nextNum); } replaceRouter(nextNum); return true; } else { showToast('已经到最后一章了喔~'); return false; } } }; const switchEpisodeNumberCatalogue = (item: Types.Acg.MediaContentListItem) => { if (validateHasBuy(item)) return; visibleCatalogue.value = false; if (dbType === HistoryVideoType.AcgText) { addHistoryReadData(item.episodeNumber, { progress: 0, scrollTop: 0 }); addHistory(item.episodeNumber); } state.episodeNumber = item.episodeNumber; replaceRouter(item.episodeNumber); }; const scrollToElement2 = (elementId: string) => { const element = document.getElementById(elementId) as HTMLElement; if (!element) return; const parent = element.closest('.episode-box') as HTMLElement; const parentRect = parent.getBoundingClientRect(); const elementRect = element.getBoundingClientRect(); const offsetLeft = elementRect.left - parentRect.left; const offsetRight = elementRect.right - parentRect.right; if (offsetLeft < 0 || offsetRight > 0) { parent.scrollLeft = element.offsetLeft - parent.offsetWidth / 2; } }; const goSectionScrollLeft = () => { const item = state.list.find((item) => item.episodeNumber === state.episodeNumber); if (!item) return; nextTick(() => { scrollToElement2(`episode-${item.id}`); }); }; const handlePopupSwitchEpisode = (num: number) => { state.episodeNumber = num; visibleCatalogue.value = false; goSectionScrollLeft(); }; return { state, currentItem, statusText, moreParams, onLoad, onRefresh, getList, visibleCatalogue, handlePopupSwitchEpisode, sort, autoList, dvPayForWatch, visiblePayForWatch, validateHasBuy, buySuccess, closePayForWatch, switchEpisodeNumber, historyData, historyEpisodeNumber, addHistory, addHistoryReadData, switchEpisodeNumberCatalogue, validatePermission, goSectionScrollTop, replaceRouter, }; };