Browse Source

框架整理完毕^_^

master
科技小王子 10 months ago
parent
commit
e1b07f82c3
  1. 6
      components/AppFooter.vue
  2. 6
      components/AppHeader.vue
  3. 75
      components/user/CollectData.vue
  4. 70
      components/user/UserInfoData.vue
  5. 12
      composables/states.ts
  6. 39
      composables/useRequest.ts
  7. 31
      composables/useServerRequest.ts
  8. 2
      ecosystem.config.cjs
  9. 49
      nuxt.config.ts
  10. 4
      package.json
  11. 4
      pages/[custom]/index.vue
  12. 4
      pages/article/[id].vue
  13. 4
      pages/article/detail/[id].vue
  14. 39
      pages/case/index.vue
  15. 72
      pages/column/[column]/index.vue
  16. 386
      pages/column/[column]/movie/[id].vue
  17. 258
      pages/column/[column]/show/index.vue
  18. 364
      pages/column/[column]/video/[id].vue
  19. 4
      pages/demo/[name].vue
  20. 6
      pages/index.vue
  21. 4
      pages/product/[name].vue
  22. 109
      pages/search/index.vue
  23. 112
      pages/user/index.vue
  24. 107
      pages/user/index2.vue
  25. 88
      pages/user/wallet-log.vue
  26. 6
      plugins/baidu.client.ts
  27. 20
      plugins/titleRender.ts
  28. 18
      plugins/xgplayer/payTip/index.css
  29. 63
      plugins/xgplayer/payTip/index.ts
  30. 3
      types/column/movie.d.ts
  31. 25
      types/column/video.d.ts
  32. 13
      types/config/index.d.ts
  33. 133
      types/global.d.ts
  34. 13
      types/index/index.d.ts
  35. 91
      utils/request.ts
  36. 71
      utils/token-util.ts
  37. 2
      utils/tool.ts
  38. BIN
      归档2.zip

6
components/AppFooter.vue

@ -1,6 +1,6 @@
<template> <template>
<div class="h-[100px]"></div> <div class="h-[100px]"></div>
<div class="text-center bg-white text-red-7 py-10">
<div class="text-center bg-white text-red-7 py-10" style="display: none">
本网站为小象CMS演示站提供的电视剧和电影资源均系收集于各大视频网站<br /> 本网站为小象CMS演示站提供的电视剧和电影资源均系收集于各大视频网站<br />
若本站收录的节目无意侵犯了贵司版权,请给我们留言,我们会及时逐步删除和规避程序自动搜索采集到的不提供分享的版权影视<br /> 若本站收录的节目无意侵犯了贵司版权,请给我们留言,我们会及时逐步删除和规避程序自动搜索采集到的不提供分享的版权影视<br />
本站仅供测试和学习交流请大家支持正版 本站仅供测试和学习交流请大家支持正版
@ -20,14 +20,14 @@
<script setup lang="ts"> <script setup lang="ts">
// //
import type {Config} from "~/types/config"; import type {Config} from "~/types/config";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
const config = ref<Config>() const config = ref<Config>()
// //
const reload = async () => { const reload = async () => {
const { data: fields } = await useRequest<ApiResult<Config>>('/cms/website-field/config', {})
const { data: fields } = await useServerRequest<ApiResult<Config>>('/cms/website-field/config', {})
config.value = fields.value?.data; config.value = fields.value?.data;
ElMessage.success('刷新成功') ElMessage.success('刷新成功')
} }

6
components/AppHeader.vue

@ -86,7 +86,7 @@
import type {Config} from "~/types/config"; import type {Config} from "~/types/config";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import type {Navigation} from "~/api/cms/navigation/model"; import type {Navigation} from "~/api/cms/navigation/model";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
const route = useRoute() const route = useRoute()
@ -131,9 +131,9 @@ function handleSelect(key: string, keyPath: any) {
// //
const reload = async () => { const reload = async () => {
await nextTick() await nextTick()
const { data: fields } = await useRequest<ApiResult<Config>>('/cms/website-field/config', {})
const { data: fields } = await useServerRequest<ApiResult<Config>>('/cms/website-field/config', {})
config.value = fields.value?.data; config.value = fields.value?.data;
const { data: navigation } = await useRequest<ApiResult<Navigation[]>>('/cms/navigation/tree',{query: {
const { data: navigation } = await useServerRequest<ApiResult<Navigation[]>>('/cms/navigation/tree',{query: {
position: 1 position: 1
}}); }});
navigations.value = navigation.value?.data; navigations.value = navigation.value?.data;

75
components/user/CollectData.vue

@ -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>

70
components/user/UserInfoData.vue

@ -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>

12
composables/states.ts

@ -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);

39
composables/useRequest.ts

@ -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);
};

31
composables/useServerRequest.ts

@ -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);
}; };

2
ecosystem.config.cjs

@ -2,7 +2,7 @@ module.exports = {
apps: [ apps: [
{ {
name: '网宿软件', name: '网宿软件',
port: '10123',
port: '16880',
exec_mode: 'cluster', exec_mode: 'cluster',
instances: 'max', instances: 'max',
script: './.output/server/index.mjs' script: './.output/server/index.mjs'

49
nuxt.config.ts

@ -1,4 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
import {domain} from "~/config";
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: '2024-04-03', compatibilityDate: '2024-04-03',
devtools: {enabled: false}, devtools: {enabled: false},
@ -12,45 +14,30 @@ export default defineNuxtConfig({
app: { app: {
head: { head: {
viewport: 'width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no', viewport: 'width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no',
title: '网宿软件 - 基于Java Spring、Vue3、Antd、Nuxt构建的现代WEB开发框架',
title: 'websoft官网 - 快速搭建现代WEB应用的开发平台,基于Java、Vue3、TS开发,支持多端分离',
meta: [ meta: [
{ name: 'keywords', content: '小象CMS,websoftCMS,企业网站,网站源码,java,nuxt,antd,vue3' },
{ name: 'keywords', content: 'websoft,小象CMS,企业网站,网站源码,java,nuxt,antd,vue3' },
{ {
name: 'description', name: 'description',
content: '网宿软件'
content: 'websoft.top是南宁市网宿信息科技有限公司研发的在线企业应用平台,助力企业信息化建设和转型,主要产品有:企业官网,电商系统,微信公众号,微信小程序应用等,使用目前最流行的技术栈打造。'
} }
], ],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }] link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
} }
}, },
devServer: { devServer: {
port: 10123
port: 16880
}, },
// nitro: {
// devProxy: {
// "/api": {
// target: 'http://127.0.0.1:9001/api',
// prependPath: true,
// changeOrigin: true,
// }
//
// }
// }
// runtimeConfig: {
// public: {
// apiBase: '/api',
// globalTitle: '网宿软件'
// }
// },
// nitro: {
// // 该配置用于服务端请求转发
// routeRules: {
// '/api/**': {
// proxy: 'https://modules.gxwebsoft.com/api/**'
// }
// }
// }
runtimeConfig: {
public: {
// 一般只需修改租户号和接口地址
tenantId: '5',
domain: 'websoft.top',
apiBase: 'https://modules.gxwebsoft.com/api',
// 以下一般不需要修改
apiServer: 'https://server.gxwebsoft.com/api',
globalTitle: '网宿软件',
},
// 私有配置项
}
}) })

4
package.json

@ -3,8 +3,8 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"build": "nuxt build --dotenv .env.production",
"dev": "nuxt dev --dotenv .env.development",
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare", "postinstall": "nuxt prepare",

4
pages/[custom]/index.vue

@ -22,7 +22,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type {Design} from "~/api/cms/design/model"; import type {Design} from "~/api/cms/design/model";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
const route = useRoute(); const route = useRoute();
const { query, params } = route; const { query, params } = route;
@ -32,7 +32,7 @@ const { custom: pageName} = params;
const form = ref<Design | any>(); const form = ref<Design | any>();
// //
const { data: design } = await useRequest<ApiResult<Design[]>>('/cms/design', {params: {
const { data: design } = await useServerRequest<ApiResult<Design[]>>('/cms/design', {params: {
path: `/${pageName}` path: `/${pageName}`
}}) }})

4
pages/article/[id].vue

@ -64,7 +64,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type {ApiResult, PageResult} from "~/api"; import type {ApiResult, PageResult} from "~/api";
import type {Article} from "~/api/cms/article/model"; import type {Article} from "~/api/cms/article/model";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
import type {Design} from "~/api/cms/design/model"; import type {Design} from "~/api/cms/design/model";
import {useClientRequest} from "~/composables/useClientRequest"; import {useClientRequest} from "~/composables/useClientRequest";
import {pageArticle} from "~/api/cms/article"; import {pageArticle} from "~/api/cms/article";
@ -94,7 +94,7 @@ const load = () => {
// //
const reload = async () => { const reload = async () => {
await useRequest<ApiResult<PageResult<Article>>>('/cms/article/page',{
await useServerRequest<ApiResult<PageResult<Article>>>('/cms/article/page',{
params: { params: {
page: page.value, page: page.value,
categoryId: id categoryId: id

4
pages/article/detail/[id].vue

@ -28,7 +28,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
import type {Goods} from "~/api/shop/goods/model"; import type {Goods} from "~/api/shop/goods/model";
import type {Article} from "~/api/cms/article/model"; import type {Article} from "~/api/cms/article/model";
@ -43,7 +43,7 @@ const rate = ref(4);
const form = ref<Article | any>(); const form = ref<Article | any>();
// //
const { data: info } = await useRequest<ApiResult<Article>>('/cms/article/' + id)
const { data: info } = await useServerRequest<ApiResult<Article>>('/cms/article/' + id)
form.value = info.value?.data; form.value = info.value?.data;
// form.value.files = JSON.parse(form.value.files); // form.value.files = JSON.parse(form.value.files);
form.value.num = 1; form.value.num = 1;

39
pages/case/index.vue

@ -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>

72
pages/column/[column]/index.vue

@ -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 }}&nbsp; </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>

386
pages/column/[column]/movie/[id].vue

@ -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 }} &nbsp; </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>

258
pages/column/[column]/show/index.vue

@ -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>

364
pages/column/[column]/video/[id].vue

@ -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 }}&nbsp;</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>

4
pages/demo/[name].vue

@ -101,7 +101,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
import type {Goods} from "~/api/shop/goods/model"; import type {Goods} from "~/api/shop/goods/model";
const route = useRoute(); const route = useRoute();
@ -115,7 +115,7 @@ const rate = ref(4);
const form = ref<Goods | any>(); const form = ref<Goods | any>();
// //
const { data: info } = await useRequest<ApiResult<Goods>>('/shop/goods/' + productName)
const { data: info } = await useServerRequest<ApiResult<Goods>>('/shop/goods/' + productName)
form.value = info.value?.data; form.value = info.value?.data;
form.value.files = JSON.parse(form.value.files); form.value.files = JSON.parse(form.value.files);
form.value.num = 1; form.value.num = 1;

6
pages/index.vue

@ -95,7 +95,7 @@
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import type {AdItem} from "~/api/cms/ad/model"; import type {AdItem} from "~/api/cms/ad/model";
import type {Link} from "~/api/cms/link/model"; import type {Link} from "~/api/cms/link/model";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
const banner = ref<any>(); const banner = ref<any>();
const links = ref<any>(); const links = ref<any>();
@ -104,8 +104,8 @@ const movieList = ref<any>();
// //
const reload = async () => { const reload = async () => {
await nextTick() await nextTick()
const getSlide = useRequest<ApiResult<AdItem[]>>('/cms/ad/side');
const getLink = useRequest<ApiResult<Link[]>>('/oa/link?linkType=友情链接');
const getSlide = useServerRequest<ApiResult<AdItem[]>>('/cms/ad/side');
const getLink = useServerRequest<ApiResult<Link[]>>('/oa/link?linkType=友情链接');
const [{data: slide}, {data: link}] = await Promise.all([getSlide, getLink]); const [{data: slide}, {data: link}] = await Promise.all([getSlide, getLink]);
banner.value = slide.value?.data; banner.value = slide.value?.data;
links.value = link.value?.data; links.value = link.value?.data;

4
pages/product/[name].vue

@ -96,7 +96,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
import {useServerRequest} from "~/composables/useServerRequest";
import type {Goods} from "~/api/shop/goods/model"; import type {Goods} from "~/api/shop/goods/model";
const route = useRoute(); const route = useRoute();
@ -110,7 +110,7 @@ const rate = ref(4);
const form = ref<Goods | any>(); const form = ref<Goods | any>();
// //
const { data: info } = await useRequest<ApiResult<Goods>>('/shop/goods/' + productName)
const { data: info } = await useServerRequest<ApiResult<Goods>>('/shop/goods/' + productName)
form.value = info.value?.data; form.value = info.value?.data;
form.value.files = JSON.parse(form.value.files); form.value.files = JSON.parse(form.value.files);
form.value.num = 1; form.value.num = 1;

109
pages/search/index.vue

@ -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>

112
pages/user/index.vue

@ -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>

107
pages/user/index2.vue

@ -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>

88
pages/user/wallet-log.vue

@ -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>

6
plugins/baidu.client.ts

@ -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);
});

20
plugins/titleRender.ts

@ -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;
}
}

18
plugins/xgplayer/payTip/index.css

@ -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;
}

63
plugins/xgplayer/payTip/index.ts

@ -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>`;
}
}

3
types/column/movie.d.ts

@ -1,3 +0,0 @@
export type UserMovieBase = { movieId: number; userId: number } | null;
export type UserRate = ({ rate: number } & NonNullable<UserMovieBase>) | null;

25
types/column/video.d.ts

@ -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[];
}

13
types/config/index.d.ts

@ -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;
}

133
types/global.d.ts

@ -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'>[];
}
}

13
types/index/index.d.ts

@ -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;
}

91
utils/request.ts

@ -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;

71
utils/token-util.ts

@ -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; //返回排好序的新对象
};

2
utils/tool.ts

@ -18,7 +18,7 @@ export function isArray(str: unknown) {
export function getBaseUrl() { export function getBaseUrl() {
console.log('process:',process.server) console.log('process:',process.server)
if (process.server) { if (process.server) {
return "http://127.0.0.1:9001/api"
return "https://modules.gxwebsoft.com/api"
} else { } else {
return "https://modules.gxwebsoft.com/api" return "https://modules.gxwebsoft.com/api"
} }

BIN
归档2.zip

Binary file not shown.
Loading…
Cancel
Save