38 changed files with 66 additions and 2249 deletions
@ -1,75 +0,0 @@ |
|||||
<template> |
|
||||
<div> |
|
||||
<ClientOnly> |
|
||||
<el-empty v-if="movieList.length === 0" description="您还未收藏视频噢~" /> |
|
||||
<div v-else class="video-list"> |
|
||||
<el-row :gutter="20"> |
|
||||
<el-col v-for="item in movieList" :key="item.movie.id" :sm="4" :xs="8"> |
|
||||
<div class="video-list__block"> |
|
||||
<nuxt-link :to="`/column/${item.movie.columnValue}/movie/${item.movie.id}`"> |
|
||||
<el-image |
|
||||
class="video-list__block__img" |
|
||||
:src="item.movie.poster || runtimeConfig.public.apiBase + '/default.jpg'" |
|
||||
fit="cover" |
|
||||
/> |
|
||||
</nuxt-link> |
|
||||
<div class="video-list__detail"> |
|
||||
<h4 class="title text-overflow">{{ item.movie.title }}</h4> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
<div class="pagination"> |
|
||||
<el-pagination |
|
||||
background |
|
||||
layout="prev, pager, next" |
|
||||
:current-page="currentPage" |
|
||||
:page-size="12" |
|
||||
:pager-count="5" |
|
||||
:total="total" |
|
||||
@current-change="handleCurrentChange" |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</ClientOnly> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup> |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
|
|
||||
const runtimeConfig = useRuntimeConfig(); |
|
||||
const movieList = ref<any[]>([]); |
|
||||
const currentPage = ref<number>(1); |
|
||||
const total = ref(0); |
|
||||
|
|
||||
async function getList() { |
|
||||
const { data: collectData, error } = await useRequest<{ rows: any[]; total: number; code: number }>( |
|
||||
'/user-collect/findByPage', |
|
||||
{ |
|
||||
query: { |
|
||||
pageNum: currentPage.value, |
|
||||
pageSize: 12 |
|
||||
} |
|
||||
} |
|
||||
); |
|
||||
if (!error.value && collectData.value?.code === 200) { |
|
||||
movieList.value = collectData.value.rows; |
|
||||
total.value = collectData.value.total; |
|
||||
} |
|
||||
} |
|
||||
getList(); |
|
||||
|
|
||||
function handleCurrentChange(page: number) { |
|
||||
currentPage.value = page; |
|
||||
getList(); |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped> |
|
||||
.pagination { |
|
||||
padding: 20px; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
} |
|
||||
</style> |
|
@ -1,70 +0,0 @@ |
|||||
<template> |
|
||||
<div class="bg-fff user-index__head flex"> |
|
||||
<img src="../../assets/images/toux.png" alt="" /> |
|
||||
<div> |
|
||||
{{ userData.data?.email }} |
|
||||
<p class="grey">ID: {{ userData.data?.userId }}</p> |
|
||||
<a class="lv lv1"></a> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
|
|
||||
// 获取用户信息 |
|
||||
const { data: userData } = await useRequest<{ data: { email: string; userId: number } }>('/web/user/info'); |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss"> |
|
||||
.user-index { |
|
||||
.over-avatar { |
|
||||
width: 160px; |
|
||||
height: 160px; |
|
||||
background: #292838; |
|
||||
border-radius: 50%; |
|
||||
color: #ffffff; |
|
||||
text-align: center; |
|
||||
line-height: 160px; |
|
||||
font-size: 60px; |
|
||||
margin: 0 auto; |
|
||||
} |
|
||||
&__personal-name { |
|
||||
text-align: center; |
|
||||
font-size: 16px; |
|
||||
font-weight: bold; |
|
||||
padding: 10px 0; |
|
||||
} |
|
||||
&__head { |
|
||||
padding: 20px; |
|
||||
> img { |
|
||||
width: 80px; |
|
||||
margin-right: 20px; |
|
||||
} |
|
||||
.lv { |
|
||||
background: url('../../assets/images/jlt.png') no-repeat 0 0; |
|
||||
display: inline-block; |
|
||||
width: 42px; |
|
||||
height: 22px; |
|
||||
vertical-align: middle; |
|
||||
margin-top: 15px; |
|
||||
&.lv1 { |
|
||||
background-position: 0 -373px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.el-card.is-always-shadow { |
|
||||
box-shadow: none; |
|
||||
border-radius: 0; |
|
||||
border: 0; |
|
||||
} |
|
||||
} |
|
||||
@media (max-width: 768px) { |
|
||||
.user-index { |
|
||||
margin-top: -60px; |
|
||||
&__head { |
|
||||
margin: 0 -15px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
@ -1,12 +0,0 @@ |
|||||
/** 用户信息 **/ |
|
||||
export const useToken = () => |
|
||||
useState<string>('token', () => { |
|
||||
const token = useCookie<string | undefined>('token'); |
|
||||
return token.value ? 'Bearer ' + token.value : ''; |
|
||||
}); |
|
||||
|
|
||||
/** 登录弹层显示状态 */ |
|
||||
export const useLoginDialogVisible = () => useState<boolean>('loginDialogVisible', () => false); |
|
||||
|
|
||||
/** 注册弹层显示状态 */ |
|
||||
export const useRegDialogVisible = () => useState<boolean>('regDialogVisible', () => false); |
|
@ -1,39 +0,0 @@ |
|||||
/** |
|
||||
* 参考文档 |
|
||||
* https://blog.csdn.net/m0_63281537/article/details/126699761
|
|
||||
*/ |
|
||||
|
|
||||
import { useFetch } from '#app'; |
|
||||
import type { UseFetchOptions } from '#app'; |
|
||||
import {getBaseUrl, isArray} from '~/utils/tool'; |
|
||||
|
|
||||
export const useRequest = <T>(url: string, opts?: UseFetchOptions<T, unknown>) => { |
|
||||
// 获取 Cookie
|
|
||||
const token = useCookie('token'); |
|
||||
|
|
||||
const defaultOptions: UseFetchOptions<unknown> = { |
|
||||
baseURL: getBaseUrl(), |
|
||||
onRequest({ options }) { |
|
||||
options.headers = (options.headers || {}) as { [key: string]: string }; |
|
||||
if (token.value) { |
|
||||
options.headers.Authorization = token.value; |
|
||||
} |
|
||||
options.headers.tenantid = `5`; |
|
||||
}, |
|
||||
onResponse({ response }) { |
|
||||
if (+response.status === 0 && +response._data.code !== 0) { |
|
||||
process.client && ElMessage.error(response._data.message); |
|
||||
} |
|
||||
if(+response.status === 500){ |
|
||||
ElMessage.error('网络请求错误') |
|
||||
} |
|
||||
return response._data.data; |
|
||||
}, |
|
||||
onResponseError({ response }) { |
|
||||
process.client && |
|
||||
ElMessage.error(isArray(response._data.data.message) ? response._data.data.message[0] : response._data.data.message); |
|
||||
} |
|
||||
}; |
|
||||
console.log('请求接口:', getBaseUrl() + '+' + url) |
|
||||
return useFetch<T>(getBaseUrl() + url, { ...defaultOptions, ...opts } as any); |
|
||||
}; |
|
@ -1,32 +1,49 @@ |
|||||
|
/** |
||||
|
* 参考文档 |
||||
|
* https://blog.csdn.net/m0_63281537/article/details/126699761
|
||||
|
*/ |
||||
|
|
||||
import { useFetch } from '#app'; |
import { useFetch } from '#app'; |
||||
import type { UseFetchOptions } from '#app'; |
import type { UseFetchOptions } from '#app'; |
||||
import {isArray} from '~/utils/tool'; |
import {isArray} from '~/utils/tool'; |
||||
import {MODULES_API_URL, SERVER_API_URL, TENANT_ID} from "~/config"; |
|
||||
|
|
||||
export const useServerRequest = <T>(url: string, opts?: UseFetchOptions<T, unknown>) => { |
export const useServerRequest = <T>(url: string, opts?: UseFetchOptions<T, unknown>) => { |
||||
|
// 配置信息
|
||||
|
const runtimeConfig = useRuntimeConfig(); |
||||
|
// 请求接口
|
||||
|
const baseUrl = ref(); |
||||
// 获取 Cookie
|
// 获取 Cookie
|
||||
const token = useCookie('token'); |
const token = useCookie('token'); |
||||
|
|
||||
|
baseUrl.value = runtimeConfig.public.apiBase; |
||||
|
// 开发环境
|
||||
|
if(process.env.NODE_ENV === 'development'){ |
||||
|
// baseUrl.value = 'http://127.0.0.1:9090/api'
|
||||
|
baseUrl.value = 'https://modules.gxwebsoft.com/api' |
||||
|
} |
||||
const defaultOptions: UseFetchOptions<unknown> = { |
const defaultOptions: UseFetchOptions<unknown> = { |
||||
baseURL: SERVER_API_URL + url, |
|
||||
|
baseURL: baseUrl.value, |
||||
onRequest({ options }) { |
onRequest({ options }) { |
||||
options.headers = (options.headers || {}) as { [key: string]: string }; |
options.headers = (options.headers || {}) as { [key: string]: string }; |
||||
if (token.value) { |
if (token.value) { |
||||
options.headers.Authorization = token.value; |
options.headers.Authorization = token.value; |
||||
} |
} |
||||
options.headers.tenantid = `${TENANT_ID}`; |
|
||||
|
options.headers.tenantid = runtimeConfig.public.tenantId; |
||||
|
options.headers.domain = runtimeConfig.public.domain; |
||||
}, |
}, |
||||
onResponse({ response }) { |
onResponse({ response }) { |
||||
if (+response.status === 0 && +response._data.code !== 0) { |
if (+response.status === 0 && +response._data.code !== 0) { |
||||
process.client && ElMessage.error(response._data.message); |
process.client && ElMessage.error(response._data.message); |
||||
} |
} |
||||
|
if(+response.status === 500){ |
||||
|
ElMessage.error('网络请求错误') |
||||
|
} |
||||
|
return response._data.data; |
||||
}, |
}, |
||||
onResponseError({ response }) { |
onResponseError({ response }) { |
||||
process.client && |
process.client && |
||||
ElMessage.error(isArray(response._data.data.message) ? response._data.data.message[0] : response._data.data.message); |
ElMessage.error(isArray(response._data.data.message) ? response._data.data.message[0] : response._data.data.message); |
||||
} |
} |
||||
}; |
}; |
||||
|
|
||||
console.log('请求接口:', SERVER_API_URL + url) |
|
||||
return useFetch<T>(SERVER_API_URL + url, { ...defaultOptions, ...opts } as any); |
|
||||
|
console.log('请求接口:', baseUrl.value+url) |
||||
|
return useFetch<T>(url, { ...defaultOptions, ...opts } as any); |
||||
}; |
}; |
||||
|
@ -1,39 +0,0 @@ |
|||||
<template> |
|
||||
<div class="text-3xl text-center">关于我们</div> |
|
||||
<div class="flex flex-wrap justify-between w-[1280px] m-auto my-3"> |
|
||||
|
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
<div class="w-[155px] h-[218px] bg-gray-300"></div> |
|
||||
|
|
||||
</div> |
|
||||
<el-card class="w-7xl m-auto"> |
|
||||
<div class="text-xl" v-for="(item,index) in config?.data"> |
|
||||
{{ item.name }}:{{ item.value }} |
|
||||
</div> |
|
||||
{{ config }} |
|
||||
</el-card> |
|
||||
<div> |
|
||||
<!-- {{ info.data }}--> |
|
||||
</div> |
|
||||
</template> |
|
||||
<script setup lang="ts"> |
|
||||
// 请求数据 |
|
||||
import {useRequest} from "~/composables/useRequest"; |
|
||||
|
|
||||
const { data: config } = await useRequest<{data: any}>('/cms/website-field', {}) |
|
||||
// 请求数据 |
|
||||
const { data: info } = await useRequest<{data: any}>('/cms/website/getSiteInfo', { |
|
||||
query: { status: 0 } |
|
||||
}) |
|
||||
|
|
||||
</script> |
|
||||
|
|
||||
<style scoped lang="scss"> |
|
||||
|
|
||||
</style> |
|
@ -1,72 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container"> |
|
||||
<Head> |
|
||||
<Title>{{ $titleRender(`最新${info?.data?.name}在线观看`) }}</Title> |
|
||||
<Meta name="description" :content="`最新最全的${info?.data?.name}在线观看尽在淳渔影视。`" /> |
|
||||
</Head> |
|
||||
|
|
||||
<el-row v-for="categoryItem in res?.data" :key="categoryItem.id" :gutter="20" class="mt-20"> |
|
||||
<el-col :sm="18"> |
|
||||
<div class="panel_hd between items-center"> |
|
||||
<div class="panel_hd__left"> |
|
||||
<h3 class="title items-center"> |
|
||||
<i class="icon-movie-box"></i> |
|
||||
<a href="/">最新{{ categoryItem.name }}</a> |
|
||||
</h3> |
|
||||
</div> |
|
||||
<div class="panel_hd__right items-center"> |
|
||||
<ul class="items-center"> |
|
||||
<li> |
|
||||
<nuxt-link :to="`/column/${route.params.column}/show?t=${categoryItem.name}`" class="items-center"> |
|
||||
更多 <el-icon><ElIconArrowRight /></el-icon> |
|
||||
</nuxt-link> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="video-list"> |
|
||||
<el-row :gutter="20"> |
|
||||
<el-col v-for="item in categoryItem.rows" :key="item.id" :sm="4" :xs="8"> |
|
||||
<div class="video-list__block"> |
|
||||
<nuxt-link :to="`/column/${item.columnValue}/movie/${item.id}`" class="img-box"> |
|
||||
<el-image lazy class="video-list__block__img" :src="item.poster" fit="cover" /> |
|
||||
<span v-if="item.movieRate">{{ |
|
||||
+item.movieRate.rate === 0 ? '暂无评分' : item.movieRate.rate.toFixed(1) |
|
||||
}}</span> |
|
||||
</nuxt-link> |
|
||||
<div class="video-list__detail"> |
|
||||
<h4 class="title text-overflow">{{ item.title }}</h4> |
|
||||
<p class="text-overflow"> |
|
||||
<template v-for="actor in item.casts"> {{ actor.actor.name }} </template> |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
<el-col :sm="6" class="hidden-sm-and-down"> |
|
||||
<Ranking :title="categoryItem.name + '榜单'" :list="categoryItem.ranks" /> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
|
|
||||
const [{ data: res }, { data: info }] = await Promise.all([ |
|
||||
useRequest<ResData<Omit<ColumnMovieItem, 'genres'>[]>>(`/web/type/${route.params.column}`), |
|
||||
useRequest<ResData<ColumnItem>>(`/column?value=${route.params.column}`) |
|
||||
]); |
|
||||
|
|
||||
if (!info.value?.data) { |
|
||||
throw createError({ |
|
||||
statusCode: 404, |
|
||||
statusMessage: 'Page Not Found', |
|
||||
fatal: true |
|
||||
}); |
|
||||
} |
|
||||
</script> |
|
@ -1,386 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container mt-20"> |
|
||||
<Head> |
|
||||
<Title>{{ $titleRender(`${detailRes?.data.title}在线观看`) }}</Title> |
|
||||
<Meta name="description" :content="detailRes?.data.summary" /> |
|
||||
</Head> |
|
||||
|
|
||||
<div class="items-center"> |
|
||||
<span class="mr-10">当前位置</span> |
|
||||
<el-breadcrumb separator-class="el-icon-arrow-right"> |
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> |
|
||||
<el-breadcrumb-item |
|
||||
:to="{ |
|
||||
path: `/column/${detailRes?.data.columnValue}/show`, |
|
||||
query: { t: detailRes?.data.genres?.split(',')[0] } |
|
||||
}" |
|
||||
> |
|
||||
{{ detailRes?.data.genres?.split(',')[0] }} |
|
||||
</el-breadcrumb-item> |
|
||||
<el-breadcrumb-item v-if="detailRes"> |
|
||||
{{ detailRes.data.title.length > 12 ? detailRes.data.title.substr(0, 12) + '...' : detailRes.data.title }} |
|
||||
</el-breadcrumb-item> |
|
||||
</el-breadcrumb> |
|
||||
</div> |
|
||||
<el-row :gutter="40" class="mt-20"> |
|
||||
<el-col :span="18" :xs="24"> |
|
||||
<h1 class="movie-detail-title hidden-sm-and-up"> |
|
||||
{{ detailRes?.data.title }} |
|
||||
<span :class="detailRes?.data.movieRate?.rateUserCount > 0 ? 'rate' : ''"> |
|
||||
{{ detailRes?.data.movieRate?.rateUserCount > 0 ? detailRes?.data.movieRate.rate.toFixed(1) : '暂无评分' }} |
|
||||
</span> |
|
||||
</h1> |
|
||||
<div class="clearfix"> |
|
||||
<div class="movies-pic"> |
|
||||
<el-image :src="detailRes?.data.poster" fit="cover" style="width: 100%"></el-image> |
|
||||
</div> |
|
||||
<div class="movies-info"> |
|
||||
<h1 class="hidden-sm-and-down"> |
|
||||
{{ detailRes?.data.title }} |
|
||||
<span :class="detailRes?.data.movieRate?.rateUserCount > 0 ? 'rate' : ''"> |
|
||||
{{ |
|
||||
detailRes?.data.movieRate?.rateUserCount > 0 ? detailRes?.data.movieRate.rate.toFixed(1) : '暂无评分' |
|
||||
}} |
|
||||
</span> |
|
||||
</h1> |
|
||||
<el-form :inline="true" label-position="right"> |
|
||||
<el-form-item label="类型:">{{ detailRes?.data.genres }}</el-form-item> |
|
||||
<el-form-item label="地区:"> |
|
||||
<template v-for="item in detailRes?.data.country"> {{ item.name }} </template> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="年份:">{{ detailRes?.data.year || '-' }}</el-form-item> |
|
||||
</el-form> |
|
||||
<el-form label-position="right"> |
|
||||
<el-form-item v-if="detailRes?.data.akas" label="别名:"> |
|
||||
<div class="text-overflow">{{ detailRes?.data.akas.split(',').join('/') }}</div> |
|
||||
</el-form-item> |
|
||||
<el-form-item v-if="detailRes?.data.tags" label="标签:"> |
|
||||
<div class="text-overflow">{{ detailRes?.data.tags.split(',').join('/') }}</div> |
|
||||
</el-form-item> |
|
||||
<div> |
|
||||
<nuxt-link |
|
||||
v-if="detailRes?.data.movieVideos?.[0]" |
|
||||
:to="`/column/${detailRes?.data.columnValue}/video/${detailRes?.data.movieVideos?.[0].id}`" |
|
||||
> |
|
||||
<el-button :icon="ElIconVideoPlay" type="primary" class="mr-10">播放</el-button> |
|
||||
</nuxt-link> |
|
||||
<el-button |
|
||||
:loading="collectLoading" |
|
||||
:icon="isCollect ? ElIconStarFilled : ElIconStar" |
|
||||
@click="handleCollect" |
|
||||
> |
|
||||
{{ isCollect ? '已收藏' : '收藏' }} |
|
||||
</el-button> |
|
||||
<ClientOnly> |
|
||||
<el-popover v-if="!isUserRate" placement="right" trigger="click"> |
|
||||
<template #reference> |
|
||||
<el-button :icon="ElIconEdit">评分</el-button> |
|
||||
</template> |
|
||||
<el-rate v-model="rate" allow-half @change="onRatechange" /> |
|
||||
</el-popover> |
|
||||
</ClientOnly> |
|
||||
</div> |
|
||||
</el-form> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div v-if="detailRes?.data.movieVideos && detailRes.data.movieVideos.length" class="mt-20"> |
|
||||
<div class="panel_hd between items-center"> |
|
||||
<div class="panel_hd__left"> |
|
||||
<h3 class="title items-center"><i class="icon-movie-box"></i>相关视频</h3> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="related_video"> |
|
||||
<ul class="clearfix"> |
|
||||
<li v-for="item in detailRes.data.movieVideos" :key="item.id"> |
|
||||
<nuxt-link :to="`/column/${detailRes.data.columnValue}/video/${item.id}`"> |
|
||||
<el-image class="img" :src="item.cover || item.video?.poster"></el-image> |
|
||||
<p :title="item.title">{{ item.title }}</p> |
|
||||
</nuxt-link> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="mt-20"> |
|
||||
<div class="panel_hd between items-center"> |
|
||||
<div class="panel_hd__left"> |
|
||||
<h3 class="title items-center"><i class="icon-movie-box"></i>剧情简介</h3> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="desc" v-html="detailRes?.data.summary || ''"></div> |
|
||||
</div> |
|
||||
<!-- 演员 --> |
|
||||
<div v-if="castsRes?.rows && castsRes?.rows.length" class="mt-20"> |
|
||||
<div class="panel_hd between items-center"> |
|
||||
<div class="panel_hd__left"> |
|
||||
<h3 class="title items-center"><i class="icon-movie-box"></i>演员</h3> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="actor"> |
|
||||
<ul> |
|
||||
<li v-for="item in castsRes.rows" :key="item.id"> |
|
||||
<el-image style="width: 110px; height: 156px" fit="cover" :src="item.actorAvatar"></el-image> |
|
||||
<div>{{ item.actorName }}</div> |
|
||||
<div v-if="item.role" class="grey">饰 {{ item.role }}</div> |
|
||||
<div v-else class="grey">{{ item.professionName }}</div> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
<el-col :span="6" class="hidden-sm-and-down"> |
|
||||
<div class="qr_code items-center column mb-20"> |
|
||||
<qrcode-vue :value="qrcodeUrl" :size="160" level="H" /> |
|
||||
<p class="mt-10">扫描二维码用手机观看</p> |
|
||||
</div> |
|
||||
<!-- 周榜单 --> |
|
||||
<Ranking title="周榜单" :list="rank?.data.weekRank" /> |
|
||||
<!-- 月榜单 --> |
|
||||
<Ranking title="月榜单" :list="rank?.data.mouthRank" /> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import QrcodeVue from 'qrcode.vue'; |
|
||||
import { useLoginDialogVisible, useToken } from '~/composables/states'; |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
import type { FetchOptions } from '~/composables/useClientRequest'; |
|
||||
import { useClientRequest } from '~/composables/useClientRequest'; |
|
||||
import type { UserMovieBase, UserRate } from '~/types/column/movie'; |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
const token = useToken(); |
|
||||
const loginDialogVisible = useLoginDialogVisible(); |
|
||||
|
|
||||
const id = route.params.id; |
|
||||
const qrcodeUrl = ref<string>(''); |
|
||||
const isCollect = ref<boolean>(false); |
|
||||
const isUserRate = ref<boolean>(false); |
|
||||
const rate = ref<number>(); |
|
||||
const collectLoading = ref<boolean>(false); |
|
||||
|
|
||||
onMounted(() => { |
|
||||
qrcodeUrl.value = window.location.href; |
|
||||
}); |
|
||||
|
|
||||
const [{ data: detailRes, refresh }, { data: castsRes }] = await Promise.all([ |
|
||||
useRequest<ResData<MovieItem>>(`/movie/${id}`), |
|
||||
useRequest<ResPage<CastListItem[]>>(`/movie/cast/list?movieId=${id}&pageNum=1&pageSize=50`) |
|
||||
]); |
|
||||
|
|
||||
if (!detailRes.value?.data) { |
|
||||
throw createError({ |
|
||||
statusCode: 404, |
|
||||
statusMessage: 'Page Not Found', |
|
||||
fatal: true |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 获取榜单 |
|
||||
const { data: rank } = await useRequest<ResData<LeaderboardItem>>('/movie/leaderboard', { |
|
||||
query: { |
|
||||
columnValue: detailRes.value?.data.columnValue, |
|
||||
pageNum: 1, |
|
||||
pageSize: 20 |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
/** 更新访问量 */ |
|
||||
useRequest(`/movie/updatePv/${id}`, { lazy: true }); |
|
||||
|
|
||||
/** 登录状态发生改变 重新获取收藏和评分状态 */ |
|
||||
watch(token, () => { |
|
||||
getUserCollect(); |
|
||||
getUserRate(); |
|
||||
}); |
|
||||
|
|
||||
/** 获取用户收藏状态 */ |
|
||||
getUserCollect(); |
|
||||
async function getUserCollect() { |
|
||||
if (!token.value) { |
|
||||
isCollect.value = false; |
|
||||
} else { |
|
||||
const { data: userCollect } = await useRequest<ResData<UserMovieBase>>('/user-collect/find', { |
|
||||
query: { movieId: id } |
|
||||
}); |
|
||||
isCollect.value = !!userCollect.value?.data; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** 收藏 */ |
|
||||
async function handleCollect() { |
|
||||
if (!token.value) { |
|
||||
loginDialogVisible.value = true; |
|
||||
} else { |
|
||||
const requestUrl: string = !isCollect.value ? '/user-collect' : `/user-collect/cancel?movieId=${id}`; |
|
||||
const requestOpts: FetchOptions = !isCollect.value |
|
||||
? { |
|
||||
body: { movieId: id }, |
|
||||
method: 'POST' |
|
||||
} |
|
||||
: {}; |
|
||||
collectLoading.value = true; |
|
||||
try { |
|
||||
const { code } = await useClientRequest<Pick<ResOptions<unknown>, 'code' | 'msg'>>(requestUrl, requestOpts); |
|
||||
if (code === 200) { |
|
||||
isCollect.value = !isCollect.value; |
|
||||
} |
|
||||
} finally { |
|
||||
collectLoading.value = false; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** 获取用户评分状态 */ |
|
||||
getUserRate(); |
|
||||
async function getUserRate() { |
|
||||
if (!token.value) { |
|
||||
isUserRate.value = false; |
|
||||
} else { |
|
||||
const { data: userRate } = await useRequest<ResData<UserRate>>('/user-rate', { |
|
||||
query: { movieId: id } |
|
||||
}); |
|
||||
isUserRate.value = !!userRate.value?.data; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** 设置评分 */ |
|
||||
async function onRatechange(value: number) { |
|
||||
if (!value) return; |
|
||||
if (!token.value) { |
|
||||
loginDialogVisible.value = true; |
|
||||
rate.value = 0; |
|
||||
} else { |
|
||||
const { code } = await useClientRequest<Pick<ResOptions<unknown>, 'code'>>('/user-rate', { |
|
||||
method: 'post', |
|
||||
body: { movieId: id, rate: rate.value } |
|
||||
}); |
|
||||
if (code === 200) { |
|
||||
await refresh(); |
|
||||
isUserRate.value = true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.movies-pic { |
|
||||
width: 190px; |
|
||||
margin-right: 20px; |
|
||||
float: left; |
|
||||
} |
|
||||
|
|
||||
.movie-detail-title { |
|
||||
color: #333; |
|
||||
font-size: 22px; |
|
||||
margin-bottom: 10px; |
|
||||
line-height: 1.2; |
|
||||
|
|
||||
span { |
|
||||
color: #666666; |
|
||||
font-size: 14px; |
|
||||
|
|
||||
&.rate { |
|
||||
font-size: 24px; |
|
||||
color: #06d706; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.movies-info { |
|
||||
h1 { |
|
||||
color: #333; |
|
||||
font-size: 22px; |
|
||||
margin-bottom: 10px; |
|
||||
line-height: 1.2; |
|
||||
padding-top: 0; |
|
||||
} |
|
||||
|
|
||||
.el-form-item { |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
|
|
||||
span { |
|
||||
color: #666666; |
|
||||
font-size: 14px; |
|
||||
|
|
||||
&.rate { |
|
||||
font-size: 24px; |
|
||||
color: #06d706; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.related_video { |
|
||||
overflow-x: auto; |
|
||||
width: 100%; |
|
||||
overflow-y: hidden; |
|
||||
|
|
||||
.img { |
|
||||
width: 160px; |
|
||||
height: 100px; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
white-space: nowrap; |
|
||||
|
|
||||
li { |
|
||||
display: inline-block; |
|
||||
margin-right: 20px; |
|
||||
position: relative; |
|
||||
|
|
||||
p { |
|
||||
display: block; |
|
||||
position: absolute; |
|
||||
bottom: 0; |
|
||||
left: 0; |
|
||||
width: 100%; |
|
||||
background: rgba(0, 0, 0, 0.6); |
|
||||
color: #ffffff; |
|
||||
height: 36px; |
|
||||
line-height: 36px; |
|
||||
padding-left: 10px; |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.title { |
|
||||
.el-icon { |
|
||||
margin-right: 15px; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.desc { |
|
||||
line-height: 28px; |
|
||||
} |
|
||||
|
|
||||
.actor { |
|
||||
overflow-x: auto; |
|
||||
width: 100%; |
|
||||
overflow-y: hidden; |
|
||||
|
|
||||
&::-webkit-scrollbar { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
white-space: nowrap; |
|
||||
|
|
||||
li { |
|
||||
display: inline-block; |
|
||||
margin-right: 20px; |
|
||||
width: 110px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@media only screen and (max-width: 991px) { |
|
||||
.movies-pic { |
|
||||
width: 120px; |
|
||||
margin-right: 10px; |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
@ -1,258 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container mt-20 show"> |
|
||||
<Head> |
|
||||
<Title>{{ $titleRender(`${title}_${info?.data?.name}`) }}</Title> |
|
||||
<Meta name="description" :content="`最新最全的${title}${info?.data?.name}尽在淳渔影视。`" /> |
|
||||
</Head> |
|
||||
|
|
||||
<el-row :gutter="40"> |
|
||||
<el-col :span="18" :xs="24"> |
|
||||
<div class="panel_hd between items-center"> |
|
||||
<div class="panel_hd__left"> |
|
||||
<h3 class="title items-center"> |
|
||||
<el-icon><ElIconVideoCamera /></el-icon><a href="/">筛选</a> |
|
||||
</h3> |
|
||||
</div> |
|
||||
</div> |
|
||||
<el-form> |
|
||||
<el-form-item label="按类型"> |
|
||||
<ul class="show__type-filter"> |
|
||||
<li :class="route.query.t === '' || route.query.t === undefined ? 'active' : ''"> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, t: '' } }">全部</nuxt-link> |
|
||||
</li> |
|
||||
<li v-for="item in genreList?.data" :key="item.id" :class="route.query.t === item.name ? 'active' : ''"> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, t: item.name } }">{{ |
|
||||
item.name |
|
||||
}}</nuxt-link> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="按地区"> |
|
||||
<ul class="show__type-filter"> |
|
||||
<li :class="route.query.c === '' || route.query.c === undefined ? 'active' : ''"> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, c: '' } }">全部</nuxt-link> |
|
||||
</li> |
|
||||
<li |
|
||||
v-for="item in countryList?.data" |
|
||||
:key="item.id" |
|
||||
:class="route.query.c && +route.query.c === +item.id ? 'active' : ''" |
|
||||
> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, c: item.id } }">{{ item.name }}</nuxt-link> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="按年份"> |
|
||||
<ul class="show__type-filter"> |
|
||||
<li :class="route.query.y === '' || route.query.y === undefined ? 'active' : ''"> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, y: '' } }">全部</nuxt-link> |
|
||||
</li> |
|
||||
<li |
|
||||
v-for="item in yearList" |
|
||||
:key="item" |
|
||||
:class="route.query.y && +route.query.y === item ? 'active' : ''" |
|
||||
> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, y: item } }">{{ item }}</nuxt-link> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="按语言"> |
|
||||
<ul class="show__type-filter"> |
|
||||
<li :class="route.query.l === '' || route.query.l === undefined ? 'active' : ''"> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, l: '' } }">全部</nuxt-link> |
|
||||
</li> |
|
||||
<li |
|
||||
v-for="item in languageList?.data" |
|
||||
:key="item.id" |
|
||||
:class="route.query.l === item.name ? 'active' : ''" |
|
||||
> |
|
||||
<nuxt-link :to="{ path: route.path, query: { ...route.query, l: item.name } }">{{ |
|
||||
item.name |
|
||||
}}</nuxt-link> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
<el-tabs v-model="orderBy" @tab-change="handleTabChange"> |
|
||||
<el-tab-pane label="按时间" name="createTime" :disabled="pending"></el-tab-pane> |
|
||||
<el-tab-pane label="按人气" name="pv" :disabled="pending"></el-tab-pane> |
|
||||
<el-tab-pane label="按评分" name="rate" :disabled="pending"></el-tab-pane> |
|
||||
</el-tabs> |
|
||||
<div v-loading="pending" class="video-list"> |
|
||||
<el-row v-if="movieList?.total !== 0" :gutter="20"> |
|
||||
<el-col v-for="item in movieList?.rows" :key="item.id" :sm="4" :xs="8"> |
|
||||
<div class="video-list__block"> |
|
||||
<nuxt-link :to="`/column/${item.columnValue}/movie/${item.id}`" class="img-box"> |
|
||||
<el-image class="video-list__block__img" :src="item.poster" fit="cover" /> |
|
||||
<span v-if="item.movieRate">{{ |
|
||||
+item.movieRate.rate === 0 ? '暂无评分' : item.movieRate.rate.toFixed(1) |
|
||||
}}</span> |
|
||||
</nuxt-link> |
|
||||
<div class="video-list__detail"> |
|
||||
<h4 class="title text-overflow">{{ item.title }}</h4> |
|
||||
<p class="text-overflow"> |
|
||||
<template v-for="actor in item.casts"> |
|
||||
{{ actor.actor.name }} |
|
||||
</template> |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
<div v-if="movieList?.total !== 0" class="pagination"> |
|
||||
<el-pagination |
|
||||
background |
|
||||
layout="prev, pager, next" |
|
||||
:current-page="currentPage" |
|
||||
:page-size="30" |
|
||||
:pager-count="5" |
|
||||
:total="movieList?.total" |
|
||||
@current-change="handleCurrentChange" |
|
||||
/> |
|
||||
</div> |
|
||||
<el-empty v-if="movieList?.total === 0" description="未找到相关数据"></el-empty> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
<el-col :span="6" class="hidden-sm-and-down"> |
|
||||
<!-- 周榜单 --> |
|
||||
<Ranking title="周榜单" :list="rank?.data.weekRank" /> |
|
||||
<!-- 月榜单 --> |
|
||||
<Ranking title="月榜单" :list="rank?.data.mouthRank" /> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
import { useClientRequest } from '~/composables/useClientRequest'; |
|
||||
|
|
||||
definePageMeta({ |
|
||||
key: route => route.fullPath |
|
||||
}); |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
const { query, params } = route; |
|
||||
const currentPage = ref<number>((route.query.page && +route.query.page) || 1); |
|
||||
const orderBy = ref<string>((query.orderBy as string) || 'createTime'); |
|
||||
const yearList = ref<number[]>([]); |
|
||||
const y = new Date().getFullYear(); |
|
||||
for (let i = 0; i <= 15; i++) { |
|
||||
yearList.value.push(y - i); |
|
||||
} |
|
||||
|
|
||||
const title = computed(() => { |
|
||||
let html = ''; |
|
||||
if (query.y) { |
|
||||
html += query.y; |
|
||||
html += '年'; |
|
||||
} |
|
||||
if (query.t) { |
|
||||
html += '最新最全的'; |
|
||||
html += query.t; |
|
||||
html += '在线观看'; |
|
||||
} |
|
||||
return html; |
|
||||
}); |
|
||||
|
|
||||
const [ |
|
||||
{ data: genreList }, |
|
||||
{ data: countryList }, |
|
||||
{ data: languageList }, |
|
||||
{ data: rank }, |
|
||||
{ data: info }, |
|
||||
{ data: movieList, pending, refresh } |
|
||||
] = await Promise.all([ |
|
||||
useRequest<ResData<Item[]>>('/basic/genre/all', { |
|
||||
query: { |
|
||||
columnValue: params.column |
|
||||
} |
|
||||
}), |
|
||||
useRequest<ResData<Item[]>>('/basic/country/all'), |
|
||||
useRequest<ResData<Item[]>>('/basic/language/all'), |
|
||||
useRequest<ResData<LeaderboardItem>>('/movie/leaderboard', { |
|
||||
query: { |
|
||||
columnValue: params.column, |
|
||||
pageNum: 1, |
|
||||
pageSize: 20 |
|
||||
} |
|
||||
}), |
|
||||
useRequest<ResData<ColumnItem>>(`/column`, { |
|
||||
query: { |
|
||||
value: params.column |
|
||||
} |
|
||||
}), |
|
||||
useAsyncData<ResPage<MovieItem[]>>('data', () => |
|
||||
useClientRequest('/movie/list', { |
|
||||
query: { |
|
||||
columnValue: params.column, |
|
||||
genres: query.t, |
|
||||
country: query.c, |
|
||||
language: query.l, |
|
||||
year: query.y, |
|
||||
pageNum: currentPage.value, |
|
||||
pageSize: 30, |
|
||||
orderBy: orderBy.value |
|
||||
} |
|
||||
}) |
|
||||
) |
|
||||
]); |
|
||||
|
|
||||
async function handleCurrentChange(page: number) { |
|
||||
const route = useRoute(); |
|
||||
await navigateTo({ |
|
||||
path: route.path, |
|
||||
query: { |
|
||||
...route.query, |
|
||||
orderBy: orderBy.value, |
|
||||
page |
|
||||
} |
|
||||
}); |
|
||||
if (process.client) { |
|
||||
window.scrollTo(0, 0); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async function handleTabChange() { |
|
||||
await refresh(); |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.show { |
|
||||
.title { |
|
||||
.el-icon { |
|
||||
margin-right: 15px; |
|
||||
} |
|
||||
} |
|
||||
&__type-filter { |
|
||||
li { |
|
||||
display: inline-block; |
|
||||
margin-right: 15px; |
|
||||
padding: 0 10px; |
|
||||
height: 24px; |
|
||||
line-height: 24px; |
|
||||
&.active { |
|
||||
background: #ff9900; |
|
||||
a { |
|
||||
color: #ffffff; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@media (max-width: 768px) { |
|
||||
&__type-filter { |
|
||||
white-space: nowrap; |
|
||||
width: 300px; |
|
||||
overflow-x: auto; |
|
||||
&::-webkit-scrollbar { |
|
||||
display: none; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.pagination { |
|
||||
padding: 20px; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
@ -1,364 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container mt-20"> |
|
||||
<Head> |
|
||||
<Title>{{ $titleRender(`${detail.movie.title}_${detail.title}在线观看`) }}</Title> |
|
||||
<Meta name="description" :content="detail.movie.summary" /> |
|
||||
</Head> |
|
||||
|
|
||||
<div class="items-center"> |
|
||||
<span class="mr-10">当前位置</span> |
|
||||
<el-breadcrumb separator-class="el-icon-arrow-right"> |
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> |
|
||||
<el-breadcrumb-item |
|
||||
:to="{ path: `/column/${detail.movie.columnValue}/show`, query: { t: detail.movie.genres.split(',')[0] } }" |
|
||||
>{{ detail.movie.genres.split(',')[0] }}</el-breadcrumb-item |
|
||||
> |
|
||||
<el-breadcrumb-item class="hidden-sm-and-down">{{ detail.title }}</el-breadcrumb-item> |
|
||||
</el-breadcrumb> |
|
||||
</div> |
|
||||
<el-row :gutter="40" class="mt-20"> |
|
||||
<el-col :span="18" :xs="24"> |
|
||||
<div id="mse"></div> |
|
||||
<div> |
|
||||
<h1 class="mb-10 mt-10 video-detail__title"> |
|
||||
{{ detail.movie.title }} {{ detail.title }} |
|
||||
<span :class="detail.movie.movieRate?.rateUserCount > 0 ? 'rate' : ''"> |
|
||||
{{ detail.movie.movieRate?.rateUserCount > 0 ? detail.movie.movieRate.rate.toFixed(1) : '暂无评分' }} |
|
||||
</span> |
|
||||
</h1> |
|
||||
<el-form :inline="true"> |
|
||||
<el-form-item label="类型:">{{ detail.movie.genres }}</el-form-item> |
|
||||
<el-form-item label="地区:"> |
|
||||
<template v-for="item in detail.country">{{ item.name }} </template> |
|
||||
</el-form-item> |
|
||||
<el-form-item label="年份:">{{ detail.movie.year }}</el-form-item> |
|
||||
</el-form> |
|
||||
</div> |
|
||||
<div> |
|
||||
<div class="panel_hd between items-center"> |
|
||||
<div class="panel_hd__left"> |
|
||||
<h3 class="title items-center"> |
|
||||
<el-icon><ElIconVideoCamera /></el-icon>相关视频 |
|
||||
</h3> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div v-if="detail.videos.length" class="related_video"> |
|
||||
<ul class="clearfix"> |
|
||||
<li v-for="item in detail.videos" :key="item.id"> |
|
||||
<nuxt-link :to="`/column/${detail.movie.columnValue}/video/${item.id}`"> |
|
||||
<el-image class="img" fit="cover" :src="item.cover || item.video?.poster || ''"></el-image> |
|
||||
<p :title="item.title"> |
|
||||
<span :class="+item.id === +route.params.id ? 'animate' : ''" |
|
||||
>{{ +item.id === +route.params.id ? '正在播放: ' : '' }}{{ item.title }}</span |
|
||||
> |
|
||||
</p> |
|
||||
</nuxt-link> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="mt-20"> |
|
||||
<div class="panel_hd between items-center"> |
|
||||
<div class="panel_hd__left"> |
|
||||
<h3 class="title items-center"> |
|
||||
<el-icon><ElIconVideoCamera /></el-icon><a href="/">猜你喜欢</a> |
|
||||
</h3> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="video-list"> |
|
||||
<el-row :gutter="20"> |
|
||||
<el-col v-for="item in likeRows.rows" :key="item.id" :sm="4" :xs="8"> |
|
||||
<div class="video-list__block"> |
|
||||
<nuxt-link :to="`/column/${item.columnValue}/movie/${item.id}`"> |
|
||||
<el-image class="video-list__block__img" :src="item.poster" fit="cover" /> |
|
||||
</nuxt-link> |
|
||||
<div class="video-list__detail"> |
|
||||
<h4 class="title text-overflow">{{ item.title }}</h4> |
|
||||
<p class="text-overflow"> |
|
||||
<template v-for="actor in item.casts"> |
|
||||
{{ actor.actor.name }} |
|
||||
</template> |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
<el-col :span="6" class="hidden-sm-and-down"> |
|
||||
<div class="qr_code items-center column mb-20"> |
|
||||
<qrcode-vue :value="qrcodeUrl" :size="160" level="H" /> |
|
||||
<p class="mt-10">扫描二维码用手机观看</p> |
|
||||
</div> |
|
||||
<!-- 周榜单 --> |
|
||||
<Ranking title="周榜单" :list="rank.data.weekRank" /> |
|
||||
<!-- 月榜单 --> |
|
||||
<Ranking title="月榜单" :list="rank.data.mouthRank" /> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'; |
|
||||
import QrcodeVue from 'qrcode.vue'; |
|
||||
import 'xgplayer/dist/index.min.css'; |
|
||||
import 'xgplayer/es/plugins/danmu/index.css'; |
|
||||
import PresetPlayer from 'xgplayer'; |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
import { useClientRequest } from '~/composables/useClientRequest'; |
|
||||
import type { MovieVideoInfo } from '~/types/column/video'; |
|
||||
import type { UserMovieBase } from '~/types/column/movie'; |
|
||||
import '~/plugins/xgplayer/payTip/index.css'; |
|
||||
import PayTip from '~/plugins/xgplayer/payTip'; |
|
||||
|
|
||||
const token = useToken(); |
|
||||
const loginDialogVisible = useLoginDialogVisible(); |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
const id = route.params.id; |
|
||||
const qrcodeUrl = ref(''); |
|
||||
// 是否购买了影片 |
|
||||
const isUserBuy = ref(false); |
|
||||
// 视频支付提示插件 |
|
||||
let payTipInstance: PayTip | null = null; |
|
||||
// 视频组件 |
|
||||
let player: PresetPlayer | null = null; |
|
||||
|
|
||||
const { data: detailRes } = await useRequest<ResData<MovieVideoInfo | null>>(`/movie/videos/${id}`); |
|
||||
|
|
||||
if (!detailRes.value?.data) { |
|
||||
throw createError({ |
|
||||
statusCode: 404, |
|
||||
statusMessage: 'Page Not Found', |
|
||||
fatal: true |
|
||||
}); |
|
||||
} |
|
||||
const detail = detailRes.value.data; |
|
||||
|
|
||||
/** 查询用户是否购买影片 */ |
|
||||
getUserMovie(); |
|
||||
async function getUserMovie() { |
|
||||
if (token.value) { |
|
||||
const { data: userBuy } = await useRequest<ResData<UserMovieBase>>(`/user-movie`, { |
|
||||
query: { movieId: detail.movieId } |
|
||||
}); |
|
||||
isUserBuy.value = !!userBuy.value?.data; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
watch(token, async () => { |
|
||||
await getUserMovie(); |
|
||||
payTipInstance?.hide(); |
|
||||
player?.play(); |
|
||||
}); |
|
||||
|
|
||||
onMounted(async () => { |
|
||||
qrcodeUrl.value = window.location.href; |
|
||||
|
|
||||
const [Player, Mp4Plugin, Danmu, PayTip] = await Promise.all([ |
|
||||
import('xgplayer'), |
|
||||
import('xgplayer-mp4'), |
|
||||
import('xgplayer/es/plugins/danmu'), |
|
||||
import('~/plugins/xgplayer/payTip') |
|
||||
]); |
|
||||
// eslint-disable-next-line new-cap |
|
||||
player = new Player.default({ |
|
||||
id: 'mse', |
|
||||
controls: { |
|
||||
autoHide: false |
|
||||
}, |
|
||||
autoplay: true, |
|
||||
volume: 0.3, |
|
||||
url: |
|
||||
process.env.NODE_ENV === 'development' |
|
||||
? detail.videoInfo?.url.replace('http://[::1]:4000', '/server') |
|
||||
: detail.videoInfo?.url, |
|
||||
playsinline: true, |
|
||||
height: '100%', |
|
||||
width: '100%', |
|
||||
plugins: [Mp4Plugin.default, Danmu.default, PayTip.default], |
|
||||
danmu: { |
|
||||
comments: [ |
|
||||
{ |
|
||||
duration: 15000, |
|
||||
id: '1', |
|
||||
start: 3000, |
|
||||
txt: '好看,精彩!!!', |
|
||||
// 弹幕自定义样式 |
|
||||
style: { |
|
||||
color: '#ff9500', |
|
||||
fontSize: '20px', |
|
||||
border: 'solid 1px #ff9500', |
|
||||
borderRadius: '50px', |
|
||||
padding: '5px 11px', |
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)' |
|
||||
} |
|
||||
} |
|
||||
], |
|
||||
area: { |
|
||||
start: 0, |
|
||||
end: 1 |
|
||||
} |
|
||||
}, |
|
||||
payTip: { |
|
||||
tip: `此为付费视频,支付${detail.movie.paymentAmount}金币继续观看?`, |
|
||||
lookTime: detail.movie.freeDuration * 60, |
|
||||
arriveTime() { |
|
||||
if (isUserBuy.value) return; |
|
||||
// 影片设置了需要购买才能观看并且是正片 |
|
||||
if (+detail.movie.isPay === 1 && +detail.typeId === 1) { |
|
||||
player?.pause(); |
|
||||
payTipInstance?.show('flex'); |
|
||||
} |
|
||||
}, |
|
||||
clickButton() { |
|
||||
if (!token.value) { |
|
||||
loginDialogVisible.value = true; |
|
||||
} else { |
|
||||
player && buyMovie(player); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
payTipInstance = player.getPlugin('payTip'); |
|
||||
}); |
|
||||
|
|
||||
/** 购买影视 */ |
|
||||
function buyMovie(player: PresetPlayer) { |
|
||||
ElMessageBox.confirm(`确定要支付${detail.movie.paymentAmount}金币购买此影片吗?`, '提示', { |
|
||||
confirmButtonText: '确定', |
|
||||
cancelButtonText: '取消', |
|
||||
type: 'warning' |
|
||||
}).then(async () => { |
|
||||
const { code, msg } = await useClientRequest<ResData<UserMovieBase>>(`/user-movie`, { |
|
||||
method: 'post', |
|
||||
body: { movieId: detail.movieId } |
|
||||
}); |
|
||||
if (code === 200) { |
|
||||
isUserBuy.value = true; |
|
||||
player.play(); |
|
||||
payTipInstance?.hide(); |
|
||||
ElMessage({ |
|
||||
type: 'success', |
|
||||
message: '购买成功' |
|
||||
}); |
|
||||
} else { |
|
||||
ElMessage({ |
|
||||
type: 'error', |
|
||||
message: msg |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/** 获取猜你喜欢、周榜单、月榜单数据 */ |
|
||||
const [{ data: likeRows }, { data: rank }] = await Promise.all([ |
|
||||
useRequest<ResPage<MovieItem[]>>(`/movie/list`, { |
|
||||
query: { |
|
||||
genres: detail.movie.genres.split(',')[0], |
|
||||
pageNum: 1, |
|
||||
pageSize: 18 |
|
||||
} |
|
||||
}), |
|
||||
useRequest<ResData<LeaderboardItem>>('/movie/leaderboard', { |
|
||||
query: { |
|
||||
columnValue: detail.movie.columnValue, |
|
||||
pageNum: 1, |
|
||||
pageSize: 20 |
|
||||
} |
|
||||
}) |
|
||||
]); |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
#mse { |
|
||||
width: 100%; |
|
||||
height: 500px !important; |
|
||||
background: #000; |
|
||||
} |
|
||||
@media (max-width: 768px) { |
|
||||
#mse { |
|
||||
height: 300px !important; |
|
||||
} |
|
||||
} |
|
||||
.title { |
|
||||
.el-icon { |
|
||||
margin-right: 15px; |
|
||||
} |
|
||||
} |
|
||||
.related_video { |
|
||||
overflow-x: auto; |
|
||||
width: 100%; |
|
||||
overflow-y: hidden; |
|
||||
.img { |
|
||||
width: 160px; |
|
||||
height: 100px; |
|
||||
} |
|
||||
ul { |
|
||||
white-space: nowrap; |
|
||||
li { |
|
||||
display: inline-block; |
|
||||
margin-right: 20px; |
|
||||
position: relative; |
|
||||
overflow: hidden; |
|
||||
p { |
|
||||
display: block; |
|
||||
position: absolute; |
|
||||
bottom: 0; |
|
||||
left: 0; |
|
||||
width: 100%; |
|
||||
background: rgba(0, 0, 0, 0.6); |
|
||||
color: #ffffff; |
|
||||
height: 36px; |
|
||||
line-height: 36px; |
|
||||
padding-left: 10px; |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
.animate { |
|
||||
padding-left: 20px; |
|
||||
white-space: nowrap; |
|
||||
display: inline-block; |
|
||||
animation: 15s wordsLoop linear infinite normal; |
|
||||
color: #ffffff; |
|
||||
} |
|
||||
|
|
||||
@keyframes wordsLoop { |
|
||||
0% { |
|
||||
transform: translateX(0px); |
|
||||
-webkit-transform: translateX(0px); |
|
||||
} |
|
||||
100% { |
|
||||
transform: translateX(-100%); |
|
||||
-webkit-transform: translateX(-100%); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@-webkit-keyframes wordsLoop { |
|
||||
0% { |
|
||||
transform: translateX(0px); |
|
||||
-webkit-transform: translateX(0px); |
|
||||
} |
|
||||
100% { |
|
||||
transform: translateX(-100%); |
|
||||
-webkit-transform: translateX(-100%); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.video-detail__title { |
|
||||
line-height: 1.2; |
|
||||
|
|
||||
span { |
|
||||
color: #666666; |
|
||||
font-size: 14px; |
|
||||
&.rate { |
|
||||
font-size: 24px; |
|
||||
color: #06d706; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
@ -1,109 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container mt-20 show"> |
|
||||
<ClientOnly> |
|
||||
<el-row :gutter="40"> |
|
||||
<el-col :span="18" :xs="24"> |
|
||||
<div class="search-form"> |
|
||||
<el-form :model="form" :inline="true"> |
|
||||
<el-form-item> |
|
||||
<el-input v-model="form.keyword" placeholder="请输入搜索的影视名"></el-input> |
|
||||
</el-form-item> |
|
||||
<el-form-item> |
|
||||
<el-button type="primary" @click="refresh">搜 索</el-button> |
|
||||
</el-form-item> |
|
||||
</el-form> |
|
||||
</div> |
|
||||
<el-tabs v-model="activeName"> |
|
||||
<el-tab-pane label="搜索结果" name="first"> |
|
||||
<div class="video-list"> |
|
||||
<el-row :gutter="20"> |
|
||||
<el-col v-for="item in data.rows" :key="item.id" :sm="4" :xs="8"> |
|
||||
<div class="video-list__block"> |
|
||||
<nuxt-link :to="`/column/${item.columnValue}/movie/${item.id}`" class="img-box"> |
|
||||
<el-image class="video-list__block__img" :src="item.poster" fit="cover" /> |
|
||||
<span v-if="item.movieRate">{{ |
|
||||
+item.movieRate.rate === 0 ? '暂无评分' : item.movieRate.rate.toFixed(1) |
|
||||
}}</span> |
|
||||
</nuxt-link> |
|
||||
<div class="video-list__detail"> |
|
||||
<h4 class="title text-overflow">{{ item.title }}</h4> |
|
||||
<p class="text-overflow"> |
|
||||
<template v-for="actor in item.casts"> |
|
||||
{{ actor.actor.name }} |
|
||||
</template> |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
<div class="pagination"> |
|
||||
<el-pagination |
|
||||
background |
|
||||
layout="prev, pager, next" |
|
||||
:current-page="currentPage" |
|
||||
:page-size="30" |
|
||||
:pager-count="5" |
|
||||
:total="data.total" |
|
||||
@current-change="handleCurrentChange" |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</el-tab-pane> |
|
||||
</el-tabs> |
|
||||
</el-col> |
|
||||
<el-col :span="6" class="hidden-sm-and-down"> |
|
||||
<Ranking title="最新影视" :list="newMovie.rows" /> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</ClientOnly> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { useClientRequest } from '~/composables/useClientRequest'; |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
const activeName = ref('first'); |
|
||||
const form = reactive<{ keyword: string | undefined }>({ |
|
||||
keyword: undefined |
|
||||
}); |
|
||||
const currentPage = ref<number>(1); |
|
||||
|
|
||||
form.keyword = route.query.keyword as string; |
|
||||
if (route.query.page) { |
|
||||
currentPage.value = +route.query.page; |
|
||||
} |
|
||||
|
|
||||
const [{ data, refresh }, { data: newMovie }] = await Promise.all([ |
|
||||
useAsyncData('data', () => |
|
||||
useClientRequest<ResPage<MovieItem[]>>('movie/list', { |
|
||||
query: { |
|
||||
keyword: form.keyword, |
|
||||
pageNum: currentPage.value, |
|
||||
pageSize: 30 |
|
||||
} |
|
||||
}) |
|
||||
), |
|
||||
useRequest<ResPage<MovieItem[]>>('movie/list', { query: { pageSize: 15 } }) |
|
||||
]); |
|
||||
|
|
||||
function handleCurrentChange(page: number) { |
|
||||
currentPage.value = page; |
|
||||
refresh(); |
|
||||
if (process.client) { |
|
||||
window.scrollTo(0, 0); |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.search-form { |
|
||||
padding: 20px 0; |
|
||||
} |
|
||||
.pagination { |
|
||||
padding: 20px; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
} |
|
||||
</style> |
|
@ -1,112 +0,0 @@ |
|||||
<template> |
|
||||
<div class="banner h-[60px]" v-if="form"> |
|
||||
<el-image :src="form.photo"></el-image> |
|
||||
</div> |
|
||||
<div v-if="form" class="container flex flex-col m-auto my-3"> |
|
||||
<el-breadcrumb class="py-2" separator="/"> |
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> |
|
||||
<el-breadcrumb-item>{{ form.categoryParent }}</el-breadcrumb-item> |
|
||||
<el-breadcrumb-item>{{ form.goodsName }}</el-breadcrumb-item> |
|
||||
</el-breadcrumb> |
|
||||
|
|
||||
<div class="goods-box bg-white p-4 m-auto mt-3 flex gap-xl"> |
|
||||
<div class="goods-image flex gap-xl"> |
|
||||
<div class="gap-xs flex flex-col"> |
|
||||
<el-avatar v-for="item in form.files" :src="item.url" size="large" shape="square" /> |
|
||||
</div> |
|
||||
<el-image :src="form.image" fit="contain" class="w-2xl h-2xl bg-gray-100 border-radius:30px"></el-image> |
|
||||
</div> |
|
||||
<div class="goods-info flex flex-col gap-xs"> |
|
||||
<div class="goods-name text-2xl">{{ form.goodsName }}</div> |
|
||||
<div class="goods-price text-2xl red">¥{{ form.salePrice * form.num }}</div> |
|
||||
<div class="text-green-7">购买得积分</div> |
|
||||
<div class="text-gray-4">配送:无需配送</div> |
|
||||
<div class="text-gray-4">保障:假一赔四 退货包运费 极速退款</div> |
|
||||
<div class="text-gray-4">销量: {{ form.sales }}</div> |
|
||||
<template v-for="spec in form.goodsSpecValue"> |
|
||||
<div class="flex items-center"> |
|
||||
<div class="text-gray-4">{{ spec.value }}:</div> |
|
||||
<el-radio-group v-model="form.radio"> |
|
||||
<el-radio v-for="(specValue,specIndex) in spec.detail" :label="specIndex" border>{{ specValue }}</el-radio> |
|
||||
</el-radio-group> |
|
||||
</div> |
|
||||
</template> |
|
||||
<div class="text-gray-4"> |
|
||||
已选中:{{ form.radio }} |
|
||||
</div> |
|
||||
<div class="text-gray-4"> |
|
||||
数量: |
|
||||
<el-input-number v-model="form.num" @change="handleChange" :min="1" :max="10" label="描述文字"></el-input-number> |
|
||||
</div> |
|
||||
<div class="py-5"> |
|
||||
<el-button-group size="large"> |
|
||||
<el-button type="primary">立即购买</el-button> |
|
||||
<el-button type="primary">加入购物车</el-button> |
|
||||
</el-button-group> |
|
||||
<el-button size="large" class="ml-3">收藏</el-button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="content w-7xl m-auto p-4 bg-white"> |
|
||||
<el-tabs v-model="activeName" :lazy="true" @tab-click="handleClick"> |
|
||||
<el-tab-pane label="参数信息" name="parameter"> |
|
||||
<el-descriptions class="margin-top" title="参数信息" :column="3" border> |
|
||||
<el-descriptions-item label="用户名">kooriookami</el-descriptions-item> |
|
||||
<el-descriptions-item label="手机号">18100000000</el-descriptions-item> |
|
||||
<el-descriptions-item label="居住地">苏州市</el-descriptions-item> |
|
||||
<el-descriptions-item label="备注"> |
|
||||
<el-tag size="small">学校</el-tag> |
|
||||
</el-descriptions-item> |
|
||||
<el-descriptions-item label="联系地址">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item> |
|
||||
</el-descriptions> |
|
||||
</el-tab-pane> |
|
||||
<el-tab-pane label="图文详情" name="content"> |
|
||||
<div class="files flex flex-col"> |
|
||||
<el-image v-for="item in form.files" :src="item.url" /> |
|
||||
</div> |
|
||||
</el-tab-pane> |
|
||||
<el-tab-pane label="用户评价" name="reviews">用户评价</el-tab-pane> |
|
||||
</el-tabs> |
|
||||
</div> |
|
||||
|
|
||||
</div> |
|
||||
<div v-if="!form"> |
|
||||
<el-empty description="404 页面不存在"></el-empty> |
|
||||
</div> |
|
||||
<div title="调试数据" class="p-8 text-center"> |
|
||||
页面ID: {{ productName }} |
|
||||
页面数据:{{ form }} |
|
||||
</div> |
|
||||
</template> |
|
||||
<script setup lang="ts"> |
|
||||
import type {ApiResult} from "~/api"; |
|
||||
import {useRequest} from "~/composables/useRequest"; |
|
||||
import type {Goods} from "~/api/shop/goods/model"; |
|
||||
|
|
||||
const route = useRoute(); |
|
||||
const { query, params } = route; |
|
||||
const { name: productName } = params; |
|
||||
const activeName = ref('parameter'); |
|
||||
console.log(productName,'productName..') |
|
||||
|
|
||||
|
|
||||
// 页面信息 |
|
||||
const form = ref<Goods | any>(); |
|
||||
|
|
||||
// 请求数据 |
|
||||
// const { data: info } = await useRequest<ApiResult<Goods>>('/shop/goods/' + productName) |
|
||||
// form.value = info.value?.data; |
|
||||
// form.value.files = JSON.parse(form.value.files); |
|
||||
// form.value.num = 1; |
|
||||
// form.value.radio = '0' |
|
||||
|
|
||||
|
|
||||
|
|
||||
const handleClick = (tab, event) => { |
|
||||
console.log(tab, event); |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped lang="scss"> |
|
||||
|
|
||||
</style> |
|
@ -1,107 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container user-index"> |
|
||||
<Head> |
|
||||
<Title>{{ $titleRender('个人中心') }}</Title> |
|
||||
<Style type="text/css" children="body { background-color: #f7f7f7; }" /> |
|
||||
</Head> |
|
||||
<el-row :gutter="30" class="mt-20"> |
|
||||
<el-col :md="6" :xs="24"> |
|
||||
<UserInfoData /> |
|
||||
<el-card class="integral"> |
|
||||
<template #header> |
|
||||
<div class="card-header"> |
|
||||
<div> |
|
||||
金币 |
|
||||
<span>{{ goldData.data?.gold || 0 }}</span> |
|
||||
</div> |
|
||||
<el-button class="button" text size="small" @click="handleGoWalletLog"> |
|
||||
详情 |
|
||||
<el-icon><ElIconArrowRight /></el-icon> |
|
||||
</el-button> |
|
||||
</div> |
|
||||
</template> |
|
||||
<el-button type="primary" @click="handleBuy">购买</el-button> |
|
||||
<el-button type="primary" @click="handleSign">{{ signData.data ? '已签到' : '签到领金币' }}</el-button> |
|
||||
</el-card> |
|
||||
</el-col> |
|
||||
<el-col :md="18" :xs="24" class="bg-fff"> |
|
||||
<el-tabs v-model="activeName"> |
|
||||
<el-tab-pane label="我的收藏" name="collect"> |
|
||||
<collect-data /> |
|
||||
</el-tab-pane> |
|
||||
</el-tabs> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import CollectData from '@/components/user/CollectData.vue'; |
|
||||
import UserInfoData from '~/components/user/UserInfoData.vue'; |
|
||||
import { useRequest } from '~/composables/useRequest'; |
|
||||
import { useClientRequest } from '~/composables/useClientRequest'; |
|
||||
|
|
||||
definePageMeta({ |
|
||||
middleware: ['auth'] |
|
||||
}); |
|
||||
|
|
||||
const activeName = ref<string>('collect'); |
|
||||
|
|
||||
const [{ data: signData, refresh }, { data: goldData, refresh: refreshGold }] = await Promise.all([ |
|
||||
// 获取用户是否签到 |
|
||||
useRequest<{ data: null | number }>('/user-sign/getSign'), |
|
||||
// 获取用户金币数量 |
|
||||
useRequest<{ data: { gold: number } }>('/user-wallet/findGold') |
|
||||
]); |
|
||||
|
|
||||
// 用户签到 |
|
||||
async function handleSign() { |
|
||||
await nextTick() |
|
||||
if (signData.value?.data) return; |
|
||||
const { code, data } = await useClientRequest<{ code: number; msg: string; data: any }>('/user-sign/sign'); |
|
||||
if (code === 200) { |
|
||||
refresh(); |
|
||||
refreshGold(); |
|
||||
ElMessage({ |
|
||||
message: `签到成功, ${data.signReward}`, |
|
||||
type: 'success' |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function handleBuy() { |
|
||||
ElMessage({ |
|
||||
message: '支付功能正在开发,敬请期待...', |
|
||||
type: 'info' |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
function handleGoWalletLog() { |
|
||||
navigateTo({ path: '/user/wallet-log' }); |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.user-index { |
|
||||
.integral { |
|
||||
margin: 10px 0; |
|
||||
.card-header { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
span { |
|
||||
color: #f66c25; |
|
||||
font-weight: bold; |
|
||||
font-size: 18px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@media (max-width: 768px) { |
|
||||
.user-index { |
|
||||
.integral { |
|
||||
margin: 10px -15px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
@ -1,88 +0,0 @@ |
|||||
<template> |
|
||||
<div class="container user-index"> |
|
||||
<el-row :gutter="30" class="mt-20"> |
|
||||
<el-col :md="6" :xs="24"> |
|
||||
<UserInfoData /> |
|
||||
</el-col> |
|
||||
<el-col :md="18" :xs="24" class="bg-fff"> |
|
||||
<client-only> |
|
||||
<el-tabs model-value="logs"> |
|
||||
<el-tab-pane label="金币记录" name="logs"> |
|
||||
<el-table :data="list"> |
|
||||
<el-table-column prop="type" label="类型"> |
|
||||
<template #default="scope"> |
|
||||
<el-tag :type="+scope.row.type === 1 ? 'success' : 'danger'">{{ |
|
||||
+scope.row.type === 1 ? '新增' : '扣除' |
|
||||
}}</el-tag> |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
<el-table-column prop="remark" label="说明" /> |
|
||||
<el-table-column prop="gold" label="金币" /> |
|
||||
<el-table-column prop="createTime" label="创建时间"> |
|
||||
<template #default="scope"> |
|
||||
{{ $dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }} |
|
||||
</template> |
|
||||
</el-table-column> |
|
||||
</el-table> |
|
||||
<div class="wallet-log__pagination"> |
|
||||
<el-pagination |
|
||||
background |
|
||||
layout="prev, pager, next" |
|
||||
:current-page="currentPage" |
|
||||
:page-size="queryParams.pageSize" |
|
||||
:pager-count="5" |
|
||||
:total="count" |
|
||||
@current-change="handleCurrentChange" |
|
||||
/> |
|
||||
</div> |
|
||||
</el-tab-pane> |
|
||||
</el-tabs> |
|
||||
</client-only> |
|
||||
</el-col> |
|
||||
</el-row> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { useClientRequest } from '~/composables/useClientRequest'; |
|
||||
|
|
||||
definePageMeta({ |
|
||||
middleware: ['auth'] |
|
||||
}); |
|
||||
|
|
||||
const list = ref([]); |
|
||||
const queryParams = reactive({ |
|
||||
pageNum: 1, |
|
||||
pageSize: 10 |
|
||||
}); |
|
||||
const count = ref(0); |
|
||||
const currentPage = ref<number>(1); |
|
||||
|
|
||||
onMounted(() => { |
|
||||
getList(); |
|
||||
}); |
|
||||
|
|
||||
async function getList() { |
|
||||
const { rows, total } = await useClientRequest<ResPage<any>>('/user-wallet/logs', { |
|
||||
query: queryParams |
|
||||
}); |
|
||||
list.value = rows; |
|
||||
count.value = total; |
|
||||
} |
|
||||
|
|
||||
function handleCurrentChange(page: number) { |
|
||||
currentPage.value = page; |
|
||||
queryParams.pageNum = page; |
|
||||
getList(); |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.wallet-log { |
|
||||
&__pagination { |
|
||||
padding: 20px; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
@ -1,6 +0,0 @@ |
|||||
export default defineNuxtPlugin(() => { |
|
||||
const hm = document.createElement('script'); |
|
||||
hm.src = 'https://hm.baidu.com/hm.js?9edadaa49ae4e9c979c6724865c04b05'; |
|
||||
const s = document.getElementsByTagName('script')[0]; |
|
||||
s.parentNode?.insertBefore(hm, s); |
|
||||
}); |
|
@ -1,20 +0,0 @@ |
|||||
export default defineNuxtPlugin(() => { |
|
||||
const runtimeConfig = useRuntimeConfig(); |
|
||||
return { |
|
||||
provide: { |
|
||||
titleRender: (msg: string) => `${msg} - ${runtimeConfig.public.globalTitle}` |
|
||||
} |
|
||||
}; |
|
||||
}); |
|
||||
|
|
||||
declare module '#app' { |
|
||||
interface NuxtApp { |
|
||||
$titleRender: string; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
declare module '@vue/runtime-core' { |
|
||||
interface ComponentCustomProperties { |
|
||||
$titleRender(msg: string): string; |
|
||||
} |
|
||||
} |
|
@ -1,18 +0,0 @@ |
|||||
.pay-tip-plugin { |
|
||||
background: rgba(0, 0, 0, 0.8); |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
position: relative; |
|
||||
z-index: 99; |
|
||||
display: none; |
|
||||
color: #ffffff; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
.pay-tip-plugin .el-button { |
|
||||
margin-top: 20px; |
|
||||
font-size: 14px; |
|
||||
padding: 15px 20px; |
|
||||
} |
|
@ -1,63 +0,0 @@ |
|||||
import PresetPlayer, { Plugin, Events } from 'xgplayer'; |
|
||||
import { IPluginOptions } from 'xgplayer/es/plugin/plugin'; |
|
||||
|
|
||||
// payTipPlugin.js
|
|
||||
export default class payTip extends Plugin { |
|
||||
private readonly arriveTime: (() => void) | undefined; |
|
||||
private readonly clickButton: (() => void) | undefined; |
|
||||
private readonly lookTime: number; |
|
||||
private readonly tip: string; |
|
||||
|
|
||||
// 插件的名称,将作为插件实例的唯一key值
|
|
||||
static get pluginName() { |
|
||||
return 'payTip'; |
|
||||
} |
|
||||
|
|
||||
static get defaultConfig() { |
|
||||
return { |
|
||||
lookTime: 60, |
|
||||
tip: '此为付费视频, 支持后继续观看?' |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
constructor(args: IPluginOptions | undefined) { |
|
||||
super(args); |
|
||||
this.lookTime = args?.config.lookTime; |
|
||||
this.tip = args?.config.tip; |
|
||||
this.arriveTime = args?.config.arriveTime; |
|
||||
this.clickButton = args?.config.clickButton; |
|
||||
} |
|
||||
|
|
||||
beforePlayerInit() { |
|
||||
// TODO 播放器调用start初始化播放源之前的逻辑
|
|
||||
} |
|
||||
|
|
||||
afterPlayerInit() { |
|
||||
// TODO 播放器调用start初始化播放源之后的逻辑
|
|
||||
} |
|
||||
|
|
||||
afterCreate() { |
|
||||
// 对当前插件根节点内部类名为.icon的元素绑定click事件
|
|
||||
this.bind('.el-button', 'click', this.clickButton); |
|
||||
|
|
||||
this.on(Events.TIME_UPDATE, (player: PresetPlayer) => { |
|
||||
if (+player.currentTime >= +this.lookTime) { |
|
||||
if (this.arriveTime && typeof this.arriveTime) { |
|
||||
this.arriveTime(); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
(this.find('.tip-text') as HTMLSpanElement).innerHTML = this.tip; |
|
||||
} |
|
||||
|
|
||||
destroy() { |
|
||||
this.unbind('.el-button', 'click', this.clickButton); |
|
||||
} |
|
||||
|
|
||||
render() { |
|
||||
return `<div id="pay-tip-plugin" class="pay-tip-plugin">
|
|
||||
<span class="tip-text"></span> |
|
||||
<button type="button" class="el-button el-button--primary el-button--small">支 付</button> |
|
||||
</div>`;
|
|
||||
} |
|
||||
} |
|
@ -1,3 +0,0 @@ |
|||||
export type UserMovieBase = { movieId: number; userId: number } | null; |
|
||||
|
|
||||
export type UserRate = ({ rate: number } & NonNullable<UserMovieBase>) | null; |
|
@ -1,25 +0,0 @@ |
|||||
export interface VideoInfo { |
|
||||
url: string; |
|
||||
} |
|
||||
|
|
||||
export interface Video { |
|
||||
poster: string; |
|
||||
name: string; |
|
||||
} |
|
||||
|
|
||||
export interface MovieVideo { |
|
||||
id: number; |
|
||||
title: string; |
|
||||
cover: string; |
|
||||
video: Video; |
|
||||
} |
|
||||
|
|
||||
export interface MovieVideoInfo { |
|
||||
movieId: number; |
|
||||
typeId: string; |
|
||||
title: string; |
|
||||
movie: MovieItem; |
|
||||
country: Item[]; |
|
||||
videoInfo: VideoInfo; |
|
||||
videos: MovieVideo[]; |
|
||||
} |
|
@ -1,13 +0,0 @@ |
|||||
export interface Config { |
|
||||
domain: string; |
|
||||
siteLogo: string; |
|
||||
siteName: string; |
|
||||
tel: string; |
|
||||
kefu2: string; |
|
||||
kefu1: string; |
|
||||
email: string; |
|
||||
icpNo: string; |
|
||||
copyright: string; |
|
||||
loginBgImg: string; |
|
||||
address: string; |
|
||||
} |
|
@ -1,133 +0,0 @@ |
|||||
export {}; |
|
||||
|
|
||||
declare global { |
|
||||
interface ResOptions<T> { |
|
||||
code: number; |
|
||||
msg: string; |
|
||||
data: T; |
|
||||
rows: T; |
|
||||
total: number; |
|
||||
} |
|
||||
|
|
||||
type ResPage<T> = Omit<ResOptions<T>, 'data'>; |
|
||||
|
|
||||
type ResData<T> = Omit<ResOptions<T>, 'rows' | 'total'>; |
|
||||
|
|
||||
interface ResBase { |
|
||||
createTime: string; |
|
||||
updateTime: string; |
|
||||
createBy: string; |
|
||||
updateBy: string; |
|
||||
remark: string; |
|
||||
} |
|
||||
|
|
||||
interface ColumnItem extends ResBase { |
|
||||
id: number; |
|
||||
name: string; |
|
||||
type: string; |
|
||||
value: string; |
|
||||
order: number; |
|
||||
status: string; |
|
||||
} |
|
||||
|
|
||||
interface Item { |
|
||||
id: number; |
|
||||
name: string; |
|
||||
} |
|
||||
|
|
||||
interface MoviePv { |
|
||||
pv: number; |
|
||||
} |
|
||||
|
|
||||
interface Actor { |
|
||||
name: string; |
|
||||
} |
|
||||
|
|
||||
interface CastItem { |
|
||||
id: number; |
|
||||
actor: Actor; |
|
||||
} |
|
||||
|
|
||||
interface MovieRate extends ResBase { |
|
||||
id: number; |
|
||||
movieId: number; |
|
||||
rate: number; |
|
||||
rateUserCount: number; |
|
||||
} |
|
||||
|
|
||||
interface MovieVideoItem extends ResBase { |
|
||||
id: number; |
|
||||
movieId: number; |
|
||||
typeId: number; |
|
||||
title: string; |
|
||||
videoId: number; |
|
||||
cover: string; |
|
||||
sort: number; |
|
||||
status: string; |
|
||||
} |
|
||||
|
|
||||
interface MovieItem extends ResBase { |
|
||||
id: number; |
|
||||
status: number; |
|
||||
releaseStatus: number; |
|
||||
releaseDate: string; |
|
||||
title: string; |
|
||||
poster: string; |
|
||||
bgColor: string; |
|
||||
titleEn: string; |
|
||||
titleOriginal: string; |
|
||||
akas: string; |
|
||||
columnValue: string; |
|
||||
genres: string; |
|
||||
year: string; |
|
||||
pubdate: string; |
|
||||
duration: string; |
|
||||
durations: string; |
|
||||
versions: string; |
|
||||
eggHunt: string; |
|
||||
color: string; |
|
||||
seasonCount: number; |
|
||||
currentSeason: number; |
|
||||
episodeCount: number; |
|
||||
currentEpisode: number; |
|
||||
theEnd: number; |
|
||||
countryIds: string; |
|
||||
languages: string; |
|
||||
tags: string; |
|
||||
summary: string; |
|
||||
isPay: number; |
|
||||
paymentAmount: number; |
|
||||
freeDuration: number; |
|
||||
country: Item[]; |
|
||||
casts: CastItem[]; |
|
||||
moviePv: MoviePv; |
|
||||
movieRate: MovieRate | null; |
|
||||
movieVideosCount: number; |
|
||||
userCollectsCount: number; |
|
||||
movieVideos?: MovieVideoItem[]; |
|
||||
} |
|
||||
interface RankMovieItem |
|
||||
extends Pick<MovieItem, 'updateTime' | 'id' | 'title' | 'columnValue' | 'currentEpisode' | 'theEnd' | 'moviePv'> {} |
|
||||
interface LeaderboardItem { |
|
||||
weekRank: RankMovieItem[]; |
|
||||
mouthRank: RankMovieItem[]; |
|
||||
} |
|
||||
interface CastListItem { |
|
||||
id: number; |
|
||||
role: string; |
|
||||
actorId: number; |
|
||||
actorAvatar: string; |
|
||||
actorName: string; |
|
||||
actorGender: string; |
|
||||
actorBirthday: string; |
|
||||
countryName: string; |
|
||||
professionId: number; |
|
||||
professionName: string; |
|
||||
professionMpath: string; |
|
||||
} |
|
||||
interface ColumnMovieItem extends Pick<ColumnItem, 'name' | 'type' | 'value'> { |
|
||||
genres: Item[]; |
|
||||
ranks: RankMovieItem[]; |
|
||||
rows: Pick<MovieItem, 'id' | 'title' | 'poster' | 'columnValue' | 'casts' | 'movieRate'>[]; |
|
||||
} |
|
||||
} |
|
@ -1,13 +0,0 @@ |
|||||
export interface BannerItem extends ResBase { |
|
||||
id: number; |
|
||||
img: string; |
|
||||
title: string; |
|
||||
url: string; |
|
||||
urlType: string; |
|
||||
} |
|
||||
|
|
||||
export interface LinkItem extends ResBase { |
|
||||
id: number; |
|
||||
text: string; |
|
||||
url: string; |
|
||||
} |
|
@ -1,91 +0,0 @@ |
|||||
/** |
|
||||
* axios 实例 |
|
||||
*/ |
|
||||
import axios from 'axios'; |
|
||||
import type { AxiosResponse } from 'axios'; |
|
||||
import {API_BASE_URL, TENANT_ID, TOKEN_HEADER_NAME} from '~/config'; |
|
||||
import { getToken, setToken } from './token-util'; |
|
||||
import type { ApiResult } from '@/api'; |
|
||||
import { getHostname, getTenantId } from '@/utils/domain'; |
|
||||
|
|
||||
const service = axios.create({ |
|
||||
baseURL: API_BASE_URL |
|
||||
}); |
|
||||
|
|
||||
/** |
|
||||
* 添加请求拦截器 |
|
||||
*/ |
|
||||
service.interceptors.request.use( |
|
||||
(config) => { |
|
||||
const tid = TENANT_ID; |
|
||||
const token = getToken(); |
|
||||
// 添加 token 到 header
|
|
||||
if (token && config.headers) { |
|
||||
config.headers.common[TOKEN_HEADER_NAME] = token; |
|
||||
} |
|
||||
// 获取租户ID
|
|
||||
if (config.headers) { |
|
||||
// 附加企业ID
|
|
||||
const companyId = localStorage.getItem('CompanyId'); |
|
||||
if (companyId) { |
|
||||
config.headers.common['CompanyId'] = companyId; |
|
||||
} |
|
||||
// 通过网站域名获取租户ID
|
|
||||
// if (getHostname()) {
|
|
||||
// config.headers.common['Domain'] = getHostname();
|
|
||||
// }
|
|
||||
// 解析二级域名获取租户ID
|
|
||||
// if (getTenantId()) {
|
|
||||
// config.headers.common['TenantId'] = getTenantId();
|
|
||||
// console.log('domain', getTenantId());
|
|
||||
// return config;
|
|
||||
// }
|
|
||||
if (TENANT_ID) { |
|
||||
console.log(tid,TENANT_ID); |
|
||||
config.headers['TenantId'] = TENANT_ID; |
|
||||
return config; |
|
||||
} |
|
||||
} |
|
||||
return config; |
|
||||
}, |
|
||||
(error) => { |
|
||||
return Promise.reject(error); |
|
||||
} |
|
||||
); |
|
||||
|
|
||||
/** |
|
||||
* 添加响应拦截器 |
|
||||
*/ |
|
||||
service.interceptors.response.use( |
|
||||
(res: AxiosResponse<ApiResult<unknown>>) => { |
|
||||
// 登录过期处理
|
|
||||
// if (res.data?.code === 401) {
|
|
||||
// const currentPath = unref(router.currentRoute).path;
|
|
||||
// if (currentPath == LAYOUT_PATH) {
|
|
||||
// logout(true);
|
|
||||
// } else {
|
|
||||
// Modal.destroyAll();
|
|
||||
// Modal.info({
|
|
||||
// title: '系统提示',
|
|
||||
// content: '登录状态已过期, 请退出重新登录!',
|
|
||||
// okText: '重新登录',
|
|
||||
// onOk: () => {
|
|
||||
// logout(false, currentPath);
|
|
||||
// }
|
|
||||
// });
|
|
||||
// }
|
|
||||
// return Promise.reject(new Error(res.data.message));
|
|
||||
// }
|
|
||||
// token 自动续期
|
|
||||
const token = res.headers[TOKEN_HEADER_NAME.toLowerCase()]; |
|
||||
if (token) { |
|
||||
setToken(token); |
|
||||
} |
|
||||
return res; |
|
||||
}, |
|
||||
(error) => { |
|
||||
return Promise.reject(error); |
|
||||
} |
|
||||
); |
|
||||
|
|
||||
export default service; |
|
@ -1,71 +0,0 @@ |
|||||
/** |
|
||||
* token 操作封装 |
|
||||
*/ |
|
||||
import { APP_SECRET, TOKEN_STORE_NAME } from '@/config'; |
|
||||
import md5 from 'js-md5'; |
|
||||
|
|
||||
/** |
|
||||
* 获取缓存的 token |
|
||||
*/ |
|
||||
export function getToken(): string | null { |
|
||||
const token = localStorage.getItem(TOKEN_STORE_NAME); |
|
||||
if (!token) { |
|
||||
return sessionStorage.getItem(TOKEN_STORE_NAME); |
|
||||
} |
|
||||
return token; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 缓存 token |
|
||||
* @param token token |
|
||||
* @param remember 是否永久存储 |
|
||||
*/ |
|
||||
export function setToken(token?: string, remember?: boolean) { |
|
||||
removeToken(); |
|
||||
if (token) { |
|
||||
if (remember) { |
|
||||
localStorage.setItem(TOKEN_STORE_NAME, token); |
|
||||
} else { |
|
||||
sessionStorage.setItem(TOKEN_STORE_NAME, token); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 移除 token |
|
||||
*/ |
|
||||
export function removeToken() { |
|
||||
localStorage.removeItem(TOKEN_STORE_NAME); |
|
||||
sessionStorage.removeItem(TOKEN_STORE_NAME); |
|
||||
localStorage.clear(); |
|
||||
} |
|
||||
|
|
||||
// 封装签名
|
|
||||
export function getSign(form) { |
|
||||
if (form == null) { |
|
||||
return false; |
|
||||
} |
|
||||
let sign = ''; |
|
||||
form.timestamp = new Date().getTime(); |
|
||||
form.version = 'v3'; |
|
||||
const arr = objKeySort(form); |
|
||||
Object.keys(arr).forEach((k) => { |
|
||||
if (form[k] != null && form[k] != '') { |
|
||||
sign = sign.concat(form[k]).concat('-'); |
|
||||
} |
|
||||
}); |
|
||||
sign = sign.concat(APP_SECRET); |
|
||||
return md5(sign); |
|
||||
} |
|
||||
// 参数按照字母顺序排序
|
|
||||
export const objKeySort = (obj) => { |
|
||||
//排序的函数
|
|
||||
const newkey = Object.keys(obj).sort(); |
|
||||
//先用Object内置类的keys方法获取要排序对象的属性名,再利用Array原型上的sort方法对获取的属性名进行排序,newkey是一个数组
|
|
||||
const newObj = {}; //创建一个新的对象,用于存放排好序的键值对
|
|
||||
for (let i = 0; i < newkey.length; i++) { |
|
||||
//遍历newkey数组
|
|
||||
newObj[newkey[i]] = obj[newkey[i]]; //向新创建的对象中按照排好的顺序依次增加键值对
|
|
||||
} |
|
||||
return newObj; //返回排好序的新对象
|
|
||||
}; |
|
Binary file not shown.
Loading…
Reference in new issue