初始化导入 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
+468
View File
@@ -0,0 +1,468 @@
<template>
<div class="wrapper">
<!-- 广告轮播 -->
<SplashAdvSwiper
class="advSwiper"
:autoTime="2500"
:list="state.launchInfo"
:isShowIndicators="state.launchInfo && state.launchInfo.length >= 2"
v-if="state.launchInfo && state.launchInfo.length"
/>
<div class="countdown" v-if="state.countdown >= 0">
<span v-show="state.countdown > 0">{{ state.countdown }}</span>
<div class="jump_over" v-show="state.countdown <= 0" @click="goApp">跳过</div>
</div>
<EnterGroupPopup v-if="state.isCheckError" />
<!-- <UpgradePopup :show="state.isUpgrade" /> -->
<div class="selectLineBox" v-if="state.loading">
<van-loading color="#1989fa" size="30" />
<div class="selectLine">选线中...</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { AdType, SourceType } from '@/enums/app';
import AppApi from '@/service/app';
import { useAppStore } from '@/stores/app';
import { useConfigStore } from '@/stores/config';
import { useUserStore } from '@/stores/user';
import { getDevType } from '@/utils';
import { getAdItem } from '@/utils/getConfig';
import { showDialog } from 'vant';
import { reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { jumpAdv } from '@/utils/index';
import { useGlobalStore } from '@/stores/global';
import setTheme from '@/assets/theme/index';
import { markHomeOldUserGuidePendingFromSplash } from '@/utils/paymentGuide';
import { detectLine } from '@/utils/detectLine';
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const userStore = useUserStore();
const configStore = useConfigStore();
const globalStore = useGlobalStore();
const state = reactive({
countdown: -1, //广告倒计时
launchInfo: <Array<Types.App.AdsInfoListItem>>[], //广告
timer: <any>null, //广告定时器
checkTimer: <any>null, //检测图片是否加载成功定时器
checkTime: 3, //图片加载成功检测时间
isCheckError: false, //是否选线失败
checkLineErrorNum: 0, //选线失败次数
isResLock: false, // 请求结果锁
loading: true, // 选线loading
isUpgrade: true, //是否升级
});
/**
* @description 检测广告图片是否加载成功
*/
const checkImgLoad = () => {
state.checkTimer = setInterval(() => {
if (state.checkTime <= 0) {
clearInterval(state.checkTimer);
state.checkTimer = null;
if (!state.timer) {
countdownStrat();
}
return;
}
state.checkTime--;
}, 1000);
};
/**
* @description 广告失败
*/
const imgError = () => {
if (!state.timer) {
countdownStrat();
if (state.checkTimer) {
clearInterval(state.checkTimer);
state.checkTimer = null;
}
}
};
/**
* @description 广告加载成功
*/
const imgLoad = () => {
if (!state.timer) {
countdownStrat();
if (state.checkTimer) {
clearInterval(state.checkTimer);
state.checkTimer = null;
}
}
};
/**
* @description 启动倒计时
*/
const countdownStrat = () => {
state.loading = false;
state.countdown = 3;
// 倒计时期间预取壳页,缩短跳过/倒计时结束后的等待(不改 3s 时长)
void import('@/views/MainView.vue');
const entry = String(configStore.appConfig?.defaultEntryPage || '').toLowerCase();
if (entry === 'drama') {
void import('@/views/shortVideo/index.vue');
} else {
void import('@/views/lldhome/index.vue');
}
userStore.updateWallet(); // 获取用户钱包
globalStore.setTabs(); // 获取tabs数据
globalStore.setAnnounce(); // 获取跑马灯数据
// 预售活动开启了,获取预售信息
if (configStore.appConfig.advanceStatus && configStore.appConfig.advanceStatus.activityStatus) {
globalStore.setPresaleActivityDetail();
}
globalStore.setVideoRoadLine(configStore.sourceInfo.VID[0]); // 默认选择第一个线路
state.timer = setInterval(() => {
if (state.countdown <= 0) {
clearInterval(state.timer);
} else {
state.countdown--;
}
}, 1000);
};
/**
* @description 开机广告
*/
function adControl() {
const launchList = getAdItem(AdType.START);
if (launchList.length > 0) {
state.launchInfo = launchList;
}
// checkImgLoad();
countdownStrat();
}
/**
* @description 进入APP
*/
const goApp = () => {
const entry = String(configStore.appConfig?.defaultEntryPage || '').toLowerCase();
if (entry === 'drama') {
// 默认进短剧:不走首页老用户吸底引导
router.replace({ path: '/shortVideo', query: { type: '4' } });
} else {
// 仅启动页进首页允许弹老用户吸底;首页消费该标记
markHomeOldUserGuidePendingFromSplash();
router.replace('/home');
}
let createAt = new Date(userStore.userInfo.createdAt).getTime();
let now = Date.now();
if (now - createAt > configStore.appConfig?.newbieSaleTime * 60 * 60 * 1000 && now - createAt > 3600000 * 24) {
getFirstLoginTime();
}
};
const getFirstLoginTime = () => {
const key = `first_login_time_${userStore.userInfo.uid}`; // userId 是登录用户的唯一标识
let firstLoginTime = localStorage.getItem(key);
firstLoginTime = Date.now().toString();
localStorage.setItem(key, firstLoginTime);
};
const toAdv = (item: any) => {
jumpAdv(item, 'ads');
};
/**
* @description 如果没有用户ID就生成一个
*/
const generateUid = () => {
if (!appStore.uid) {
// 配合商店包,如果链接中存在设备ID,就当作uuid,没有就使用自动生成的
if (route.query.uuid && JSON.stringify(route.query.uuid).indexOf('null') == -1) {
appStore.setUid(route.query.uuid.toString());
} else {
appStore.generateUid();
}
}
};
/**
* @description 登陆
* @returns 是否登陆成功
*/
const login = async (): Promise<boolean> => {
try {
const uid = appStore.uid;
const sysType = getDevType();
const isAppStore = route.query.isAppStore ? true : false;
let cutInfos: any = undefined;
// 判断是否来自商店包
if (isAppStore) {
// 判断是否有pc值,也就是粘贴板是否有内容
const pcStr: any = route.query.pc || route.query.dc;
if (pcStr) {
// 解析pc的JSON字符串
const pc = JSON.parse(pcStr);
// 如果JSON对象有以下值,则直接传pc值
if (pc.dc || pc.pc || pc.ch) {
cutInfos = route.query.pc || route.query.dc;
}
}
} else {
// 非商店包,原本的读取方式
cutInfos = Object.keys(route.query).length > 0 ? JSON.stringify(route.query) : undefined;
}
let userInfo = await AppApi.login(uid, sysType, cutInfos, isAppStore);
await userStore.saveLoginResult({ userInfo, token: userInfo.token });
return true;
} catch (error) {
return false;
}
};
const restoreUserFromStorage = () => {
if (userStore.userInfo && userStore.token) return;
const userInfo = localStorage.getItem('userInfo');
const token = localStorage.getItem('token');
if (userInfo && token) {
userStore.saveToken(JSON.parse(token));
userStore.saveUserInfo(JSON.parse(userInfo));
}
};
const ensureAuthAndConfig = async (): Promise<boolean> => {
restoreUserFromStorage();
if (!(userStore.userInfo && userStore.token)) {
const loginOk = await login();
if (!loginOk) {
showDialog({
title: '温馨提示',
message: '用户登陆失败',
confirmButtonText: '重试',
theme: 'round-button',
}).then(() => {
appStore.$reset();
userStore.$reset();
configStore.$reset();
state.isResLock = false;
init();
});
return false;
}
}
const configOk = await getConfig();
if (!configOk) {
showDialog({
title: '温馨提示',
message: '获取配置信息失败',
confirmButtonText: '重试',
theme: 'round-button',
}).then(() => {
appStore.$reset();
userStore.$reset();
configStore.$reset();
userStore.saveToken('');
localStorage.removeItem('userInfo');
localStorage.removeItem('token');
init();
});
return false;
}
adControl();
return true;
};
/**
* @description 初始化资源域名配置
* @param list 资源列表
*/
const initSourceInfo = (list: Array<Types.App.SourceListItem>): void => {
let sourceInfo: Types.App.SourceInfo = {
GROUP: '',
GUIDE: '',
AUDIO: '',
QRCODE: '',
IMAGE: '',
VID: [],
LAND: '',
PROXYRULE: '',
FAQ: '',
WELFARE: '',
ACT: '',
AppStore: '',
UP: '',
PRESALEBGIMG: '',
};
for (let item of list) {
switch (item.type) {
case SourceType.GROUP:
sourceInfo.GROUP = item.domain[0]?.url;
break;
case SourceType.GUIDE:
sourceInfo.GUIDE = item.domain[0]?.url;
break;
case SourceType.AUDIO:
sourceInfo.AUDIO = item.domain[0]?.url;
break;
case SourceType.PRESALEBGIMG:
sourceInfo.PRESALEBGIMG = item.domain[0]?.url;
break;
case SourceType.IMAGE:
sourceInfo.IMAGE = item.domain[0]?.url;
break;
case SourceType.VID:
sourceInfo.VID = item.domain;
break;
case SourceType.LAND:
sourceInfo.LAND = item.domain[0]?.url;
break;
case SourceType.PROXYRULE:
sourceInfo.PROXYRULE = item.domain[0]?.url;
break;
case SourceType.FAQ:
sourceInfo.FAQ = item.domain[0]?.url;
break;
case SourceType.QRCODE:
sourceInfo.QRCODE = item.domain[0]?.url;
break;
case SourceType.WELFARE:
sourceInfo.WELFARE = item.domain[0]?.url;
break;
case SourceType.AppStore:
sourceInfo.AppStore = item.domain[0]?.url;
break;
case SourceType.ACT:
sourceInfo.ACT = item.domain[0]?.url;
break;
case SourceType.UP:
sourceInfo.UP = item.domain[0]?.url;
break;
default:
break;
}
}
configStore.saveSourceInfo(sourceInfo);
};
/**
* @description 获取配置信息
* @returns 获取配置信息是否成功
*/
const getConfig = async (): Promise<boolean> => {
try {
const configList = await AppApi.queryDomain();
configStore.saveAppConfig(configList);
globalStore.setAdvanceStatus(configList.advanceStatus);
initSourceInfo(configList.sourceList);
setTheme(configList.festivalUi);
return true;
} catch (error) {
state.isResLock = false;
return false;
}
};
/**
* @description 选线失败:首次弹「选线失败」可重试,重试仍失败则弹进群引导
*/
const onLineCheckFail = () => {
if (state.checkLineErrorNum == 1) {
state.isCheckError = true;
return;
}
showDialog({
title: '温馨提示',
message: '选线失败',
confirmButtonText: '重试',
theme: 'round-button',
}).then(() => {
state.checkLineErrorNum++;
appStore.$reset();
userStore.$reset();
configStore.$reset();
init();
});
};
/**
* 初始化
*/
const init = async () => {
generateUid();
route.query.isAppStore
? localStorage.setItem('isAppStore', route.query.isAppStore.toString())
: localStorage.setItem('isAppStore', ''); // 保存是否来自商店包,用于判断是否跳转外链
// 选线:本地线路 → 全挂则 DNS 解析兜底,同安卓 DetectLineManager
const line = await detectLine();
if (!line) {
onLineCheckFail();
return;
}
if (state.isResLock) return;
state.isResLock = true;
configStore.saveBaseUrl(line);
await ensureAuthAndConfig();
};
/**
* created
*/
init();
</script>
<style lang="scss" scoped>
.wrapper {
width: 100%;
height: 100%;
background: url('../assets/images/webp/splash.webp') no-repeat;
background-size: 100% 100%;
position: relative;
.advSwiper {
width: 100vw;
height: 100vh;
border-radius: 0px !important;
position: relative;
:deep(.adv-swiper-slide) {
height: 100% !important;
border-radius: 0px !important;
}
}
.countdown {
position: absolute;
top: 30px;
right: 25px;
z-index: 999;
width: 76px;
height: 36px;
border-radius: 18px;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 20px;
font-family: Inter;
font-weight: 800;
.jump_over {
font-size: 16px;
font-weight: 400;
}
}
.selectLineBox {
height: 120px;
width: 150px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 10px;
position: fixed;
z-index: 1;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.selectLine {
margin-top: 20px;
font-size: 16px;
}
}
}
</style>