[Human] chore(project): 初始化导入 91dj H5 客户端项目代码

从本地工程首次入库;.npmrc(含私有 npm 源 token)与 tsbuildinfo 已加入 .gitignore,不随仓库提交。
This commit is contained in:
谢宇宁
2026-10-08 14:22:35 +07:00
commit eada71e211
1421 changed files with 144498 additions and 0 deletions
@@ -0,0 +1,490 @@
<template>
<div class="introduction-wrap overflow-y-auto">
<div class="titleBox ellipsis2">
{{ props.info.title }}
</div>
<div class="tags">
<span
class="tag"
@click="goAcgTagPage(item.id, item.name, 'video')"
v-for="item in info.tagDetails || []"
:key="item.id"
>
#{{ item.name }}
</span>
</div>
<div class="videoInfoBox flex-vcenter">
<div class="item margin-r-88">
<span>{{ watchCount(props.info.countBrowse) }} 观看量</span>
</div>
<div class="item" @click="onToogleLiked">
<span
class="acg-action-icon acg-action-icon--like"
:class="{ active: props.info.mediaStatus.hasLiked }"
aria-hidden="true"
/>
<span>{{ props.info.mediaStatus.hasLiked ? watchCount(props.info.countLike) : '点赞' }}</span>
</div>
<div class="item" @click="onToogleCollected">
<span
class="acg-action-icon acg-action-icon--collect"
:class="{ active: props.info.mediaStatus.hasCollected }"
aria-hidden="true"
/>
<span>{{ props.info.mediaStatus.hasCollected ? watchCount(props.info.countCollect) : '收藏' }}</span>
</div>
<div class="item" @click="openShare">
<img class="acg-share-icon" src="@/assets/images/ui/long-video-share.svg" width="24" height="24" alt="" />
<span>分享</span>
</div>
</div>
<div
class="actress-box flex-center-between"
v-if="props.info.actress && props.info.actress.id"
@click="
$router.push({
name: 'AcgActress',
query: {
id: props.info.actress.id,
},
})
"
>
<lazyImg class="portrait" :src="props.info.actress.portrait"></lazyImg>
<div class="info">
<p class="name">{{ props.info.actress.name }}</p>
<div class="count-box flex-vcenter">
<SvgIcon name="acg-video-icon-a-video" />
{{ watchCount(props.info.actress.acgCount) }}部
<SvgIcon name="acg-video-icon-a-collect" style="margin-left: 12px" />
{{ watchCount(props.info.actress.collectCount) }}
</div>
</div>
<SvgIcon class="icon" name="moreIcon" />
</div>
<!-- 选集区域 -->
<div class="episodeArea">
<div class="episodeHeader flex-center-between padding16">
<div class="episodeTitle">选集</div>
<div class="episodeAll flex-vcenter" @click="visibleCatalogue = true">
全{{ props.info.totalEpisode }}话
<SvgIcon class="icon" name="moreIcon" />
</div>
</div>
<div class="episodeBox" @touchstart.stop @touchmove.stop @touchend.stop>
<Episode
class="item"
@click="switchEpisodeNumberCatalogue(item)"
v-for="item in autoList"
:key="item.id"
:info="item"
:episode-number="state.episodeNumber"
></Episode>
</div>
</div>
<HomeHeader :advType="AdType.VIDEOADV" />
<!-- <RecommendList
:media-id="props.info.id"
:contentType="props.info.mediaType"
title="猜你喜欢"
:marginHide="true"
></RecommendList> -->
<RecommendedList :type="info.mediaType" :tagId="info.tagDetails?.[0]?.id" />
<!-- 分享弹窗 -->
<AppSharePopup
:is-open="visibleShare"
@close="visibleShare = false"
coverType="portrait"
:cover="props.info.verticalCover || props.info.horizontalCover"
/>
<Teleport to=".video-detail-wrap">
<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">
剧集列表
<!-- <span @click="sort = !sort">
{{ sort ? '正序' : '倒序' }}
</span> -->
</div>
<div class="grading-group" v-if="gradingGroup.length > 1">
<div
class="grading-group-item"
v-for="(item, index) in gradingGroup"
:key="index"
@click.stop="state.gradingGroupIndex = index"
>
{{ item[0].episodeNumber }}-{{ item[item.length - 1].episodeNumber }}
</div>
</div>
<div class="list-box overflow-y-auto">
<PullRefresh :params="moreParams" @onLoad="onLoad" @onRefresh="onRefresh">
<div class="list">
<Episode
class="item"
:episodeNumber="state.episodeNumber"
:info="item"
v-for="item in gradingGroup[state.gradingGroupIndex]"
:key="item.id"
@click="state.episodeNumber = item.episodeNumber"
></Episode>
</div>
</PullRefresh>
</div>
</div>
</van-popup>
</Teleport>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed, watchEffect, Ref } from 'vue';
import { HistoryVideoType } from '@/enums/app';
import { goAcgTagPage } from '../../utils/common';
import { watchCount, videoTime } from '@/utils/index';
// import RecommendList from '../../components/recommendList.vue';
import { handleCollected, Collected_emit } from '../../utils/collect';
import { AdType } from '@/enums/app';
import mediaContentInfo from '../../utils/mediaContentInfo';
import CommunityApi from '@/service/community';
import { useGlobalStore } from '@/stores/global';
import Episode from '../../components/episode.vue';
import {
getVideoType,
reportCollectCommentEvent,
reportCollectFn,
reportLikeCommentEvent,
} from '@/utils/eventReporter/videoComment';
const emits = defineEmits(['updateLiked', 'updateCollected', 'updateItem', 'openRoadLine']);
const globalStore = useGlobalStore();
const props = withDefaults(
defineProps<{
info: Types.Media.MediaInfo;
}>(),
{
info: {},
},
);
const {
state,
currentItem,
autoList,
// switchEpisodeNumberCatalogue,
visibleCatalogue,
sort,
moreParams,
onLoad,
onRefresh,
getList,
buySuccess,
replaceRouter,
} = mediaContentInfo({
isGetInfo: false,
isGetHistoryInit: true,
pageSize: 30,
dbType: HistoryVideoType.AcgVideo,
isScrollTop: false,
});
const gradingGroup = computed(() => {
let arr = autoList.value || [];
if (arr.length > 30) {
const result = [];
for (let i = 0; i < arr.length; i += 30) {
result.push(arr.slice(i, i + 30));
}
return result;
}
return [arr];
});
watch(
() => currentItem.value.id,
() => {
emits('updateItem', currentItem.value);
},
{ immediate: true },
);
defineExpose({
buySuccess,
});
const switchEpisodeNumberCatalogue = (item: Types.Acg.MediaContentListItem) => {
visibleCatalogue.value = false;
state.episodeNumber = item.episodeNumber;
replaceRouter(item.episodeNumber);
};
// 打开分享
const visibleShare = ref(false);
const openShare = () => {
const { id, verticalCover, horizontalCover } = props.info;
let obj = {
id,
cover: verticalCover || horizontalCover,
};
// globalStore.setShareInfo(obj);
visibleShare.value = true;
};
const onToogleCollected = () => {
try {
const {
id,
mediaStatus: { hasCollected },
} = props.info;
const tempIsCollect = !hasCollected;
tempIsCollect ? reportCollectFn(1, 1, props.info) : reportCollectFn(1, 2, props.info);
handleCollected(id, tempIsCollect).then(() => {
emits('updateCollected', tempIsCollect);
Collected_emit(id, tempIsCollect);
});
} catch (e) {
console.log(e);
}
};
const onToogleLiked = () => {
const zan = !props.info.mediaStatus.hasLiked;
zan ? reportCollectFn(2, 1, props.info) : reportCollectFn(2, 2, props.info);
(zan ? CommunityApi.zan : CommunityApi.cancleZan)(
zan
? {
type: 'video',
objID: props.info.id,
}
: {
type: 'video',
objIDs: [props.info.id],
},
).then(() => {
emits('updateLiked', zan);
});
};
</script>
<style lang="scss" scoped>
@import '@/assets/styles/mixins.scss';
.introduction-wrap {
height: 100%;
padding: 0 16px;
color: var(--app-text-primary);
.titleBox {
font-size: 14px;
font-weight: 500;
line-height: 22px;
margin: 18px 0;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 8px 10px;
margin: 10px 0 14px;
.tag {
padding: 4px 8px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.05);
color: var(--app-text-primary);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
}
}
.videoInfoBox {
color: #989898;
font-size: 12px;
font-weight: 400;
margin: 10px 0 18px;
justify-content: space-between;
.item {
display: flex;
align-items: center;
justify-content: center;
.acg-action-icon {
display: inline-block;
width: 24px;
height: 24px;
margin-right: 4px;
flex-shrink: 0;
background-color: #989898;
&--like {
mask: url('@/assets/images/ui/long-video-like.svg') center / contain no-repeat;
}
&--collect {
mask: url('@/assets/images/ui/long-video-collect.svg') center / contain no-repeat;
}
&.active {
background-color: #f52c56;
}
}
.acg-share-icon {
width: 24px;
height: 24px;
margin-right: 4px;
flex-shrink: 0;
}
> span {
color: #989898;
}
}
}
.actress-box {
height: 60px;
margin: 24px 0;
.portrait {
width: 60px;
height: 60px;
flex-shrink: 0;
border-radius: 50%;
overflow: hidden;
margin-right: 12px;
}
.info {
flex: 1;
.name {
color: var(--app-text-primary);
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: normal;
margin-bottom: 5px;
}
.count-box {
color: var(--app-text-secondary);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
svg {
width: 16px;
height: 16px;
margin-right: 4px;
}
}
}
}
.episodeArea {
margin-bottom: 14px;
border-top: 0.5px solid rgba(255, 255, 255, 0.1);
border-bottom: 0.5px solid rgba(255, 255, 255, 0.1);
padding-top: 10px;
.episodeHeader {
font-weight: 500;
.episodeTitle {
font-size: 16px;
}
.episodeAll {
color: var(--app-text-secondary);
font-size: 12px;
svg {
width: 18px;
height: 18px;
}
}
}
.episodeBox {
display: flex;
overflow-x: auto;
margin: 12px 0;
.item {
margin-right: 12px;
flex-shrink: 0;
}
}
}
}
.play-list-box {
$paddingBottom: 38px;
width: 100%;
height: 100%;
border-radius: 20px 20px 0px 0px;
background: var(--app-background);
padding: 18px 16px $paddingBottom;
@include SafeArea('padding', 'bottom', 0);
display: flex;
flex-direction: column;
color: var(--app-text-primary);
font-size: 12px;
color: var(--app-text-primary);
> .title {
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 25px; /* 140% */
margin: 16px 0;
span {
color: var(--app-text-secondary);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
}
}
.line {
width: 36px;
height: 5px;
border-radius: 100px;
background: #464646;
margin: 0 auto;
}
.list-box {
flex: 1;
.list {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
grid-column-gap: 5px;
grid-row-gap: 9px;
}
}
}
.margin-r-88 {
margin-right: 88px;
}
.introduction-wrap,
.play-list-box {
:deep(.episode) {
color: var(--app-text-secondary);
border-color: transparent;
background: rgba(255, 255, 255, 0.05);
font-weight: 500;
&.active {
color: var(--app-primary);
border-color: var(--app-primary);
background: rgba(255, 126, 0, 0.2);
}
.byte-wrap .item {
background-color: var(--app-primary);
}
}
}
.introduction-wrap :deep(.recommendedList .horizontalVideoBox) {
.coverBox .vip_icon:not(.coins_icon):not(.free_trial_icon) {
background: var(--app-primary);
}
.videoTitle {
color: var(--app-text-primary);
font-weight: 400;
}
.acg-info span,
.videoTitleInfo {
color: var(--app-text-secondary);
}
}
</style>