初始化

This commit is contained in:
谢宇宁
2026-09-15 15:25:05 +07:00
commit 77c16ab4a2
1310 changed files with 132250 additions and 0 deletions
+773
View File
@@ -0,0 +1,773 @@
<template>
<div class="detail-wrap overflow-y-auto" @scroll="onScroll">
<template v-if="state.info.id">
<div class="header flex-center-between" :style="headStyle">
<SvgIcon name="back_white_round1" class="back" @click="$router.go(-1)" />
<p v-show="scrollTop >= 100" class="title ellipsis">{{ state.info.title }}</p>
<div class="bookself">
<div class="menuItem">
<SvgIcon
class="collected"
name="acg-collect-img-1"
v-if="historyData?.mediaStatus?.hasCollected"
@click="onToogleCollected"
></SvgIcon>
<SvgIcon class="collected" name="acg-collect-img-0" v-else @click="onToogleCollected"></SvgIcon>
<SvgIcon name="acg-share1" @click="openShare" />
</div>
</div>
</div>
<div class="cover-box flex-vcenter">
<lazyImg class="cover" :src="state.info.verticalCover || state.info.horizontalCover"></lazyImg>
<div class="content">
<h1 class="title ellipsis">{{ state.info.title }}</h1>
<div class="status">
<!-- <template v-if="state.info.updateStatus == 2">已完结 共{{ state.info.totalEpisode }}话</template>
<template v-else>更新至第{{ state.info.currentEpisode || state.info.totalEpisode }}话</template> -->
</div>
</div>
</div>
<div class="info-box">
<div class="watch" :class="state.info.mediaType">
<div class="info">
<div class="count flex-vcenter">
<div class="count-item">
<SvgIcon name="eye-open" />
{{ watchCount(state.info.countBrowse) }}
</div>
<div class="count-item">
<SvgIcon name="acg-heart-2" />
{{ watchCount(state.info.countLike) }}
</div>
<div class="count-item">
{{ state.info.permission == 0 ? 'VIP' : (state.info.price || '0') + '金币' }}
</div>
<!-- <div class="collected flex-vcenter" @click="onToogleCollected">
<SvgIcon v-if="!hasCollected" name="acg-collect-star-0" />
<SvgIcon v-else name="acg-collect-star-1" style="color: #f52c56" />
{{ watchCount(state.info.countCollect) }}
</div> -->
</div>
<div :class="['summary', !showMoreSummary && 'ellipsis3']">{{ state.info.summary || '' }}</div>
<div
v-if="state.info.summary"
:class="['more-summary', showMoreSummary && 'showMoreSummary']"
@click="showMoreSummary = !showMoreSummary"
>
更多简介
<SvgIcon name="rightArrow1" />
</div>
<div class="tags" v-if="tagDetails.length">
<div
class="tag"
v-for="item in tagDetails"
:key="item.id"
@click="goAcgTagPage(item.id, item.name, 'image')"
>
{{ item.name }}
</div>
</div>
</div>
<div class="nav" @click="visibleCatalogue = true">
<span class="title">选集</span>
<span class="count flex-vcenter">
{{ EpisodeTitle }}
<SvgIcon name="rightArrow1" />
</span>
</div>
<div
class="episode-box"
ref="episodeBox"
@touchstart.stop
@touchmove.stop
@touchend.stop
@scroll="onScrollEpisodeBox"
>
<Episode
showType="text"
:id="'episode-' + item.id"
class="item"
:episodeNumber="childState.episodeNumber"
:info="item"
v-for="item in autoList?.slice(0, 4) || []"
:key="item.id"
@click="childState.episodeNumber = item.episodeNumber"
></Episode>
</div>
</div>
<RecommendedList :type="state.info.mediaType" :tagId="tagDetails[0]?.id" class="recommendedBox" />
<div class="action">
<div class="btn flex-vcenter" @click="onToogleLiked">
<SvgIcon v-if="historyData?.mediaStatus?.hasLiked" name="acg-heart-1" />
<SvgIcon v-else name="acg-heart-0" />
喜欢
</div>
<div class="btn flex-vcenter go-page" @click="goWatch">
{{ currentEpisodeName }}
</div>
</div>
</div>
</template>
<Loading v-else />
</div>
<van-popup v-model:show="visibleCatalogue" position="bottom" z-index="1001" :style="{ height: '438px' }">
<div class="play-list-box">
<div class="line"></div>
<div class="title flex-center-between">
漫画列表
<div class="right">
<SvgIcon name="acg_sort_0" v-if="sort" />
<SvgIcon name="acg_sort_1" v-else />
<span @click="sort = !sort">
{{ sort ? '正序' : '倒序' }}
</span>
</div>
</div>
<div class="list-box overflow-y-auto">
<PullRefresh :params="moreParams" @onLoad="onLoad" @onRefresh="onRefresh">
<div class="list">
<Episode
showType="text"
class="item"
:episodeNumber="childState.episodeNumber"
:info="item"
v-for="item in autoList || []"
:key="item.id"
@click="handlePopupSwitchEpisode(item.episodeNumber)"
></Episode>
</div>
</PullRefresh>
</div>
</div>
</van-popup>
<PayForWatch
v-if="childState.validateItem && visiblePayForWatch"
ref="dvPayForWatch"
:isOpen="visiblePayForWatch"
:info="childState.validateItem"
@close="visiblePayForWatch = false"
@buySuccess="beforeBuySuccess"
></PayForWatch>
<AppSharePopup
coverType="portrait"
:is-open="visibleShare"
:cover="state.info.verticalCover || state.info.horizontalCover"
@close="visibleShare = false"
/>
</template>
<script lang="ts">
export default {
name: 'AcgImageDetail',
};
</script>
<script lang="ts" setup>
import { ref, toRaw, watch, computed, watchEffect, provide, onDeactivated, onActivated } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import PayForWatch from '../components/pay/PayForWatch.vue';
import tabAction from '@/utils/tabAction';
import { AdType } from '@/enums/app';
import { HistoryVideoType } from '@/enums/app';
import MediaInfo from '../utils/mediaInfo';
import mediaContentInfo from '../utils/mediaContentInfo';
import Episode from '../components/episode.vue';
import { handleCollected } from '../utils/collect';
import { dateFormatYyyyMmDd, watchCount } from '@/utils/index';
import { Pay_parent_on, Pay_child_on } from '../utils/pay-mitt';
import type { PayInfo } from '../utils/pay-mitt';
import { goAcgTagPage } from '../utils/common';
import CommunityApi from '@/service/community';
const route = useRoute();
const router = useRouter();
const dvComment = ref();
const showMoreSummary = ref(false);
const { state, historyEpisodeNumber, historyData, addHistory } = MediaInfo({
dbType: HistoryVideoType.AcgImage,
});
Pay_parent_on({ id: route.query.id as string, state, historyData, addHistory });
provide('historyData', historyData);
const {
state: childState,
autoList,
visibleCatalogue,
handlePopupSwitchEpisode,
moreParams,
sort,
onLoad,
onRefresh,
visiblePayForWatch,
validateHasBuy,
currentItem,
} = mediaContentInfo({ dbType: HistoryVideoType.AcgImage, isGetHistoryInit: true, isGetInfo: false });
Pay_child_on(childState.id, childState);
const { id = '' } = route.query;
watch(historyEpisodeNumber, (num) => {
childState.episodeNumber = num;
});
const currentEpisodeName = computed(() => {
return '开始阅读';
});
const EpisodeTitle = computed(() => {
return '全' + state.info.totalEpisode + '话';
});
const tagDetails = computed(() => {
const list = state.info.tagDetails || [];
const seenIds = new Set<string>();
const seenNames = new Set<string>();
return list.filter((item) => {
if (!item) return false;
if (seenIds.has(item.id) || seenNames.has(item.name)) return false;
seenIds.add(item.id);
seenNames.add(item.name);
return true;
});
});
const goWatch = () => {
if (validateHasBuy(currentItem.value)) {
return;
}
router.push({
path: '/AcgImageDetailWatch',
query: {
id,
episode: childState.episodeNumber,
},
});
};
watch(dvComment, (el) => {
if (!el) return;
if (!id) return;
dvComment.value[0].state.videoInfo = toRaw(state.info);
dvComment.value[0].getCommentList();
});
const beforeBuySuccess = (obj: PayInfo) => {
if (obj.type === 'all') {
state.info.mediaStatus.hasPaid = true;
historyData.value.mediaStatus.hasPaid = true;
addHistory();
childState.list.forEach((item) => {
item.hasBuy = true;
});
}
buySuccess(obj);
};
const buySuccess = ({ type }: PayInfo) => {
if (type === 'one') {
const item = (childState.list as Types.Acg.MediaContentListItem[]).find(
(item) => item.episodeNumber === currentItem.value.episodeNumber,
);
if (item) {
item.hasBuy = true;
}
}
visiblePayForWatch.value = false;
goWatch();
};
let isInitCollected = false;
watch(
() => state.info,
(obj) => {
if (isInitCollected) return;
isInitCollected = true;
},
);
// 收藏
const onToogleCollected = async () => {
try {
const tempIsCollect = !historyData.value.mediaStatus.hasCollected;
handleCollected(state.info.id, tempIsCollect).then(() => {
historyData.value.mediaStatus.hasCollected = tempIsCollect;
});
} catch (e) {
console.log(e);
}
};
// 点赞
const onToogleLiked = () => {
const zan = !historyData.value.mediaStatus.hasLiked;
(zan ? CommunityApi.zan : CommunityApi.cancleZan)(
zan
? {
type: 'image',
objID: state.info.id,
}
: {
type: 'image',
objIDs: [state.info.id],
},
).then(() => {
historyData.value.mediaStatus.hasLiked = zan;
});
};
const scrollTop = ref(0);
const onScroll = (e: Event) => {
const currentTop = (e.target as HTMLElement).scrollTop;
scrollTop.value = currentTop;
};
const headStyle = computed(() => {
return {
background: `rgba(0, 0, 0, ${scrollTop.value / 100})`,
};
});
const visibleShare = ref(false);
const openShare = () => {
const { id, verticalCover, horizontalCover } = historyData.value;
let obj = {
id,
cover: verticalCover || horizontalCover,
};
// globalStore.setShareInfo(obj);
visibleShare.value = true;
};
const episodeBox = ref();
let episodeBoxLeft = 0;
let isDeactivated = false;
const onScrollEpisodeBox = (e: Event) => {
if (!episodeBox.value) return;
const scrollLeft = (e.target as HTMLElement).scrollLeft;
episodeBoxLeft = scrollLeft;
};
onDeactivated(() => {
isDeactivated = true;
});
onActivated(() => {
if (episodeBox.value && episodeBoxLeft) {
episodeBox.value.scrollLeft = episodeBoxLeft;
}
});
watch(
() => childState.episodeNumber,
(num: number) => {
if (isDeactivated) {
handlePopupSwitchEpisode(num);
isDeactivated = false;
}
},
);
</script>
<style lang="scss" scoped>
@import '@/assets/styles/mixins.scss';
@import '../assets/style.scss';
.detail-wrap {
$themeBgColor2: #222324;
width: 100%;
height: 100%;
@include vanTab();
--van-tab-active-text-color: rgba(255, 255, 255, 0.9);
.borderBottomDiv {
margin: 0 14px;
width: calc(100% - 28px);
}
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
// background: $themeBgColor2;
height: 39px;
z-index: 10;
padding: 0 16px;
.back {
width: 24px;
height: 24px;
flex-shrink: 0;
color: #292929;
}
.title {
color: #fff;
font-size: 18px;
font-style: normal;
font-weight: 900;
line-height: 24px; /* 133.333% */
margin: 0 16px;
}
.bookself {
display: inline-flex;
justify-content: center;
align-items: center;
font-size: 11px;
font-style: normal;
font-weight: 500;
line-height: 16px;
border-radius: 11px;
color: #ffffff99;
.menuItem {
display: flex;
align-items: center;
}
.collected {
margin-right: 10px;
color: #fff;
}
svg {
width: 24px;
height: 24px;
}
}
}
.cover-box {
width: 100%;
height: 216px;
padding: 11px 16px 23px;
margin-bottom: 25px;
position: relative;
align-items: flex-end;
.cover {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
&::after {
content: '';
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000 100%);
}
}
.content {
z-index: 3;
flex: 1;
overflow: hidden;
.title {
color: #fff;
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: normal;
}
.status {
color: #e0e0e0;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
margin: 4px 0 10px;
}
}
}
.info-box {
width: 100%;
border-radius: 24px 24px 0px 0px;
background: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0) 100%);
}
.watch {
padding: 16px 0 6px;
margin-top: -50px;
border-radius: 20px 20px 0px 0px;
position: relative;
z-index: 3;
.info {
padding: 0 10px 10px;
.tags {
display: flex;
flex-wrap: wrap;
gap: 8px 10px;
margin: 12px 0 14px;
color: #e0e0e0;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
.tag {
border-radius: 3px;
background: rgba(255, 255, 255, 0.05);
padding: 4px 8px;
font-weight: 400;
color: rgba(255, 255, 255, 0.55);
}
}
.summary {
color: #989898;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 142.857% */
}
.more-summary {
color: #efefef;
font-size: 12px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: center;
svg {
width: 18px;
height: 18px;
color: #fff;
margin-left: 4px;
}
}
.showMoreSummary {
svg {
transform: rotate(-90deg);
}
}
.count {
color: #a7a7a7;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 180% */
.count-item {
font-size: 12px;
display: flex;
align-items: center;
margin: 10px 24px 16px 0;
svg {
width: 16px;
height: 16px;
color: #fff;
margin-right: 4px;
}
}
}
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 14px;
.title {
font-size: 18px;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
}
.count {
font-size: 12px;
color: #989898;
font-weight: 400;
svg {
width: 18px;
height: 18px;
margin-left: 4px;
}
}
}
.episode-box {
margin: 9px 0 0;
padding-left: 16px;
width: 100%;
overflow-y: auto;
display: flex;
flex-wrap: nowrap;
flex-direction: column;
.item {
flex-shrink: 0;
margin-right: 11px;
}
}
// .btn {
// display: flex;
// width: 249px;
// height: 39px;
// justify-content: center;
// align-items: center;
// border-radius: 19px;
// background: $themeBgLgColor;
// color: #fff;
// font-size: 16px;
// font-weight: 500;
// margin: 0 auto;
// }
}
.play-list {
@include categoryWrap();
border: none;
margin-top: 0;
padding-top: 16px;
.title {
padding: 0 14px;
@include categoryTitle();
}
.list {
@include categoryList();
padding-left: 14px;
.item {
height: 118px;
flex-shrink: 0;
width: 97px;
border-radius: 3px;
display: flex;
flex-direction: column;
margin-right: 4px;
position: relative;
overflow: hidden;
.cover {
width: 100%;
height: 97px;
position: relative;
overflow: hidden;
.icon {
position: absolute;
top: 4px;
right: 4px;
z-index: 1;
width: 18px;
height: 14px;
}
.count {
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
width: 97px;
height: 26px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 11px;
font-weight: 500;
display: flex;
align-items: center;
.play {
width: 16px;
height: 16px;
margin: 0 5px;
}
}
}
.title-box {
width: 100%;
height: 21px;
padding: 0 5px;
background: transparent;
display: flex;
align-items: flex-end;
flex: 1;
color: $textColor;
font-size: 11px;
font-weight: 400;
line-height: normal;
@include textEllipsis();
}
}
}
}
.action {
position: fixed;
bottom: 0;
left: 0;
z-index: 10;
width: 100%;
background: linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, #000 100%);
height: 68px;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 0 16px;
@include SafeArea('padding', 'bottom', 0);
display: flex;
.btn {
width: 108px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.2);
height: 44px;
color: #989898;
text-align: center;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: normal;
justify-content: center;
&.go-page {
background: $themeColor;
width: 223px;
color: #fff;
}
svg {
width: 20px;
height: 20px;
margin-right: 10px;
}
}
}
}
.play-list-box {
$paddingBottom: 38px;
width: 100%;
height: 100%;
border-radius: 16px 16px 0px 0px;
background: $popupBg;
padding: 18px 16px $paddingBottom;
@include SafeArea('padding', 'bottom', 0);
display: flex;
flex-direction: column;
color: $textColor;
font-size: 12px;
color: #fff;
> .title {
font-size: 16px;
font-style: normal;
font-weight: 900;
line-height: 25px; /* 140% */
margin: 16px 0 0;
.right {
display: flex;
align-items: center;
svg {
color: rgba(255, 255, 255, 0.55);
margin-right: 4px;
width: 20px;
height: 20px;
}
span {
color: rgba(255, 255, 255, 0.55);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
}
}
}
.line {
width: 36px;
height: 5px;
border-radius: 100px;
background: #989898;
margin: 0 auto;
}
.list-box {
flex: 1;
}
}
.recommendedBox {
padding: 0 16px;
:deep(.van-tabs__wrap) {
// top: 39px !important;
}
}
</style>
+761
View File
@@ -0,0 +1,761 @@
<template>
<div
v-if="state.info.id"
ref="dvCaricatureWrap"
class="caricature-wrap overflow-y-auto"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@scroll="onContentScroll"
>
<Transition name="top">
<div v-show="visibleHead" class="nav" @click.stop="visibleHead = false">
<SvgIcon name="back_white" class="back" @click.stop="$router.go(-1)" />
<span class="title">{{ currentItem.name }}</span>
<div class="right flex-vcenter">
<div
class="collect"
:class="{ collected: historyData.mediaStatus?.hasCollected }"
@click.stop="onToogleCollected"
>
<SvgIcon name="acg-collect-img-0" class="svg-icon" v-if="!historyData.mediaStatus?.hasCollected" />
<SvgIcon name="acg-collect-img-1" class="svg-icon" style="color: #f52c56" v-else />
</div>
<SvgIcon class="share" name="acg-share1" @click.stop="openShare" />
</div>
<!-- <SvgIcon class="back right" name="acg_catalogue" @click.stop="visibleCatalogue = true" /> -->
</div>
</Transition>
<div class="like-box" :class="isToolShow ? 'top150' : ''" v-show="visibleHead" @click.stop="onToogleLiked">
<SvgIcon v-if="historyData?.mediaStatus?.hasLiked" name="acg-heart-1" />
<SvgIcon v-else name="acg-heart-0" />
喜欢
</div>
<div class="list" @click="onClick">
<ImgObserver
v-for="(url, index) in currentEpisode"
class="item"
:style="getImgHeight(url)"
:class="{ move: visibleCatalogue }"
:key="url"
:index="index"
:url="url"
:init="index - currentProgress <= 1"
@updateIndex="updateIndex"
@updateSize="updateSize"
></ImgObserver>
</div>
<div
v-show="running"
class="running-box"
@click.stop="
{
running = false;
visibleHead = true;
}
"
>
<img class="running" src="@/assets/gif/running.gif" />
</div>
<Transition name="bottom">
<div v-show="visibleHead && !running" ref="bottomRef" class="action">
<div class="box">
<div class="func" @click.stop="visibleCatalogue = true">
<SvgIcon class="icon" name="acg_catalogue" />
目录
</div>
<div class="func" @click.stop="visibleComment = true">
<SvgIcon class="icon" name="acg_comment" />
评论
</div>
<div class="func" @click.stop="isToolShow = !isToolShow">
<SvgIcon class="icon" :name="isToolShow ? 'ddown' : 'acg_progress'" />
进度
</div>
<div class="func" @click.stop="running = true">
<SvgIcon class="icon" name="acg_automatic" />
自动
</div>
<!-- <div class="prev" @click.stop="switchEpisodeNumber(-1)">
<SvgIcon class="icon" name="acg_prev" />
上一话
</div>
<div class="next" @click.stop="switchEpisodeNumber(1)">
<SvgIcon class="icon" name="acg_prev" />
下一话
</div> -->
</div>
<Transition name="van-slide-up">
<div v-show="isToolShow && !running" class="tools">
<div class="prev" @click.stop="switchEpisodeNumber(-1)">
<SvgIcon class="icon" name="backWhite" />
<span>上一篇</span>
</div>
<div class="slider">
<van-slider
style="--van-slider-button-background: #ffd300"
:model-value="progressRatio"
@update:model-value="(v) => (progressRatio = v)"
active-color="#FFD300"
inactive-color="#fff3"
button-size="12px"
bar-height="4px"
@change="reposition"
></van-slider>
</div>
<div class="next" @click.stop="switchEpisodeNumber(1)">
<SvgIcon class="icon" name="backWhite" />
<span>下一篇</span>
</div>
</div>
</Transition>
</div>
</Transition>
<!-- <div v-show="!visibleHead" class="tips">
第{{ state.episodeNumber }}话 {{ currentProgress }}/{{ currentEpisode.length }}
</div> -->
</div>
<Loading v-else />
<van-popup v-model:show="visibleCatalogue" position="left" :style="{ width: '50%', height: '100%' }">
<div class="play-list-box">
<div class="header">
<!-- <SvgIcon class="close" name="popup_close" @click.stop="visibleCatalogue = false" /> -->
<div class="header-title ellipsis">{{ historyData.title }}</div>
<div class="sort flex-center-between">
<span class="menu-info">
共{{ historyData.totalEpisode }}话 {{ getUpdateStatusText(historyData.updateStatus) }}
</span>
<span class="btn" @click="sort = !sort">
<SvgIcon v-show="sort" class="section" name="acg_sort_0" />
<SvgIcon v-show="!sort" class="section" name="acg_sort_1" />
<span>{{ sort ? '正序' : '倒序' }}</span>
</span>
</div>
</div>
<div class="list overflow-y-auto">
<PullRefresh :params="moreParams" @onLoad="onLoad" @onRefresh="onRefresh" finishedText="">
<div
:id="`section_${item.episodeNumber}`"
class="item"
:class="{ active: item.episodeNumber === state.episodeNumber }"
v-for="item in autoList"
:key="item.id"
@click="switchEpisodeNumberCatalogue(item)"
>
{{ item.episodeNumber }}.{{ item.name }}
</div>
</PullRefresh>
</div>
</div>
</van-popup>
<PayForWatch
v-if="state.info.id"
ref="dvPayForWatch"
:isOpen="visiblePayForWatch"
:info="state.validateItem.id ? state.validateItem : state.info"
@close="closePayForWatch"
@buySuccess="beforeBuySuccess"
></PayForWatch>
<AppSharePopup
:is-open="visibleShare"
coverType="portrait"
@close="visibleShare = false"
:cover="historyData.verticalCover || state.info.cover"
/>
<CommentBox
v-if="state.info.id"
ref="dvComment"
class="comment"
:isOpen="visibleComment"
objType="cartoon"
@close="visibleComment = false"
:showCollected="false"
></CommentBox>
</template>
<script lang="ts">
export default {
name: 'AcgImageDetailWatch',
};
</script>
<script lang="ts" setup>
import { ref, watch, computed, onDeactivated, onUnmounted, onBeforeUnmount, provide, reactive, toRaw } from 'vue';
import PayForWatch from '../components/pay/PayForWatch.vue';
import mediaContentInfo from '../utils/mediaContentInfo';
import ImgObserver from '../components/ImgObserver.vue';
import { HistoryVideoType } from '@/enums/app';
import { showToast } from 'vant';
import { Pay_emit } from '../utils/pay-mitt';
import type { PayInfo } from '../utils/pay-mitt';
import { handleCollected, Collected_emit } from '../utils/collect';
import CommunityApi from '@/service/community';
import { getUpdateStatusText } from '../utils/common';
import { debounce } from '@/utils';
// import { useGlobalStore } from '@/stores/global';
// const globalStore = useGlobalStore();
const running = ref(false);
const isToolShow = ref(false);
const visibleHead = ref(true);
const progressRatio = ref(0);
const contentMaxScrollTop = ref(0);
const contentScrollTop = ref(0);
const visibleSides = ref(true);
const bottomRef = ref();
const {
state,
currentItem,
visibleCatalogue,
dvPayForWatch,
visiblePayForWatch,
closePayForWatch,
buySuccess,
switchEpisodeNumber,
autoList,
moreParams,
onLoad,
onRefresh,
switchEpisodeNumberCatalogue,
historyData,
addHistoryReadData,
sort,
} = mediaContentInfo({ isGetHistoryInit: true, dbType: HistoryVideoType.AcgImage });
provide('historyData', historyData);
const dvCaricatureWrap = ref();
const currentProgress = ref(1);
const currentEpisode = computed(() => {
return state.info.urlSet || [];
});
const reposition = () => {
if (dvCaricatureWrap.value) {
dvCaricatureWrap.value.scrollTop = (contentMaxScrollTop.value * progressRatio.value) / 100;
}
};
const currentHistoryReadData = computed(() => {
const arr = historyData.value.historyReadData || [];
return arr.find((item) => item.episodeNumber === state.episodeNumber) || { scrollTop: 0, imgs: {} };
});
watch(
dvCaricatureWrap,
() => {
dvCaricatureWrap.value.scrollTop = currentHistoryReadData.value.scrollTop || 0;
},
{
once: true,
},
);
const onContentScroll = (e: Event) => {
const target = e.target as HTMLElement;
contentScrollTop.value = target.scrollTop;
contentMaxScrollTop.value = target.scrollHeight - target.offsetHeight;
if (contentMaxScrollTop.value > 0) {
progressRatio.value = (contentScrollTop.value / contentMaxScrollTop.value) * 100;
}
const progress =
contentMaxScrollTop.value <= 0 ? 100 : Math.max(1, Math.min(100, Math.ceil((contentScrollTop.value / contentMaxScrollTop.value) * 100)));
addHistoryReadData(state.episodeNumber, {
progress,
scrollTop: contentScrollTop.value,
});
const isEnd = contentScrollTop.value + 10 >= contentMaxScrollTop.value;
if (isEnd) {
console.log('阅读结束')
running.value = false;
visibleSides.value = true;
bottomRef.value?.showTool();
return;
}
if (!running.value) {
if (contentScrollTop.value < 100) {
visibleSides.value = true;
}
// visibleSides.value = contentScrollTop.value < 100
}
};
const getImgHeight = (url: string) => {
const imgs = (currentHistoryReadData.value.imgs || {})[url];
const { width = 0, height = 0 } = imgs || {};
if (width && height) {
return {
height: (window.innerWidth / width) * height + 'px',
};
}
return {};
};
const updateIndex = ({ index, url }: { index: number; url: string }) => {
let progress = 0;
if (dvCaricatureWrap.value.scrollTop === 0) {
progress = 1;
} else {
progress = Number(index) + 1;
}
currentProgress.value = progress;
addHistoryReadData(state.episodeNumber, {
progress: Math.ceil((progress / currentEpisode.value.length) * 100),
progressUrl: url,
scrollTop: dvCaricatureWrap.value.scrollTop,
});
};
const updateSize = (imgObj: any) => {
addHistoryReadData(state.episodeNumber, {}, imgObj);
};
watch(
() => state.episodeNumber,
(num) => {
addHistoryReadData(num, { progress: 1 });
if (dvCaricatureWrap.value) {
dvCaricatureWrap.value.scrollTop = 0;
}
visibleHead.value = true;
},
);
addHistoryReadData(state.episodeNumber + 1, {});
onBeforeUnmount(() => {
addHistoryReadData(state.episodeNumber, {
scrollTop: dvCaricatureWrap.value.scrollTop,
});
addHistoryReadData(state.episodeNumber + 1, {});
});
// 点赞
const onToogleLiked = () => {
const zan = !historyData.value.mediaStatus.hasLiked;
(zan ? CommunityApi.zan : CommunityApi.cancleZan)(
zan
? {
type: 'image',
objID: historyData.value.id,
}
: {
type: 'image',
objIDs: [historyData.value.id],
},
).then(() => {
historyData.value.mediaStatus.hasLiked = zan;
});
};
const onClick = (e: MouseEvent) => {
if (running.value) {
visibleHead.value = !visibleHead.value;
return;
}
const { clientY } = e;
let scrollTop = 0;
const OneThird = window.innerHeight / 3.5;
if (clientY < OneThird) {
scrollTop = -300;
} else if (clientY > OneThird * 2) {
scrollTop = 300;
} else {
visibleHead.value = !visibleHead.value;
return;
}
if (scrollTop !== 0) {
visibleHead.value = false;
dvCaricatureWrap.value.scrollTo({ top: scrollTop + dvCaricatureWrap.value.scrollTop, behavior: 'smooth' });
}
};
let lastScrollTop = 0;
const onTouchStart = (e: TouchEvent) => {
const { clientY } = e.targetTouches[0];
lastScrollTop = clientY;
};
const onTouchMove = (e: TouchEvent) => {
const { clientY } = e.targetTouches[0];
const distance = lastScrollTop - clientY;
if (distance > 30) {
visibleHead.value = false;
return;
}
if (distance < -30) {
visibleHead.value = true;
return;
}
};
// watch(visibleCatalogue, (bool) => {
// if (bool) {
// visibleHead.value = false;
// }
// });
const scrollSpeed = 1;
let moveInterval = null as ReturnType<typeof setInterval> | null;
const cancelAutoMove = () => {
moveInterval && clearInterval(moveInterval);
moveInterval = null;
running.value = false;
};
const autoMove = () => {
const { scrollTop, scrollHeight } = dvCaricatureWrap.value;
if (scrollTop >= scrollHeight) {
// 停止自动移动
cancelAutoMove();
return;
}
let newPosition = scrollTop + scrollSpeed;
dvCaricatureWrap.value.scrollTo(0, newPosition);
};
onDeactivated(() => {
cancelAutoMove();
});
onUnmounted(() => {
cancelAutoMove();
});
watch(running, (bool) => {
if (bool) {
moveInterval = setInterval(autoMove, 10);
showToast({
message: '自动阅读模式',
onClose: () => {
if (moveInterval) {
visibleHead.value = false;
}
},
});
} else {
cancelAutoMove();
}
});
const beforeBuySuccess = (obj: PayInfo) => {
Pay_emit(historyData.value.id, obj);
buySuccess(obj.type);
};
// 收藏
const onToogleCollected = async () => {
try {
const {
id,
mediaStatus: { hasCollected },
} = historyData.value;
const tempIsCollect = !hasCollected;
handleCollected(id, tempIsCollect).then(() => {
historyData.value.mediaStatus.hasCollected = tempIsCollect;
Collected_emit(id, tempIsCollect);
});
} catch (e) {
console.log(e);
}
};
const visibleShare = ref(false);
const openShare = () => {
const { id, verticalCover, horizontalCover } = historyData.value;
let obj = {
id,
cover: verticalCover || horizontalCover,
};
// globalStore.setShareInfo(obj);
visibleShare.value = true;
};
const visibleComment = ref(false);
const dvComment = ref();
watch(dvComment, (el) => {
if (!el) return;
if (!state.info.id || !dvComment.value?.state) return;
console.log(state, 'state.info', state.validateItem);
dvComment.value.state.videoInfo = toRaw({
...state.info,
id: state.id,
});
dvComment.value.getCommentList();
});
</script>
<style lang="scss" scoped>
@import '@/assets/styles/mixins.scss';
.caricature-wrap {
width: 100%;
height: 100%;
// @include SafeArea('padding', 'bottom', 60px);
.like-box {
position: fixed;
bottom: 81px;
right: 0;
z-index: 100;
border-radius: 999px 0px 0px 999px;
background: rgba(0, 0, 0, 0.5);
width: 86px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #fff;
svg {
margin-right: 5px;
width: 24px;
height: 24px;
}
}
.top150 {
bottom: 150px !important;
}
.nav {
position: fixed;
left: 0;
top: 0;
z-index: 2;
width: 100%;
height: 44px;
background: #181824;
display: flex;
align-items: center;
padding: 0 6px;
font-size: 16px;
font-weight: 500;
color: #fff;
.back {
flex-shrink: 0;
width: 24px;
height: 24px;
flex-shrink: 0;
color: #fff;
margin-right: 4px;
}
.title {
flex: 1;
text-align: left;
}
.right {
flex-shrink: 0;
margin-left: 4px;
.collect {
svg {
width: 24px;
height: 24px;
}
}
.share {
width: 19px;
height: 19px;
margin: 0 16px 0 12px;
}
}
}
> .list {
padding-top: 1px;
.item {
width: 100%;
min-height: 200px;
height: auto;
:deep(.animationImg) {
overflow: visible;
.loading {
height: 200px;
}
}
}
}
.action {
position: fixed;
box-sizing: content-box;
@include SafeArea('padding', 'bottom', 0);
// left: 88px;
// right: 88px;
height: 48px;
background: #000;
width: 100%;
left: 0;
bottom: 0;
transition: all 0.3s;
.box {
z-index: 2;
border-radius: 100px;
padding: 12px 16px;
// background: rgba(0, 0, 0, 0.60)
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
> div {
display: flex;
flex-direction: column;
align-items: center;
color: rgba(255, 255, 255, 0.45);
font-size: 12px;
.icon {
width: 24px;
height: 24px;
color: rgba(255, 255, 255, 0.9);
}
&.next .icon {
transform: rotate(180deg);
}
&.disabled {
color: rgba(0, 0, 0, 0.4);
}
}
}
}
// .tips {
// position: fixed;
// @include SafeAreaFixed('bottom', 40px);
// right: 20px;
// border-radius: 8px;
// background: rgba(0, 0, 0, 0.6);
// padding: 8px 12px;
// color: #f1f1f1;
// font-size: 14px;
// font-weight: 500;
// }
}
.play-list-box {
position: relative;
width: 100%;
height: 100%;
background: #0f0f0f;
display: flex;
flex-direction: column;
color: rgba(255, 255, 255, 0.9);
.header {
padding-top: 85px;
width: 100%;
.close {
width: 14px;
height: 14px;
}
.header-title {
width: 100%;
text-indent: 16px;
font-size: 18px;
font-weight: 900;
font-family: Roboto;
}
.sort {
flex-shrink: 0;
margin: 16px 0 14px;
padding: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
font-weight: 500;
color: #141414;
background: #f68804;
.btn {
display: flex;
align-items: center;
.section {
width: 20px;
height: 20px;
margin-right: 4px;
}
}
.menu-info {
font-size: 14px;
font-weight: 500;
}
}
}
.list {
flex: 1;
.item {
width: 100%;
height: 19px;
line-height: 19px;
padding: 0 16px;
margin-bottom: 11px;
color: rgba(255, 255, 255, 0.55);
font-size: 16px;
font-weight: 400;
@include textEllipsis();
&.active {
color: #f68804;
}
}
}
}
.running-box {
background-color: #fff;
padding: 10px;
border-radius: 50%;
position: fixed;
bottom: 50px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
.running {
width: 36px;
height: 36px;
}
}
.tools {
position: absolute;
top: -70px;
left: 0;
width: 100%;
height: 70px;
padding: 0 16px;
color: #fff;
background-color: #000;
display: flex;
justify-content: space-between;
.prev,
.next {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 12px;
line-height: 18px;
.icon {
width: 24px;
height: 24px;
margin-bottom: 4px;
}
}
.next .icon {
transform: rotate(180deg);
}
.slider {
width: 245px;
align-self: center;
}
}
.top-enter-active,
.top-leave-active,
.bottom-enter-active,
.bottom-leave-active {
will-change: transform;
transition: transform 0.4s ease;
}
.top-enter-from,
.top-leave-to {
transform: translateY(-100%);
}
.bottom-enter-from,
.bottom-leave-to {
transform: translateY(200%);
}
</style>