初始化导入 hjsq H5 项目代码

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-11-05 12:01:36 +07:00
co-authored by Claude Opus 5.5
commit f61585f786
1367 changed files with 136187 additions and 0 deletions
@@ -0,0 +1,291 @@
<!-- 首页短剧:样式 5 按专题切换作品,其余样式保留模块排序。 -->
<template>
<div class="drama-module overflow-y-auto" :id="'home' + tabIndex">
<van-pull-refresh v-model="refreshing" :head-height="0" :pull-distance="50" @refresh="onRefresh">
<template #pulling />
<template #loosing />
<template #loading />
<div v-if="isSectionStyle && sections.length" class="filters" @touchstart.stop @touchmove.stop @touchend.stop>
<button
v-for="section in sections"
:key="section.sectionID"
type="button"
class="filter"
:class="{ 'is-active': activeSectionId === section.sectionID }"
:aria-pressed="activeSectionId === section.sectionID"
@click="selectSection(section.sectionID)"
>
{{ section.sectionName }}
</button>
</div>
<div
v-else-if="!isSectionStyle && sortRules.length"
class="filters"
@touchstart.stop
@touchmove.stop
@touchend.stop
>
<button
v-for="sort in sortRules"
:key="sort.val"
type="button"
class="filter"
:class="{ 'is-active': moduleSort === sort.val }"
:aria-pressed="moduleSort === sort.val"
@click="moduleSort = sort.val"
>
{{ sort.name }}
</button>
</div>
<ModuleBanners :banners="currentBanners" :moduleType="12" />
<van-list
v-model:loading="loading"
v-model:error="error"
:finished="finished"
:immediate-check="false"
:offset="100"
:finished-text="items.length ? '没有更多数据了' : ''"
error-text="请求失败,点击重新加载"
@load="onLoad"
>
<div v-if="items.length" class="drama-grid">
<DramaCoverCard
v-for="item in items"
:key="item.series.id"
:item="item.series"
variant="home"
@click="goDramaPlay(item.series.id)"
>
<template #badge>
<span v-if="item.badge" class="permission" :class="'permission--' + item.badge">
{{ badgeLabels[item.badge] }}
</span>
</template>
</DramaCoverCard>
</div>
<Empty v-else-if="finished" />
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import HomeApi from '@/service/home';
import MediaApi from '@/service/media';
import { mapSeries } from '@/service/short-drama';
import { useConfigStore } from '@/stores/config';
import DramaCoverCard from '@/views/shortVideo/short-drama/components/DramaCoverCard.vue';
const props = defineProps<{
moduleId: string;
tabIndex: number;
haiJiaoStyle: Types.Home.HaiJiaoStyle;
pureVersion?: boolean;
}>();
type DramaModuleItem = Types.ShortDrama.DramaInfo &
Pick<Types.Media.AllMediaInfoItem, 'permission' | 'permissionIconHide' | 'price'>;
type DramaModuleSection = Types.Media.allSection & {
banners?: Types.Home.ModuleBanner[] | null;
};
type DramaModuleResponse = {
allMediaInfo?: DramaModuleItem[] | null;
list?: DramaModuleItem[] | null;
allSection?: DramaModuleSection[] | null;
banners?: Types.Home.ModuleBanner[] | null;
hasNext?: boolean;
};
type Badge = 'vip' | 'coin' | 'free';
const badgeLabels = { vip: 'VIP', coin: '金币', free: '免费' };
const router = useRouter();
const configStore = useConfigStore();
const media = ref<DramaModuleItem[]>([]);
const moduleBanners = ref<Types.Home.ModuleBanner[]>([]);
const isSectionStyle = computed(() => props.haiJiaoStyle?.sectionStyle === 5);
const sections = ref<DramaModuleSection[]>([]);
const activeSectionId = ref('');
const currentBanners = computed(() =>
isSectionStyle.value
? sections.value.find((section) => section.sectionID === activeSectionId.value)?.banners || []
: moduleBanners.value,
);
const items = computed(() =>
media.value.map((info) => {
let badge: Badge | undefined;
if (!info.permissionIconHide) {
if (info.permission === 0 && configStore.appConfig.vipMark) badge = 'vip';
if (info.permission === 1 && configStore.appConfig.coinMark) badge = 'coin';
if (info.permission === 2 && configStore.appConfig.freeMark) badge = 'free';
}
return { series: mapSeries(info), badge };
}),
);
const sortRules = computed(() => (props.haiJiaoStyle?.sortShow === 1 ? props.haiJiaoStyle.sortRules || [] : []));
const moduleSort = ref(sortRules.value[0]?.val || 0);
const loading = ref(false);
const refreshing = ref(false);
const finished = ref(false);
const error = ref(false);
let pageNumber = 1;
let requestVersion = 0;
let pending = false;
let sectionsLoaded = false;
async function loadPage(reset = false, reloadSections = false) {
if (!reset && (pending || finished.value)) return;
const version = ++requestVersion;
const page = reset ? 1 : pageNumber;
pending = true;
loading.value = true;
error.value = false;
if (reset) finished.value = false;
try {
let res: DramaModuleResponse;
if (isSectionStyle.value) {
if (!sectionsLoaded || reloadSections) {
const module = (await HomeApi.getModuleList({
id: props.moduleId,
data: { pageNumber: 1, pageSize: 12, moduleSort: 0 },
})) as unknown as DramaModuleResponse;
if (version !== requestVersion) return;
sections.value = module?.allSection || [];
sectionsLoaded = true;
if (!sections.value.some((section) => section.sectionID === activeSectionId.value)) {
activeSectionId.value = sections.value[0]?.sectionID || '';
media.value = [];
}
}
if (!activeSectionId.value) {
media.value = [];
finished.value = true;
return;
}
res = (await MediaApi.getMediaTopic({
sId: activeSectionId.value,
pageNumber: page,
pageSize: 20,
})) as unknown as DramaModuleResponse;
} else {
res = (await HomeApi.getModuleList({
id: props.moduleId,
data: { pageNumber: page, pageSize: 12, moduleSort: moduleSort.value },
})) as unknown as DramaModuleResponse;
}
if (version !== requestVersion) return;
if (!isSectionStyle.value && page === 1) {
moduleBanners.value = res?.banners || [];
}
// 短剧专题作品接口使用 list;模块接口使用 allMediaInfo。
const list = (isSectionStyle.value ? res?.list : res?.allMediaInfo) || [];
media.value = page === 1 ? list : [...media.value, ...list];
pageNumber = page + 1;
finished.value = !res?.hasNext || !list.length;
} catch {
if (version === requestVersion) error.value = true;
} finally {
if (version === requestVersion) {
pending = false;
loading.value = false;
refreshing.value = false;
}
}
}
const onLoad = () => loadPage();
const onRefresh = () => {
pageNumber = 1;
return loadPage(true, true);
};
const selectSection = (sectionId: string) => {
if (activeSectionId.value === sectionId) return;
activeSectionId.value = sectionId;
media.value = [];
pageNumber = 1;
void loadPage(true);
};
const goDramaPlay = (id: string) => router.push({ path: '/shortDramaPlay', query: { id, episode: '1' } });
watch(
() => [props.moduleId, isSectionStyle.value, moduleSort.value],
() => {
media.value = [];
moduleBanners.value = [];
sections.value = [];
activeSectionId.value = '';
sectionsLoaded = false;
pageNumber = 1;
void loadPage(true);
},
{ immediate: true },
);
onBeforeUnmount(() => {
requestVersion += 1;
});
</script>
<style scoped lang="scss">
.drama-module {
height: calc(100vh - 44px * 2 - 55px);
:deep(.van-pull-refresh) {
min-height: 100%;
}
:deep(.advBox) {
margin: 12px 0;
.firstRow,
.adv-line-2,
.banner_box {
padding: 0 16px;
}
}
}
.filters {
display: flex;
gap: 10px;
overflow-x: auto;
padding: 8px 16px;
}
.filter {
flex-shrink: 0;
border: 0;
border-radius: 20px;
padding: 4px 10px;
font-size: 12px;
line-height: 18px;
color: rgba(255, 255, 255, 0.55);
background: rgba(255, 255, 255, 0.05);
&.is-active {
color: #111;
background: #ffc000;
font-weight: 600;
}
}
.drama-grid {
display: flex;
flex-wrap: wrap;
gap: 12px 7px;
padding: 12px 16px;
}
.permission {
position: absolute;
top: 0;
right: 0;
padding: 0 6px;
border-radius: 3px;
font-size: 10px;
line-height: 16px;
color: #fff;
&--vip {
background: linear-gradient(60.44deg, #ff8000 10.737%, #ff7a01 66.041%);
}
&--free {
background: #1cd302;
}
&--coin {
color: #1f2029;
background: linear-gradient(135deg, #ffe580 0%, #facc15 100%);
}
}
</style>