初始化黄果短剧 H5 客户端

对接后端 hgmjapi.mfcg.me:
- 接口解密 key 更新为 resKey360
- 登录 sysType 使用原生 getDevType(后端已放开 pc/android)
- 修正榜单接口路径 /vid/rangking → /vid/ranking,方法改为 GET

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-10-08 19:41:43 +07:00
co-authored by Claude Opus 4.8
commit c957b45bad
633 changed files with 51497 additions and 0 deletions
+333
View File
@@ -0,0 +1,333 @@
<template>
<div>
<div class="loadingBox" v-if="state.dataloading">
<van-loading class="loading" color="#fff" vertical>加载中...</van-loading>
</div>
<div v-else>
<mobile v-if="isMobile" />
<pc v-else />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onUnmounted, reactive } from 'vue';
import { useAppStore } from '@/stores/app';
import { useConfigStore } from '@/stores/config';
import { useUserStore } from '@/stores/user';
import { useGlobalStore } from '@/stores/global';
import { getDevType } from '@/utils';
import { getAdItem } from '@/utils/getConfig';
import { useRoute, useRouter } from 'vue-router';
import { AdType, SourceType } from '@/enums/app';
import { useHomeStore } from '@/stores/home';
import axios from 'axios';
import AppApi from '@/service/app';
import mobile from './../mobile/index.vue';
import pc from './../pc/index.vue';
const isMobile = ref(false);
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const userStore = useUserStore();
const configStore = useConfigStore();
const globalStore = useGlobalStore();
const homeStore = useHomeStore();
const state = reactive({
isCheckError: false, //是否选线失败
checkLineErrorNum: 0, //选线失败次数
isResLock: false, // 请求结果锁
resResult: <any>[], // 响应结果
pingCheckCount: 0, // 请求的ping失败次数
loading: true, // 选线loading
videoPlayer: true,
dataloading: true,
});
const setDeviceType = () => {
isMobile.value = window.innerWidth <= 768;
// if (isMobile.value) {
// router.push('/home')
// } else {
// router.push('/welfare')
// }
};
setDeviceType();
// 监听屏幕大小变化
window.addEventListener('resize', () => {
setDeviceType();
});
onUnmounted(() => {
window.removeEventListener('resize', setDeviceType);
});
/**
* @description 如果没有用户ID就生成一个
*/
const generateUid = () => {
if (!appStore.uid) {
appStore.generateUid();
}
};
/**
* @description 登陆
* @returns 是否登陆成功
*/
const login = async (): Promise<boolean> => {
try {
const uid = appStore.uid;
const sysType = getDevType();
const cutInfos = Object.keys(route.query).length > 0 ? JSON.stringify(route.query) : undefined;
let userInfo = await AppApi.login(uid, sysType, cutInfos);
userStore.saveUserInfo(userInfo);
userStore.saveToken(userInfo.token);
return true;
} catch (error) {
return false;
}
};
/**
* @description 初始化资源域名配置
* @param list 资源列表
*/
const initSourceInfo = (list: Array<Types.App.SourceListItem>): void => {
let sourceInfo: Types.App.SourceInfo = {
GROUP: '',
GUIDE: '',
QRCODE: '',
IMAGE: '',
VID: [],
LAND: '',
PROXYRULE: '',
FAQ: '',
WELFARE: '',
ACT: '',
AppStore: '',
UP: '',
TELEGRAM: '',
EMAIL: '',
AUDIO: '',
Download: '',
};
for (let item of list) {
switch (item.type) {
case SourceType.GROUP:
sourceInfo.GROUP = item.domain[0]?.url;
break;
case SourceType.TELEGRAM:
sourceInfo.TELEGRAM = item.domain[0]?.url;
break;
case SourceType.GUIDE:
sourceInfo.GUIDE = 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;
case SourceType.EMAIL:
sourceInfo.EMAIL = item.domain[0]?.url;
break;
case SourceType.AUDIO:
sourceInfo.AUDIO = item.domain[0]?.url;
break;
case SourceType.Download:
sourceInfo.Download = item.domain;
break;
default:
break;
}
}
configStore.saveSourceInfo(sourceInfo);
};
/**
* @description 获取配置信息
* @returns 获取配置信息是否成功
*/
const getConfig = async (): Promise<boolean> => {
try {
const configList = await AppApi.queryDomain();
state.dataloading = false;
configStore.saveAppConfig(configList);
initSourceInfo(configList.sourceList);
return true;
} catch (error) {
return false;
}
};
const pingErrorPop = (hosts: any) => {
// 只有最后一条域名出错时,判断响应结果不存在true的时候,说明全部不通,即弹出选线失败
state.pingCheckCount++;
if (state.pingCheckCount == hosts.length) {
state.pingCheckCount = 0;
let index = state.resResult.indexOf(true);
if (index == -1) {
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 queryContactInfo = async () => {
try {
let req = {
type: 2,
};
const res: any = await AppApi.getBusiness(req);
useConfigStore().setContactInfo(res || []);
// console.log(res);
// contactInfo
// configStore.saveAppConfig(configList);
// initSourceInfo(configList.sourceList);
// return true;
} catch (error) {
// return false;
}
};
/**
* 初始化
*/
const init = async () => {
generateUid();
const baseUrls: string = process.env.VITE_BASE_URL || '[]';
const hosts = JSON.parse(baseUrls);
state.resResult = new Array(hosts.length);
hosts.forEach((url: any, index: number) => {
axios({
method: 'GET',
url: url + '/api/app/ping/check',
timeout: 1000 * 10, // 10秒超时
})
.then(async (res) => {
if (res.data.code == 200) {
if (!state.isResLock) {
state.isResLock = true;
state.resResult[index] = true;
//选线成功
useConfigStore().saveBaseUrl(url);
console.log(configStore.sourceInfo.VID[0]);
state.loading = false;
//登陆
if (!(userStore.userInfo && userStore.token)) {
let userInfo: any = localStorage.getItem('userInfo');
let token = localStorage.getItem('token');
if (userInfo && token) {
userStore.saveUserInfo(JSON.parse(userInfo));
userStore.saveToken(JSON.parse(token));
} else {
const loginOk = await login();
if (!loginOk) {
// showDialog({
// title: '温馨提示',
// message: '用户登陆失败',
// confirmButtonText: '重试',
// theme: 'round-button',
// }).then(() => {
// appStore.$reset();
// userStore.$reset();
// configStore.$reset();
// init();
// });
return;
}
}
}
homeStore.getHomeData();
queryContactInfo();
//获取配置信息
const configOk = await getConfig();
globalStore.setVideoRoadLine(configStore.sourceInfo.VID[0]); // 默认选择第一个线路
if (!configOk) {
// showDialog({
// title: '温馨提示',
// message: '获取配置信息失败',
// confirmButtonText: '重试',
// theme: 'round-button',
// }).then(() => {
// appStore.$reset();
// userStore.$reset();
// configStore.$reset();
// init();
// userStore.saveToken('');
// localStorage.removeItem('userInfo');
// localStorage.removeItem('token');
// });
return;
}
// const launchList = getAdItem(AdType.START);
// if (launchList.length > 0) {
// state.launchInfo = launchList[0];
// state.advsList = launchList || [];
// }
//开机广告
// adControl();
}
} else {
pingErrorPop(hosts);
}
})
.catch(() => {
pingErrorPop(hosts);
});
});
};
/**
* created
*/
init();
</script>
<style scoped lang="scss">
.loadingBox {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
// .loading {
// width: 40px;
// }
}
</style>
+252
View File
@@ -0,0 +1,252 @@
<template>
<div class="collect overflow-y-auto" ref="contentRef">
<MobileHeader class="header" />
<div class="contentBox">
<div class="subTitle">我的收藏</div>
<div class="btnBox">
<div class="cancelBtn" v-if="state.isEdit" @click="cancelEdit">取消</div>
<div class="editBtn" :class="{ disableEdit: !state.delList.length }" v-if="state.isEdit" @click="delVideo">
删除
</div>
<div class="editBtn" @click="edit" v-else>编辑</div>
</div>
<div class="allSelectBox" v-if="state.isEdit" @click="allSelect">
<SvgIcon name="selectedIcon" class="selectedIcon" v-if="state.delList.length == state.list.length" />
<div class="selectIcon" v-else></div>
全选
</div>
<div class="videoList">
<div class="mobileVideoBox" v-for="item in state.list" :key="item.id">
<MobileVideoBox :videoInfo="item" />
<div class="videoMask" v-if="state.isEdit" @click.stop="selectVideo(item)">
<SvgIcon name="selectedIcon" class="selected" v-if="handleSelectSta(item.id)" />
<div class="select" v-else></div>
</div>
</div>
</div>
<MobilePagination
class="pagination"
:pageNumber="state.pageNumber"
:pageSize="state.pageSize"
:total="state.total"
@changePage="changePage"
v-if="state.total > state.pageSize"
/>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { showToast } from 'vant';
import MineApi from '@/service/mine';
const globalStore = useGlobalStore();
const contentRef = ref();
const state = reactive({
isEdit: false,
pageNumber: 1,
pageSize: 6,
total: 0,
list: <any>[],
delList: <any>[],
});
const userInfo = computed(() => {
const data = localStorage.getItem('userInfo');
if (data) {
return JSON.parse(data);
}
return data;
});
const edit = () => {
state.isEdit = true;
};
const cancelEdit = () => {
state.isEdit = false;
state.delList = [];
};
const selectVideo = (item: any) => {
let index = state.delList.findIndex((i: any) => {
return item.id == i;
});
if (index != -1) {
state.delList.splice(index, 1);
} else {
state.delList.push(item.id);
}
};
const handleSelectSta = (id: any) => {
let result = state.delList.some((i: any) => {
return i == id;
});
return result;
};
const allSelect = () => {
if (state.delList.length) {
state.delList = [];
} else {
let arr: any = [];
state.list.forEach((item: any) => {
arr.push(item.id);
});
state.delList = arr;
}
};
const delVideo = async () => {
let req = {
objIds: state.delList,
type: 'video',
};
await MineApi.batchUnCollect(req);
// const res: any = await delAllData(HistoryVideoType.VIDEO, state.delList);
queryList();
state.isEdit = false;
state.delList = [];
showToast('删除成功');
};
const changePage = (pageNumber: number) => {
state.pageNumber = pageNumber;
contentRef.value.scrollTo({ top: 0 });
queryList();
};
const queryList = async () => {
let req = {
pageNumber: state.pageNumber,
pageSize: state.pageSize,
type: 'video',
uid: userInfo.value.uid,
};
const res: any = await MineApi.queryCollectionList(req);
state.total = res.total;
state.list = res.list || [];
};
queryList();
</script>
<style scoped lang="scss">
.collect {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.contentBox {
padding: 26px 16px 150px;
.subTitle {
display: flex;
width: 80.323px;
height: 41px;
flex-direction: column;
justify-content: center;
flex-shrink: 0;
margin-bottom: 10px;
}
.btnBox {
display: flex;
align-items: center;
justify-content: flex-end;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 24px;
.cancelBtn {
width: 72px;
height: 30px;
border-radius: 4px;
border: 1px solid #ffc303;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
color: #ffcd3f;
text-align: center;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.editBtn {
color: #fff;
text-align: center;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
width: 72px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
background: linear-gradient(270deg, #ffc303 0%, #ffdd01 100%);
}
.disableEdit {
opacity: 0.3;
}
}
.allSelectBox {
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 16px;
.selectIcon {
width: 16px;
height: 16px;
border-radius: 50%;
border: 2.2px solid #999;
margin-right: 8px;
}
.selectedIcon {
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 8px;
}
}
.videoList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 16px;
grid-column-gap: 12px;
margin-bottom: 45px;
.mobileVideoBox {
position: relative;
.videoMask {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 3;
display: flex;
justify-content: flex-end;
padding: 4px;
box-sizing: border-box;
.select {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2.2px solid #999;
}
.selected {
width: 18px;
height: 18px;
}
}
}
}
}
}
</style>
@@ -0,0 +1,158 @@
<template>
<div class="classification">
<div class="topicBox" v-for="i in state.topicList" :key="i.id">
<div class="topicTitle">{{ i.subModuleName }}</div>
<div class="tagList">
<div class="tagItem" v-for="item in i.allSection" :key="item.id" @click="jumpTopice(item)">
{{ item.name }}
<span>({{ item.videoCount }})</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { useRoute, useRouter } from 'vue-router';
import HomeApi from '@/service/home';
const route = useRoute();
const router = useRouter();
const globalStore = useGlobalStore();
const state = reactive({
topicList: <any>[],
topicName: route.query.name,
});
const jumpTopice = (item: any) => {
let queryData = {
tid: item.id,
name: item.name,
};
router
.push({
path: '/sectionDetails',
query: queryData,
})
// .then(() => {
// location.reload();
// });
};
const getTagList = async () => {
const res: any = await HomeApi.getTabs({ isSection: true });
state.topicList = res.homePage || [];
};
const init = () => {
getTagList();
};
init();
</script>
<style scoped lang="scss">
.classification {
padding: 18px 16px 0;
box-sizing: border-box;
.advBanner {
width: 100%;
height: 70px;
}
.topicBox {
margin-bottom: 24px;
.topicTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 28px; /* 155.556% */
margin-bottom: 12.25px;
}
.tagList {
display: flex;
flex-wrap: wrap;
align-items: center;
.tagItem {
padding: 4px 12px;
box-sizing: border-box;
color: #fff;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
margin: 0 8px 8px 0;
span {
color: #a0a6b4;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
}
}
}
.topicBox:last-child {
margin-bottom: 60px;
}
.dropdownBox {
width: 76px;
height: 33px;
border-radius: 6.986px;
border: 0.873px solid #212121;
background: #151515;
margin-bottom: 13.6px;
// background: skyblue;
:deep(.van-dropdown-menu__bar) {
background: rgba(0, 0, 0, 0);
height: 100%;
}
:deep(.van-dropdown-menu__title) {
padding: 0;
padding-right: 6px;
}
:deep(.van-dropdown-menu__title--active:after) {
border-color: transparent transparent #a0a6b4 #a0a6b4;
}
:deep(.van-ellipsis) {
color: #a0a6b4;
font-size: 12px;
}
:deep(.van-dropdown-item) {
width: 76px;
right: 16px;
left: auto;
// right: 16px !important;
// top: 69px !important;
.van-dropdown-item__content {
border-radius: 5.548px;
border: 0.693px solid #212121;
background: #151515 !important;
padding: 7px 0;
box-sizing: border-box;
.van-dropdown-item__option {
background: #151515 !important;
padding: 0 13.87px;
// height: 20px;
}
.van-cell__title {
color: #a0a6b4;
font-size: 9.709px;
}
}
.van-cell:after {
display: none !important;
}
.van-cell__value {
display: none;
}
}
}
}
</style>
+192
View File
@@ -0,0 +1,192 @@
<template>
<div class="homeItem">
<van-notice-bar
class="homeNotice"
scrollable
background="#2B2C2E"
color="#FFF"
:speed="60"
v-if="state.announceText && state.announceText.length"
>
<span v-for="(item, index) in state.announceText" :key="index">{{ item }}</span>
</van-notice-bar>
<BannerSwiper :list="state.bannerList" class="bannerSwiper" v-if="state.bannerList && state.bannerList.length" />
<MobileSwiper :list="state.rankList" v-if="state.rankList && state.rankList.length" class="rankSwiper" />
<div class="topicBox" v-for="(item, index) in state.topicsList" :key="item.sectionID">
<div class="topicTitleBox">
<div class="topicTitle">{{ item.sectionName }}</div>
<div class="moreBtn" @click="jumpTopic(item)">
<SvgIcon name="moreIcon" class="moreIcon" />
<span>更多</span>
</div>
</div>
<div class="videoList">
<MobileVideoBox :videoInfo="i" v-for="i in item.allVideoInfo" :key="i.id" />
</div>
<AdvBanner
class="advBanner"
:advInfo="state.advList[index % state.advList.length]"
:key="state.advList[index % state.advList.length]"
v-if="state.advList.length"
/>
<!-- <AdvBanner class="advBanner" :advInfo="i" v-for="i in state.advList" :key="i.id" /> -->
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { AdType } from '@/enums/app';
import { getAdItem } from '@/utils/getConfig';
import { useRoute, useRouter } from 'vue-router';
import HomeApi from '@/service/home';
const route = useRoute();
const router = useRouter();
const props = defineProps({
tid: {
type: String,
required: true,
},
});
const globalStore = useGlobalStore();
const state = reactive({
bannerList: [],
announceText: '',
topicsList: <any>[],
rankList: <any>[],
advList: <any>[],
});
const queryList = async () => {
let req = {
id: props.tid,
data: {
pageNumber: 1,
pageSize: 10,
videoCount: 8,
},
};
let res: any = await HomeApi.getModuleList(req);
let allSection = res.allSection || [];
allSection.forEach((item: any) => {
if (item.showType == 2) {
state.bannerList = state.bannerList.concat(item.allVideoInfo || []);
} else {
state.topicsList = state.topicsList.concat(item || []);
}
// console.log(state.topicsList, '-=-==-');
});
};
const queryRanks = async () => {
let req = {
pageNumber: 1,
pageSize: 20,
};
let res: any = await HomeApi.getRangking(req);
state.rankList = res.videos || [];
console.log(state.rankList);
};
const getAnnounce = async () => {
let res: any = await HomeApi.getAnnounce();
state.announceText = res.announcement || [];
};
const jumpTopic = (item: any) => {
const queryData = {
tid: item.sectionID,
name: item.sectionName,
};
router.push({ path: '/sectionDetails', query: queryData }).then(() => {
// location.reload();
});
};
const init = () => {
state.advList = getAdItem(AdType.MOBILEHOMETOPIC);
queryList();
queryRanks();
getAnnounce();
};
init();
</script>
<style scoped lang="scss">
.homeItem {
background: #000;
.homeNotice {
height: 31px;
padding: 0;
span {
margin-right: 60px;
}
}
.bannerSwiper {
height: 162px;
margin-bottom: 15px;
}
.rankSwiper {
margin-bottom: 24.36px;
}
.videoSwiper {
margin-bottom: 24.36px;
}
.topicBox {
padding: 0 16px;
box-sizing: border-box;
margin-bottom: 24px;
.topicTitleBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.topicTitle {
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 112.5% */
letter-spacing: 0.5px;
}
.moreBtn {
display: flex;
align-items: center;
justify-content: center;
.moreIcon {
margin-right: 3.43px;
width: 16px;
height: 16px;
}
span {
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 13.714px;
color: rgba(255, 255, 255, 0.4);
}
}
}
.videoList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 16px;
grid-column-gap: 12px;
}
.advBanner {
margin-top: 24px;
border-radius: 8px;
overflow: hidden;
height: 67px;
:deep(img) {
border-radius: 8px;
}
}
}
.topicBox:last-child {
margin-bottom: 29px;
}
}
.showAdvBanner {
height: calc(100vh - 160px);
}
</style>
@@ -0,0 +1,318 @@
<template>
<div class="topicDetails">
<div class="optBox">
<div class="subTitle">{{ state.topicName }}</div>
<van-dropdown-menu ref="menuRef" class="dropdownBox" :overlay="false">
<van-dropdown-item v-model="state.moduleSort" :options="state.options" @change="changeOpt" />
</van-dropdown-menu>
</div>
<div class="videoList">
<div class="videoItem" v-for="item in state.list" :key="item.id">
<AdvBox :advInfo="item" v-if="item.isAdv" />
<MobileVideoBox :videoInfo="item" v-else />
</div>
</div>
<MobilePagination
class="pagination"
:pageNumber="state.pageNumber"
:pageSize="state.pageSize"
:total="state.total"
@changePage="changePage"
v-if="state.total > state.pageSize"
/>
<div class="topicBox" v-for="i in state.topicList" :key="i.id">
<div class="topicTitle">{{ i.subModuleName }}</div>
<div class="tagList">
<div class="tagItem" v-for="item in i.allSection" :key="item.id" @click="jumpTopice(item)">
{{ item.name }}
<span>({{ item.videoCount }})</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { useRoute, useRouter } from 'vue-router';
import { getAdItem } from '@/utils/getConfig';
import { AdType } from '@/enums/app';
import insertAdv from '@/utils/insertAdv'
import HomeApi from '@/service/home';
const route = useRoute();
const router = useRouter();
const globalStore = useGlobalStore();
const state = reactive({
options: [
{ text: '默认', value: 1 },
{ text: '最新上架', value: 2 },
{ text: '最多观看', value: 3 },
{ text: '最多收藏', value: 4 },
],
pageNumber: <any>1,
pageSize: 7,
moduleSort: route.query.sort || 1,
list: <any>[],
advList: <any>[],
topicList: <any>[],
total: 0,
topicName: route.query.name,
});
const queryList = async () => {
let req = {
id: route.query.tid,
data: {
pageNumber: state.pageNumber,
pageSize: state.pageSize,
moduleSort: state.moduleSort,
},
};
let res: any = await HomeApi.getModuleList(req);
// const updatedArray = insertAdv({
// insertNum: 6,
// pageSize,
// pageNumber: pageNumber,
// originArray: res.videos,
// advNum: 5
// })
let videoList = res.allVideoInfo || [];
let params = {
insertNum: 6,
pageSize: state.pageSize,
pageNumber: state.pageNumber,
advNum: 6,
originArray: videoList,
};
state.list = insertAdv(params);
// console.log(newarr);
// videoList.forEach((i,j) => {
// let index = state.pageSize * (state.pageNumber - 1) + j;
// })
// state.list = res.allVideoInfo || [];
state.total = res.total || 0;
// console.log(state.list);
// allSection.forEach((item: any) => {
// // if (item.showType == 2) {
// // state.bannerList = state.bannerList.concat(item.allVideoInfo || []);
// // } else {
// // state.topicsList = state.topicsList.concat(item || []);
// // }
// // // console.log(state.topicsList, '-=-==-');
// });
};
const changePage = (pageNumber: any) => {
let queryData = {
tid: route.query.tid,
stype: route.query.stype,
pageNumber: pageNumber,
sort: route.query.sort,
name: route.query.name,
};
router
.push({
path: '/home',
query: queryData,
})
.then(() => {
location.reload();
});
};
const changeOpt = (val: any) => {
let queryData = {
tid: route.query.tid,
stype: route.query.stype,
name: route.query.name,
pageNumber: 1,
sort: val,
};
router.replace({
path: '/home',
query: queryData,
});
// .then(() => {
// // location.reload();
// });
queryList();
};
const jumpTopice = (item: any) => {
let queryData = {
tid: item.id,
name: item.name,
};
router.push({
path: '/sectionDetails',
query: queryData,
});
// .then(() => {
// location.reload();
// });
};
const getTagList = async () => {
const res: any = await HomeApi.getTabs({ isSection: true });
state.topicList = res.homePage || [];
};
const init = () => {
state.advList = getAdItem(AdType.HOMETOPBANNER);
state.pageNumber = Number(route.query.pageNumber) || 1;
state.moduleSort = Number(route.query.sort) || 1;
queryList();
getTagList();
};
init();
</script>
<style scoped lang="scss">
.topicDetails {
padding: 18px 16px 0;
box-sizing: border-box;
// padding-top: 27px;
// box-sizing: border-box;
.advBanner {
width: 100%;
height: 70px;
}
.optBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 16px; /* 88.889% */
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.videoNum {
color: #fff;
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 153.846% */
margin-bottom: 16px;
span {
color: #a0a6b4;
font-size: 12px;
}
}
.videoList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 28px;
grid-column-gap: 12px;
margin-bottom: 28px;
}
.pagination {
margin-bottom: 24px;
}
.topicBox {
margin-bottom: 24px;
.topicTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 28px; /* 155.556% */
margin-bottom: 12.25px;
}
.tagList {
display: flex;
flex-wrap: wrap;
align-items: center;
.tagItem {
padding: 4px 12px;
box-sizing: border-box;
color: #fff;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
margin: 0 8px 8px 0;
span {
color: #a0a6b4;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
}
}
}
.topicBox:last-child {
margin-bottom: 60px;
}
.dropdownBox {
width: 76px;
height: 33px;
border-radius: 6.986px;
border: 0.873px solid #212121;
background: #151515;
margin-bottom: 13.6px;
// background: skyblue;
:deep(.van-dropdown-menu__bar) {
background: rgba(0, 0, 0, 0);
height: 100%;
}
:deep(.van-dropdown-menu__title) {
padding: 0;
padding-right: 6px;
}
:deep(.van-dropdown-menu__title--active:after) {
border-color: transparent transparent #a0a6b4 #a0a6b4;
}
:deep(.van-ellipsis) {
color: #a0a6b4;
font-size: 12px;
}
:deep(.van-dropdown-item) {
width: 76px;
right: 16px;
left: auto;
// right: 16px !important;
// top: 69px !important;
.van-dropdown-item__content {
border-radius: 5.548px;
border: 0.693px solid #212121;
background: #151515 !important;
padding: 7px 0;
box-sizing: border-box;
.van-dropdown-item__option {
background: #151515 !important;
padding: 0 13.87px;
// height: 20px;
}
.van-cell__title {
color: #a0a6b4;
font-size: 9.709px;
}
}
.van-cell:after {
display: none !important;
}
.van-cell__value {
display: none;
}
}
}
}
</style>
+300
View File
@@ -0,0 +1,300 @@
<template>
<div class="welfare">
<div class="box1">
<div class="img-list">
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(0, 3)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(3, 6)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(6, 10)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(10, 13)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(13, 17)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(17, 20)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(20, 23)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
</div>
<div class="img-list-one">
<DecryptImg
v-for="(item, index) in state.smallSuure.slice(23, 24)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="title">
<p>在線視頻</p>
<p>来自一站式你所熟悉的蛋黄派</p>
</div>
</div>
<div class="box2">
<div class="subTitle">
<p v-for="(item, index) in state.bgImage1" :key="index">{{ item }}</p>
</div>
<swiper
ref="gameSwiper"
slides-per-view="auto"
:observer="true"
:navigation="true"
:space-between="6"
:slidesOffsetBefore="0"
class="gameSwiper"
>
<swiper-slide v-for="i in state.verticalSlide" :key="i.id" class="gameSwiperItem" @click="jumpAdv(i, true)">
<DecryptImg :src="i.icon" class="gameSwiperImg" :key="i.id" />
</swiper-slide>
</swiper>
</div>
<div class="box3">
<div class="subTitle">
<p v-for="(item, index) in state.bgImage2" :key="index">{{ item }}</p>
</div>
<swiper
ref="topGameSwiper"
slides-per-view="auto"
:observer="true"
:navigation="true"
:space-between="6"
:slidesOffsetBefore="-20"
class="topGameSwiper"
>
<swiper-slide
v-for="i in state.verticalTwoSlide"
:key="i.id"
class="topGameSwiperItem"
@click="jumpAdv(i, true)"
>
<DecryptImg :src="i.icon" class="topGameSwiperImg" :key="i.id" />
</swiper-slide>
</swiper>
<swiper
ref="topGameSwiper"
slides-per-view="auto"
:observer="true"
:navigation="true"
:space-between="6"
:slidesOffsetBefore="14"
class="topGameSwiper"
>
<swiper-slide
v-for="i in state.smallSqureSlide"
:key="i.id"
class="topGameSwiperItem"
@click="jumpAdv(i, true)"
>
<DecryptImg :src="i.icon" class="topGameSwiperImg" :key="i.id" />
</swiper-slide>
</swiper>
</div>
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { reactive, nextTick } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { jumpAdv } from '@/utils/index';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Autoplay, Pagination, FreeMode } from 'swiper';
import request from '@/utils/request';
import 'swiper/swiper.min.css';
import AppApi from '@/service/app';
const globalStore = useGlobalStore();
const state = reactive({
smallSuure: <any>[],
verticalSlide: <any>[],
bgImage1: <any>[],
verticalTwoSlide: <any>[],
bgImage2: <any>[],
smallSqureSlide: <any>[],
isScroll: true,
});
const queryConfig = async () => {
try {
let res = await AppApi.queryWelfare();
const resdata = res.list || []
const data = resdata.find((item: any) => item.desc === '福利模块') || {};
const dataList = data.comboList || [];
state.smallSuure = dataList.find((item: any) => item.displayType === 'smallSuure').list;
state.verticalSlide = dataList.find((item: any) => item.displayType === 'verticalSlide').list;
// #换行
state.bgImage1 = dataList.find((item: any) => item.displayType === 'bgImage1').list[0]?.desc?.split('#') || [];
state.verticalTwoSlide = dataList.find((item: any) => item.displayType === 'verticalTwoSlide').list;
// #换行
state.bgImage2 = dataList.find((item: any) => item.displayType === 'bgImage2').list[0]?.desc?.split('#') || [];
state.smallSqureSlide = dataList.find((item: any) => item.displayType === 'smallSqureSlide').list;
} catch (error) {
console.log();
}
};
nextTick(() => {
queryConfig();
});
</script>
<style scoped lang="scss">
.welfare {
.box1 {
width: 100%;
height: 425px;
background: #fff;
.img-list {
display: flex;
// align-items: center;
justify-content: space-between;
.item-list:nth-child(2n) {
padding-top: 18px !important;
}
.cover {
width: 46px;
height: 46px;
margin-bottom: 8.4px;
}
}
.img-list-one {
width: 63.4px;
height: 63.4px;
margin: 0 auto 52.11px;
}
.title {
color: #000;
text-align: center;
font-family: Roboto;
font-size: 22px;
font-style: normal;
font-weight: 900;
line-height: 30px; /* 77.921% */
}
}
.box2 {
height: 348px;
background: url('../../../assets/images/pc/game_bg.png') no-repeat;
background-size: 100% 100%;
background-color: #000;
padding-top: 79px;
.subTitle {
color: #fff;
font-size: 22px;
font-weight: 900;
line-height: 18.333px; /* 83.333% */
margin-bottom: 57.45px;
p {
text-align: center;
margin-bottom: 11.55px;
}
p:last-child {
margin-bottom: 0;
}
}
.gameSwiper {
.gameSwiperItem {
width: 112px;
height: 138px;
.gameSwiperImg {
width: 112px;
height: 138px;
border-radius: 4.67px;
overflow: hidden;
:deep(img) {
border-radius: 4.67px;
}
}
}
}
}
.box3 {
background-color: #fff;
padding-top: 59px;
box-sizing: border-box;
.subTitle {
color: #000;
font-size: 22px;
font-weight: 900;
line-height: 18.333px; /* 83.333% */
padding-bottom: 49px;
p {
text-align: center;
margin-bottom: 11.55px;
}
p:last-child {
margin-bottom: 0;
}
}
.topGameSwiper {
margin-bottom: 17.4px;
.topGameSwiperItem {
width: 112px;
height: 138px;
.topGameSwiperImg {
width: 112px;
height: 138px;
border-radius: 4.67px;
overflow: hidden;
:deep(img) {
border-radius: 4.67px;
}
}
}
}
.topGameSwiper:last-child {
margin-bottom: 0;
padding-bottom: 48.6px;
}
}
}
</style>
+86
View File
@@ -0,0 +1,86 @@
<template>
<div class="connectInfo overflow-y-auto">
<MobileHeader class="header" />
<div class="contentBox">
<div class="subTitle">官方联系</div>
<div class="connectItem" v-for="(item, index) in contactInfo" :key="index" @click="openPage(item.officialUrl)">
<lazyImg class="connectImg" :src="item.officialImg" />
<div class="infoBox">
<div class="conectName">{{ item.officialName }}</div>
<div class="conectDesc">{{ item.officialDesc }}</div>
</div>
</div>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { computed } from 'vue';
import { useConfigStore } from '@/stores/config';
const configStore = useConfigStore();
const contactInfo = computed(() => {
return configStore.contactInfo;
// return route.query.tid ? homeStore.homeData[0];
});
const openPage = (url: string) => {
window.open(url);
};
</script>
<style scoped lang="scss">
.connectInfo {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.contentBox {
padding: 31px 16px 90px;
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 41px; /* 205% */
padding-bottom: 7px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 31px;
}
.connectItem {
display: flex;
align-items: center;
margin-bottom: 39.5px;
.connectImg {
width: 47.5px;
height: 47.5px;
margin-right: 9.4px;
}
.infoBox {
.conectName {
color: #fff;
font-family: 'PingFang SC';
font-size: 9.945px;
font-style: normal;
font-weight: 600;
line-height: 9.945px; /* 100% */
margin-bottom: 5.5px;
}
.conectDesc {
color: #fff;
font-family: 'PingFang SC';
font-size: 7.735px;
font-style: normal;
font-weight: 400;
line-height: 9.945px; /* 128.571% */
}
}
}
.connectItem:last-child {
margin-bottom: 0;
}
}
}
</style>
+180
View File
@@ -0,0 +1,180 @@
<template>
<div class="mobileHome overflow-y-auto">
<MobileHeader class="header" />
<HomeItem v-if="currTab?.showType == 1" :tid="currTab.id" />
<TopicDetails v-else-if="currTab?.showType == 2" :tid="currTab.id" :key="currTab.id" />
<Welfare v-else-if="currTab?.showType == 4" :tid="currTab.id" />
<Classification v-else-if="currTab?.showType == 5" :tid="currTab.id" :key="'calss' + currTab.id" />
<MobileBottom />
<!-- 弹窗广告 -->
<HomePopupAdv
v-if="isShowAdvPopup"
:advList="state.advList"
@close="closeHomePopupAdv"
/>
<!-- <van-popup v-model:show="showTip" overlay-class="tipPop" class="tip18">
<div class="tipPopBox">
<div class="logo"></div>
<div class="tipTitle">欢迎</div>
<div class="tipPopLine"></div>
<div class="tipPopContent">
🔞 您欲觀看的頁面包含成人內容 🔞
<br />
本站內容均遵守本國法律規定若您是未成年者,
<br />
請選擇離開,滿 18 歲。
<br />
点击确认进入。
</div>
<div class="tipConfirm" @click="confirm">确认进入</div>
</div>
</van-popup> -->
</div>
</template>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { useHomeStore } from '@/stores/home';
import { getAdItem } from '@/utils/getConfig';
import { AdType } from '@/enums/app';
import { useRoute } from 'vue-router';
import HomeItem from './components/HomeItem.vue';
import TopicDetails from './components/TopicDetails.vue';
import Welfare from './components/Welfare.vue';
import Classification from './components/Classification.vue';
const route = useRoute();
const homeStore = useHomeStore();
const globalStore = useGlobalStore();
// const showTip = computed(() => {
// return !globalStore.showTipFirst;
// });
// 首页弹窗广告
const isShowAdvPopup = computed(() => {
return state.advList?.length > 0;
});
const state = reactive({
advList: <any>[],
});
const currTab = computed(() => {
if (route.query.tid) {
// let tabObj = homeStore.homeData.filter((i) => {
// return i.id == route.query.tid;
// });
const tabObj = {
id: route.query.tid,
showType: route.query.stype,
name: route.query.name,
};
return tabObj;
} else {
return homeStore.homeData[0];
}
// return route.query.tid ? homeStore.homeData[0];
});
const confirm = () => {
globalStore.setShowTipFirst();
};
const closeHomePopupAdv = () => {
globalStore.setHomeAdvFirst();
};
const init = () => {
state.advList = getAdItem(AdType.HOMEPOPUPADV);
state.advList.reverse();
console.log(state.advList, 9999);
};
init();
</script>
<style scoped lang="scss">
.mobileHome {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
// .navTab {
// :deep(.van-tabs__line) {
// display: none;
// }
// .tabTitle {
// font-size: 16px;
// }
// :deep(.van-tab--active) {
// .tabTitle {
// font-size: 18px;
// }
// }
// }
}
.tipPop {
// opacity: 0.6;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(2px);
}
.tip18 {
.tipPopBox {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.logo {
width: 130px;
height: 84px;
background: url('../../assets/images/mobile/logo.png') no-repeat;
background-size: 100% 100%;
margin-bottom: 25px;
}
.tipTitle {
color: #fff;
text-align: center;
font-family: 'PingFang SC';
font-size: 30px;
font-style: normal;
font-weight: 600;
line-height: 41px; /* 136.667% */
margin-bottom: 10px;
}
.tipPopLine {
width: 302px;
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin-bottom: 12px;
}
.tipPopContent {
color: #fff;
text-align: center;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 28px; /* 200% */
margin-bottom: 35px;
}
.tipConfirm {
display: flex;
align-items: center;
justify-content: center;
width: 70.8px;
height: 30.2px;
border-radius: 6px;
background: #ffc400;
box-shadow: 0px -1.8px 4.8px 0px rgba(249, 255, 139, 0.6) inset, 0px 0px 4.8px 0px rgba(255, 196, 0, 0.5);
color: #482f28;
text-align: center;
font-family: 'PingFang SC';
font-size: 10.5px;
font-style: normal;
font-weight: 600;
line-height: 10.5px; /* 100% */
}
}
</style>
+103
View File
@@ -0,0 +1,103 @@
<template>
<div class="connectInfo overflow-y-auto">
<MobileHeader class="header" />
<div class="contentBox">
<div class="subTitle">商务合作</div>
<div
class="connectItem"
v-for="(item, index) in state.contactInfo"
:key="index"
@click="openPage(item.officialUrl)"
>
<lazyImg class="connectImg" :src="item.officialImg" />
<div class="conectName">{{ item.officialName }}:</div>
<div class="conectDesc">点击这里</div>
</div>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { reactive } from 'vue';
import AppApi from '@/service/app';
const state = reactive({
contactInfo: <any>[],
});
const queryContactInfo = async () => {
try {
let req = {
type: 3,
};
const res: any = await AppApi.getBusiness(req);
state.contactInfo = res || [];
} catch (error) {
// return false;
}
};
queryContactInfo();
const openPage = (url: string) => {
window.open(url);
};
</script>
<style scoped lang="scss">
.connectInfo {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.contentBox {
padding: 31px 16px 90px;
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 41px; /* 205% */
padding-bottom: 7px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 31px;
}
.connectItem {
display: flex;
align-items: center;
margin-bottom: 39.5px;
.connectImg {
width: 47.5px;
height: 47.5px;
margin-right: 9.4px;
}
.conectName {
color: #fff;
text-align: center;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 10.047px; /* 71.761% */
}
.conectDesc {
color: var(--Style, #ffcd3f);
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 10.047px;
text-decoration-line: underline;
text-decoration-style: solid;
text-decoration-skip-ink: auto;
text-decoration-thickness: auto;
text-underline-offset: auto;
text-underline-position: from-font;
}
}
.connectItem:last-child {
margin-bottom: 0;
}
}
}
</style>
+236
View File
@@ -0,0 +1,236 @@
<template>
<div class="collect overflow-y-auto" ref="contentRef">
<MobileHeader class="header" />
<div class="contentBox">
<div class="subTitle">观看历史</div>
<div class="btnBox">
<div class="cancelBtn" v-if="state.isEdit" @click="cancelEdit">取消</div>
<div class="editBtn" :class="{ disableEdit: !state.delList.length }" v-if="state.isEdit" @click="delVideo">
删除
</div>
<div class="editBtn" @click="edit" v-else>编辑</div>
</div>
<div class="allSelectBox" v-if="state.isEdit" @click="allSelect">
<SvgIcon name="selectedIcon" class="selectedIcon" v-if="state.delList.length == state.historyList.length" />
<div class="selectIcon" v-else></div>
全选
</div>
<div class="videoList">
<div class="mobileVideoBox" v-for="item in state.historyList" :key="item.id">
<MobileVideoBox :videoInfo="item" />
<div class="videoMask" v-if="state.isEdit" @click.stop="selectVideo(item)">
<SvgIcon name="selectedIcon" class="selected" v-if="handleSelectSta(item.id)" />
<div class="select" v-else></div>
</div>
</div>
</div>
<MobilePagination
class="pagination"
:pageNumber="state.pageNumber"
:pageSize="state.pageSize"
:total="state.total"
@changePage="changePage"
v-if="state.total > state.pageSize"
/>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { HistoryVideoType } from '@/enums/app';
import { queryHistoryVideo, queryHistoryTotal, delAllData } from '@/utils';
import { showToast } from 'vant';
const globalStore = useGlobalStore();
const contentRef = ref();
const state = reactive({
isEdit: false,
pageNumber: 1,
pageSize: 10,
total: 0,
historyList: <any>[],
delList: <any>[],
});
const edit = () => {
state.isEdit = true;
};
const cancelEdit = () => {
state.isEdit = false;
state.delList = [];
};
const selectVideo = (item: any) => {
let index = state.delList.findIndex((i: any) => {
return item.id == i;
});
if (index != -1) {
state.delList.splice(index, 1);
} else {
state.delList.push(item.id);
}
};
const handleSelectSta = (id: any) => {
let result = state.delList.some((i: any) => {
return i == id;
});
return result;
};
const allSelect = () => {
if (state.delList.length) {
state.delList = [];
} else {
let arr: any = [];
state.historyList.forEach((item: any) => {
arr.push(item.id);
});
state.delList = arr;
}
};
const delVideo = async () => {
const res: any = await delAllData(HistoryVideoType.VIDEO, state.delList);
getHistoryList();
state.isEdit = false;
state.delList = [];
showToast('删除成功');
};
const changePage = (pageNumber: number) => {
state.pageNumber = pageNumber;
contentRef.value.scrollTo({ top: 0 });
getHistoryList();
};
const getHistoryList = async () => {
const list: any = await queryHistoryVideo(state.pageNumber, state.pageSize, HistoryVideoType.VIDEO);
const total: any = await queryHistoryTotal(HistoryVideoType.VIDEO);
state.total = total;
state.historyList = list;
};
getHistoryList();
</script>
<style scoped lang="scss">
.collect {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.contentBox {
padding: 26px 16px 150px;
.subTitle {
display: flex;
width: 80.323px;
height: 41px;
flex-direction: column;
justify-content: center;
flex-shrink: 0;
margin-bottom: 10px;
}
.btnBox {
display: flex;
align-items: center;
justify-content: flex-end;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 24px;
.cancelBtn {
width: 72px;
height: 30px;
border-radius: 4px;
border: 1px solid #ffc303;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
color: #ffcd3f;
text-align: center;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.editBtn {
color: #fff;
text-align: center;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
width: 72px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
background: linear-gradient(270deg, #ffc303 0%, #ffdd01 100%);
}
.disableEdit {
opacity: 0.3;
}
}
.allSelectBox {
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 16px;
.selectIcon {
width: 16px;
height: 16px;
border-radius: 50%;
border: 2.2px solid #999;
margin-right: 8px;
}
.selectedIcon {
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 8px;
}
}
.videoList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 16px;
grid-column-gap: 12px;
margin-bottom: 45px;
.mobileVideoBox {
position: relative;
.videoMask {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 3;
display: flex;
justify-content: flex-end;
padding: 4px;
box-sizing: border-box;
.select {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2.2px solid #999;
}
.selected {
width: 18px;
height: 18px;
}
}
}
}
}
}
</style>
+326
View File
@@ -0,0 +1,326 @@
<template>
<div class="search overflow-y-auto" ref="searchRef">
<MobileAdvBanner
class="advBanner"
:list="state.advList"
v-if="showAdvBanner && state.advList && state.advList.length"
/>
<div class="contentBox">
<van-dropdown-menu ref="menuRef" class="dropdownBox" :overlay="false">
<van-dropdown-item v-model="state.activeOpt" :options="state.options" @change="changeOpt" />
</van-dropdown-menu>
<div class="searchBox">
<van-field v-model="state.searchValue" :border="false" class="searchInput" />
<div class="searchBtn" @click="search()">搜索一下</div>
</div>
<div class="hotSearch">
<div class="hotSearchItem" v-for="i in state.hotKeywords.slice(0, 10)" :key="i" @click="search(i.name)">
{{ i.name }}
</div>
</div>
<div class="result" v-if="state.isSearch">
<div class="subTitle">搜索结果</div>
<div class="videoNum">
全部视频
<span>({{ state.total }})</span>
</div>
<div class="videoList">
<div class="videoItem" v-for="item in state.resultList" :key="item.id">
<AdvBox :advInfo="item" v-if="item.isAdv" />
<MobileVideoBox :videoInfo="item" v-else />
</div>
</div>
</div>
<div class="recommend" v-else>
<div class="subTitle">大家都在搜</div>
<div class="videoList">
<div class="videoItem" v-for="item in state.recommendList" :key="item.id">
<AdvBox :advInfo="item" v-if="item.isAdv" />
<MobileVideoBox :videoInfo="item" v-else />
</div>
</div>
</div>
<MobilePagination
class="pagination"
:total="state.total"
:pageNumber="state.pageNumber"
:pageSize="state.pageSize"
v-if="state.total > state.pageSize && state.isSearch"
@changePage="changePage"
/>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts" setup>
import { reactive, computed, ref } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { getAdItem } from '@/utils/getConfig';
import { AdType } from '@/enums/app';
import { useRoute, useRouter } from 'vue-router';
import CommunityApi from '@/service/community';
import insertAdv from '@/utils/insertAdv';
const route = useRoute();
const router = useRouter();
const globalStore = useGlobalStore();
const searchRef = ref();
const state = reactive({
options: [
{ text: '默认', value: undefined },
{ text: '最新上架', value: 0 },
{ text: '最多观看', value: 2 },
{ text: '最多收藏', value: 3 },
],
activeOpt: undefined,
searchValue: '',
hotKeywords: <any>[],
advList: <any>[],
resultList: <any>[],
recommendList: <any>[],
pageSize: 7,
pageNumber: 1,
total: 0,
isSearch: false,
});
const showAdvBanner = computed(() => {
return globalStore.showAdvBanner;
});
const changeOpt = (val: any) => {
search();
};
const search = async (value?: string, isChangePage?: boolean) => {
state.isSearch = true;
if (value) {
state.searchValue = value;
}
if (!isChangePage) {
state.pageNumber = 1;
}
let req = {
SortV: state.activeOpt,
keyWords: [state.searchValue],
pageNumber: state.pageNumber,
pageSize: state.pageSize,
realm: 'video',
};
const res: any = await CommunityApi.searchPost(req);
let videoList = res.list || [];
let params = {
insertNum: 6,
pageSize: state.pageSize,
pageNumber: state.pageNumber,
advNum: 6,
originArray: videoList,
};
state.resultList = insertAdv(params);
// state.resultList = res.list || [];
state.total = res.total || 0;
};
const queryRecommend = async () => {
try {
const res: any = await CommunityApi.searchVideo({});
let videoList = res.videos || [];
console.log(videoList);
let params = {
insertNum: 6,
pageSize: videoList.length,
pageNumber: state.pageNumber,
advNum: 6,
originArray: videoList,
};
state.recommendList = insertAdv(params);
// state.recommendList = res.videos || [];
} catch (error) {}
};
const changePage = (pageNumber: number) => {
state.pageNumber = pageNumber;
searchRef.value.scrollTo({ top: 0 });
search(undefined, true);
};
const getHotTag = async () => {
const res: any = await CommunityApi.getHotRecommend({
pageNumber: 1,
pageSize: 10,
});
state.hotKeywords = res.list || [];
};
const init = () => {
state.advList = getAdItem(AdType.HOMETOPBANNER);
getHotTag();
queryRecommend();
};
init();
</script>
<style scoped lang="scss">
.search {
height: 100vh;
// padding-top: 27px;
// box-sizing: border-box;
.advBanner {
width: 100%;
height: 70px;
}
.contentBox {
padding: 27px 16px 0;
box-sizing: border-box;
.searchBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 14px;
.searchInput {
width: 250px;
height: 42px;
border-radius: 6.986px;
border: 0.873px solid #ffc400;
background: #000;
padding: 0 14.85px;
font-size: 14px;
:deep(.van-field__control) {
color: #fff;
}
:deep(.van-cell__value) {
display: flex;
align-items: center;
}
}
.searchBtn {
width: 78.7px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6.986px;
background: #ffc400;
box-shadow: 0px 0.873px 1.747px 0px rgba(0, 0, 0, 0.05);
color: #482f28;
font-size: 12.226px;
font-style: normal;
font-weight: 600;
line-height: 12.226px;
}
}
.hotSearch {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
padding: 0 17.1px;
box-sizing: border-box;
margin-bottom: 50.58px;
.hotSearchItem {
margin: 0 7px;
height: 21px;
color: #e5e7eb;
font-family: 'PingFang SC';
font-size: 13.972px;
font-style: normal;
font-weight: 400;
line-height: 20.958px; /* 150% */
text-decoration-line: underline;
text-decoration-style: solid;
text-decoration-skip-ink: auto;
text-decoration-thickness: auto;
text-underline-offset: auto;
text-underline-position: from-font;
}
}
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 16px; /* 88.889% */
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.videoNum {
color: #fff;
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 153.846% */
margin-bottom: 16px;
span {
color: #a0a6b4;
font-size: 12px;
}
}
.videoList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 28px;
grid-column-gap: 12px;
margin-bottom: 28px;
}
.pagination {
margin-bottom: 30px;
}
}
.dropdownBox {
width: 76px;
height: 33px;
border-radius: 6.986px;
border: 0.873px solid #212121;
background: #151515;
margin-bottom: 13.6px;
// background: skyblue;
:deep(.van-dropdown-menu__bar) {
background: rgba(0, 0, 0, 0);
height: 100%;
}
:deep(.van-dropdown-menu__title) {
padding: 0;
padding-right: 6px;
}
:deep(.van-dropdown-menu__title--active:after) {
border-color: transparent transparent #a0a6b4 #a0a6b4;
}
:deep(.van-ellipsis) {
color: #a0a6b4;
font-size: 12px;
}
:deep(.van-dropdown-item) {
width: 76px;
left: 16px;
// top: 69px !important;
.van-dropdown-item__content {
border-radius: 5.548px;
border: 0.693px solid #212121;
background: #151515 !important;
padding: 7px 0;
box-sizing: border-box;
.van-dropdown-item__option {
background: #151515 !important;
padding: 0 13.87px;
// height: 20px;
}
.van-cell__title {
color: #a0a6b4;
font-size: 9.709px;
}
}
.van-cell:after {
display: none !important;
}
.van-cell__value {
display: none;
}
}
}
}
</style>
+74
View File
@@ -0,0 +1,74 @@
<template>
<div class="privacyPolicy overflow-y-auto">
<MobileHeader class="header" />
<div class="contentBox">
<div class="title">USC 2257</div>
<div class="contentText">
DHP.Video 不是网站 (DHP.Video) 上任何和所有内容的制作者(主要或次要)。关于根据 18 USC 2257
记录本网站上的任何和所有内容,请将您的请求直接发送给制作内容的网站。 DHP.Video
是一个视频共享网站,允许上传、共享和观看各种类型的成人内容。虽然 DHP.Video 尽最大努力验证合规性,但它可能不是
100% 准确。 DHP.Video 遵守以下程序以确保合规:
<p>
<span>1.</span>
要求所有用户必须年满 18 岁才能观看和上传视频。
</p>
<p>
<span>2.</span>
上传时,用户必须核实内容;保证自己已年满 18 岁;证明自己保留了内容中模特的记录并且这些模特已年满 18 岁。
</p>
如需进一步帮助和/或信息以查找内容的原始站点,请通过支持表格联系 DHP.Video 合规部门。 DHP.Video
允许将内容标记为不当内容。如果任何内容被标记为非法、违法、骚扰、有害、冒犯或其他各种原因,DHP.Video
应立即将其从网站上删除。
</div>
</div>
<!-- <HomeItem /> -->
<MobileBottom />
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { useGlobalStore } from '@/stores/global';
import HomeItem from '../components/HomeItem.vue';
const globalStore = useGlobalStore();
const state = reactive({});
</script>
<style scoped lang="scss">
.privacyPolicy {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.contentBox {
padding: 31.2px 24.5px 50px;
.title {
color: #fff;
font-family: 'PingFang SC';
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 41px; /* 205% */
padding-bottom: 7px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 20px;
}
.contentText {
color: #fff;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 171.429% */
p {
display: flex;
span {
margin-right: 4px;
}
}
}
}
}
</style>
+82
View File
@@ -0,0 +1,82 @@
<template>
<div class="releaseAddress overflow-y-auto">
<MobileHeader class="header" />
<div class="contentBox">
<div class="subTitle">备用地址</div>
<div class="urlItem" v-for="(item, index) in urlList" :key="index">
<div class="urlTitle">{{ item.desc }}: </div>
<div class="url" @click="openPage(item.url)">{{ item.url }}</div>
</div>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useConfigStore } from '@/stores/config';
const configStore = useConfigStore();
const state = reactive({});
const urlList: any = computed(() => {
return configStore.sourceInfo.Download;
});
const openPage = (url: string) => {
window.open(url);
};
</script>
<style scoped lang="scss">
.releaseAddress {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.contentBox {
padding: 31px 16px 200px;
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 41px; /* 205% */
padding-bottom: 7px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 60px;
}
.urlItem {
display: flex;
align-items: center;
margin-bottom: 56px;
.urlTitle {
color: #fff;
text-align: center;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 10.047px; /* 71.761% */
}
.url {
color: var(--Style, #ffcd3f);
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 10.047px;
text-decoration-line: underline;
text-decoration-style: solid;
text-decoration-skip-ink: auto;
text-decoration-thickness: auto;
text-underline-offset: auto;
text-underline-position: from-font;
}
}
.urlItem:last-child {
margin-bottom: 0;
}
}
}
</style>
+310
View File
@@ -0,0 +1,310 @@
<template>
<div class="mobileHome overflow-y-auto">
<MobileHeader class="header" />
<div class="topicDetails">
<div class="optBox">
<div class="subTitle">{{ state.topicName }}</div>
<van-dropdown-menu ref="menuRef" class="dropdownBox" :overlay="false">
<van-dropdown-item v-model="state.moduleSort" :options="state.options" @change="changeOpt" />
</van-dropdown-menu>
</div>
<div class="videoList">
<div class="videoItem" v-for="item in state.list" :key="item.id">
<AdvBox :advInfo="item" v-if="item.isAdv" />
<MobileVideoBox :videoInfo="item" v-else />
</div>
<!-- <MobileVideoBox :videoInfo="item" v-for="item in state.list" :key="item.id" /> -->
</div>
<MobilePagination
class="pagination"
:pageNumber="state.pageNumber"
:pageSize="state.pageSize"
:total="state.total"
@changePage="changePage"
v-if="state.total > state.pageSize"
/>
<div class="topicBox" v-for="i in state.topicList" :key="i.id">
<div class="topicTitle">{{ i.subModuleName }}</div>
<div class="tagList">
<div class="tagItem" v-for="item in i.allSection" :key="item.id" @click="jumpTopice(item)">
{{ item.name }}
<span>({{ item.videoCount }})</span>
</div>
</div>
</div>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { useRoute, useRouter } from 'vue-router';
import insertAdv from '@/utils/insertAdv';
import VideoApi from '@/service/video';
import HomeApi from '@/service/home';
const route = useRoute();
const router = useRouter();
const globalStore = useGlobalStore();
const state = reactive({
options: [
{ text: '默认', value: undefined },
{ text: '最新上架', value: 'new' },
{ text: '最多观看', value: 'watch' },
{ text: '最多收藏', value: 'like' },
],
pageNumber: <any>1,
pageSize: 7,
moduleSort: route.query.sort || undefined,
list: <any>[],
topicList: <any>[],
total: 0,
topicName: route.query.name,
});
const queryList = async () => {
let req = {
pageNumber: state.pageNumber,
pageSize: state.pageSize,
sortType: state.moduleSort,
playTimeType: 1,
};
const id = route.query.tid as string;
const res: any = await HomeApi.getVidSectionList(id, req);
let videoList = res.videos || [];
let params = {
insertNum: 6,
pageSize: state.pageSize,
pageNumber: state.pageNumber,
advNum: 6,
originArray: videoList,
};
state.list = insertAdv(params);
// state.list = res.videos || [];
state.total = res.total || 0;
};
const changePage = (pageNumber: any) => {
let queryData = {
tid: route.query.tid,
pageNumber: pageNumber,
sort: route.query.sort,
name: route.query.name,
};
router
.push({
path: '/sectionDetails',
query: queryData,
})
.then(() => {
location.reload();
});
};
const changeOpt = (val: any) => {
let queryData = {
tid: route.query.tid,
name: route.query.name,
pageNumber: 1,
sort: val,
};
router.replace({
path: '/sectionDetails',
query: queryData,
});
// .then(() => {
// // location.reload();
// });
queryList();
};
const jumpTopice = (item: any) => {
let queryData = {
tid: item.id,
stype: 2,
pageNumber: 1,
sort: 'new',
name: item.name,
};
router
.push({
path: '/sectionDetails',
query: queryData,
})
.then(() => {
location.reload();
});
};
const getTagList = async () => {
const res: any = await HomeApi.getTabs({ isSection: true });
state.topicList = res.homePage || [];
};
const init = () => {
state.pageNumber = Number(route.query.pageNumber) || 1;
state.moduleSort = route.query.sort || 'new';
queryList();
getTagList();
};
init();
</script>
<style scoped lang="scss">
.mobileHome {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.topicDetails {
padding: 18px 16px 0;
box-sizing: border-box;
// padding-top: 27px;
// box-sizing: border-box;
.advBanner {
width: 100%;
height: 70px;
}
.optBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 16px; /* 88.889% */
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.videoNum {
color: #fff;
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 153.846% */
margin-bottom: 16px;
span {
color: #a0a6b4;
font-size: 12px;
}
}
.videoList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 28px;
grid-column-gap: 12px;
margin-bottom: 28px;
}
.pagination {
margin-bottom: 24px;
}
.topicBox {
margin-bottom: 24px;
.topicTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 28px; /* 155.556% */
margin-bottom: 12.25px;
}
.tagList {
display: flex;
flex-wrap: wrap;
align-items: center;
.tagItem {
padding: 4px 12px;
box-sizing: border-box;
color: #fff;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
margin: 0 8px 8px 0;
span {
color: #a0a6b4;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
}
}
}
.topicBox:last-child {
margin-bottom: 60px;
}
.dropdownBox {
width: 76px;
height: 33px;
border-radius: 6.986px;
border: 0.873px solid #212121;
background: #151515;
margin-bottom: 13.6px;
// background: skyblue;
:deep(.van-dropdown-menu__bar) {
background: rgba(0, 0, 0, 0);
height: 100%;
}
:deep(.van-dropdown-menu__title) {
padding: 0;
padding-right: 6px;
}
:deep(.van-dropdown-menu__title--active:after) {
border-color: transparent transparent #a0a6b4 #a0a6b4;
}
:deep(.van-ellipsis) {
color: #a0a6b4;
font-size: 12px;
}
:deep(.van-dropdown-item) {
width: 76px;
right: 16px;
left: auto;
// right: 16px !important;
// top: 69px !important;
.van-dropdown-item__content {
border-radius: 5.548px;
border: 0.693px solid #212121;
background: #151515 !important;
padding: 7px 0;
box-sizing: border-box;
.van-dropdown-item__option {
background: #151515 !important;
padding: 0 13.87px;
// height: 20px;
}
.van-cell__title {
color: #a0a6b4;
font-size: 9.709px;
}
}
.van-cell:after {
display: none !important;
}
.van-cell__value {
display: none;
}
}
}
}
}
</style>
+302
View File
@@ -0,0 +1,302 @@
<template>
<div class="mobileHome overflow-y-auto">
<MobileHeader class="header" />
<div class="topicDetails">
<div class="optBox">
<div class="subTitle">{{ state.topicName }}</div>
<van-dropdown-menu ref="menuRef" class="dropdownBox" :overlay="false">
<van-dropdown-item v-model="state.moduleSort" :options="state.options" @change="changeOpt" />
</van-dropdown-menu>
</div>
<div class="videoList">
<div class="videoItem" v-for="item in state.list" :key="item.id">
<AdvBox :advInfo="item" v-if="item.isAdv" />
<MobileVideoBox :videoInfo="item" v-else />
</div>
</div>
<MobilePagination
class="pagination"
:pageNumber="state.pageNumber"
:pageSize="state.pageSize"
:total="state.total"
@changePage="changePage"
v-if="state.total > state.pageSize"
/>
<div class="topicBox" v-for="i in state.topicList" :key="i.id">
<div class="topicTitle">{{ i.subModuleName }}</div>
<div class="tagList">
<div class="tagItem" v-for="item in i.allSection" :key="item.id" @click="jumpTopice(item)">
{{ item.name }}
<span>({{ item.videoCount }})</span>
</div>
</div>
</div>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useGlobalStore } from '@/stores/global';
import { useRoute, useRouter } from 'vue-router';
import VideoApi from '@/service/video';
import HomeApi from '@/service/home';
import insertAdv from '@/utils/insertAdv';
const route = useRoute();
const router = useRouter();
const globalStore = useGlobalStore();
const state = reactive({
options: [
{ text: '默认', value: undefined },
{ text: '最新上架', value: 0 },
{ text: '最多观看', value: 1 },
{ text: '最多收藏', value: 2 },
],
pageNumber: <any>1,
pageSize: 7,
moduleSort: route.query.sort || undefined,
list: <any>[],
topicList: <any>[],
total: 0,
topicName: route.query.name,
});
const queryList = async () => {
let req = {
tagID: route.query.tid,
pageNumber: state.pageNumber,
pageSize: state.pageSize,
sortType: state.moduleSort,
};
const res: any = await VideoApi.searchTagPost(req);
let videoList = res.list || [];
let params = {
insertNum: 6,
pageSize: state.pageSize,
pageNumber: state.pageNumber,
advNum: 6,
originArray: videoList,
};
state.list = insertAdv(params);
// state.list = res.list || [];
state.total = res.total || 0;
};
const changePage = (pageNumber: any) => {
let queryData = {
tid: route.query.tid,
pageNumber: pageNumber,
sort: route.query.sort,
name: route.query.name,
};
router.push({
path: '/tagDetails',
query: queryData,
});
// .then(() => {
// location.reload();
// });
};
const changeOpt = (val: any) => {
let queryData = {
tid: route.query.tid,
name: route.query.name,
pageNumber: 1,
sort: val,
};
router.replace({
path: '/tagDetails',
query: queryData,
});
// .then(() => {
// // location.reload();
// });
queryList();
};
const jumpTopice = (item: any) => {
let queryData = {
tid: item.id,
name: item.name,
};
router.push({
path: '/sectionDetails',
query: queryData,
});
// .then(() => {
// location.reload();
// });
};
const getTagList = async () => {
const res: any = await HomeApi.getTabs({ isSection: true });
state.topicList = res.homePage || [];
};
const init = () => {
state.pageNumber = Number(route.query.pageNumber) || 1;
state.moduleSort = Number(route.query.sort) || 1;
queryList();
getTagList();
};
init();
</script>
<style scoped lang="scss">
.mobileHome {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.topicDetails {
padding: 18px 16px 0;
box-sizing: border-box;
// padding-top: 27px;
// box-sizing: border-box;
.advBanner {
width: 100%;
height: 70px;
}
.optBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 16px; /* 88.889% */
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.videoNum {
color: #fff;
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 153.846% */
margin-bottom: 16px;
span {
color: #a0a6b4;
font-size: 12px;
}
}
.videoList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 28px;
grid-column-gap: 12px;
margin-bottom: 28px;
}
.pagination {
margin-bottom: 24px;
}
.topicBox {
margin-bottom: 24px;
.topicTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 28px; /* 155.556% */
margin-bottom: 12.25px;
}
.tagList {
display: flex;
flex-wrap: wrap;
align-items: center;
.tagItem {
padding: 4px 12px;
box-sizing: border-box;
color: #fff;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
margin: 0 8px 8px 0;
span {
color: #a0a6b4;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
}
}
}
.topicBox:last-child {
margin-bottom: 60px;
}
.dropdownBox {
width: 76px;
height: 33px;
border-radius: 6.986px;
border: 0.873px solid #212121;
background: #151515;
margin-bottom: 13.6px;
// background: skyblue;
:deep(.van-dropdown-menu__bar) {
background: rgba(0, 0, 0, 0);
height: 100%;
}
:deep(.van-dropdown-menu__title) {
padding: 0;
padding-right: 6px;
}
:deep(.van-dropdown-menu__title--active:after) {
border-color: transparent transparent #a0a6b4 #a0a6b4;
}
:deep(.van-ellipsis) {
color: #a0a6b4;
font-size: 12px;
}
:deep(.van-dropdown-item) {
width: 76px;
right: 16px;
left: auto;
// right: 16px !important;
// top: 69px !important;
.van-dropdown-item__content {
border-radius: 5.548px;
border: 0.693px solid #212121;
background: #151515 !important;
padding: 7px 0;
box-sizing: border-box;
.van-dropdown-item__option {
background: #151515 !important;
padding: 0 13.87px;
// height: 20px;
}
.van-cell__title {
color: #a0a6b4;
font-size: 9.709px;
}
}
.van-cell:after {
display: none !important;
}
.van-cell__value {
display: none;
}
}
}
}
}
</style>
+142
View File
@@ -0,0 +1,142 @@
<template>
<div class="userHomepage overflow-y-auto">
<MobileHeader class="header" />
<div class="contentBox">
<div class="subTitle">編輯個人資料</div>
<div class="input-box">
<input type="text" placeholder="昵称" v-model="state.name" />
</div>
<div class="input-box">
<input type="text" placeholder="密码" v-model="state.password" />
</div>
<div class="tip">
<p>密码8-20位</p>
<p>至少为字母、数字或字符中任意两种的组合</p>
</div>
<div class="save-btn-list">
<div class="btn1" @click="confirmExchange">保存</div>
<!-- <div class="btn2">取消</div> -->
</div>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { showToast } from 'vant';
import { reactive, computed } from 'vue';
import { useUserStore } from '@/stores/user';
import UserApi from '@/service/user';
const userStore = useUserStore();
const userInfo = computed(() => {
const data = localStorage.getItem('userInfo');
if (data) {
return JSON.parse(data);
}
return data;
});
const state = reactive({
name: userInfo.value.name,
password: '',
});
const confirmExchange = async () => {
if (!state.name) return showToast('请输入新昵称');
if (state.password && !validatePassword(state.password)) {
showToast('密码格式有误');
return;
}
try {
await UserApi.editPersonInfo({ name: state.name, passWord: state.password });
showToast('设置成功');
userInfo.value.name = state.name;
userStore.saveUserInfo(userInfo.value);
} catch (error) {}
};
// 密码校验正则
function validatePassword(password: string) {
const regex = /^(?=.*[A-Za-z])(?=.*[\d@#$%^&*!])[A-Za-z\d@#$%^&*!]{8,20}$/;
return regex.test(password);
}
</script>
<style scoped lang="scss">
.userHomepage {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
.contentBox {
padding: 24px 16px 40px;
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 41px; /* 205% */
padding-bottom: 7px;
// border-bottom: 1px solid rgba(255, 255, 255, 0.1);
// margin-bottom: 35px;
}
.input-box {
width: 100%;
margin-top: 35px;
height: 56px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
input {
all: unset;
font-size: 26px;
font-weight: 600;
width: 100%;
text-align: left;
color: #fff;
flex: 1;
line-height: 62px;
}
input::placeholder {
color: rgba(255, 255, 255, 0.4);
font-size: 26px;
font-weight: 600;
}
}
.tip {
padding-top: 16px;
font-size: 14px;
line-height: 20px;
color: #999;
}
.save-btn-list {
display: flex;
height: 42px;
margin-top: 30px;
.btn1 {
width: 88px;
height: 42px;
@include flexCenter;
border-radius: 4px;
background: linear-gradient(270deg, #ffc303 0%, #ffdd01 100%);
color: #000;
font-size: 18px;
font-weight: 600;
margin-right: 20px;
cursor: pointer;
}
.btn2 {
width: 88px;
height: 42px;
@include flexCenter;
border-radius: 4px;
color: #fff;
font-size: 18px;
font-weight: 600;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.4);
cursor: pointer;
}
}
}
}
</style>
+352
View File
@@ -0,0 +1,352 @@
<template>
<div class="videoDetails overflow-y-auto">
<MobileHeader class="header" />
<div class="video">
<HorizontalVideo v-if="state.videoInfo.id" :videoInfo="state.videoInfo" ref="verticalVideoRef" />
</div>
<div class="contentBox">
<div class="videoTitle ellipsis">{{ state.videoInfo.title }}</div>
<div class="videoInfo">
<div class="rankBox" v-if="state.rankNum">
<div class="rank">TOP {{ state.rankNum }}</div>
<div class="rankTitle">热播榜</div>
<div class="infoLine"></div>
</div>
<!-- <div class="palyerCount">
{{ watchCount(state.videoInfo.playCount) }}
<div class="infoLine"></div>
</div> -->
<!-- <div class="palyerTime">{{ fromNowTime(state.videoInfo.createdAt) }}</div> -->
</div>
<div class="tagBox">
<div class="tagList" v-if="state.videoInfo.tags && state.videoInfo.tags.length">
<div
class="tagItem"
v-for="item in state.videoInfo.tags.slice(0, 4)"
:key="item.id"
@click.stop="jumpTag(item)"
>
{{ item.name }}
</div>
</div>
<div class="likeBox" @click="collect(state.videoInfo.vidStatus?.hasCollected)">
<SvgIcon :name="state.videoInfo.vidStatus?.hasCollected ? 'likeedIcon' : 'likeIcon'" class="likeIcon" />
{{ watchCount(state.videoInfo.collectCount || 0) }}
</div>
</div>
<!-- <BannerSwiper :list="state.advList" type="adv" class="bannerSwiper" /> -->
<div class="bannerBox" v-if="state.advList && state.advList.length">
<DecryptImg class="bannerItem" :src="item.cover" v-for="item in state.advList.slice(0, 2)" :key="item.id" />
</div>
<div class="subTitle">大家都在看💦</div>
<div class="recommendList">
<!-- <MobileVideoBox class="recommendItem" v-for="i in state.recommendList" :videoInfo="i" :key="i.id" /> -->
<div class="recommendItem" v-for="item in state.recommendList" :key="item.id">
<AdvBox :advInfo="item" v-if="item.isAdv" />
<MobileVideoBox :videoInfo="item" v-else />
</div>
</div>
<MobilePagination
class="pagination"
:pageNumber="state.pageNumber"
:pageSize="state.pageSize"
:total="state.total"
@changePage="changePage"
v-if="state.total > state.pageSize"
/>
</div>
<MobileBottom />
</div>
</template>
<script lang="ts" setup>
import { reactive, nextTick } from 'vue';
import { showToast } from 'vant';
import { useGlobalStore } from '@/stores/global';
import { useRoute, useRouter } from 'vue-router';
import { getAdItem } from '@/utils/getConfig';
import { AdType } from '@/enums/app';
import { watchCount, fromNowTime } from '@/utils/index';
import VideoApi from '@/service/video';
import MineApi from '@/service/mine';
import { useUserStore } from '@/stores/user';
import { eventBus } from '@/utils/eventBus';
import insertAdv from '@/utils/insertAdv';
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const globalStore = useGlobalStore();
const state = reactive({
advList: <any>[],
videoInfo: <any>{},
rankNum: route.query.rankNum,
pageNumber: 1,
pageSize: 7,
total: 0,
recommendList: <any>[],
userInfo: <Types.User.UserInfoType>{},
});
const queryVideoInfo = async () => {
try {
const req = {
videoID: route.query.vid,
};
const res: any = await VideoApi.queryVideoInfo(req);
state.videoInfo = res;
} catch (e) {
console.log(e);
}
};
const getRecommendList = async () => {
try {
const req = {
id: route.query.vid,
pageSize: state.pageSize,
pageNumber: state.pageNumber,
};
const res: any = await VideoApi.queryvidPlay(req);
let videoList = res.videos || [];
let params = {
insertNum: 6,
pageSize: state.pageSize,
pageNumber: state.pageNumber,
advNum: 6,
originArray: videoList,
};
state.recommendList = insertAdv(params);
// state.recommendList = res.videos || [];
state.total = res.total;
} catch (e) {
console.log(e);
}
};
nextTick(async () => {
/**
* 获取用户信息
*/
state.userInfo = userStore.userInfo;
});
const collect = async (isCollect: boolean) => {
if (!state.userInfo.account) {
eventBus.emit('SHOW_LOGIN');
return;
}
await MineApi.mineCollect({
isCollect: !isCollect,
objID: state.videoInfo.id,
type: 'video',
});
if (isCollect) {
state.videoInfo.vidStatus.hasCollected = false;
if (state.videoInfo.collectCount > 0) {
state.videoInfo.collectCount--;
}
showToast('取消收藏成功');
} else {
state.videoInfo.vidStatus.hasCollected = true;
state.videoInfo.collectCount++;
showToast('收藏成功');
}
};
const changePage = (pageNumber: number) => {
state.pageNumber = pageNumber;
getRecommendList();
};
const init = () => {
state.advList = getAdItem(AdType.VIDEODETAILSADV);
queryVideoInfo();
getRecommendList();
};
init();
const jumpTag = (item: any) => {
router.push({
path: '/tagDetails',
query: {
tid: item.id,
name: item.name,
},
});
};
</script>
<style scoped lang="scss">
.videoDetails {
height: 100vh;
.advBanner {
width: 100%;
height: 70px;
}
.video {
width: 387px;
height: 218px;
margin-bottom: 38.5px;
}
.contentBox {
padding: 0 16px;
box-sizing: border-box;
.videoTitle {
font-size: 19.231px;
font-style: normal;
font-weight: 600;
line-height: 23.077px;
margin-bottom: 10px;
}
.videoInfo {
display: flex;
align-items: center;
margin-bottom: 15px;
.rankBox {
display: flex;
align-items: center;
.rank {
height: 17.3px;
padding: 0 3.85px;
box-sizing: border-box;
border-radius: 1.923px 0px 0px 1.923px;
background: linear-gradient(270deg, #ffc303 0%, #ffdd01 100%);
color: #111319;
font-size: 11.538px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.rankTitle {
height: 17.3px;
padding: 0 3.85px;
box-sizing: border-box;
border-radius: 1.923px;
background: rgba(255, 255, 255, 0.2);
color: #fff;
font-size: 11.538px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
}
.palyerCount {
color: #ececec;
font-family: 'PingFang SC';
font-size: 13.462px;
font-style: normal;
font-weight: 400;
line-height: 17.308px; /* 128.571% */
display: flex;
align-items: center;
}
.palyerTime {
color: #ececec;
font-family: 'PingFang SC';
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 100% */
}
.infoLine {
width: 1px;
height: 9.6px;
margin: 0 5.77px;
background: #2d2f34;
}
}
.tagBox {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
.tagList {
display: flex;
align-items: center;
.tagItem {
height: 17.8px;
padding: 0 3.85px;
color: rgba(255, 255, 255, 0.4);
font-family: 'PingFang SC';
font-size: 9px;
font-style: normal;
font-weight: 500;
line-height: 9px; /* 100% */
border-radius: 2px;
border: 1px solid #212121;
background: #151515;
display: flex;
align-items: center;
justify-content: center;
margin-right: 4px;
}
}
}
.bannerBox {
width: 100%;
margin-bottom: 25.75px;
.bannerItem {
width: 100%;
height: 71.25px;
margin-bottom: 11.75px;
border-radius: 4px;
overflow: hidden;
:deep(img) {
border-radius: 4px;
}
}
.bannerItem:last-child {
margin-bottom: 0;
}
}
.likeBox {
display: flex;
align-items: center;
color: #a9a9ac;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
.likeIcon {
width: 24px;
height: 24px;
margin-right: 4px;
}
}
.bannerSwiper {
height: 58.5px;
width: 100%;
margin-bottom: 25px;
border-radius: 2px;
overflow: hidden;
:deep(.name) {
font-size: 14px !important;
}
:deep(img) {
border-radius: 2px;
}
}
.subTitle {
color: #fff;
font-family: 'PingFang SC';
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 112.5% */
margin-bottom: 16px;
}
.recommendList {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 16px;
grid-column-gap: 12px;
margin-bottom: 24px;
}
.pagination {
margin-bottom: 72px;
}
}
}
</style>
+24
View File
@@ -0,0 +1,24 @@
<template>
<div class="welfare overflow-y-auto">
<MobileHeader class="header" />
<MobileBottom />
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import { reactive } from 'vue';
import { useGlobalStore } from '@/stores/global';
const globalStore = useGlobalStore();
const state = reactive({});
</script>
<style scoped lang="scss">
.welfare {
height: 100vh;
.header {
position: sticky;
top: 0;
z-index: 11;
}
}
</style>
+76
View File
@@ -0,0 +1,76 @@
<template>
<div class="contact">
<div class="title">商务合作</div>
<div class="contact-list">
<div class="item" v-for="(item, index) in state.contactInfo" :key="index" @click="openPage(item.officialUrl)">
<lazyImg class="connectImg" :src="item.officialImg" />
<div class="text">
<p class="name">{{ item.officialName }}</p>
<p class="described">{{ item.officialDesc }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import AppApi from '@/service/app';
const state = reactive({
contactInfo: <any>[],
});
const queryContactInfo = async () => {
try {
let req = {
type: 3,
};
const res: any = await AppApi.getBusiness(req);
state.contactInfo = res || [];
} catch (error) {
}
};
queryContactInfo();
const openPage = (url: string) => {
window.open(url);
};
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.contact {
color: #fff;
width: 1104px;
margin: 112px auto 0;
padding-bottom: 70px;
.title {
color: #FFF;
font-size: 20px;
font-weight: 600;
line-height: 41px;
}
.contact-list {
display: flex;
justify-content: space-between;
padding-top: 61px;
.item {
display: flex;
align-items: center;
cursor: pointer;
}
.connectImg {
width: 86px;
height: 86px;
border-radius: 50%;
margin-right: 17px;
}
.name {
font-size: 18px;
font-weight: 600;
}
.described {
font-size: 14px;
font-weight: 400;
margin-top: 10px;
}
}
}
}
</style>
+70
View File
@@ -0,0 +1,70 @@
<template>
<div class="classification">
<div class="item-wrap" v-for="(item, index) in tagList" :key="index">
<div class="title">{{ item.subModuleName }}</div>
<div class="tag-list">
<span v-for="(tag, i) in item.allSection" :key="i" @click="toListOfTopics(tag)">{{ tag.name }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import HomeApi from '@/service/home';
import { ref} from 'vue'
const router = useRouter()
const tagList = ref([])
const getTagList = async() => {
const res: any = await HomeApi.getTabs({ isSection: true })
tagList.value = res.homePage;
}
getTagList()
const toListOfTopics = (item: any) => {
router.push({
path: '/listOfTopics',
query: {
sectionid: item.id,
name: item.name,
page: 1
}
})
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.classification {
width: 1248px;
min-height: 500px;
padding: 24px;
margin: 36px auto;
}
.item-wrap {
padding-bottom: 44px;
.title {
color: #FFF;
font-size: 24px;
font-weight: 600;
line-height: 32px;
padding-bottom: 24px;
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 20px 12px;
span {
width: 190px;
height: 32px;
color: #FFF;
font-size: 16px;
font-weight: 400;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
@include flexCenter;
cursor: pointer;
}
}
}
}
</style>
+66
View File
@@ -0,0 +1,66 @@
<template>
<div class="contact">
<div class="title">官方联系</div>
<div class="contact-list">
<div class="item" v-for="(item, index) in contactInfo" :key="index" @click="openPage(item.officialUrl)">
<lazyImg class="connectImg" :src="item.officialImg" />
<div class="text">
<p class="name">{{ item.officialName }}</p>
<p class="described">{{ item.officialDesc }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useConfigStore } from '@/stores/config';
const configStore = useConfigStore();
const contactInfo = computed(() => {
return configStore.contactInfo;
});
const openPage = (url: string) => {
window.open(url);
};
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.contact {
color: #fff;
width: 1104px;
margin: 112px auto 0;
padding-bottom: 70px;
.title {
color: #FFF;
font-size: 20px;
font-weight: 600;
line-height: 41px;
}
.contact-list {
display: flex;
justify-content: space-between;
padding-top: 61px;
.item {
display: flex;
align-items: center;
cursor: pointer;
}
.connectImg {
width: 86px;
height: 86px;
border-radius: 50%;
margin-right: 17px;
}
.name {
font-size: 18px;
font-weight: 600;
}
.described {
font-size: 14px;
font-weight: 400;
margin-top: 10px;
}
}
}
}
</style>
+258
View File
@@ -0,0 +1,258 @@
<template>
<div class="banner-wrap">
<swiper :loop="true" ref="swiperDom"
:autoplay="{ delay: 5000, disableOnInteraction: false, pauseOnMouseEnter: true, }" :modules="[Autoplay]"
@slideChange="onSlideChange">
<swiper-slide v-for="(item, index) in swiperList" :key="index" @click="toMoivePlay(item.id)">
<DecryptImg class="banner-img" :src="item.cover" />
</swiper-slide>
</swiper>
<div class="btn-list">
<img @click="toMoivePlay(swiperList[currentIndex]?.id)" src="@/assets/images/pc/play.png" alt="">
<img @click="toMoivePlay(swiperList[currentIndex]?.id)" src="@/assets/images/pc/like1.png" alt="">
</div>
<img @click="slideNext" class="left-arrow" src="@/assets/images/pc/left_arrow.png" alt="">
<img @click="slidePrev" class="right-arrow" src="@/assets/images/pc/left_arrow.png" alt="">
<div class="point-list">
<i @click="changeSlideTo(i)" v-for="i in swiperList.length" :key="i"
:class="{ active: currentIndex === i - 1 }"></i>
</div>
<div class="mask"></div>
<div class="slide-wrap" v-if="bannerList.length">
<swiper ref="swiperDom1" :modules="[Pagination, FreeMode]" free-mode :slides-per-view="3.2" :space-between="24">
<swiper-slide v-for="(item, index) in bannerList" :key="index">
<div class="card-item" @click="jumpPage('/moviePlay?page=1&vid=' + item.id + '&topindex=' + (index + 1))">
<div class="item-mask"></div>
<div :class="'top-img' + index"></div>
<DecryptImg class="cover" :src="item.cover" />
</div>
</swiper-slide>
</swiper>
<img @click="slidePrev1" class="left-arrow1" src="@/assets/images/pc/left_arrow.png" alt="">
<div class="rigth-mask">
<img @click="slideNext1" class="right-arrow1" src="@/assets/images/pc/left_arrow.png" alt="">
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Autoplay, Pagination, FreeMode } from 'swiper'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/swiper.min.css';
import { useRouter } from 'vue-router';
const router = useRouter();
withDefaults(defineProps<{
swiperList: Array<any>;
bannerList: Array<any>;
}>(), {
swiperList: () => <Array<any>>[],
bannerList: () => <Array<any>>[]
})
const currentIndex = ref(0)
const onSlideChange = (swiper: any) => {
console.log(swiper.realIndex);
currentIndex.value = swiper.realIndex;
}
const swiperDom = ref(null)
const slideNext = () => {
swiperDom.value && swiperDom.value.$el.swiper.slideNext()
}
const slidePrev = () => {
swiperDom.value && swiperDom.value.$el.swiper.slidePrev()
}
const changeSlideTo = (i: number) => {
swiperDom.value && swiperDom.value.$el.swiper.slideTo(i, 500);
}
const swiperDom1 = ref(null)
const slideNext1 = () => {
swiperDom1.value && swiperDom1.value.$el.swiper.slideNext()
}
const slidePrev1 = () => {
swiperDom1.value && swiperDom1.value.$el.swiper.slidePrev()
}
const toMoivePlay = (id: string) => {
router.push({
path: '/moviePlay',
query: {
vid: id,
page: 1
}
})
}
const jumpPage = (path: string) => {
router.push(path)
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.banner-wrap {
width: 100%;
height: 830px;
margin-bottom: 190px;
position: relative;
cursor: pointer;
.banner-img {
width: 100%;
height: 830px;
object-fit: cover;
}
.left-arrow {
width: 56px;
height: 56px;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 34px;
z-index: 2;
cursor: pointer;
}
.right-arrow {
width: 56px;
height: 56px;
position: absolute;
top: 50%;
transform: translateY(-50%) rotate(180deg);
transform-origin: center;
left: 34px;
z-index: 2;
cursor: pointer;
}
.point-list {
display: flex;
position: absolute;
bottom: 102px;
right: 56px;
z-index: 2;
cursor: pointer;
i {
width: 8px;
height: 8px;
border-radius: 1.5px;
background: rgba(255, 255, 255, 0.30);
margin-left: 8px;
}
.active {
background-color: #fff;
width: 16px;
}
}
.mask {
width: 100%;
height: 217px;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.80) 1.8%, rgba(0, 0, 0, 0.00) 78.8%);
position: absolute;
bottom: 0;
z-index: 1;
}
.slide-wrap {
width: 1516px;
height: 260px;
position: absolute;
bottom: -190px;
left: 50%;
transform: translateX(-50%);
z-index: 3;
.card-item {
width: 462px;
height: 260px;
border-radius: 8px;
overflow: hidden;
position: relative;
.item-mask {
width: 100%;
height: 130px;
position: absolute;
bottom: 0;
left: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.00) 0%, rgba(0, 0, 0, 0.60) 83%);
z-index: 1;
}
}
.cover {
width: 462px;
height: 260px;
}
.left-arrow1 {
width: 20px;
height: 20px;
position: absolute;
top: 50%;
transform: translateY(-50%) rotate(180deg);
transform-origin: center;
left: -40px;
z-index: 2;
cursor: pointer;
}
.rigth-mask {
width: 100px;
height: 260px;
background: linear-gradient(90deg, rgba(0, 0, 0, 0.01) 0%, #000 80%);
position: absolute;
top: 0;
right: 0;
z-index: 3;
}
.right-arrow1 {
width: 20px;
height: 20px;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0px;
z-index: 2;
cursor: pointer;
}
}
}
.btn-list {
width: 152px;
height: 60px;
position: absolute;
bottom: 123px;
left: 206px;
z-index: 2;
display: flex;
justify-content: space-between;
img {
width: 60px;
height: 60px;
cursor: pointer;
}
}
@for $i from 0 through 19 {
.top-img#{$i} {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 2;
width: 170px;
height: 68px;
background: url('@/assets/images/pc/top/top#{$i + 1}.png') center no-repeat;
background-size: contain;
}
}
}
</style>
+104
View File
@@ -0,0 +1,104 @@
<template>
<div class="content">
<div class="thematic" v-for="(item, index) in videoList" :key="index">
<div class="title">
{{ item.sectionName }}
<div class="more" @click="toListOfTopics(item)">
<img class="left-arrow" src="@/assets/images/pc/more.png" alt="">
更多
</div>
</div>
<div class="card-list">
<movieCard :itemData="itemData" v-for="(itemData, i) in item.allVideoInfo" :key="i" />
</div>
<div class="adv-list-wrap" v-if="index % 2 === 1">
<DecryptImg v-for="(item, i) in renderAdvList(index)" class="adv" :src="item.cover" @click="jumpAdv(item)" :key="i"/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { getAdItem } from '@/utils/getConfig';
import { jumpAdv } from '@/utils/index';
import { useRouter } from 'vue-router';
withDefaults(defineProps<{
videoList: Array<any>[]
}>(), {
videoList: () => []
})
const router = useRouter()
const toListOfTopics = (item: any) => {
router.push({
path: '/listOfTopics',
query: {
sectionid: item.sectionID,
name: item.sectionName,
page: 1
}
})
}
const renderAdvList = (index: number) => {
const list = getAdItem(3)
for (let j = 0; j < 10; j++) {
list.push(...list)
}
const i = Math.floor(index / 2 )
const startIndex = i * 2
const endindex = (i + 1) * 2
return list.slice(startIndex, endindex)
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.content {
width: 1516px;
margin: auto;
.thematic {
.title {
width: 100%;
height: 56px;
color: #FFF;
font-size: 36px;
font-weight: 600;
padding-bottom: 28px;
padding-top: 50px;
box-sizing: content-box;
@include flexBetween;
.more {
@include flexBetween;
font-size: 18px;
font-weight: 400;
color: rgba(255, 255, 255, 0.4);
cursor: pointer;
img {
width: 24px;
height: 24px;
margin-right: 7px;
}
}
}
.card-list {
display: flex;
flex-wrap: wrap;
column-gap: 24px;
row-gap: 36px;
}
}
.adv-list-wrap {
display: flex;
gap: 24px;
.adv {
margin-top: 30px;
width: 746px;
height: 146px;
border-radius: 8px;
overflow: hidden;
}
}
}
}
</style>
+42
View File
@@ -0,0 +1,42 @@
<template>
<!-- 轮播 -->
<banner :swiperList="swiperList" :bannerList="bannerList" />
<content :videoList="videoList" v-if="videoList.length > 0" />
</template>
<script setup lang="ts" name="home">
import { ref, watchEffect } from 'vue'
import { useHomeStore } from '@/stores/home'
import banner from './banner.vue';
import content from './content.vue';
import HomeApi from '@/service/home';
const homeStore = useHomeStore()
const swiperList = ref([])
const videoList = ref([])
watchEffect(async() => {
const homeData = homeStore.homeData.find(item => item.showType === 1) || {}
if (homeData.id) {
const res: any = await HomeApi.getModuleList({
id: homeData.id,
data: {
pageNumber: 1,
pageSize: 10,
videoCount: 16,
moduleSort: homeData.moduleSort
}
})
getBannerList()
const allSection = res.allSection
swiperList.value = (allSection.find((item: any) => item.showType === 2) || {}).allVideoInfo || []
videoList.value = allSection.filter((item: any) => item.showType !== 2) || []
}
})
const bannerList = ref([])
const getBannerList = async () => {
const res: any = await HomeApi.getRangking({
pageNumber: 1,
pageSize: 10,
})
bannerList.value = res.videos
}
</script>
+442
View File
@@ -0,0 +1,442 @@
<template>
<div id="home">
<div class="header-wrap-box">
<div class="adv-wrap" v-if="showHeaderAdv && getAdItem(1).length > 0">
<swiper loop :autoplay="{ delay: 5000, disableOnInteraction: false, pauseOnMouseEnter: true, }" :modules="[Autoplay]">
<swiper-slide class="adv" v-for="(item, index) in getAdItem(1)" :key="index" @click="jumpAdv(item)">
<DecryptImg class="banner-img" :src="item.cover" />
</swiper-slide>
</swiper>
<img @click="closeHeaderAdv" class="close-adv" src="@/assets/images/pc/adv_close.png" />
</div>
<div class="header-wrap">
<div class="flex-vcenter left-wrap">
<img @click="jumpPage('/home')" class="logo" src="@/assets/images/pc/logo.png" />
<div class="tab-list">
<span
class="item"
:class="{ active: activeIndex === index }"
@click="changeTab(item, index)"
v-for="(item, index) in homeStore.homeData"
:key="index">
{{ item.subModuleName }}
<span v-show="activeIndex === index">✨</span>
</span>
</div>
</div>
<div class="right-list">
<div class="search-box">
<input type="text" v-model="searchText" @keydown.enter="tosearch" placeholder="输入你搜索的关键字">
<div class="search-btn" @click="tosearch">
<img class="search" src="@/assets/images/pc/search.png" />
</div>
</div>
<div class="item history-box">
<div @click="toHistory" @mouseover="getHistoryList">
<img class="search" src="@/assets/images/pc/history.png" />
<p>观看历史</p>
</div>
<div class="history-list-box-wrap" v-if="historyList.length > 0">
<div class="history-list-box">
<img class="top-tip" src="@/assets/images/pc/history_bg.png" />
<div class="movie-list" @click="toMoivePlay(item.id)" v-for="(item, index) in historyList" :key="index">
<DecryptImg class="cover" :src="item.cover" :key="item.cover + Math.random()" />
<div class="described ellipsis2">{{ item.title }}</div>
</div>
<div class="more-btn" @click="toHistory">更多 <img src="@/assets/images/pc/left_arrow.png" alt=""></div>
</div>
</div>
</div>
<div class="item" @click="jumpPage('/business')">
<img class="search" src="@/assets/images/pc/business.png" />
<p>商务合作</p>
</div>
<DecryptImg @click="userHandle" class="avatar" v-if="userInfo?.portrait" :src="userInfo.portrait" :key="userInfo?.portrait" />
<img v-else @click="userHandle" class="avatar" src="@/assets/images/pc/avatar.png" />
<div class="official" @click="jumpPage('/contact')">联系官方</div>
</div>
</div>
</div>
<router-view></router-view>
<pc-footer></pc-footer>
<!-- <Mask @closeMaskPop="setAdvPopup"/> -->
<!-- 弹窗广告 -->
<!-- v-if="isShowAdvPopup" -->
<HomePopupAdv v-if="isShowAdvPopup" :advList="getAdItem(9).reverse()" @close="closeHomePopupAdv" />
</div>
</template>
<script setup lang="ts">
import { ref, watchEffect, computed, watch, onMounted } from 'vue'
import { jumpAdv } from '@/utils/index';
import { getAdItem } from '@/utils/getConfig'
import { Autoplay } from 'swiper'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/swiper.min.css';
import { useHomeStore } from '@/stores/home'
import { HistoryVideoType } from '@/enums/app';
import { queryHistoryVideo } from '@/utils';
import Mask from './mask.vue';
import { useRouter, useRoute } from 'vue-router';
import { useGlobalStore } from '@/stores/global';
import { eventBus } from "@/utils/eventBus";
const globalStore = useGlobalStore();
const router = useRouter();
const route = useRoute()
const homeStore = useHomeStore()
const userInfo = computed(() => {
const data = localStorage.getItem('userInfo') || '{}'
if (data) {
return JSON.parse(data)
}
return data
})
const searchText = ref('')
const activeIndex = ref(0)
// const isShowAdvPopup = ref(false)
// const setAdvPopup = () => {
// isShowAdvPopup.value = !globalStore.homeAdvFirst && getAdItem(9)?.length > 0 && !!sessionStorage.getItem('maskPop');
// }
// 首页弹窗广告
const isShowAdvPopup = computed(() => {
return getAdItem(9)?.length > 0;
});
const closeHomePopupAdv = () => {
globalStore.setHomeAdvFirst();
};
watchEffect(() => {
if (['/home','/welfare', '/classification', '/pageList'].includes(route.path)) {
activeIndex.value = Number(route.query.index || 0)
} else {
activeIndex.value = -1
}
})
watch(() => route.path, (path) => {
if (path !== '/search') {
searchText.value = ''
}
})
const changeTab = (item: any, index: number) => {
activeIndex.value = index
if (item.showType === 2) {
router.push({
path: '/pageList',
query: {
index,
id: item.id,
name: item.subModuleName,
page: 1
}
})
}
if (item.showType === 1) {
router.push({
path: '/home',
query: {
index,
id: item.id,
}
})
}
if (item.showType === 4) {
router.push('/welfare?index=' + index)
}
if (item.showType === 5) {
router.push('/classification?index=' + index)
}
}
const tosearch = () => {
router.push({
path: '/search',
query: {
id: searchText.value,
page: 1
}
})
}
const jumpPage = (path: string) => {
router.push(path)
}
const showHeaderAdv = ref(true)
showHeaderAdv.value = !sessionStorage.getItem('HeaderAdv')
const closeHeaderAdv = () => {
sessionStorage.setItem('HeaderAdv', 'true')
showHeaderAdv.value = false
}
const userHandle = () => {
if (userInfo.value.account) {
router.push('/mine')
} else {
eventBus.emit('SHOW_LOGIN')
}
}
const historyList = ref([])
const getHistoryList = async () => {
const list: any = await queryHistoryVideo(1, 3, HistoryVideoType.VIDEO)
historyList.value = list
};
const toMoivePlay = (id: string) => {
router.push({
path: '/moviePlay',
query: {
vid: id,
page: 1
}
})
}
getHistoryList()
const toHistory = () => {
if (userInfo.value.account) {
router.push('/mine/history')
} else {
eventBus.emit('SHOW_LOGIN')
}
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
#home {
min-width: 1280px;
height: 100vh;
overflow: auto;
background-color: #000;
}
.header-wrap-box {
position: sticky;
top: 0;
z-index: 100;
.adv-wrap {
border: 1px solid #34363B;
background: #1A1C22;
width: 100%;
height: 100px;
position: relative;
.adv {
height: 100%;
width: max-content;
}
.banner-img {
width: 100%;
height: 100px;
}
.close-adv {
width: 50px;
height: 20px;
position: absolute;
top: 20px;
right: 200px;
z-index: 1;
}
}
}
.header-wrap {
width: 100%;
height: 72px;
background: #1A1C22;
@include flexCenter;
.left-wrap {
min-width: 900px;
margin-right: 3vw;
}
.logo {
width: 100px;
height: 32px;
cursor: pointer;
}
.logo:hover {
transform: scale(1.02);
}
.tab-list {
display: flex;
align-items: center;
color: #A3A4A7;
font-size: 16px;
font-weight: 400;
.item {
padding: 14px;
cursor: pointer;
white-space: nowrap;
}
.active {
color: #FFF;
font-size: 18px;
}
.item:hover {
color: #FFF;
}
}
.search-box {
width: 240px;
height: 36px;
padding: 10px 0 10px 12px;
box-sizing: border-box;
border-radius: 4px;
background: rgba(255, 255, 255, 0.20);
display: flex;
align-items: center;
input {
all: unset;
color: #fff;
flex: 1;
}
.search-btn {
cursor: pointer;
}
.search {
width: 24px;
height: 24px;
margin: 0 11px;
}
}
.right-list {
display: flex;
align-items: center;
.item {
color: rgba(255, 255, 255, 0.8);
font-size: 12px;
font-weight: 400;
text-align: center;
padding-left: 30px;
cursor: pointer;
white-space: nowrap;
img {
width: 32px;
height: 32px;
}
}
.item:hover {
color: #fff;
}
.avatar {
width: 38px;
height: 38px;
border-radius: 50%;
overflow: hidden;
margin: 30px;
cursor: pointer;
transition: all 0.2s;
}
.avatar:hover {
transform: scale(1.1);
transition: all 0.2s;
}
.official {
width: 87px;
height: 36px;
border-radius: 4px;
border: 1px solid #fff;
color: #fff;
font-size: 12px;
font-weight: 500;
@include flexCenter;
cursor: pointer;
}
.official:hover {
color: #FFC303;
}
}
}
.history-box {
position: relative;
.history-list-box-wrap {
width: 282px;
min-height: 100px;
position: absolute;
top: 45px;
transform: translateX(-50%);
left: 50%;
display: none;
}
.history-list-box {
width: 282px;
padding: 16px;
position: absolute;
left: 50%;
top: 60px;
transform: translateX(-50%);
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.25);
background: #1A1C22;
.top-tip {
position: absolute;
top: -18px;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
}
.movie-list {
display: flex;
align-items: center;
margin-bottom: 16px;
}
.cover {
width: 110px;
height: 62px;
border-radius: 4px;
overflow: hidden;
margin-right: 8px;
}
.described {
width: 130px;
height: 44px;
text-align: left;
color: #A9A9AC;
font-size: 14px;
font-weight: 400;
line-height: 22px;
border-bottom: 2px solid rgba(255, 255, 255, 0.12);
}
.more-btn {
@include flexCenter;
height: 44px;
color: #A9A9AC;
font-size: 14px;
font-weight: 400;
img {
width: 16px;
height: 16px;
}
}
}
}
.history-box:hover {
.history-list-box-wrap {
display: block;
}
}
:deep() {
.imgBox {
width: 474px !important;
height: 664px !important;
.closeIcon {
top: 23px !important;
right: 29px !important;
width: 52px !important;
height: 52px !important;
cursor: pointer;
}
}
}
}
</style>
+201
View File
@@ -0,0 +1,201 @@
<template>
<div class="content">
<div class="left-wrap">
<div class="thematic">
<div class="title">
{{ route.query.name }}
<div class="more">
<div class="all">{{ sortText }} <img class="arrow" src="@/assets/images/pc/down_arrow.png" alt=""></div>
<div class="meun-wrap">
<div class="meun-list">
<div @click="changeSort(item, index)" :class="{ active: activeIndex === index }"
v-for="(item, index) in sortList" :key="index">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div class="card-list">
<movieCard :itemData="item" v-for="(item, index) in videoList" :key="index" />
</div>
</div>
<div class="page-wrap" v-if="total > 0">
<Pagination :total="total" :pageSize="pageSize" @changePage="changePage" />
</div>
</div>
<tag-list />
</div>
</template>
<script setup lang="ts">
import insertAdv from '@/utils/insertAdv'
import { onMounted, ref, watch, watchEffect } from 'vue'
import { useRouter, useRoute } from 'vue-router';
import HomeApi from '@/service/home';
const router = useRouter();
const route = useRoute()
const activeIndex = ref(0)
const sortText = ref('全部視頻')
const sortList = ref([
{ name: '最新上架', value: 'new'},
{ name: '最多观看', value: 'watch'},
{ name: '最多收藏', value: 'like'},
])
const videoList = ref([])
let pageNumber = 1
const pageSize = 21
const total = ref(0)
const changeSort = (item: any, index: number) => {
activeIndex.value = index
sortText.value = item.name
getDataList(item.value)
}
watchEffect(() => {
pageNumber = Number(route.query.page || 1)
})
const getDataList = async (sortType: string) => {
try {
const sectionid = route.query.sectionid as string
const res: any = await HomeApi.getVidSectionList(sectionid, {
pageNumber,
pageSize,
sortType,
playTimeType: 1
})
const updatedArray = insertAdv({
insertNum: 6,
pageSize,
pageNumber: pageNumber,
originArray: res.videos,
advNum: 5
})
videoList.value = updatedArray
total.value = res.total
} catch (error) {
videoList.value = []
total.value = 0
}
}
const changePage = (page: number) => {
pageNumber = page
router.push({
pāth: route.path,
query: {
...route.query,
page
}
})
getDataList('new')
}
onMounted(() => {
getDataList('new')
})
watch(() => route.query.sectionid, (sectionid) => {
if (sectionid) {
getDataList('new')
}
})
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.content {
width: 1560px;
margin: auto;
padding-top: 65px;
display: flex;
.left-wrap {
width: 1128px;
margin: 0 42px;
box-sizing: content-box;
}
.thematic {
.title {
width: 100%;
height: 56px;
color: #FFF;
font-size: 48px;
font-weight: 600;
padding-bottom: 28px;
box-sizing: content-box;
@include flexBetween;
.more {
@include flexBetween;
display: inline-flex;
height: 48px;
padding: 0px 20px;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
cursor: pointer;
position: relative;
.all {
color: #A0A6B4;
font-size: 14px;
img {
width: 8px;
height: 8px;
margin-left: 4px;
}
}
.meun-wrap {
width: 100%;
height: 143px;
position: absolute;
top: 45px;
left: 0;
height: 0;
overflow: hidden;
transition: height 0.2s;
}
.meun-list {
margin-top: 10px;
width: 100%;
height: 105px;
z-index: 1;
color: #fff;
color: #A0A6B4;
font-size: 14px;
font-weight: 400;
line-height: 28px;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
text-align: center;
padding-top: 10px;
}
.active {
color: #FFF;
}
}
.more:hover {
.meun-wrap {
height: 143px;
transition: height 0.1s;
}
}
}
.card-list {
display: flex;
flex-wrap: wrap;
column-gap: 24px;
row-gap: 36px;
}
}
}
.page-wrap {
padding-top: 63px;
@include flexCenter;
}
}
</style>
+312
View File
@@ -0,0 +1,312 @@
<template>
<div class="login-page" v-if="modelValue">
<!-- 获取验证码 -->
<div class="form-content" v-if="state.showSendCode">
<img class="close" @click="close" src="@/assets/images/pc/close.png" />
<div class="title">找回密码</div>
<!-- 手机号 -->
<div class="form-item">
<div class="input-box">
<input type="text" placeholder="请输入绑定的手机号" v-model="state.formData.mobile" @blur="verificationMobile">
</div>
<div class="verification" :class="{ show: state.mobileVerific }">手机号格式有误</div>
</div>
<!-- 短信验证码 -->
<div class="form-item">
<div class="input-box">
<input type="text" placeholder="请输入短信验证码" v-model="state.formData.code" @blur="verificationCode">
<div class="codetimer" v-if="state.timer > 0">{{ state.timer }}S</div>
<div class="getcodebtn" @click="countdown" v-else>获得验证码</div>
</div>
<div class="verification" :class="{ show: state.codeVerific }">验证码不正确</div>
</div>
<div class="submit" @click="sumbit">提交</div>
<div class="box1">
<div class="registered" @click="showLogin">回到登录</div>
</div>
</div>
<!-- 设置新密码 -->
<div class="form-content" v-else>
<img class="close" @click="close" src="@/assets/images/pc/close.png" />
<div class="title">设置新密码</div>
<div class="form-item">
<div class="input-box">
<input type="text" placeholder="请输入新密码" v-model="state.formData.password" @blur="verificationPassword">
</div>
<div class="verification" :class="{ show: state.passwordVerific }">密码格式有误</div>
</div>
<div class="submit" @click="setNewPassword">提交</div>
<div class="box1">
<div class="set-mobile" @click="state.showSendCode = true">重新输入手机号</div>
<div class="registered" @click="showLogin">回到登录</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import UserApi from '@/service/user';
import { showToast } from 'vant';
const props = withDefaults(
defineProps<{
modelValue: boolean
}>(),
{
modelValue: false
}
)
const state = reactive({
formData: {
mobile: '',
code: '',
password: ''
},
mobileVerific: false,
codeVerific: false,
passwordVerific: false,
timer: 0,
showSendCode: true
})
// 手机号
const verificationMobile = () => {
state.mobileVerific = !validatePhoneNumber(state.formData.mobile)
}
// 验证码
const verificationCode = () => {
state.codeVerific = !state.formData.code
}
// 密码
const verificationPassword = () => {
state.passwordVerific = !validatePassword(state.formData.password)
}
// 密码校验正则
function validatePassword(password: string) {
const regex = /^(?=.*[A-Za-z])(?=.*[\d@#$%^&*!])[A-Za-z\d@#$%^&*!]{8,20}$/;
return regex.test(password);
}
// 手机号验证
function validatePhoneNumber(phone: string) {
const regex = /^1[3-9]\d{9}$/; // 正则表达式匹配中国大陆手机号
return regex.test(phone);
}
// 提交
const sumbit = async () => {
if (!validatePhoneNumber(state.formData.mobile)) {
state.mobileVerific = true
return
}
if (!state.formData.code) {
state.codeVerific = true
return
}
try {
await UserApi.mobileverify({
mobile: '+86' + state.formData.mobile,
code: state.formData.code,
});
state.showSendCode = false
showToast('验证通过')
} catch (error) {
// state.showSendCode = false
// state.codeVerific = true
}
}
/**
* 获取验证码倒计时
*/
const countdown = () => {
if (!validatePhoneNumber(state.formData.mobile)) {
state.mobileVerific = true
return;
}
sendCode();
};
/**
* 发送短信验证码
*/
const sendCode = async () => {
try {
await UserApi.getCaptcha({
type: 2,
mobile: '+86' + state.formData.mobile,
});
state.timer = 60;
let countdownTime = setInterval(() => {
state.timer--;
if (state.timer <= 0) {
clearInterval(countdownTime);
}
}, 1000);
} catch (error) {
console.log(error);
}
};
//设置新密码
const setNewPassword = async () => {
if(!validatePassword(state.formData.password)) {
state.passwordVerific = true
return
}
try {
await UserApi.resetpassword({
mobile: '+86' + state.formData.mobile,
passWord: state.formData.password
})
showToast('设置成功');
showLogin()
} catch (error) {
}
}
const emit = defineEmits(['update:modelValue', 'login'])
const close = () => {
emit('update:modelValue', false)
}
const showLogin = () => {
close()
emit('login')
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.login-page {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.00) 0%, #000 35%);
z-index: 1111;
}
.form-content{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
height: 500px;
background-color: #fff;
border-radius: 8px;
padding: 40px;
text-align: center;
.title {
color: #222;
text-align: center;
font-size: 18px;
font-weight: 600;
line-height: 28px;
}
.close {
position: absolute;
top: 12px;
right: 12px;
width: 23px;
height: 23px;
cursor: pointer;
z-index: 1;
}
.form-item {
width: 100%;
margin: 24px 0;
.input-box {
width: 100%;
height: 60px;
padding: 0 12px;
border-radius: 4px;
border: 1px solid #FFCD3F;
display: flex;
align-items: center;
color: #000;
font-size: 16px;
.codetimer {
color: #000;
font-size: 16px;
}
.getcodebtn {
font-size: 14px;
width: 100px;
color: #FFCD3F;
cursor: pointer;
}
.getcodebtn:hover {
text-decoration: underline;
}
.eyes {
width: 20px;
height: 20px;
cursor: pointer;
}
}
input {
all: unset;
font-size: 16px;
font-weight: 500;
width: 100%;
text-align: left;
}
input::placeholder {
color: #999;
font-size: 14px;
font-weight: 400;
}
.verification {
margin-top: 8px;
color: #FF5356;
font-size: 12px;
font-weight: 400;
line-height: 16px;
text-align: left;
opacity: 0;
}
.show {
opacity: 1;
}
}
.submit {
width: 100%;
height: 44px;
border-radius: 4px;
background: linear-gradient(270deg, #FFC303 0%, #FFDD01 100%);
color: #502D1E;
font-size: 16px;
font-weight: 600;
@include flexCenter;
cursor: pointer;
}
.forgot-password {
width: 100%;
height: 44px;
border-radius: 4px;
border: 1px solid #FFC303;
@include flexCenter;
color: #FFC303;
font-size: 16px;
font-weight: 500;
margin-top: 24px;
cursor: pointer;
}
.box1 {
@include flexCenter;
color: #FFC303;
font-size: 14px;
font-weight: 600;
margin-top: 24px;
gap: 20px;
}
.registered {
text-align: center;
}
.registered:hover, .set-mobile:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
</style>
+240
View File
@@ -0,0 +1,240 @@
<template>
<div class="login-page" v-if="modelValue">
<div class="form-content">
<img class="close" @click="close" src="@/assets/images/pc/close.png" />
<div class="title">登陆</div>
<!-- 账号 -->
<div class="form-item">
<div class="input-box">
<input type="text" placeholder="请输入账号" v-model="state.formData.account" @blur="verificationaccount">
</div>
<div class="verification" :class="{ show: state.accountVerific }">账号格式有误</div>
</div>
<!-- 密码 -->
<div class="form-item">
<div class="input-box">
<input :type="state.openEyes ? 'text' : 'password'" placeholder="密码" v-model="state.formData.password" @blur="verificationPassword">
<!-- 设置password -->
<img @click="state.openEyes = !state.openEyes" class="eyes" v-if="state.openEyes" src="@/assets/images/pc/eyes.png" />
<img @click="state.openEyes = !state.openEyes" class="eyes" v-else src="@/assets/images/pc/eyes1.png" />
</div>
<div class="verification" :class="{ show: state.passwordVerific }">密码格式有误</div>
</div>
<div class="submit" @click="sumbit">登录</div>
<div class="forgot-password" @click="showForgotPassword">忘记密码</div>
<div class="registered">没有账号? <span @click="showRegist"> 注册</span></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { showToast } from 'vant';
import UserApi from '@/service/user';
import { reactive } from 'vue';
import { useUserStore } from '@/stores/user';
import { clearnIndexDb } from '@/utils/indexDB';
const userStore = useUserStore();
const props = withDefaults(
defineProps<{
modelValue: boolean
}>(),
{
modelValue: false
}
)
const state = reactive({
formData: {
account: '',
password: ''
},
accountVerific: false,
passwordVerific: false,
openEyes: false
})
// 账号
const verificationaccount = () => {
state.accountVerific = !validateString(state.formData.account)
}
// 密码
const verificationPassword = () => {
state.passwordVerific = !validatePassword(state.formData.password)
}
// 账号验证
function validateString(account: string) {
const regex = /^[a-zA-Z0-9]{2,16}$/; // 验证2到16位的字母和数字组合
return regex.test(account)
}
// 密码校验正则
function validatePassword(password: string) {
const regex = /^(?=.*[A-Za-z])(?=.*[\d@#$%^&*!])[A-Za-z\d@#$%^&*!]{8,20}$/;
return regex.test(password);
}
const sumbit = async () => {
if (!validateString(state.formData.account)) {
state.accountVerific = true
return;
}
if (!validatePassword(state.formData.password)) {
state.passwordVerific = true
return;
}
try {
let req: any = {
account: state.formData.account,
passWord: state.formData.password,
};
let res: any = await UserApi.userLogin(req);
userStore.saveUserInfo(res);
userStore.saveToken(res.token);
clearnIndexDb()
showToast('登录成功');
close()
setTimeout(() => {
window.location.reload()
}, 100)
} catch (error) {
}
}
const emit = defineEmits(['update:modelValue', 'regist', 'forgotPasswordFn'])
const close = () => {
emit('update:modelValue', false)
}
const showRegist = () => {
close()
emit('regist')
}
const showForgotPassword = () => {
close()
emit('forgotPasswordFn')
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.login-page {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.00) 0%, #000 35%);
z-index: 1111;
}
.form-content{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
height: 500px;
background-color: #fff;
border-radius: 8px;
padding: 40px;
text-align: center;
.title {
color: #222;
text-align: center;
font-size: 18px;
font-weight: 600;
line-height: 28px;
}
.close {
position: absolute;
top: 12px;
right: 12px;
width: 23px;
height: 23px;
cursor: pointer;
z-index: 1;
}
.form-item {
width: 100%;
margin: 24px 0;
.input-box {
width: 100%;
height: 60px;
padding: 0 12px;
border-radius: 4px;
border: 1px solid #FFCD3F;
display: flex;
align-items: center;
.eyes {
width: 20px;
height: 20px;
cursor: pointer;
}
}
input {
all: unset;
font-size: 16px;
font-weight: 500;
width: 100%;
text-align: left;
color: #000;
font-size: 16px;
}
input::placeholder {
color: #999;
font-size: 14px;
font-weight: 400;
}
.verification {
margin-top: 8px;
color: #FF5356;
font-size: 12px;
font-weight: 400;
line-height: 16px;
text-align: left;
opacity: 0;
}
.show {
opacity: 1;
}
}
.submit {
width: 100%;
height: 44px;
border-radius: 4px;
background: linear-gradient(270deg, #FFC303 0%, #FFDD01 100%);
color: #502D1E;
font-size: 16px;
font-weight: 600;
@include flexCenter;
cursor: pointer;
}
.forgot-password {
width: 100%;
height: 44px;
border-radius: 4px;
border: 1px solid #FFC303;
@include flexCenter;
color: #FFC303;
font-size: 16px;
font-weight: 500;
margin-top: 24px;
cursor: pointer;
}
.registered {
color: #999;
text-align: center;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-top: 24px;
span {
color: #FFCD3F;
}
span:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
}
</style>
+256
View File
@@ -0,0 +1,256 @@
<template>
<div class="login-page" v-if="modelValue">
<div class="form-content">
<img class="close" @click="close" src="@/assets/images/pc/close.png" />
<div class="title">注册</div>
<!-- 账号 -->
<div class="form-item">
<div class="input-box">
<input type="text" placeholder="账号" v-model="state.formData.account" @blur="verificationAccount">
</div>
<div class="verification" :class="{ show: state.accountVerific }">账号格式有误</div>
</div>
<!-- 手机号 -->
<div class="form-item">
<div class="input-box">
<input type="text" placeholder="手机号" v-model="state.formData.mobile" @blur="verificationMobile">
</div>
<div class="verification" :class="{ show: state.mobileVerific }">手机号格式有误</div>
</div>
<!-- 密码 -->
<div class="form-item">
<div class="input-box">
<input :type="state.openEyes ? 'text' : 'password'" placeholder="密码" v-model="state.formData.password" @blur="verificationPassword">
<!-- 设置password -->
<img @click="state.openEyes = !state.openEyes" class="eyes" v-if="state.openEyes" src="@/assets/images/pc/eyes.png" />
<img @click="state.openEyes = !state.openEyes" class="eyes" v-else src="@/assets/images/pc/eyes1.png" />
</div>
<div class="verification" :class="{ show: state.passwordVerific }">密码格式有误</div>
</div>
<div class="submit" @click="sumbit">注册</div>
<div class="registered">已有账号? <span @click="showLogin">登陆</span></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { showToast } from 'vant';
import UserApi from '@/service/user';
import { reactive } from 'vue';
import { useUserStore } from '@/stores/user';
import { clearnIndexDb } from '@/utils/indexDB';
const userStore = useUserStore();
const props = withDefaults(
defineProps<{
modelValue: boolean
}>(),
{
modelValue: false
}
)
const state = reactive({
formData: {
account: '',
mobile: '',
password: ''
},
accountVerific: false,
mobileVerific: false,
passwordVerific: false,
openEyes: false
})
// 账号
const verificationAccount = () => {
state.accountVerific = !validateString(state.formData.account)
}
// 手机号
const verificationMobile = () => {
state.mobileVerific = !validatePhoneNumber(state.formData.mobile)
}
// 密码
const verificationPassword = () => {
state.passwordVerific = !validatePassword(state.formData.password)
}
// 账号验证
function validateString(account: string) {
const regex = /^[a-zA-Z0-9]{2,16}$/; // 验证2到16位的字母和数字组合
return regex.test(account)
}
// 密码校验正则
function validatePassword(password: string) {
const regex = /^(?=.*[A-Za-z])(?=.*[\d@#$%^&*!])[A-Za-z\d@#$%^&*!]{8,20}$/;
return regex.test(password);
}
// 手机号验证
function validatePhoneNumber(phone: string) {
const regex = /^1[3-9]\d{9}$/; // 正则表达式匹配中国大陆手机号
return regex.test(phone);
}
const sumbit = async () => {
if (!validateString(state.formData.account)) {
state.accountVerific = true
return;
}
if (!validatePhoneNumber(state.formData.mobile)) {
state.mobileVerific = true
return;
}
if (!validatePassword(state.formData.password)) {
state.passwordVerific = true
return;
}
try {
let req: any = {
account: state.formData.account,
mobile: '+86' + state.formData.mobile,
passWord: state.formData.password,
};
let res: any = await UserApi.register(req);
userStore.saveUserInfo(res);
userStore.saveToken(res.token);
clearnIndexDb()
showToast('注册成功');
close()
setTimeout(() => {
window.location.reload()
}, 100)
} catch (error) {
}
}
const emit = defineEmits(['update:modelValue', 'login'])
const close = () => {
emit('update:modelValue', false)
}
const showLogin = () => {
close()
emit('login')
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.login-page {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.00) 0%, #000 35%);
z-index: 1111;
}
.form-content{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
height: 550px;
background-color: #fff;
border-radius: 8px;
padding: 40px;
text-align: center;
.title {
color: #222;
text-align: center;
font-size: 18px;
font-weight: 600;
line-height: 28px;
}
.close {
position: absolute;
top: 12px;
right: 12px;
width: 23px;
height: 23px;
cursor: pointer;
z-index: 1;
}
.form-item {
width: 100%;
margin: 12px 0;
.input-box {
width: 100%;
height: 60px;
padding: 0 12px;
border-radius: 4px;
border: 1px solid #FFCD3F;
display: flex;
align-items: center;
color: #000;
font-size: 16px;
.eyes {
width: 20px;
height: 20px;
cursor: pointer;
}
}
input {
all: unset;
font-size: 16px;
font-weight: 500;
width: 100%;
text-align: left;
}
input::placeholder {
color: #999;
font-size: 14px;
font-weight: 400;
}
.verification {
margin-top: 8px;
color: #FF5356;
font-size: 12px;
font-weight: 400;
line-height: 16px;
text-align: left;
opacity: 0;
}
.show {
opacity: 1;
}
}
.submit {
width: 100%;
height: 44px;
border-radius: 4px;
background: linear-gradient(270deg, #FFC303 0%, #FFDD01 100%);
color: #502D1E;
font-size: 16px;
font-weight: 600;
@include flexCenter;
cursor: pointer;
}
.forgot-password {
width: 100%;
height: 44px;
border-radius: 4px;
border: 1px solid #FFC303;
@include flexCenter;
color: #FFC303;
font-size: 16px;
font-weight: 500;
margin-top: 24px;
cursor: pointer;
}
.registered {
color: #999;
text-align: center;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-top: 24px;
span {
color: #FFCD3F;
}
span:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
}
</style>
+92
View File
@@ -0,0 +1,92 @@
<template>
<div class="mask-pop" v-if="showMask">
<div class="mask"></div>
<div class="content">
<img class="logo" src="@/assets/images/pc/logo1.png" />
<div class="title">欢迎</div>
<div class="line"></div>
<div class="text">🔞 您欲觀看的頁面包含成人內容 🔞</div>
<div class="text">本站內容均遵守本國法律規定若您是未成年者,請選擇離開。 滿 18 歲。</div>
<div class="text">請点击确认进入。</div>
<div class="btn" @click="close">确认进入</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const showMask = ref(true)
const emit = defineEmits(['closeMaskPop'])
showMask.value = !sessionStorage.getItem('maskPop')
const close = () => {
showMask.value = false
sessionStorage.setItem('maskPop', 'true');
emit('closeMaskPop')
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.mask-pop {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 1000;
.mask {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
opacity: 0.6;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.00) 0%, #000 47.31%);
backdrop-filter: blur(250px);
}
.content {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
.logo {
width: 237.057px;
height: 153.492px;
flex-shrink: 0;
}
.title {
color: #FFF;
font-size: 30px;
font-weight: 600;
margin-top: 54px;
}
.line {
width: 213px;
height: 1px;
opacity: 0.1;
background: #FFF;
margin: 10px auto;
}
.text {
color: #FFF;
text-align: center;
font-size: 14px;
font-weight: 400;
line-height: 28px;
}
.btn {
width: 118px;
height: 50px;
text-align: center;
line-height: 50px;
border-radius: 10px;
background: #FFC400;
box-shadow: 0px -3px 8px 0px rgba(249, 255, 139, 0.60) inset, 0px 0px 8px 0px rgba(255, 196, 0, 0.50);
margin: 22px auto 0;
color: #482F28;
font-size: 18px;
cursor: pointer;
}
}
}
}
</style>
+154
View File
@@ -0,0 +1,154 @@
<template>
<div class="history">
<h1 class="title">
<span>观看历史</span>
<span class="edit-btn" v-if="!state.isEdit" @click="state.isEdit = true">编辑</span>
<div class="list-btn" v-else>
<span class="btn1" @click="state.isEdit = false">取消</span>
<span class="edit-btn btn2" @click="deleteHistory">删除</span>
</div>
</h1>
<div class="movie-list">
<div class="all-select" v-if="state.isEdit" @click="allSelectFn">
<img v-if="!state.allSelect" src="@/assets/images/pc/select1.png" alt="">
<img v-else src="@/assets/images/pc/select2.png" alt="">
全选
</div>
<div class="item-wrap" v-for="(item, index) in historyList" :key="index">
<movieCard :itemData="item" />
<div class="mask" v-if="state.isEdit" @click="item.check = !item.check">
<img v-if="!item.check" src="@/assets/images/pc/select1.png" alt="">
<img v-else src="@/assets/images/pc/select2.png" alt="">
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="mine-history">
import { reactive, ref } from 'vue'
import { HistoryVideoType } from '@/enums/app';
import { queryHistoryVideo, deletePost } from '@/utils';
const historyList = ref([])
const getHistoryList = async () => {
const list: any = await queryHistoryVideo(1, 999, HistoryVideoType.VIDEO)
list.forEach((item: any) => {
item.check = false
})
historyList.value = list
};
getHistoryList()
const state = reactive({
isEdit: false,
select: false,
allSelect: false
})
const deleteHistory = () => {
// 选中删除
const filterDeleteData = historyList.value.filter(item => item.check)
filterDeleteData.forEach((item => {
deletePost(item.id, HistoryVideoType.VIDEO)
}))
setTimeout(() => {
getHistoryList()
}, 300)
}
const allSelectFn = () => {
state.allSelect = !state.allSelect
historyList.value.forEach(item => {
item.check = state.allSelect
})
}
</script>
<style scoped lang="scss">
.history {
margin: 70px 0 0 250px;
width: 1120px;
.title {
font-size: 30px;
font-weight: 600;
line-height: 61px;
border-bottom: 1px solid #fff2;
display: flex;
justify-content: space-between;
align-items: center;
.edit-btn {
@include flexCenter;
width: 72px;
height: 30px;
border-radius: 4px;
background: linear-gradient(270deg, #FFC303 0%, #FFDD01 100%);
color: #FFF;
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
.list-btn {
@include flexCenter;
gap: 16px;
.btn1 {
@include flexCenter;
width: 72px;
height: 30px;
border-radius: 4px;
border: 1px solid #FFC303;
color: #FFC303;
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
}
}
.movie-list {
padding-top: 50px;
display: flex;
gap: 20px;
flex-wrap: wrap;
.all-select {
height: 52px;
display: flex;
align-items: center;
position: absolute;
top: 0;
right: 0;
img {
width: 24px;
height: 24px;
cursor: pointer;
margin-right: 8px;
}
}
.item-wrap {
width: 360px;
height: 320px;
border-radius: 8px;
position: relative;
.mask {
position: absolute;
top: 0;
left: 0;
z-index: 2;
width: 100%;
height: 100%;
background: rgba(26, 28, 34, 0.5);
cursor: pointer;
img {
width: 24px;
height: 24px;
position: absolute;
top: 10px;
right: 10px;
z-index: 11;
cursor: pointer;
}
}
}
}
}
</style>
+170
View File
@@ -0,0 +1,170 @@
<template>
<div class="likes">
<h1 class="title">
<span>我的收藏</span>
<span class="edit-btn" v-if="!state.isEdit" @click="state.isEdit = true">编辑</span>
<div class="list-btn" v-else>
<span class="btn1" @click="state.isEdit = false">取消</span>
<span class="edit-btn btn2" @click="cancelCollection">取消收藏</span>
</div>
</h1>
<div class="movie-list">
<div class="all-select" v-if="state.isEdit" @click="allSelectFn">
<img v-if="!state.allSelect" src="@/assets/images/pc/select1.png" alt="">
<img v-else src="@/assets/images/pc/select2.png" alt="">
全选
</div>
<div class="item-wrap" v-for="(item, index) in likesList" :key="index">
<movieCard :itemData="item" />
<div class="mask" v-if="state.isEdit" @click="item.check = !item.check">
<img v-if="!item.check" src="@/assets/images/pc/select1.png" alt="">
<img v-else src="@/assets/images/pc/select2.png" alt="">
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="mine-likes">
import { ref, computed, reactive } from 'vue'
import MineApi from '@/service/mine'
const userInfo = computed(() => {
const data = localStorage.getItem('userInfo')
if (data) {
return JSON.parse(data)
}
return data
})
const likesList = ref([])
const geLikeList = async () => {
const res: any = await MineApi.queryCollectionList({
pageNumber: 1,
pageSize: 10,
type: 'video',
uid: userInfo.value.uid
})
const list = res.list
list.forEach((item: any) => {
item.check = false
})
likesList.value = list
};
geLikeList()
const state = reactive({
isEdit: false,
select: false,
allSelect: false
})
const allSelectFn = () => {
state.allSelect = !state.allSelect
likesList.value.forEach(item => {
item.check = state.allSelect
})
}
const cancelCollection = async () => {
// 选中删除
const filterDeleteData = likesList.value.filter(item => item.check)
const objIds = filterDeleteData.map(item => item.id)
await MineApi.batchUnCollect({
isCollect: true,
objIds,
type: "video"
})
setTimeout(() => {
geLikeList()
}, 300)
}
</script>
<style scoped lang="scss">
.likes {
margin: 70px 0 0 250px;
width: 1120px;
.title {
font-size: 30px;
font-weight: 600;
line-height: 61px;
border-bottom: 1px solid #fff2;
display: flex;
justify-content: space-between;
align-items: center;
.edit-btn {
@include flexCenter;
width: 72px;
height: 30px;
border-radius: 4px;
background: linear-gradient(270deg, #FFC303 0%, #FFDD01 100%);
color: #FFF;
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
.list-btn {
@include flexCenter;
gap: 16px;
.btn1 {
@include flexCenter;
width: 72px;
height: 30px;
border-radius: 4px;
border: 1px solid #FFC303;
color: #FFC303;
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
}
}
.movie-list {
padding-top: 50px;
display: flex;
gap: 20px;
flex-wrap: wrap;
.all-select {
height: 52px;
display: flex;
align-items: center;
position: absolute;
top: 0;
right: 0;
img {
width: 24px;
height: 24px;
cursor: pointer;
margin-right: 8px;
}
}
.item-wrap {
width: 360px;
height: 320px;
border-radius: 8px;
position: relative;
.mask {
position: absolute;
top: 0;
left: 0;
z-index: 2;
width: 100%;
height: 100%;
background: rgba(26, 28, 34, 0.5);
cursor: pointer;
img {
width: 24px;
height: 24px;
position: absolute;
top: 10px;
right: 10px;
z-index: 11;
cursor: pointer;
}
}
}
}
}
</style>
+128
View File
@@ -0,0 +1,128 @@
<template>
<div class="mine-setting">
<h1 class="mine-setting__title">编辑个人资料</h1>
<div class="input-box">
<input type="text" placeholder="昵称" v-model="state.name">
</div>
<div class="input-box">
<input type="text" placeholder="密码" v-model="state.password">
</div>
<div class="tip">
<p>密码8-20位</p>
<p>至少为字母、数字或字符中任意两种的组合</p>
</div>
<div class="save-btn-list">
<div class="btn1" @click="confirmExchange">保存</div>
<!-- <div class="btn2">取消</div> -->
</div>
</div>
</template>
<script setup lang="ts" name="mine-setting">
import { showToast } from 'vant';
import { reactive, computed } from 'vue';
import { useUserStore } from '@/stores/user'
import UserApi from '@/service/user';
const store = useUserStore()
const userInfo = computed(() => {
const data = localStorage.getItem('userInfo')
if (data) {
return JSON.parse(data)
}
return data
})
const state = reactive({
name: userInfo.value.name,
password: ''
})
const confirmExchange = async () => {
if (!state.name) return showToast('请输入新昵称');
if (!validatePassword(state.password)) {
showToast('密码格式有误');
return;
}
try {
await UserApi.editPersonInfo({ name: state.name, passWord: state.password })
showToast('设置成功');
userInfo.value.name = state.name;
store.saveUserInfo(userInfo.value);
} catch (error) {
}
};
// 密码校验正则
function validatePassword(password: string) {
const regex = /^(?=.*[A-Za-z])(?=.*[\d@#$%^&*!])[A-Za-z\d@#$%^&*!]{8,20}$/;
return regex.test(password);
}
</script>
<style scoped lang="scss">
.mine-setting {
margin: 70px 0 0 250px;
width: 1100px;
&__title {
font-size: 30px;
font-weight: 600;
line-height: 61px;
border-bottom: 1px solid #fff2;
}
.input-box {
width: 504px;
margin-top: 30px;
height: 62px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
input {
all: unset;
font-size: 26px;
font-weight: 600;
width: 100%;
text-align: left;
color: #fff;
flex: 1;
line-height: 62px;
}
input::placeholder {
color: rgba(255, 255, 255, 0.40);
font-size: 26px;
font-weight: 600;
}
}
.tip {
padding-top: 16px;
font-size: 14px;
line-height: 20px;
color: #999;
}
.save-btn-list {
display: flex;
height: 42px;
margin-top: 30px;
.btn1 {
width: 88px;
height: 42px;
@include flexCenter;
border-radius: 4px;
background: linear-gradient(270deg, #FFC303 0%, #FFDD01 100%);
color: #000;
font-size: 18px;
font-weight: 600;
margin-right: 20px;
cursor: pointer;
}
.btn2 {
width: 88px;
height: 42px;
@include flexCenter;
border-radius: 4px;
color: #FFF;
font-size: 18px;
font-weight: 600;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.4);
cursor: pointer;
}
}
}
</style>
+111
View File
@@ -0,0 +1,111 @@
<template>
<div class="slide-menu">
<div class="slide-menu__top flex-center-center">
<DecryptImg class="avatar" v-if="userInfo.portrait" :src="userInfo.portrait" :key="userInfo.portrait" />
<img v-else class="avatar" src="@/assets/images/pc/avatar.png" />
<span>{{ userInfo.name }}</span>
</div>
<div class="slide-menu__content">
<div class="cell-item" :class="{ selected: menu.pathName === $route.name }" v-for="(menu, i) in menus" :key="i"
@click="cellAction(menu)">
<div class="cell flex-vcenter">
<svg-icon :name="menu.icon"></svg-icon>
<span>{{ menu.text }}</span>
</div>
<div v-if="menu.line" class="line"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { clearnIndexDb } from '@/utils/indexDB';
const userInfo = computed(() => {
const data = localStorage.getItem('userInfo')
if (data) {
return JSON.parse(data)
}
return data
})
const router = useRouter()
const menus = [
{ text: '观看历史', icon: 'mine_history', pathName: 'mine-history' },
{ text: '编辑个人资料', icon: 'mine_set', line: true, pathName: 'mine-setting' },
// { text: '我的账户', icon: 'mine_mine', pathName: 'mine-account' },
{ text: '我的收藏', icon: 'mine_like', line: true, pathName: 'mine-likes' },
{ text: '登出', icon: 'mine_logout', action: logout },
]
function logout() {
localStorage.clear()
sessionStorage.clear()
clearnIndexDb()
window.location.replace('/')
}
function cellAction(menu: { action?: () => void, pathName?: string }) {
if (menu.action) {
menu.action()
} else {
router.push({ name: menu.pathName })
}
}
</script>
<style scoped lang="scss">
.slide-menu {
min-height: 460px;
&__top {
height: 96px;
background: rgba(6,252,160) linear-gradient(270deg, rgba(255, 211, 12, 0.01) 0%, #FFD401 100%);
color: 18px;
color: black;
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
overflow: hidden;
margin-right: 13px;
}
}
&__content {
padding: 0 32px;
.cell-item {
color: #ececec;
cursor: pointer;
&.selected {
color: #FFCD3F;
}
.cell {
height: 56px;
svg {
width: 17px;
height: 17px;
}
span {
margin-left: 13px;
font-size: 16px;
}
}
.line {
margin: 8px 0;
background-color: #fff2;
height: 1px;
}
}
}
}
</style>
+28
View File
@@ -0,0 +1,28 @@
<template>
<div class="mine-page flex">
<div class="left-menu">
<SlideMenu></SlideMenu>
</div>
<div class="right-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import SlideMenu from './components/SlideMenu.vue'
</script>
<style lang="scss" scoped>
.mine-page {
.left-menu {
width: 320px;
}
.right-content {
flex: 1;
overflow: scroll;
}
}
</style>
+387
View File
@@ -0,0 +1,387 @@
<template>
<div class="movie-play">
<!-- <div class="banner-wrap">
<div class="banner-list">
<div class="item" v-for="(item, index) in getAdItem(7)" :key="index" @click="jumpAdv(item)">
<span> {{ item.title }}</span>
<span class="href"> {{ item.href }}</span>
</div>
</div>
</div> -->
<div class="play-box">
<div class="left-wrap">
<div class="play" :key="videoData.id">
<HorizontalVideo v-if="videoData.id" :videoInfo="videoData" ref="verticalVideoRef" />
</div>
<div class="like" @click="collect(videoData.vidStatus.hasCollected, videoData.id as string)">
<img v-if="videoData?.vidStatus?.hasCollected" src="@/assets/images/pc/like.png" alt="" />
<img v-else src="@/assets/images/pc/unlike.png" alt="" />
{{ videoData.collectCount }}
</div>
</div>
<div class="recommend-wrap">
<div class="title">相关推荐</div>
<div class="recommend-list">
<recommendCard :itemData="item" class="item" v-for="(item, index) in state.relationVideos" :key="index" />
</div>
</div>
</div>
<div class="movie-name">{{ videoData.title }}</div>
<div class="movie-tags">
<span v-for="(tag, index) in videoData.tags" :key="index" @click="toTagPage(tag)">{{ tag.name }}</span>
</div>
<!-- <div class="top-box">
<div class="top" v-if="route.query.topindex">top {{ route.query.topindex }}</div>
<i class="line" v-if="route.query.topindex"></i>
<span class="text">{{ watchCount(videoData.playCount) }}</span>
<i class="line"></i>
<span class="text">{{ fromNowTime(videoData.createdAt) }}</span>
</div> -->
<div class="adv-wrap" v-if="showAdv && getAdItem(7).length > 0">
<div class="advItem" v-for="(item, index) in getAdItem(7).slice(0, 2)" :key="index" @click="jumpAdv(item)">
<DecryptImg class="banner-img" :src="item.cover" />
</div>
<!-- <swiper loop :autoplay="{ delay: 5000, disableOnInteraction: false, pauseOnMouseEnter: true, }" :modules="[Autoplay]">
<swiper-slide class="adv" v-for="(item, index) in getAdItem(7)" :key="index" @click="jumpAdv(item)">
<DecryptImg class="banner-img" :src="item.cover" />
</swiper-slide>
</swiper>
<img @click="showAdv = false" class="close-adv" src="@/assets/images/pc/close2.png" /> -->
</div>
<div class="everyone-watch-wrap">
<div class="title">大家正在看💦</div>
<div class="movie-list">
<movieCard :itemData="item" v-for="(item, index) in state.everyoneWatchData" :key="index" />
</div>
</div>
<div class="flex-center-center" v-if="state.total > 0">
<Pagination :total="state.total" :pageSize="pageSize" @changePage="changePage" />
</div>
</div>
</template>
<script setup lang="ts">
import { jumpAdv } from '@/utils/index';
import { getAdItem } from '@/utils/getConfig';
import insertAdv from '@/utils/insertAdv';
import { watch, onMounted, ref, reactive, computed } from 'vue';
import { showToast } from 'vant';
import { fromNowTime, watchCount } from '@/utils/index';
import recommendCard from './recommend-card.vue';
import VideoApi from '@/service/video';
import MineApi from '@/service/mine';
import { eventBus } from '@/utils/eventBus';
import { Autoplay } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/swiper.min.css';
const userInfo = computed(() => {
const data = localStorage.getItem('userInfo');
if (data) {
return JSON.parse(data);
}
return data;
});
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
let pageSize = 14;
interface VideoData {
id?: string;
collectCount?: number;
title?: string;
tags?: Array<any>;
playCount?: number;
createdAt?: string;
}
const state = reactive({
everyoneWatchData: [],
relationVideos: [],
total: 0,
pageNumber: 1,
});
const videoData = ref<VideoData>({});
// 获取视频详情
const getDetails = async () => {
try {
const req = {
videoID: route.query.vid,
};
const res: any = await VideoApi.queryVideoInfo(req);
videoData.value = res;
getEveryoneWatch();
getRelation();
} catch (e) {
console.log(e);
}
};
const getEveryoneWatch = async () => {
try {
const req = {
id: route.query.vid,
pageSize,
pageNumber: state.pageNumber,
};
const res: any = await VideoApi.queryvidPlay(req);
const updatedArray = insertAdv({
insertNum: 6,
pageSize,
pageNumber: state.pageNumber,
originArray: res.videos,
advNum: 5,
});
state.everyoneWatchData = updatedArray;
state.total = res.total;
} catch (e) {
console.log(e);
}
};
const getRelation = async () => {
try {
const req = {
vid: route.query.vid,
pageSize: 15,
pageNumber: 1,
};
const res: any = await VideoApi.queryvidRelation(req);
state.relationVideos = res.videos;
} catch (e) {
console.log(e);
}
};
const toTagPage = (tag: any) => {
router.push({
path: '/tagPageList',
query: {
id: tag.id,
tagName: tag.name,
page: 1,
},
});
};
const collect = async (isCollect: boolean, id: string) => {
if (!userInfo.value.account) {
console.log('登陆');
eventBus.emit('SHOW_LOGIN');
return;
}
await MineApi.mineCollect({
isCollect: !isCollect,
objID: id,
type: 'video',
});
if (isCollect) {
videoData.value.vidStatus.hasCollected = false;
showToast('取消收藏成功');
} else {
videoData.value.vidStatus.hasCollected = true;
showToast('收藏成功');
}
};
const changePage = (page: number) => {
state.pageNumber = page;
router.push({
pāth: route.path,
query: {
...route.query,
page,
},
});
getEveryoneWatch();
};
onMounted(() => {
getDetails();
});
watch(
() => route.query.vid,
(value: any) => {
if (value) {
getDetails();
}
},
);
const showAdv = ref(true);
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.movie-play {
width: 1516px;
margin: 0 auto;
.banner-wrap {
width: 100%;
height: 72px;
overflow: hidden;
background: #23252a;
color: #a9a9ac;
font-size: 18px;
font-weight: 400;
cursor: pointer;
.banner-list {
display: flex;
align-items: center;
gap: 40px;
height: 72px;
width: max-content;
animation: slide 15s linear infinite;
}
}
.banner-wrap:hover {
.banner-list {
animation-play-state: paused;
}
}
.play-box {
width: 100%;
height: 692px;
background: #1a1c22;
display: flex;
margin-top: 74px;
.left-wrap {
flex: 1;
.play {
width: 100%;
height: 648px;
background: #333;
}
.like {
display: flex;
align-items: center;
height: 44px;
padding: 0px 20px 0px 15px;
color: #a9a9ac;
font-size: 14px;
font-weight: 400;
img {
width: 24px;
height: 24px;
margin-right: 4px;
cursor: pointer;
}
}
}
.recommend-wrap {
width: 366px;
height: 100%;
padding: 35px 20px;
.title {
color: #9b9b9b;
font-size: 16px;
font-weight: 400;
line-height: 22px;
padding-bottom: 25px;
}
.recommend-list {
width: 100%;
height: 590px;
overflow: auto;
.item {
margin-bottom: 22px;
}
}
}
}
.movie-name {
color: #fff;
font-size: 34px;
font-weight: 600;
line-height: 42px;
padding-top: 36px;
}
.movie-tags {
display: flex;
gap: 8px;
padding-top: 10px;
span {
padding: 4px 8px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.08);
color: #fff;
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.5);
font-size: 14px;
font-weight: 400;
cursor: pointer;
}
}
.top-box {
display: flex;
align-items: center;
gap: 8px;
padding-top: 18px;
.top {
height: 24px;
line-height: 24px;
border-radius: 4px;
background: linear-gradient(270deg, #ffc303 0%, #ffdd01 100%);
padding: 0 8px;
color: #502d1e;
font-size: 14px;
font-weight: 600;
}
.line {
width: 1px;
height: 10px;
background: #2d2f34;
}
.text {
color: #ececec;
font-size: 16px;
font-weight: 400;
}
}
.adv-wrap {
width: 1512px;
// height: 260px;
margin-top: 47px;
display: grid;
grid-template-columns: 1fr 1fr;
// grid-row-gap: 8px;
grid-column-gap: 24px;
// position: relative;
// .close-adv {
// width: 86px;
// height: 38px;
// position: absolute;
// top: 15px;
// right: 15px;
// z-index: 11111;
// }
.advItem {
width: 744px;
border-radius: 8px;
overflow: hidden;
height: 155px;
}
}
.everyone-watch-wrap {
.title {
color: #fff;
font-size: 36px;
font-weight: 600;
line-height: 36px;
padding-top: 55px;
padding-bottom: 30px;
}
.movie-list {
display: flex;
flex-wrap: wrap;
column-gap: 24px;
row-gap: 36px;
}
}
}
@keyframes slide {
0% {
transform: translateX(20%);
}
100% {
transform: translateX(-100%);
}
}
}
</style>
+97
View File
@@ -0,0 +1,97 @@
<template>
<div class="recommend-card" @click="jumpPage('/moviePlay?page=1&vid=' + itemData.id)">
<DecryptImg class="cover" :src="itemData.cover" />
<div class="described">
<p class="name ellipsis2">{{ itemData.title }}</p>
<p class="tags">
<span v-for="(item, index) in itemData.tags.slice(0,3)" :key="index">{{ item.name }}</span>
</p>
<!-- <div class="time-view">
<div class="time">
<img src="@/assets/images/pc/time.png" alt="">
{{ fromNowTime(itemData.createdAt) }}
</div>
<div class="view">
<img src="@/assets/images/pc/view.png" alt="">
{{ itemData.playCount }}
</div>
</div> -->
</div>
</div>
</template>
<script setup lang="ts">
import { fromNowTime, videoTime } from '@/utils/index';
import { useRouter } from 'vue-router';
const router = useRouter();
const props = withDefaults(
defineProps<{
itemData: any
}>(),
{
itemData: () => ({}),
},
);
const jumpPage = (path: string) => {
router.push(path)
}
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.recommend-card {
display: flex;
cursor: pointer;
.cover {
width: 128px;
height: 71px;
border-radius: 4px;
margin-right: 9px;
}
.described {
flex: 1;
.name {
color: #ECECEC;
font-size: 14px;
font-weight: 400;
line-height: 18px;
}
.tags {
display: flex;
gap: 4px;
span {
padding: 3px 6px;
border-radius: 2px;
border: 1px solid #212121;
background: #151515;
color: rgba(255, 255, 255, 0.40);
font-size: 9px;
font-weight: 500;
cursor: pointer;
}
}
}
.time-view {
display: flex;
align-items: center;
font-size: 12px;
color: rgba(255, 255, 255, 0.40);
margin-top: 6px;
img {
width: 14px;
height: 14px;
margin-right: 4px;
}
.time,
.view {
display: flex;
align-items: center;
margin-right: 16px;
}
}
}
}
</style>
+212
View File
@@ -0,0 +1,212 @@
<template>
<div class="content">
<div class="left-wrap">
<div class="thematic">
<div class="title">
{{ route.query.name }}
<div class="more">
<div class="all">{{ sortText }} <img class="arrow" src="@/assets/images/pc/down_arrow.png" alt=""></div>
<div class="meun-wrap">
<div class="meun-list">
<div @click="changeSort(item, index)" :class="{ active: activeIndex === index }"
v-for="(item, index) in sortList" :key="index">{{ item }}</div>
</div>
</div>
</div>
</div>
<div class="card-list">
<movieCard :itemData="item" v-for="(item, index) in videoList" :key="index"/>
</div>
</div>
<div class="page-wrap" v-if="total > 0">
<Pagination :total="total" :pageSize="pageSize" @changePage="changePage" />
</div>
</div>
<tag-list />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch, watchEffect } from 'vue'
import { useRouter, useRoute } from 'vue-router';
import HomeApi from '@/service/home';
import insertAdv from '@/utils/insertAdv'
const router = useRouter();
const route = useRoute()
const activeIndex = ref(0)
const sortText = ref('全部視頻')
const sortList = ref(['最新上架', '最多观看', '最多收藏'])
const videoList = ref([])
const pageSize = 21
let pageNumber = 1
const total = ref(0)
const changeSort = (item: string, index: number) => {
activeIndex.value = index
sortText.value = item
getDataList(index + 1)
}
watchEffect(() => {
pageNumber = Number(route.query.page || 1)
})
const getDataList = async (moduleSort: number) => {
const res: any = await HomeApi.getModuleList({
id: route.query.id,
data: {
pageNumber,
pageSize,
moduleSort
}
})
const updatedArray = insertAdv({
insertNum: 6,
pageSize,
pageNumber,
originArray: res.allVideoInfo,
advNum: 5
})
videoList.value = updatedArray
total.value = res.total
}
// function insertEveryFive(array: Array<any>, insertNum: number) {
// // insertNum 每隔insertNum个元素插入一次
// const result = [];
// let j = 0
// const currentPageAdvCout = Math.floor(pageSize / insertNum)
// // 当前页广告下标开始的值
// j = currentPageAdvCout * pageNumber
// for (let i = 0; i < array.length; i++) {
// result.push(array[i]);
// // 每页多少条广告
// if ((i + 1) % insertNum === 0) {
// if (!getAdItem(5)[j]) {
// j = 0
// }
// result.push(Object.assign({}, getAdItem(5)[j], {isAdv: true}));
// j++
// }
// }
// return result;
// }
const changePage = (page: number) => {
pageNumber = page
router.push({
pāth: route.path,
query: {
...route.query,
page
}
})
getDataList(activeIndex.value + 1)
}
onMounted(() => {
getDataList(1)
})
watch(() => route.query.id, (id) => {
if (id) {
getDataList(1)
}
})
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.content {
width: 1560px;
margin: auto;
padding-top: 65px;
display: flex;
.left-wrap {
width: 1128px;
margin: 0 42px;
box-sizing: content-box;
}
.thematic {
.title {
width: 100%;
height: 56px;
color: #FFF;
font-size: 48px;
font-weight: 600;
padding-bottom: 28px;
box-sizing: content-box;
@include flexBetween;
.more {
@include flexBetween;
display: inline-flex;
height: 48px;
padding: 0px 20px;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
cursor: pointer;
position: relative;
.all {
color: #A0A6B4;
font-size: 14px;
img {
width: 8px;
height: 8px;
margin-left: 4px;
}
}
.meun-wrap {
width: 100%;
height: 143px;
position: absolute;
top: 45px;
left: 0;
height: 0;
overflow: hidden;
transition: height 0.2s;
}
.meun-list {
margin-top: 10px;
width: 100%;
height: 105px;
z-index: 1;
color: #fff;
color: #A0A6B4;
font-size: 14px;
font-weight: 400;
line-height: 28px;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
text-align: center;
padding-top: 10px;
}
.active {
color: #FFF;
}
}
.more:hover {
.meun-wrap {
height: 143px;
transition: height 0.1s;
}
}
}
.card-list {
display: flex;
flex-wrap: wrap;
column-gap: 24px;
row-gap: 36px;
}
}
}
.page-wrap {
padding-top: 63px;
@include flexCenter;
}
}
</style>
+40
View File
@@ -0,0 +1,40 @@
<template>
<div class="privacy">
<div class="title">USC 2257</div>
<div class="text">
DHP.Video 不是网站 (DHP.Video) 上任何和所有内容的制作者(主要或次要)。关于根据 18 USC 2257 记录本网站上的任何和所有内容,请将您的请求直接发送给制作内容的网站。<br/>
DHP.Video 是一个视频共享网站,允许上传、共享和观看各种类型的成人内容。虽然 DHP.Video 尽最大努力验证合规性,但它可能不是 100% 准确。<br/>
DHP.Video 遵守以下程序以确保合规:<br/>
要求所有用户必须年满 18 岁才能观看和上传视频。<br/>
上传时,用户必须核实内容;保证自己已年满 18 岁;证明自己保留了内容中模特的记录并且这些模特已年满 18 岁。<br/>
如需进一步帮助和/或信息以查找内容的原始站点,请通过支持表格联系 DHP.Video 合规部门。<br/>
DHP.Video 允许将内容标记为不当内容。如果任何内容被标记为非法、违法、骚扰、有害、冒犯或其他各种原因,DHP.Video 应立即将其从网站上删除。
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.privacy {
width: 1150px;
margin: 100px auto;
.title {
color: #FFF;
font-size: 30px;
font-weight: 600;
line-height: 41px;
padding-bottom: 41px;
}
.text {
color: #FFF;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px;
}
}
}
</style>
+53
View File
@@ -0,0 +1,53 @@
<template>
<div class="publishingAddress">
<div class="title">备用地址</div>
<div class="wrap">
<div class="list" v-for="(item, index) in urlList" :key="index">
<span class="name">{{ item.desc }} :</span>
<span class="link" @click="openPage(item.url)">{{ item.url }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, computed } from 'vue';
import { useConfigStore } from '@/stores/config';
const configStore = useConfigStore();
const state = reactive({});
const urlList: any = computed(() => {
return configStore.sourceInfo.Download;
});
const openPage = (url: string) => {
window.open(url);
};
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.publishingAddress {
color: #fff;
width: 1104px;
margin: 112px auto 0;
padding-bottom: 70px;
.title {
color: #FFF;
font-size: 20px;
font-weight: 600;
line-height: 41px;
padding-bottom: 60px;
}
.wrap {
flex-wrap: wrap;
font-size: 18px;
.list {
margin-bottom: 30px;
}
.link {
color: #FFCD3F;
cursor: pointer;
text-decoration: underline;
}
}
}
}
</style>
+396
View File
@@ -0,0 +1,396 @@
<template>
<div class="search-page">
<div class="search-box">
<div class="movie-tag-list">
<div class="all">{{ state.sortText }} <img class="arrow" src="@/assets/images/pc/down_arrow.png" alt=""></div>
<div class="meun-wrap">
<div class="meun-list">
<div @click="changeSort(item, index)" :class="{ active: state.activeIndex === index }"
v-for="(item, index) in state.sortList" :key="index">{{ item }}</div>
</div>
</div>
</div>
<div class="search" @click.stop>
<input type="text" @focus.stop="state.showHistoryList = true" @keydown.enter="search" v-model="state.searchValue" placeholder="輸入番號,女優或視頻名">
<div class="history-list" v-show="state.showHistoryList">
<div class="history-box">
<div class="title">
<span>搜索历史</span>
<img class="arrow" @click="clearnAll" src="@/assets/images/pc/clearn.png" alt="">
</div>
<div class="history">
<div class="item" v-for="(item, index) in state.historyData" :key="index" @click="selectTag(item)">
{{ item }}
<img @click.stop="clearnTag(index)" src="@/assets/images/pc/close1.png" alt="">
</div>
</div>
</div>
<div class="hot-search-tag-box">
<div class="title">
热门搜索
</div>
<div class="hot-item" v-for="(item, index) in state.hotTagList.slice(0,6)" :key="index" @click="selectTag(item.name)">
<span :class="['index', index < 3 ? 'top3' : '']">{{ index + 1 }}</span>
<span>{{ item.name }}</span>
</div>
</div>
</div>
</div>
<div class="search-btn" @click="search">搜索一下</div>
</div>
<div class="tag-lst">
<span v-for="(item, index) in state.hotTagList.slice(0,10)" :key="index" @click="selectTag(item.name)">{{ item.name }}</span>
</div>
<!-- 搜索结果 -->
<div class="results-wrap">
<div class="title">搜索结果</div>
<div class="result">全部视频 <span>({{ state.total }})</span></div>
<div class="card-list">
<movieCard :itemData="item" v-for="(item, index) in state.searchResults" :key="index" />
</div>
</div>
<div class="flex-center-center" v-if="state.total > 0">
<Pagination :pageSize="pageSize" :total="state.total" @changePage="changePage" />
</div>
</div>
</template>
<script setup lang="ts">
import insertAdv from '@/utils/insertAdv'
import { onMounted, reactive, ref, watch } from 'vue'
import CommunityApi from '@/service/community';
import { useRouter, useRoute } from 'vue-router';
const route = useRoute()
const router = useRouter()
const pageSize = 24
const state = reactive({
searchValue: '',
activeIndex: 0,
sortText: '全部視頻',
sortList: ['最新上架', '最多观看', '最多收藏'],
hotTagList: [],
pageNumber: 1,
total: 0,
showHistoryList: false,
historyData: [],
searchResults: [],
history
})
// 获热搜列表
const getHotTag = async () => {
const res: any = await CommunityApi.getHotRecommend({
pageNumber: 1,
pageSize: 10
})
state.hotTagList = res.list
}
getHotTag()
const changeSort = (item: string, index: number) => {
state.activeIndex = index
state.sortText = item
search()
}
// 搜索
const search = async () => {
AddSearchHistory()
const res: any = await CommunityApi.searchPost({
SortV: state.activeIndex,
keyWords: [state.searchValue],
pageNumber: state.pageNumber,
pageSize,
realm: 'video'
})
const updatedArray = insertAdv({
insertNum: 6,
pageSize,
pageNumber: state.pageNumber,
originArray: res.list || [],
advNum: 5
})
state.searchResults = updatedArray
state.total = res.total || 0
getSearchHistoryData()
}
// 分页
const changePage = (page: number) => {
state.pageNumber = page
router.push({
pāth: route.path,
query: {
...route.query,
page
}
})
search()
}
onMounted(() => {
const value = route.query.id
if (value) {
state.pageNumber = Number(route.query.page || 1)
state.searchValue = value as string
search()
}
})
watch(() => route.query.id, (value) => {
if (value) {
state.pageNumber = Number(route.query.page || 1)
state.searchValue = value as string
search()
}
})
const selectTag = (item: string) => {
state.searchValue = item
state.showHistoryList = false
search()
}
const clearnTag = (index: number) => {
state.historyData.splice(index, 1)
localStorage.setItem('SEARCH_HISTORY', state.historyData.join(','))
}
const clearnAll = () => {
state.historyData = []
localStorage.removeItem('SEARCH_HISTORY')
}
// 添加历史记录
const AddSearchHistory = () => {
// 搜索记录加数据
if (state.historyData.length > 0) {
// 去重
const arr = [...new Set(state.historyData)]
const narr = arr.slice(0,3)
narr.unshift(state.searchValue)
localStorage.setItem('SEARCH_HISTORY', narr.join(','))
} else {
localStorage.setItem('SEARCH_HISTORY', state.searchValue)
}
}
watch(() => state.showHistoryList, (value) => {
if (value) {
getSearchHistoryData()
}
})
// 获取历史记录
const getSearchHistoryData = () => {
const data = localStorage.getItem('SEARCH_HISTORY')
if (data) {
const arr = data.split(',')
state.historyData = [...new Set(arr)];
}
}
document.addEventListener('click', () => {
state.showHistoryList = false
});
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.search-page {
width: 1516px;
color: #fff;
padding-top: 120px;
margin: auto;
.search-box {
@include flexCenter;
margin: auto;
}
.movie-tag-list {
height: 60px;
padding: 0px 25px;
border-radius: 10px;
border: 1px solid #212121;
background: #151515;
@include flexCenter;
cursor: pointer;
.all {
color: #A0A6B4;
font-size: 18px;
img {
width: 10px;
height: 10px;
margin-left: 5px;
}
}
.meun-wrap {
width: 100%;
height: 143px;
position: absolute;
top: 45px;
left: 0;
height: 0;
overflow: hidden;
transition: height 0.2s;
}
.meun-list {
margin-top: 10px;
width: 100%;
height: 105px;
z-index: 1;
color: #fff;
color: #A0A6B4;
font-size: 14px;
font-weight: 400;
line-height: 28px;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
text-align: center;
padding-top: 10px;
}
.active {
color: #FFF;
}
}
.movie-tag-list:hover {
.meun-wrap {
height: 143px;
transition: height 0.1s;
}
}
.search {
max-width: 700px;
display: flex;
height: 60px;
padding: 16px 21px;
flex-direction: column;
align-items: flex-start;
flex: 1 0 0;
border-radius: 10px;
border: 1px solid #FFC400;
box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
margin: 0 20px;
position: relative;
input {
all: unset;
width: 100%;
font-size: 20px;
color: #FFF;
}
.history-list {
width: 100%;
height: 486px;
padding: 17px;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: #1A1C22;
position: absolute;
top: 70px;
left: 0;
z-index: 1;
.title {
@include flexBetween;
color: #999;
font-size: 12px;
font-weight: 400;
height: 32px;
img {
width: 16px;
height: 16px;
cursor: pointer;
}
}
.history {
.item {
@include flexBetween;
color: #BCBDBE;
font-size: 14px;
font-weight: 400;
height: 16px;
margin-top: 24px;
img {
width: 16px;
height: 16px;
cursor: pointer;
}
}
}
.hot-search-tag-box {
color: #BCBDBE;
font-size: 14px;
padding-top: 30px;
.hot-item {
margin-top: 24px;
.index {
color: #CCC;
margin-right: 8px;
}
.top3 {
color: #FFCD3F;
font-weight: 600;
}
}
}
}
}
.search-btn {
height: 60px;
width: 112px;
@include flexCenter;
color: #482F28;
font-weight: 600;
font-size: 18px;
border-radius: 10px;
background: #FFC400;
box-shadow: 0px -3px 8px 0px rgba(249, 255, 139, 0.60) inset, 0px 0px 8px 0px rgba(255, 196, 0, 0.50);
cursor: pointer;
}
.tag-lst {
@include flexCenter;
padding-top: 20px;
column-gap: 20px;
span {
font-size: 20px;
color: #E5E7EB;
font-weight: 400;
cursor: pointer;
}
span:hover {
text-decoration: underline;
}
}
.results-wrap {
width: 1516px;
margin: 70px auto;
.title {
color: #FFF;
font-size: 36px;
font-weight: 600;
line-height: 48px;
}
.result {
color: #FFF;
font-size: 16px;
line-height: 20px;
margin-top: 12px;
span {
color: #A0A6B4;
font-size: 14px;
}
}
.card-list {
padding-top: 34px;
display: flex;
flex-wrap: wrap;
column-gap: 24px;
row-gap: 36px;
}
}
}
}
</style>
+190
View File
@@ -0,0 +1,190 @@
<template>
<div class="content">
<div class="left-wrap">
<div class="thematic">
<div class="title">
{{ route.query.tagName }}
<div class="more">
<div class="all">{{ sortText }} <img class="arrow" src="@/assets/images/pc/down_arrow.png" alt=""></div>
<div class="meun-wrap">
<div class="meun-list">
<div @click="changeSort(item, index)" :class="{ active: activeIndex === index }"
v-for="(item, index) in sortList" :key="index">{{ item }}</div>
</div>
</div>
</div>
</div>
<div class="card-list">
<movieCard :itemData="item" v-for="(item, index) in videoList" :key="index" />
</div>
</div>
<div class="page-wrap" v-if="total > 0">
<Pagination :total="total" :pageSize="pageSize" @changePage="changePage" />
</div>
</div>
<tag-list />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch, watchEffect } from 'vue'
import { useRouter, useRoute } from 'vue-router';
import VideoApi from '@/service/video';
import insertAdv from '@/utils/insertAdv'
const router = useRouter();
const route = useRoute()
const pageSize = 10
const activeIndex = ref(0)
const sortText = ref('全部視頻')
const sortList = ref(['最新上架', '最多观看', '最多收藏'])
const videoList = ref([])
let pageNumber = 1
const total = ref(0)
const changeSort = (item: string, index: number) => {
activeIndex.value = index
sortText.value = item
getDataList()
}
watchEffect(() => {
pageNumber = Number(route.query.page || 1)
})
const getDataList = async () => {
const res: any = await VideoApi.searchTagPost({
pageNumber,
pageSize,
tagID: route.query.id,
sortType: activeIndex.value + 1,
})
const updatedArray = insertAdv({
insertNum: 6,
pageSize,
pageNumber,
originArray: res.list,
advNum: 5
})
videoList.value = updatedArray
total.value = res.total
}
const changePage = (page: number) => {
pageNumber = page
router.push({
pāth: route.path,
query: {
...route.query,
page
}
})
getDataList()
}
onMounted(() => {
getDataList()
})
watch(() => route.query.id, (id) => {
if (id) {
getDataList()
}
})
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.content {
width: 1560px;
margin: auto;
padding-top: 65px;
display: flex;
.left-wrap {
width: 1128px;
margin: 0 42px;
box-sizing: content-box;
}
.thematic {
.title {
width: 100%;
height: 56px;
color: #FFF;
font-size: 48px;
font-weight: 600;
padding-bottom: 28px;
box-sizing: content-box;
@include flexBetween;
.more {
@include flexBetween;
display: inline-flex;
height: 48px;
padding: 0px 20px;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
cursor: pointer;
position: relative;
.all {
color: #A0A6B4;
font-size: 14px;
img {
width: 8px;
height: 8px;
margin-left: 4px;
}
}
.meun-wrap {
width: 100%;
height: 143px;
position: absolute;
top: 45px;
left: 0;
height: 0;
overflow: hidden;
transition: height 0.2s;
}
.meun-list {
margin-top: 10px;
width: 100%;
height: 105px;
z-index: 1;
color: #fff;
color: #A0A6B4;
font-size: 14px;
font-weight: 400;
line-height: 28px;
border-radius: 8px;
border: 1px solid #212121;
background: #151515;
text-align: center;
padding-top: 10px;
}
.active {
color: #FFF;
}
}
.more:hover {
.meun-wrap {
height: 143px;
transition: height 0.1s;
}
}
}
.card-list {
display: flex;
flex-wrap: wrap;
column-gap: 24px;
row-gap: 36px;
}
}
}
.page-wrap {
padding-top: 63px;
@include flexCenter;
}
}
</style>
+351
View File
@@ -0,0 +1,351 @@
<template>
<div class="welfare-page">
<div class="box1">
<div class="img-list">
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(0, 1)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(1, 3)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(3, 6)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(6, 9)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(9, 13)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(13, 16)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(16, 20)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(20, 23)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(23, 26)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(26, 28)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="item-list">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(28, 29)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
</div>
<div class="img-list-one">
<DecryptImg
v-for="(item, index) in state.smallSuure?.slice(29, 30)"
class="cover"
@click="jumpAdv(item, true)"
:src="item.icon"
:key="item.icon + index"
/>
</div>
<div class="title">
<p>在線視頻</p>
<p>来自一站式你所熟悉的蛋黄派</p>
</div>
</div>
<div class="game-wrap">
<div class="title">
<p v-for="(item, index) in state.bgImage1" :key="index">{{ item }}</p>
</div>
<swiper
:modules="[Autoplay, Pagination, FreeMode]"
free-mode
:loop="true"
:slides-per-view="6.2"
:space-between="16"
>
<swiper-slide v-for="(item, index) in state.verticalSlide" :key="index">
<div class="card-item" @click="jumpAdv(item, true)">
<DecryptImg class="cover" :src="item.icon" :key="item.icon + index" />
</div>
</swiper-slide>
</swiper>
</div>
<div class="game-wrap1">
<div class="title">
<p v-for="(item, index) in state.bgImage2" :key="index">{{ item }}</p>
</div>
<swiper :modules="[Autoplay, Pagination, FreeMode]" free-mode loop :slides-per-view="6.2" :space-between="16">
<swiper-slide v-for="(item, index) in state.verticalTwoSlide" :key="index">
<div class="card-item">
<DecryptImg class="cover" :src="item.icon" :key="item.icon + index" @click="jumpAdv(item, true)" />
</div>
</swiper-slide>
</swiper>
<swiper
style="padding: 60px 0"
:modules="[Autoplay, Pagination, FreeMode]"
loop
free-mode
:slides-per-view="6.2"
:space-between="16"
>
<swiper-slide v-for="(item, index) in state.smallSqureSlide" :key="index">
<div class="card-item">
<DecryptImg class="cover" :src="item.icon" :key="item.icon + index" @click="jumpAdv(item, true)" />
</div>
</swiper-slide>
</swiper>
</div>
</div>
</template>
<script setup lang="ts">
import { jumpAdv } from '@/utils/index';
import { Autoplay, Pagination, FreeMode } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import request from '@/utils/request';
import 'swiper/swiper.min.css';
import { reactive } from 'vue';
const state = reactive({
smallSuure: [],
verticalSlide: [],
bgImage1: [],
verticalTwoSlide: [],
bgImage2: [],
smallSqureSlide: [],
});
request.get('recreation/list_v2').then((res: any) => {
const resdata = res.list
console.log(resdata);
const data = resdata.find((item: any) => item.desc === '福利模块') || {};
const dataList = data.comboList || [];
console.log(dataList);
state.smallSuure = dataList.find((item: any) => item.displayType === 'smallSuure').list;
state.verticalSlide = dataList.find((item: any) => item.displayType === 'verticalSlide').list;
// #换行
const bgImage1List = dataList.find((item: any) => item.displayType === 'bgImage1').list
state.bgImage1 = bgImage1List && bgImage1List[0]?.desc?.split('#') || [];
state.verticalTwoSlide = dataList.find((item: any) => item.displayType === 'verticalTwoSlide').list;
// #换行
const bgImage2 = dataList.find((item: any) => item.displayType === 'bgImage2').list
state.bgImage2 = bgImage2 && bgImage2[0]?.desc?.split('#') || [];
state.smallSqureSlide = dataList.find((item: any) => item.displayType === 'smallSqureSlide').list;
});
</script>
<style lang="scss" scoped>
@media (min-width: 768px) {
.box1 {
width: 100%;
height: 1100px;
background-color: #fff;
.title {
padding-top: 70px;
color: #000;
text-align: center;
font-family: Roboto;
font-size: 70px;
font-weight: 900;
line-height: 76px;
margin-bottom: 157px;
p {
padding-top: 32px;
}
}
}
.img-list {
width: 100%;
height: 600px;
padding: 0 137px;
display: flex;
gap: 26px;
overflow: hidden;
.item-list {
width: 126px;
}
.cover {
width: 126px;
height: 126px;
border-radius: 17px;
overflow: hidden;
margin: 18px 0;
}
}
.img-list-one {
margin: auto;
width: 173px;
height: 126px;
animation: slide1 10s linear infinite;
.cover {
width: 173px;
height: 173px;
border-radius: 24px;
overflow: hidden;
margin: 18px 0;
}
}
.item-list:nth-child(2n) {
animation: slide 10s linear infinite;
}
.item-list:nth-child(2n + 1) {
animation: slide1 10s linear infinite;
}
.box1:hover {
.item-list,
.img-list-one {
animation-play-state: paused;
}
}
.game-wrap {
width: 100%;
height: 1028px;
background: url('@/assets/images/pc/game_bg.png') no-repeat #000;
background-position: bottom center;
background-size: 100% 100%;
.title {
padding-top: 170px;
color: #fff;
text-align: center;
font-family: Roboto;
font-size: 76px;
font-weight: 900;
line-height: 76px;
margin-bottom: 157px;
p {
padding-top: 32px;
}
}
.card-item {
width: 15vw;
// height: 532px;
border-radius: 12px;
.cover {
width: 15vw;
height: 360px;
border-radius: 12px;
overflow: hidden;
}
}
}
.game-wrap1 {
width: 100%;
// height: 1144px;
background: #fff;
.title {
padding-top: 170px;
color: #000;
text-align: center;
font-family: Roboto;
font-size: 76px;
font-weight: 900;
line-height: 76px;
margin-bottom: 157px;
p {
padding-top: 32px;
}
}
.card-item {
width: 15vw;
height: 360px;
border-radius: 18px;
.cover {
width: 15vw;
// height: 532px;
border-radius: 18px;
overflow: hidden;
}
}
}
/* 关键帧动画 */
@keyframes slide {
0% {
transform: translateY(-63px);
}
50% {
transform: translateY(0);
}
100% {
transform: translateY(-63px);
}
}
/* 关键帧动画 */
@keyframes slide1 {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-63px);
}
100% {
transform: translateY(0);
}
}
}
</style>
@@ -0,0 +1,113 @@
<template>
<div class="img-decypt">
<van-image :src="decyImg" class="vanImage" fit="cover" :round="round" @load="emit('load')" @error="emit('error')">
<template v-slot:loading>
<div class="loading" v-if="!isSplash" />
<div class="splash-loading-img" v-else></div>
</template>
<template v-slot:error>
<div class="loading" v-if="!isSplash" />
<div class="splash-error-img" v-else></div>
</template>
<slot></slot>
</van-image>
</div>
</template>
<script setup lang="ts">
import { Image as VanImage } from 'vant';
import { ref } from 'vue';
import AppApi from '@/service/app';
const props = defineProps({
//图片地址
src: {
type: String,
required: true,
default() {
return '';
},
},
baseUrl: {
type: String,
required: true,
default() {
return 'https://dkatmweejckwm.cloudfront.net';
},
},
isSplash: {
//是否来自启动页
type: Boolean,
required: false,
default() {
return false;
},
},
round: {
//图片是否显示为圆形
type: Boolean,
required: false,
default() {
return false;
},
},
});
let decyImg = ref('');
const emit = defineEmits(['load', 'error']);
const init = () => {
if (!props.src) {
return;
}
try {
const imgSrc = props.baseUrl + '/' + props.src
AppApi.imgDecypt(imgSrc)
.then((url: any) => {
decyImg.value = url;
})
.catch(() => {
emit('error');
});
} catch (error) {
emit('error');
}
};
init();
</script>
<style lang="scss" scoped>
:deep(.van-image__loading) {
// background-color: #2d2d2d !important;
overflow: hidden;
width: 100%;
height: 100%;
}
.splash-loading-img,
.splash-error-img {
width: 100%;
height: 100%;
background: url('../../assets/images/png/splash.webp') no-repeat;
background-size: cover;
position: relative;
}
.img-decypt {
width: 100%;
height: 100%;
.vanImage {
width: 100%;
height: 100%;
// background-color: #2d2d2d !important;
.loading {
width: 100%;
}
:deep(.van-image__error) {
// background-color: #2d2d2d !important;
overflow: hidden;
width: 100%;
}
}
}
</style>
+15
View File
@@ -0,0 +1,15 @@
import { getDevType } from '@/utils';
export default function jumpAdvClick(item: any) {
window.open(item.url);
fetch('https://dhpht.cestalt.com/api/app/recreation/click',{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: item.id,
sysType: getDevType(),
type: item.type
}),
}).then((response) => response.json())
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

+359
View File
@@ -0,0 +1,359 @@
<template>
<div id="polymerization">
<div class="box0">
<div class="contactOfficial" @click="tolinkUrl">联系官方</div>
<div class="adv-box0">
<div class="slid-list slid-list1" :style="{ '--slid-size': 5 }">
<MyDecryptImg
class="cover"
:baseUrl="state.baseImgUrl"
:src="item.icon"
v-for="(item, index) in state.smallSqureSlide"
:key="index"
@click="jumpAdvClick(item)"
/>
</div>
<div class="slid-list slid-list2">
<MyDecryptImg
class="cover"
:baseUrl="state.baseImgUrl"
:src="item.icon"
v-for="(item, index) in state.smallSqureSlide2"
:key="index"
@click="jumpAdvClick(item)"
/>
</div>
<div class="slid-list slid-list3">
<MyDecryptImg
class="cover"
:baseUrl="state.baseImgUrl"
:src="item.icon"
v-for="(item, index) in state.smallSqureSlide3"
:key="index"
@click="jumpAdvClick(item)"
/>
</div>
</div>
<div class="text-box"></div>
<div class="swiper-list">
<swiper :modules="[Pagination, FreeMode]" loop free-mode :slides-per-view="1.5" :space-between="15">
<swiper-slide v-for="(item, index) in state.verticalSlide" :key="index">
<div class="card-item" @click="jumpAdvClick(item)">
<MyDecryptImg class="cover" :baseUrl="state.baseImgUrl" :src="item.icon" />
</div>
</swiper-slide>
</swiper>
</div>
</div>
<div class="box1">
<div class="title">蛋黄派推荐🔥🔥🔥</div>
<div class="download-box" v-for="(item, index) in state.hengOneUP" :key="index" @click="jumpAdvClick(item)">
<MyDecryptImg class="cover" :baseUrl="state.baseImgUrl" :src="item.icon" />
<div class="introduced">
<div class="name">{{ item.name }}</div>
<div class="desc">{{ item.desc }}</div>
</div>
<div class="btn">下载</div>
<div class="line"></div>
</div>
</div>
<div class="box2">
<div class="title">精品推荐😍😍😍</div>
<div class="box2-list">
<div class="box2-item" v-for="(item, index) in state.hengTwoUp" :key="index" @click="jumpAdvClick(item)">
<MyDecryptImg class="cover" :baseUrl="state.baseImgUrl" :src="item.icon" />
<div class="introduced">
<div class="name">{{ item.name }}</div>
<div class="desc">{{ item.desc }}</div>
</div>
</div>
</div>
</div>
<div class="box1">
<div class="title">精品老牌💦💦💦</div>
<div class="download-box" v-for="(item, index) in state.hengOneDown" :key="index" @click="jumpAdvClick(item)">
<MyDecryptImg class="cover" :baseUrl="state.baseImgUrl" :src="item.icon" />
<div class="introduced">
<div class="name">{{ item.name }}</div>
<div class="desc">{{ item.desc }}</div>
</div>
<div class="btn">下载</div>
<div class="line"></div>
</div>
</div>
<div class="box4">
<div class="title">最新上线🥳🥳🥳</div>
<div class="box4-list">
<div class="box4-item" v-for="(item, index) in state.hengFour" :key="index" @click="jumpAdvClick(item)">
<MyDecryptImg class="cover" :src="item.icon" :baseUrl="state.baseImgUrl" :key="item.icon + index" />
<div class="name">{{ item.name }}</div>
<div class="btn">下载</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import MyDecryptImg from './MyDecryptImg/index.vue'
import { Pagination, FreeMode } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/swiper.min.css';
import { reactive } from 'vue';
import jumpAdvClick from './advClick'
const state = reactive({
hengOneUP: [],
hengTwoUp: [],
hengTwoDown: [],
hengOneDown: [],
hengFour: [],
smallSqureSlide: [],
smallSqureSlide2: [],
smallSqureSlide3: [],
verticalSlide: [],
baseImgUrl: ''
});
fetch('https://dhpht.cestalt.com/api/app/recreation/list_v2').then((response) => response.json()).then((res: any) => {
const resdata = res.data.list
const obj = resdata.find((item: any) => item.name === '聚合下载') || {};
const data = obj.comboList || [];
state.hengOneUP = data.find((item: any) => item.displayType === 'hengOneUP').list;
state.hengTwoUp = data.find((item: any) => item.displayType === 'hengTwoUp').list;
state.hengTwoDown = data.find((item: any) => item.displayType === 'hengTwoDown').list;
state.hengOneDown = data.find((item: any) => item.displayType === 'hengOneDown').list;
state.hengFour = data.find((item: any) => item.displayType === 'hengFour').list;
state.verticalSlide = data.find((item: any) => item.displayType === 'verticalSlide').list;
state.smallSqureSlide = data.find((item: any) => item.displayType === 'smallSqureSlide').list;
state.smallSqureSlide2 = data.find((item: any) => item.displayType === 'smallSqureSlide2').list;
state.smallSqureSlide3 = data.find((item: any) => item.displayType === 'smallSqureSlide3').list;
state.baseImgUrl = res.data.imgUrl?.domain[0]?.url
});
const tolinkUrl = () => {
window.open('https://t.me/gfqunzhu')
};
</script>
<style scoped lang="scss">
#polymerization {
width: 100%;
overflow: auto;
height: 100vh;
background-color: #fff;
color: #000;
.title {
font-size: 18px;
font-weight: 500;
line-height: 24px;
padding-bottom: 18px;
padding-top: 30px;
}
.box0 {
width: 100%;
height: 980px;
background: url('./bg.jpg') no-repeat center;
background-size: 100%;
position: relative;
.contactOfficial {
position: absolute;
top: 40px;
right: 41px;
color: #000;
font-size: 12px;
width: 82px;
height: 32px;
border-radius: 4px;
border: 2px solid #000;
background: #fff;
z-index: 10;
@include flexCenter;
}
.adv-box0 {
padding-top: 188px;
width: 100%;
overflow: hidden;
}
.slid-list {
display: flex;
padding-bottom: 9px;
padding-left: 20px;
width: max-content;
.cover {
width: 72px;
height: 72px;
border-radius: 16px;
overflow: hidden;
margin-right: 9px;
}
}
.slid-list1 {
animation: slide 50s linear infinite;
}
.slid-list2 {
animation: slide 50s linear alternate-reverse infinite;
animation-delay: -10s;
}
.slid-list3 {
animation: slide 50s linear infinite;
}
.text-box {
width: 299px;
height: 105px;
background: url('./text.png') no-repeat center;
background-size: 100%;
margin: 30px auto;
}
.swiper-list {
.card-item {
width: 248px;
height: 306px;
border-radius: 10px;
overflow: hidden;
}
}
}
.box1 {
padding: 0 20px;
}
.download-box {
display: flex;
align-items: center;
color: #000;
position: relative;
padding-bottom: 10px;
margin-bottom: 10px;
.cover {
width: 62px;
height: 62px;
border-radius: 12px;
overflow: hidden;
margin-right: 12px;
}
.introduced {
font-size: 16px;
font-weight: 500;
line-height: 20px;
flex: 1;
.desc {
color: #555664;
font-size: 12px;
font-weight: 400;
line-height: 20px;
margin-top: 6px;
}
}
.btn {
width: 72px;
height: 36px;
line-height: 36px;
text-align: center;
border-radius: 999px;
background: linear-gradient(270deg, rgba(255, 195, 3, 0.2) 0%, rgba(255, 221, 1, 0.2) 100%);
color: #ffb420;
font-size: 14px;
font-weight: 500;
}
.line {
width: 261px;
height: 1px;
background: rgba(198, 198, 200, 0.5);
position: absolute;
bottom: 0;
right: 0;
z-index: 1;
}
}
.box2 {
padding: 0 18px;
.box2-list {
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 11px 16px;
}
.box2-item {
display: flex;
align-items: center;
width: 160px;
height: 64px;
padding: 8px;
box-sizing: border-box;
border-radius: 6px;
border: 1px solid rgba(198, 198, 200, 0.5);
.cover {
width: 48px;
height: 48px;
border-radius: 9px;
overflow: hidden;
margin-right: 4px;
}
.introduced {
.name {
color: #000;
font-size: 12px;
}
.desc {
color: #999aa2;
font-size: 11px;
}
}
}
}
.box4 {
padding: 0 20px;
padding-bottom: 50px;
.box4-list {
display: flex;
flex-wrap: wrap;
gap: 26px 9px;
.box4-item {
width: 76px;
text-align: center;
.cover {
width: 76px;
height: 76px;
border-radius: 16px;
overflow: hidden;
}
.name {
color: #000;
font-size: 14px;
font-weight: 500;
line-height: 20px;
margin-top: 7px;
margin-bottom: 12px;
}
.btn {
width: 72px;
height: 36px;
line-height: 36px;
text-align: center;
border-radius: 999px;
background: linear-gradient(270deg, rgba(255, 195, 3, 0.2) 0%, rgba(255, 221, 1, 0.2) 100%);
color: #ffb420;
font-size: 14px;
font-weight: 500;
}
}
}
}
}
/* 关键帧动画 */
@keyframes slide {
0% {
transform: translateX(0);
}
50% {
transform: translateX(-50%);
}
100% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB