Files
91fans_h5/src/views/acgModule/utils/mediaContentInfo.ts
T
2026-09-15 15:25:05 +07:00

543 lines
14 KiB
TypeScript

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<Types.Acg.MediaContentListItem>;
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,
};
};