Browse Source

终于搞通网络请求部分

master
科技小王子 10 months ago
parent
commit
87b7d7a16e
  1. 2
      api/cms/ad/index.ts
  2. 2
      api/cms/article/index.ts
  3. 2
      api/cms/article/model/index.ts
  4. 2
      api/cms/category/index.ts
  5. 2
      api/cms/design/index.ts
  6. 2
      api/cms/docs-book/index.ts
  7. 2
      api/cms/docs-content/index.ts
  8. 2
      api/cms/docs/index.ts
  9. 2
      api/cms/domain/index.ts
  10. 2
      api/cms/form-record/index.ts
  11. 2
      api/cms/form/index.ts
  12. 2
      api/cms/link/index.ts
  13. 2
      api/cms/mp-menu/index.ts
  14. 2
      api/cms/mp/index.ts
  15. 2
      api/cms/mpAd/index.ts
  16. 2
      api/cms/mpField/index.ts
  17. 2
      api/cms/mpPages/index.ts
  18. 19
      api/cms/navigation/index.ts
  19. 20
      api/cms/website/field/index.ts
  20. 2
      api/cms/website/index.ts
  21. 2
      api/layout/index.ts
  22. 2
      api/passport/login/index.ts
  23. 4
      assets/css/main.css
  24. BIN
      assets/images/icon_1.png
  25. BIN
      assets/images/icon_12.png
  26. BIN
      assets/images/icon_2.png
  27. BIN
      assets/images/icon_26.png
  28. BIN
      assets/images/icon_30.png
  29. BIN
      assets/images/jlt.png
  30. BIN
      assets/images/toux.png
  31. BIN
      assets/logo.png
  32. 3
      assets/scss/var.scss
  33. 14
      components/AppFooter.vue
  34. 208
      components/AppHeader.vue
  35. 75
      components/user/CollectData.vue
  36. 70
      components/user/UserInfoData.vue
  37. 12
      composables/states.ts
  38. 14
      composables/useClientRequest.ts
  39. 39
      composables/useRequest.ts
  40. 21
      composables/useServerRequest.ts
  41. 10
      config/index.ts
  42. 11
      ecosystem.config.cjs
  43. 43
      nuxt.config.ts
  44. 16
      package.json
  45. 61
      pages/[custom]/index.vue
  46. 88
      pages/article/[categoryId].vue
  47. 67
      pages/article/detail/[id].vue
  48. 6
      pages/case/index.vue
  49. 6
      pages/column/[column]/index.vue
  50. 23
      pages/column/[column]/movie/[id].vue
  51. 12
      pages/column/[column]/show/index.vue
  52. 16
      pages/column/[column]/video/[id].vue
  53. 82
      pages/index.vue
  54. 61
      pages/plug/index.vue
  55. 76
      pages/product/[name].vue
  56. 61
      pages/product/index.vue
  57. 4
      pages/search/index.vue
  58. 7
      pages/user/index.vue
  59. 6
      plugins/baidu.client.ts
  60. 20
      plugins/titleRender.ts
  61. 18
      plugins/xgplayer/payTip/index.css
  62. 63
      plugins/xgplayer/payTip/index.ts
  63. 1079
      pnpm-lock.yaml
  64. BIN
      public/logo.png
  65. 6
      store/index.ts
  66. 134
      store/modules/chat.ts
  67. 40
      store/modules/params.ts
  68. 85
      store/modules/tenant.ts
  69. 707
      store/modules/theme.ts
  70. 128
      store/modules/user.ts
  71. 6
      utils/domain.ts
  72. 254
      utils/page-tab-util.ts
  73. 60
      utils/request.ts
  74. 2
      utils/token-util.ts
  75. 9
      utils/tool.ts
  76. BIN
      归档.zip

2
api/cms/ad/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Ad, AdParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
* 广

2
api/cms/article/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Article, ArticleParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/article/model/index.ts

@ -14,6 +14,8 @@ export interface Article {
showType?: any;
// 文章类型
categoryId?: number;
// 文章分类
categoryName?: string;
// 封面图
image?: string;
// 附件

2
api/cms/category/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ArticleCategory, ArticleCategoryParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/design/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Design, DesignParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/docs-book/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { DocsBook, DocsBookParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/docs-content/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { DocsContent, DocsContentParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/docs/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Docs, DocsParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/domain/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Domain, DomainParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/form-record/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { FormRecord, FormRecordParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/form/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Form, FormParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/link/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Link, LinkParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/mp-menu/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { MpMenu, MpMenuParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/mp/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Mp, MpParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/mpAd/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { MpAd, MpAdParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
* 广

2
api/cms/mpField/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { MpField, MpFieldParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/cms/mpPages/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { MpPages, MpPagesParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

19
api/cms/navigation/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Navigation, NavigationParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*
@ -141,3 +141,20 @@ export async function checkExistence(
}
return Promise.reject(new Error(res.data.message));
}
/**
*
* @param params
*/
export async function treeNavigation(params?: NavigationParam) {
const res = await request.get<ApiResult<Navigation[]>>(
MODULES_API_URL + '/cms/navigation/tree',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

20
api/cms/website/field/index.ts

@ -4,7 +4,8 @@ import type {
WebsiteField,
WebsiteFieldParam
} from '@/api/cms/website/field/model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
import type {Config} from "~/types/config";
/**
*
@ -140,3 +141,20 @@ export async function undeleteWebsiteField(id?: number) {
}
return Promise.reject(new Error(res.data.message));
}
/**
* ()
*/
export async function getSiteConfig(params: WebsiteFieldParam) {
const res = await request.get<ApiResult<unknown>>(
MODULES_API_URL + '/cms/website-field/config',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

2
api/cms/website/index.ts

@ -1,7 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Website, WebsiteParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
import { MODULES_API_URL } from '~/config';
/**
*

2
api/layout/index.ts

@ -2,7 +2,7 @@ import request from '@/utils/request';
import type { ApiResult } from '@/api';
import type { User } from '@/api/system/user/model';
import type { UpdatePasswordParam, NoticeResult } from './model';
import { MODULES_API_URL, SERVER_API_URL } from '@/config/setting';
import { MODULES_API_URL, SERVER_API_URL } from '~/config';
import type {Website} from "~/api/cms/website/model";
/**

2
api/passport/login/index.ts

@ -7,7 +7,7 @@ import type {
CaptchaResult,
SmsCaptchaResult
} from './model';
import { SERVER_API_URL } from '@/config/setting';
import { SERVER_API_URL } from '~/config';
/**
*

4
assets/css/main.css

@ -5,7 +5,7 @@
body {
margin: 0;
font-family: "Microsoft YaHei", "微软雅黑", "STHeiti", "WenQuanYi Micro Hei", SimSun, sans-serif;
font-family: Inter, "Inter Fallback: Arial",ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
font-size: 14px;
line-height: 140%;
}
@ -28,7 +28,7 @@ h1, h2, h3, h4, h5, h6 {
}
.container {
width: 1380px;
width: 1280px !important;
margin: 0 auto;
}

BIN
assets/images/icon_1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/icon_12.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/images/icon_2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/images/icon_26.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/images/icon_30.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/images/jlt.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
assets/images/toux.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

3
assets/scss/var.scss

@ -0,0 +1,3 @@
$drak-blue: #292838;
$light-gray: #666;

14
components/AppFooter.vue

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

208
components/AppHeader.vue

@ -1,25 +1,75 @@
<template>
<header class="header">
<header class="header bg-black">
<div class="container between">
<div class="header__left">
<nuxt-link to="/" class="logo">{{ config?.siteName || '网站名称' }}</nuxt-link>
<nav v-if="route.path.indexOf('/user') === -1" class="hidden-sm-and-down">
<ul>
<li
v-for="item in navigation?.data"
:key="item.navigationId"
:class="route.path === item.path ? 'active' : ''"
>
<nuxt-link v-if="item.target === '_blank'" :to="item.path" target="_blank">{{ item.title }}</nuxt-link>
<nuxt-link v-else :to="`${item.path}`">{{ item.title }}</nuxt-link>
</li>
</ul>
</nav>
<div class="header__left flex">
<div class="logo w-[180px] flex items-center" @click="reload">
<nuxt-link v-if="config?.siteLogo" to="/">
<el-image :src="config.siteLogo" shape="square" :alt="config.siteName" :title="config.siteName" class="w-[120px]" />
</nuxt-link>
<nuxt-link v-else to="/">
<text>{{ config?.siteName || '网宿软件' }}</text>
</nuxt-link>
</div>
<div v-if="route.path.indexOf('/user') === -1" class="hidden-sm-and-down">
<el-menu
:default-active="currentIndex"
mode="horizontal"
background-color="#000000"
text-color="#fff"
active-text-color="#ffd04b"
:collapse="true"
:ellipsis="false"
@select="handleSelect"
>
<template v-for="(item, index) in navigations">
<el-menu-item :index="item.path" :key="index" v-if="index < visibleNumber"
>
<template v-if="item.chd">
<template slot="title">我的工作台</template>
</template>
<template v-else>
<text class="text-[17px]">{{ item.title }}</text>
</template>
</el-menu-item
>
</template>
<!-- 顶部菜单超出数量折叠 -->
<el-sub-menu index="more" v-if="navigations && navigations.length > visibleNumber">
<template #title>更多菜单</template>
<template v-for="(item, index) in navigations">
<el-menu-item
:index="item.path"
:key="index"
v-if="index >= visibleNumber"
>
{{ item.title }}</el-menu-item
>
</template>
</el-sub-menu>
</el-menu>
</div>
<!-- <nav v-if="route.path.indexOf('/user') === -1" class="hidden-sm-and-down">-->
<!-- <ul>-->
<!-- <li-->
<!-- v-for="item in navigations"-->
<!-- :key="item.navigationId"-->
<!-- class="relative"-->
<!-- :class="route.path === item.path ? 'active' : ''"-->
<!-- >-->
<!-- <nuxt-link v-if="item.target === '_blank'" :to="item.path" target="_blank">{{ item.title }}</nuxt-link>-->
<!-- <nuxt-link v-else :to="`${item.path}`">{{ item.title }}</nuxt-link>-->
<!-- <div class="absolute p-2 h-[100px]" v-if="item.children">-->
<!-- <nuxt-link v-for="(sub,index2) in item.children" :to="`${sub.path}`">{{ sub.title }}</nuxt-link>-->
<!-- </div>-->
<!-- </li>-->
<!-- </ul>-->
<!-- </nav>-->
</div>
<div class="header__right items-center">
<el-input
class="w-50 m-2 mr-20"
placeholder="请输入搜索的影视名"
placeholder="搜索应用名称"
:suffix-icon="ElIconSearch"
v-model="searchValue"
@keyup.enter.native="handleSearch"
@ -49,6 +99,7 @@
</ul>
</div>
</header>
<div class="header__height__placeholder"></div>
</template>
@ -56,14 +107,20 @@
import type {Config} from "~/types/config";
import type {ApiResult} from "~/api";
import type {Navigation} from "~/api/cms/navigation/model";
import {useRequest} from "~/composables/useRequest";
const route = useRoute()
const searchValue = ref<string>('')
const searchValue = ref<string>()
const token = ref<string | undefined>(undefined)
//
const { data: config } = await useServerRequest<Config>('/cms/website-field/config', {})
const { data: navigation } = useServerRequest<ApiResult<Navigation[]>>('/cms/navigation');
const navigations = ref<Navigation[] | any>([])
const config = ref<Config | any>({
siteLogo: '/logo.png'
})
//
const visibleNumber = ref<number>(10);
// index
const currentIndex = ref<string>('2');
function handleCommand(command: string) {
switch (command) {
@ -80,104 +137,31 @@ function logOut() {}
function goLogin() {}
function handleSearch() {
const handleSearch = (key: string, keyPath: string) => {
console.log(key, keyPath);
navigateTo('/search?keyword=' + searchValue.value)
}
</script>
<style lang="scss">
.header {
position: fixed;
top: 0;
z-index: 999;
width: 100%;
height: 55px;
background-color: #292838;
&__left {
display: flex;
.logo {
display: flex;
width: 150px;
height: 55px;
line-height: 55px;
font-size: 24px;
color: #FF9900;
font-weight: bold;
background-position: 50% 50% !important;
background-size: cover !important;
overflow: hidden;
}
nav {
ul {
display: flex;
li {
a {
display: inline-block;
height: 55px;
line-height: 55px;
font-size: 15px;
padding: 0 20px;
color: #fff;
}
&.active {
a {
background-color: #155FAA;
color: #fff;
}
}
}
}
}
}
&__height__placeholder {
height: 55px;
}
function handleSelect(key: string, keyPath: any) {
currentIndex.value = key;
navigateTo(`${key}`)
}
@media only screen and (max-width:991px){
.header {
position: relative;
.mobile-nav {
border-top: #666 solid 1px;
background: #292838;
position: absolute;
height: 46px;
bottom: -46px;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
box-sizing: border-box;
padding-top: 8px;
z-index: 9;
&::-webkit-scrollbar {
display: none;
}
ul {
white-space: nowrap;
li {
display: inline-block;
position: relative;
&.active {
a {
color: #1583f3;
}
}
a {
display: inline-block;
padding: 5px 19px;
color: #ffffff;
font-size: 16px;
}
}
}
}
}
.header__height__placeholder {
height: 46px;
}
//
const reload = async () => {
await nextTick()
const { data: fields } = await useRequest<ApiResult<Config>>('/cms/website-field/config', {})
config.value = fields.value?.data;
const { data: navigation } = await useRequest<ApiResult<Navigation[]>>('/cms/navigation/tree',{query: {
position: 1
}});
navigations.value = navigation.value?.data;
}
reload();
</script>
<style lang="scss">
</style>

75
components/user/CollectData.vue

@ -0,0 +1,75 @@
<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

@ -0,0 +1,70 @@
<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

@ -0,0 +1,12 @@
/** 用户信息 **/
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);

14
composables/useClientRequest.ts

@ -1,4 +1,5 @@
import { isArray } from '~/utils/tool';
import {MODULES_API_URL, TENANT_ID} from "~/config";
type FetchType = typeof $fetch;
export type FetchOptions = Parameters<FetchType>[1];
@ -8,22 +9,23 @@ export const useClientRequest = <T = unknown>(url: string, opts?: FetchOptions)
const runtimeConfig = useRuntimeConfig();
const defaultOptions: FetchOptions = {
baseURL: runtimeConfig.public.apiBase,
baseURL: MODULES_API_URL,
onRequest({ options }) {
options.headers = (options.headers || {}) as { [key: string]: string };
if (token.value) {
options.headers.Authorization = 'Bearer ' + token.value;
options.headers.Authorization = token.value;
options.headers.Tenantd = '5';
}
},
onResponse({ response }) {
if (+response.status === 200 && +response._data.code !== 200) {
ElMessage.error(response._data.msg);
if (+response.status === 0 && +response._data.code !== 0) {
ElMessage.error(response._data.message);
}
},
onResponseError({ response }) {
ElMessage.error(isArray(response._data.data.msg) ? response._data.data.msg[0] : response._data.data.msg);
ElMessage.error(isArray(response._data.data.message) ? response._data.data.message[0] : response._data.data.message);
}
};
return $fetch<T>(url, { ...defaultOptions, ...opts });
return $fetch<T>(MODULES_API_URL + url, { ...defaultOptions, ...opts });
};

39
composables/useRequest.ts

@ -0,0 +1,39 @@
/**
*
* https://blog.csdn.net/m0_63281537/article/details/126699761
*/
import { useFetch } from '#app';
import type { UseFetchOptions } from '#app';
import {getBaseUrl, isArray} from '~/utils/tool';
import {TENANT_ID} from "~/config";
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 = `${TENANT_ID}`;
},
onResponse({ response }) {
if (+response.status === 0 && +response._data.code !== 0) {
process.client && ElMessage.error(response._data.message);
}
if(+response.status === 500){
ElMessage.error('网络请求错误')
}
},
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);
};

21
composables/useServerRequest.ts

@ -1,29 +1,32 @@
import { useFetch, UseFetchOptions } from '#app';
import { useFetch } from '#app';
import type { UseFetchOptions } from '#app';
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>) => {
const token = useCookie<string | undefined>('token');
const runtimeConfig = useRuntimeConfig();
// 获取 Cookie
const token = useCookie('token');
const defaultOptions: UseFetchOptions<unknown> = {
baseURL: runtimeConfig.public.apiBase,
baseURL: SERVER_API_URL + url,
onRequest({ options }) {
options.headers = (options.headers || {}) as { [key: string]: string };
if (token.value) {
options.headers.Authorization = token.value;
}
options.headers.tenantid = '10199'
options.headers.tenantid = `${TENANT_ID}`;
},
onResponse({ response }) {
if (+response.status === 200 && +response._data.code !== 200) {
process.client && ElMessage.error(response._data.msg);
if (+response.status === 0 && +response._data.code !== 0) {
process.client && ElMessage.error(response._data.message);
}
},
onResponseError({ response }) {
process.client &&
ElMessage.error(isArray(response._data.data.msg) ? response._data.data.msg[0] : response._data.data.msg);
ElMessage.error(isArray(response._data.data.message) ? response._data.data.message[0] : response._data.data.message);
}
};
return useFetch<T>(url, { ...defaultOptions, ...opts } as any);
console.log('请求接口:', SERVER_API_URL + url)
return useFetch<T>(SERVER_API_URL + url, { ...defaultOptions, ...opts } as any);
};

10
config/setting.ts → config/index.ts

@ -5,10 +5,10 @@ export const APP_SECRET = 'ffd6eee985af45e4a75098422d1decbb';
export const domain = 'https://websoft.top';
// 主节点
export const SERVER_API_URL = import.meta.env.VITE_SERVER_URL;
export const SERVER_API_URL = import.meta.env.VITE_SERVER_URL || 'https://modules.gxwebsoft.com/api';
// 模块节点
export const MODULES_API_URL = import.meta.env.VITE_API_URL;
export const THINK_API_URL = import.meta.env.VITE_THINK_URL;
export const MODULES_API_URL = import.meta.env.VITE_API_URL || 'https://server.gxwebsoft.com/api';
export const THINK_API_URL = import.meta.env.VITE_THINK_URL || 'https://gxtyzx-api.gxwebsoft.com/api';
// 文件服务器地址
export const FILE_SERVER = 'https://file.wsdns.cn';
// 图片前缀
@ -19,7 +19,7 @@ export const IMG_URL = 'https://gxtyzx.gxsportscenter.com/uploads/images/';
*/
// 接口地址
export const API_BASE_URL: string = import.meta.env.VITE_API_URL;
export const PROJECT_NAME: string = import.meta.env.VITE_APP_NAME;
export const PROJECT_NAME: string = import.meta.env.VITE_APP_NAME || '网宿软件 https://websoft.top';
// 不显示侧栏的路由
export const HIDE_SIDEBARS: string[] = ['/home'];
// 不显示页脚的路由
@ -58,7 +58,7 @@ export const TAB_KEEP_ALIVE = true;
// token 传递的 header 名称
export const TOKEN_HEADER_NAME = 'Authorization';
// token 存储的名称
export const TOKEN_STORE_NAME = 'access_token';
export const TOKEN_STORE_NAME = 'token';
// 主题配置存储的名称
export const THEME_STORE_NAME = 'theme:';
// i18n 缓存的名称

11
ecosystem.config.cjs

@ -0,0 +1,11 @@
module.exports = {
apps: [
{
name: '网宿软件',
port: '10198',
exec_mode: 'cluster',
instances: 'max',
script: './.output/server/index.mjs'
}
]
}

43
nuxt.config.ts

@ -2,38 +2,55 @@
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
devtools: {enabled: false},
modules: ["@element-plus/nuxt",'@unocss/nuxt'],
modules: ["@element-plus/nuxt", '@unocss/nuxt', "@nuxt/image"],
css: [
'element-plus/dist/index.css',
'element-plus/theme-chalk/display.css',
'@/assets/css/main.css',
],
ssr: false,
app: {
head: {
viewport: 'width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no',
title: '小象CMS-影视类网站模板',
title: '网宿软件 - 基于Java Spring、Vue3、Antd、Nuxt构建的现代WEB开发框架',
meta: [
{ name: 'keywords', content: '小象CMS,影视,最新电影,最新电视剧' },
{ name: 'keywords', content: '小象CMS,websoftCMS,企业网站,网站源码,java,nuxt,antd,vue3' },
{
name: 'description',
content: '小象CMS'
content: '网宿软件'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
}
},
runtimeConfig: {
public: {
apiBase: '/server',
globalTitle: '小象影视'
}
devServer: {
port: 10189
},
nitro: {
// 该配置用于服务端请求转发
routeRules: {
'/server/**': {
proxy: 'http://127.0.0.1:9001/api/**'
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/**'
// }
// }
// }
})

16
package.json

@ -14,9 +14,12 @@
"lintfix": "prettier --write --list-different . && yarn lint:js --fix"
},
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.1.0",
"@antv/g2": "^5.1.22",
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"@typescript-eslint/parser": "^7.1.1",
"@antv/g2": "^5.1.22",
"ant-design-vue": "^3.2.11",
"axios": "^1.7.4",
"dayjs": "^1.11.5",
"dayjs-nuxt": "^1.1.2",
@ -24,25 +27,22 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"js-md5": "^0.7.3",
"less": "^4.1.3",
"nuxt": "^3.12.4",
"qrcode.vue": "^3.3.3",
"sass": "^1.57.1",
"vue": "latest",
"vue-i18n": "^9.2.2",
"xgplayer": "^3.0.5",
"xgplayer-mp4": "^3.0.5",
"ele-admin-pro": "^1.10.1",
"ant-design-vue": "^3.2.11",
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.1.0",
"vue-router": "^4.1.5",
"js-md5": "^0.7.3"
"xgplayer": "^3.0.5",
"xgplayer-mp4": "^3.0.5"
},
"packageManager": "pnpm@9.3.0+sha512.ee7b93e0c2bd11409c6424f92b866f31d3ea1bef5fbe47d3c7500cdc3c9668833d2e55681ad66df5b640c61fa9dc25d546efa54d76d7f8bf54b13614ac293631",
"devDependencies": {
"@element-plus/nuxt": "^1.0.9",
"@nuxt/devtools": "^0.6.1",
"@nuxt/image": "^1.7.0",
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"@typescript-eslint/parser": "^7.1.1",
"@unocss/nuxt": "^0.62.2",

61
pages/[custom]/index.vue

@ -0,0 +1,61 @@
<template>
<div class="banner" v-if="form">
<el-image :src="form.photo"></el-image>
</div>
<div v-if="form" class="container flex flex-col w-[1280px] m-auto">
<el-breadcrumb class="my-5" separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{ form.name }}</el-breadcrumb-item>
</el-breadcrumb>
<div class="w-7xl m-auto bg-gray-50">
<div class="title text-3xl text-center py-10">{{ form.name }}</div>
<div class="p-4 leading-7" v-html="form.content">
</div>
</div>
</div>
<div v-if="!form">
<el-empty description="404 页面不存在"></el-empty>
</div>
</template>
<script setup lang="ts">
import type {Design} from "~/api/cms/design/model";
import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
const route = useRoute();
const { query, params } = route;
const { custom: pageName} = params;
//
const form = ref<Design | any>();
//
const { data: design } = await useRequest<ApiResult<Design[]>>('/cms/design', {params: {
path: `/${pageName}`
}})
if (design.value) {
design.value?.data?.map((d,i) => {
if(i == 0){
form.value = d;
console.log(d.name)
useHead({
title: `${d.name} 网宿软件`,
meta: [{ name: "keywords", content: "Nuxt Vue SSR Typescript" }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: "console.log('Hello World')",
},
],
});
}
})
}
</script>
<style scoped lang="scss">
</style>

88
pages/article/[categoryId].vue

@ -0,0 +1,88 @@
<template>
<!-- <div class="banner" v-if="list">-->
<!-- <el-image :src="list.photo"></el-image>-->
<!-- </div>-->
<div v-if="list" class="container flex flex-col w-[1280px] m-auto my-3">
<el-breadcrumb class="py-2" separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-if="categoryName" :to="{ path: `/article/${categoryId}` }">{{ categoryName }}</el-breadcrumb-item>
<!-- <el-breadcrumb-item>{{ list.count }}</el-breadcrumb-item>-->
</el-breadcrumb>
<el-card :title="`标题`" class="w-7xl m-auto mt-10">
<template v-for="(item,index) in list">
<a :href="`/article/detail/${item.articleId}`" target="_blank" class="title text-3xl text-center">{{ item.title }}</a>
<div v-html="item.content">
</div>
</template>
</el-card>
<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>
</div>
<div v-if="!list">
<el-empty description="404 页面不存在"></el-empty>
</div>
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import type {Article} from "~/api/cms/article/model";
import {useRequest} from "~/composables/useRequest";
const route = useRoute();
const { query, params } = route;
const { categoryId } = params;
console.log(categoryId,'categoryId')
//
const list = ref<Article[] | any>();
const title = ref();
const categoryName = ref();
const count = ref()
//
const { data: articleList } = await useRequest<ApiResult<PageResult<Article>>>('/cms/article/page',{
params: {
categoryId
}
})
console.log(articleList);
if (articleList.value) {
count.value = articleList.value.data?.count;
list.value = articleList.value.data?.list.map((d,i)=>{
if(i === 0){
categoryName.value = d.categoryName;
useHead({
title: `${d.categoryName} - 网宿软件`,
meta: [{ name: "keywords", content: d.title }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: "console.log('Hello World')",
},
],
});
}
return d;
});
}
</script>
<style scoped lang="scss">
</style>

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

@ -0,0 +1,67 @@
<template>
<div class="banner" v-if="form.photo">
<el-image :src="form.photo"></el-image>
</div>
<div v-if="form" class="container flex flex-col w-[1280px] m-auto my-3">
<el-breadcrumb class="py-2" separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: `/article/${form.categoryId}` }">{{ form.categoryName }}</el-breadcrumb-item>
<el-breadcrumb-item>{{ form.title }}</el-breadcrumb-item>
</el-breadcrumb>
<div :title="`标题${form.name}`" class="w-7xl m-auto mt-10">
<div class="title text-3xl text-center">{{ form.name }}</div>
<div v-html="form.content">
</div>
</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>
</div>
<div v-if="!form">
<el-empty description="404 页面不存在"></el-empty>
</div>
</template>
<script setup lang="ts">
import type {ApiResult} from "~/api";
import type {Article} from "~/api/cms/article/model";
import {useRequest} from "~/composables/useRequest";
const route = useRoute();
const { query, params } = route;
const { id} = params;
//
const form = ref<Article | any>();
//
const { data: article } = await useRequest<ApiResult<Article>>('/cms/article/' + id)
if (article.value) {
form.value = article.value.data;
useHead({
title: `${form.value.title} - 网宿软件`,
meta: [{ name: "keywords", content: "Nuxt Vue SSR Typescript" }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: "console.log('Hello World')",
},
],
});
}
</script>
<style scoped lang="scss">
</style>

6
pages/about.vue → pages/case/index.vue

@ -24,9 +24,11 @@
</template>
<script setup lang="ts">
//
const { data: config } = await useServerRequest<{data: any}>('/cms/website-field', {})
import {useRequest} from "~/composables/useRequest";
const { data: config } = await useRequest<{data: any}>('/cms/website-field', {})
//
const { data: info } = await useServerRequest<{data: any}>('/cms/website/getSiteInfo', {
const { data: info } = await useRequest<{data: any}>('/cms/website/getSiteInfo', {
query: { status: 0 }
})

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

@ -53,13 +53,13 @@
</template>
<script setup lang="ts">
import { useServerRequest } from '~/composables/useServerRequest';
import { useRequest } from '~/composables/useRequest';
const route = useRoute();
const [{ data: res }, { data: info }] = await Promise.all([
useServerRequest<ResData<Omit<ColumnMovieItem, 'genres'>[]>>(`/web/type/${route.params.column}`),
useServerRequest<ResData<ColumnItem>>(`/column?value=${route.params.column}`)
useRequest<ResData<Omit<ColumnMovieItem, 'genres'>[]>>(`/web/type/${route.params.column}`),
useRequest<ResData<ColumnItem>>(`/column?value=${route.params.column}`)
]);
if (!info.value?.data) {

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

@ -144,9 +144,10 @@
<script setup lang="ts">
import QrcodeVue from 'qrcode.vue';
import { useLoginDialogVisible, useToken } from '~/composables/states';
import { useServerRequest } from '~/composables/useServerRequest';
import { FetchOptions, useClientRequest } from '~/composables/useClientRequest';
import { UserMovieBase, UserRate } from '~/types/column/movie';
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();
@ -164,8 +165,8 @@
});
const [{ data: detailRes, refresh }, { data: castsRes }] = await Promise.all([
useServerRequest<ResData<MovieItem>>(`/movie/${id}`),
useServerRequest<ResPage<CastListItem[]>>(`/movie/cast/list?movieId=${id}&pageNum=1&pageSize=50`)
useRequest<ResData<MovieItem>>(`/movie/${id}`),
useRequest<ResPage<CastListItem[]>>(`/movie/cast/list?movieId=${id}&pageNum=1&pageSize=50`)
]);
if (!detailRes.value?.data) {
@ -177,7 +178,7 @@
}
//
const { data: rank } = await useServerRequest<ResData<LeaderboardItem>>('/movie/leaderboard', {
const { data: rank } = await useRequest<ResData<LeaderboardItem>>('/movie/leaderboard', {
query: {
columnValue: detailRes.value?.data.columnValue,
pageNum: 1,
@ -186,7 +187,7 @@
});
/** 更新访问量 */
useServerRequest(`/movie/updatePv/${id}`, { lazy: true });
useRequest(`/movie/updatePv/${id}`, { lazy: true });
/** 登录状态发生改变 重新获取收藏和评分状态 */
watch(token, () => {
@ -200,7 +201,7 @@
if (!token.value) {
isCollect.value = false;
} else {
const { data: userCollect } = await useServerRequest<ResData<UserMovieBase>>('/user-collect/find', {
const { data: userCollect } = await useRequest<ResData<UserMovieBase>>('/user-collect/find', {
query: { movieId: id }
});
isCollect.value = !!userCollect.value?.data;
@ -237,7 +238,7 @@
if (!token.value) {
isUserRate.value = false;
} else {
const { data: userRate } = await useServerRequest<ResData<UserRate>>('/user-rate', {
const { data: userRate } = await useRequest<ResData<UserRate>>('/user-rate', {
query: { movieId: id }
});
isUserRate.value = !!userRate.value?.data;
@ -277,7 +278,7 @@
line-height: 1.2;
span {
color: $light-gray;
color: #666666;
font-size: 14px;
&.rate {
@ -301,7 +302,7 @@
}
span {
color: $light-gray;
color: #666666;
font-size: 14px;
&.rate {

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

@ -123,7 +123,7 @@
</template>
<script setup lang="ts">
import { useServerRequest } from '~/composables/useServerRequest';
import { useRequest } from '~/composables/useRequest';
import { useClientRequest } from '~/composables/useClientRequest';
definePageMeta({
@ -162,21 +162,21 @@
{ data: info },
{ data: movieList, pending, refresh }
] = await Promise.all([
useServerRequest<ResData<Item[]>>('/basic/genre/all', {
useRequest<ResData<Item[]>>('/basic/genre/all', {
query: {
columnValue: params.column
}
}),
useServerRequest<ResData<Item[]>>('/basic/country/all'),
useServerRequest<ResData<Item[]>>('/basic/language/all'),
useServerRequest<ResData<LeaderboardItem>>('/movie/leaderboard', {
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
}
}),
useServerRequest<ResData<ColumnItem>>(`/column`, {
useRequest<ResData<ColumnItem>>(`/column`, {
query: {
value: params.column
}

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

@ -106,10 +106,10 @@
import 'xgplayer/dist/index.min.css';
import 'xgplayer/es/plugins/danmu/index.css';
import PresetPlayer from 'xgplayer';
import { useServerRequest } from '~/composables/useServerRequest';
import { useRequest } from '~/composables/useRequest';
import { useClientRequest } from '~/composables/useClientRequest';
import { MovieVideoInfo } from '~/types/column/video';
import { UserMovieBase } from '~/types/column/movie';
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';
@ -126,7 +126,7 @@
//
let player: PresetPlayer | null = null;
const { data: detailRes } = await useServerRequest<ResData<MovieVideoInfo | null>>(`/movie/videos/${id}`);
const { data: detailRes } = await useRequest<ResData<MovieVideoInfo | null>>(`/movie/videos/${id}`);
if (!detailRes.value?.data) {
throw createError({
@ -141,7 +141,7 @@
getUserMovie();
async function getUserMovie() {
if (token.value) {
const { data: userBuy } = await useServerRequest<ResData<UserMovieBase>>(`/user-movie`, {
const { data: userBuy } = await useRequest<ResData<UserMovieBase>>(`/user-movie`, {
query: { movieId: detail.movieId }
});
isUserBuy.value = !!userBuy.value?.data;
@ -255,14 +255,14 @@
/** 获取猜你喜欢、周榜单、月榜单数据 */
const [{ data: likeRows }, { data: rank }] = await Promise.all([
useServerRequest<ResPage<MovieItem[]>>(`/movie/list`, {
useRequest<ResPage<MovieItem[]>>(`/movie/list`, {
query: {
genres: detail.movie.genres.split(',')[0],
pageNum: 1,
pageSize: 18
}
}),
useServerRequest<ResData<LeaderboardItem>>('/movie/leaderboard', {
useRequest<ResData<LeaderboardItem>>('/movie/leaderboard', {
query: {
columnValue: detail.movie.columnValue,
pageNum: 1,
@ -353,7 +353,7 @@
line-height: 1.2;
span {
color: $light-gray;
color: #666666;
font-size: 14px;
&.rate {
font-size: 24px;

82
pages/index.vue

@ -1,17 +1,17 @@
<template>
<div class="banner" v-if="banner">
<el-carousel :interval="5000" arrow="always" :height="banner?.height ? `${banner?.height}px` : 'px'">
<el-carousel-item v-for="item in banner.data" :key="item.uid">
<nuxt-link v-if="item.path" :to="item.path">
<el-image :src="item.url" style="width: 100%" fit="cover"/>
</nuxt-link>
<a v-else :href="item.path">
<el-image :src="item.url" style="width: 100%" fit="cover"/>
</a>
</el-carousel-item>
</el-carousel>
</div>
<div class="container index">
<div class="banner">
<el-carousel :interval="5000" arrow="always">
<el-carousel-item v-for="item in banner?.data" :key="item.uid">
<nuxt-link v-if="item.path" :to="item.path">
<el-image :src="item.url" style="width: 100%" fit="cover" />
</nuxt-link>
<a v-else :href="item.path">
<el-image :src="item.url" style="width: 100%" fit="cover" />
</a>
</el-carousel-item>
</el-carousel>
</div>
<el-row :gutter="20" class="mt-20" v-for="categoryItem in movieList">
<el-col :sm="18">
<div class="panel_hd between items-center">
@ -22,11 +22,18 @@
</div>
<div class="panel_hd__right items-center">
<ul class="items-center">
<li class="hidden-sm-and-down"><nuxt-link :to="`/c`">动作片</nuxt-link></li>
<li class="hidden-sm-and-down"><nuxt-link :to="`/c`">科幻片</nuxt-link></li>
<li class="hidden-sm-and-down">
<nuxt-link :to="`/c`">动作片</nuxt-link>
</li>
<li class="hidden-sm-and-down">
<nuxt-link :to="`/c`">科幻片</nuxt-link>
</li>
<li>
<nuxt-link :to="`/c-`" class="items-center">
更多 <el-icon><ElIconArrowRight /></el-icon>
更多
<el-icon>
<ElIconArrowRight/>
</el-icon>
</nuxt-link>
</li>
</ul>
@ -37,7 +44,7 @@
<el-col :sm="4" :xs="8" v-for="item in categoryItem.rows">
<div class="video-list__block">
<nuxt-link :to="`/c-`" class="img-box">
<el-image lazy class="video-list__block__img" :src="'/default.jpg'" fit="cover" />
<el-image lazy class="video-list__block__img" :src="'/default.jpg'" fit="cover"/>
<span>暂无评分</span>
</nuxt-link>
<div class="video-list__detail">
@ -71,11 +78,13 @@
</el-col>
</el-row>
<div class="friendly-link flex items-center mt-20">
<el-icon><ElIconLink/></el-icon>
<el-icon>
<ElIconLink/>
</el-icon>
友情链接
</div>
<div class="friendly-link__content">
<a v-for="item in links?.data" :href="item.url" target="_blank">{{ item.name }}</a>
<a v-for="item in links" :href="item.url" target="_blank">{{ item.name }}</a>
</div>
</div>
</template>
@ -84,12 +93,24 @@
import type {ApiResult} from "~/api";
import type {AdItem} from "~/api/cms/ad/model";
import type {Link} from "~/api/cms/link/model";
import {useRequest} from "~/composables/useRequest";
import {getCaptcha} from "~/api/passport/login";
const banner = ref<any>();
const links = ref<any>();
const movieList = ref<any>();
//
const getSlide = useServerRequest<ApiResult<AdItem[]>>('http://127.0.0.1:9001/api/cms/ad/side/281');
const getLink = useServerRequest<ApiResult<Link[]>>('/oa/link?linkType=友情链接');
const [{ data: banner }, { data: links }] = await Promise.all([getSlide, getLink]);
console.log(links,'links')
const reload = async () => {
await nextTick()
const getSlide = useRequest<ApiResult<AdItem[]>>('/cms/ad/side');
const getLink = useRequest<ApiResult<Link[]>>('/oa/link?linkType=友情链接');
const [{data: slide}, {data: link}] = await Promise.all([getSlide, getLink]);
banner.value = slide.value?.data;
links.value = link.value?.data;
}
reload();
</script>
<style lang="scss">
@ -105,7 +126,7 @@ console.log(links,'links')
height: 380px;
}
@media (max-width: 768px){
@media (max-width: 768px) {
.el-carousel__container {
height: 200px;
}
@ -120,11 +141,14 @@ console.log(links,'links')
border-bottom: #eee solid 1px;
padding: 10px 0;
font-size: 18px;
> img {
margin-right: 10px;
}
&__content {
padding: 20px 0;
a {
padding-right: 15px;
}
@ -188,6 +212,7 @@ console.log(links,'links')
border-bottom: #eeeeee solid 1px;
height: 46px;
margin-bottom: 15px;
.title {
font-size: 18px;
line-height: 24px;
@ -196,9 +221,11 @@ console.log(links,'links')
margin-right: 10px;
}
}
&__right {
li {
position: relative;
&::before {
content: '';
display: block;
@ -210,9 +237,11 @@ console.log(links,'links')
transform: translateY(-30%);
right: 0;
}
&:last-child::before {
display: none;
}
a {
padding: 0 10px;
color: #999;
@ -225,14 +254,17 @@ console.log(links,'links')
.video-list {
&__block {
padding: 10px 0;
&__img {
width: 100%;
height: 218px;
}
.img-box {
position: relative;
height: 218px;
display: block;
span {
position: absolute;
bottom: 0;
@ -241,7 +273,7 @@ console.log(links,'links')
line-height: 30px;
left: 0;
display: inline-block;
background-image: linear-gradient(transparent,rgba(0,0,0,.5));
background-image: linear-gradient(transparent, rgba(0, 0, 0, .5));
color: #fff;
font-size: 12px;
text-align: right;
@ -268,7 +300,7 @@ console.log(links,'links')
}
}
@media only screen and (max-width:991px){
@media only screen and (max-width: 991px) {
.video-list {
&__block {
&__img, .img-box {

61
pages/plug/index.vue

@ -0,0 +1,61 @@
<template>
<div class="banner" v-if="form">
<el-image :src="form.photo"></el-image>
</div>
<div v-if="form" class="container flex flex-col w-[1280px] m-auto">
<el-breadcrumb class="my-5" separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{ form.name }}</el-breadcrumb-item>
</el-breadcrumb>
<div class="w-7xl m-auto bg-gray-50">
<div class="title text-3xl text-center py-10">{{ form.name }}</div>
<div class="p-4 leading-7" v-html="form.content">
</div>
</div>
</div>
<div v-if="!form">
<el-empty description="404 页面不存在"></el-empty>
</div>
</template>
<script setup lang="ts">
import type {Design} from "~/api/cms/design/model";
import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
const route = useRoute();
const { query, params } = route;
const { custom: pageName} = params;
//
const form = ref<Design | any>();
//
const { data: design } = await useRequest<ApiResult<Design[]>>('/cms/design', {params: {
path: `/${pageName}`
}})
if (design.value) {
design.value?.data?.map((d,i) => {
if(i == 0){
form.value = d;
console.log(d.name)
useHead({
title: `${d.name} 网宿软件`,
meta: [{ name: "keywords", content: "Nuxt Vue SSR Typescript" }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: "console.log('Hello World')",
},
],
});
}
})
}
</script>
<style scoped lang="scss">
</style>

76
pages/product/[name].vue

@ -0,0 +1,76 @@
<template>
<div class="banner" v-if="form">
<el-image :src="form.photo"></el-image>
</div>
<div v-if="form" class="container flex flex-col w-[1280px] m-auto my-3">
<el-breadcrumb class="py-2" separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{ form.name }}</el-breadcrumb-item>
</el-breadcrumb>
<div :title="`标题${form.name}`" class="w-7xl m-auto mt-10">
<template v-for="(item,index) in design?.data">
<div class="title text-3xl text-center">{{ item.name }}</div>
<div v-html="item.content">
</div>
</template>
</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>
</div>
<div v-if="!form">
<el-empty description="404 页面不存在"></el-empty>
</div>
</template>
<script setup lang="ts">
import type {Design} from "~/api/cms/design/model";
import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
const route = useRoute();
const { query, params } = route;
const { name: productName } = params;
console.log(productName,'productName..')
//
const form = ref<Design | any>();
//
const { data: design } = await useRequest<ApiResult<Design[]>>('/cms/design', {params: {
path: `/${productName}`
}})
if (design.value) {
design.value?.data?.map((d,i) => {
if(i == 0){
form.value = d;
useHead({
title: form.value.name + "Nuxt3学习实践 ~ 坚持",
meta: [{ name: "keywords", content: "Nuxt Vue SSR Typescript" }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: "console.log('Hello World')",
},
],
});
}
})
}
</script>
<style scoped lang="scss">
</style>

61
pages/product/index.vue

@ -0,0 +1,61 @@
<template>
<div class="banner">
</div>
<div v-if="form" class="container flex flex-col w-[1280px] m-auto">
<el-breadcrumb class="my-5" separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{ form.name }}</el-breadcrumb-item>
</el-breadcrumb>
<div class="w-7xl m-auto bg-gray-50">
<div class="title text-3xl text-center py-10">{{ form.name }}</div>
<div class="p-4 leading-7" v-html="form.content">
</div>
</div>
</div>
<div v-if="!form">
<el-empty description="404 页面不存在"></el-empty>
</div>
</template>
<script setup lang="ts">
import type {Design} from "~/api/cms/design/model";
import type {ApiResult} from "~/api";
import {useRequest} from "~/composables/useRequest";
const route = useRoute();
const { query, params } = route;
const { custom: pageName} = params;
//
const form = ref<Design | any>();
//
const { data: design } = await useRequest<ApiResult<Design[]>>('/cms/design', {params: {
path: `/${pageName}`
}})
if (design.value) {
design.value?.data?.map((d,i) => {
if(i == 0){
form.value = d;
console.log(d.name)
useHead({
title: `${d.name} 网宿软件`,
meta: [{ name: "keywords", content: "Nuxt Vue SSR Typescript" }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: "console.log('Hello World')",
},
],
});
}
})
}
</script>
<style scoped lang="scss">
</style>

4
pages/search/index.vue

@ -61,7 +61,7 @@
<script setup lang="ts">
import { useClientRequest } from '~/composables/useClientRequest';
import { useServerRequest } from '~/composables/useServerRequest';
import { useRequest } from '~/composables/useRequest';
const route = useRoute();
const activeName = ref('first');
@ -85,7 +85,7 @@
}
})
),
useServerRequest<ResPage<MovieItem[]>>('movie/list', { query: { pageSize: 15 } })
useRequest<ResPage<MovieItem[]>>('movie/list', { query: { pageSize: 15 } })
]);
function handleCurrentChange(page: number) {

7
pages/user/index.vue

@ -38,7 +38,7 @@
<script setup lang="ts">
import CollectData from '@/components/user/CollectData.vue';
import UserInfoData from '~/components/user/UserInfoData.vue';
import { useServerRequest } from '~/composables/useServerRequest';
import { useRequest } from '~/composables/useRequest';
import { useClientRequest } from '~/composables/useClientRequest';
definePageMeta({
@ -49,13 +49,14 @@
const [{ data: signData, refresh }, { data: goldData, refresh: refreshGold }] = await Promise.all([
//
useServerRequest<{ data: null | number }>('/user-sign/getSign'),
useRequest<{ data: null | number }>('/user-sign/getSign'),
//
useServerRequest<{ data: { gold: number } }>('/user-wallet/findGold')
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) {

6
plugins/baidu.client.ts

@ -0,0 +1,6 @@
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

@ -0,0 +1,20 @@
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

@ -0,0 +1,18 @@
.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

@ -0,0 +1,63 @@
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>`;
}
}

1079
pnpm-lock.yaml

File diff suppressed because it is too large

BIN
public/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

6
store/index.ts

@ -1,6 +0,0 @@
/**
* pinia
*/
import { createPinia } from 'pinia';
export default createPinia();

134
store/modules/chat.ts

@ -1,134 +0,0 @@
import { defineStore } from 'pinia';
import { io, Socket } from 'socket.io-client';
import { getToken } from '@/utils/token-util';
import { ChatConversation, ChatMessage } from '@/api/system/chat/model';
import {
pageChatConversation,
updateChatConversation
} from '@/api/system/chat';
import { emitter } from '@/utils/common';
const SOCKET_URL: string = import.meta.env.VITE_SOCKET_URL;
interface ConnectionOptions {
token: string;
userId: number;
isAdmin: boolean;
}
export interface ChatState {
socket: Socket | undefined;
conversations: ChatConversation[];
}
export const useChatStore = defineStore({
id: 'chat',
state: (): ChatState => ({
socket: undefined,
conversations: []
}),
getters: {
unReadLetter(): number {
return this.conversations.reduce((count, item) => count + item.unRead, 0);
},
unReadConversations(): ChatConversation[] {
return this.conversations
.filter((item) => item.unRead > 0)
.sort((a, b) => {
return (
new Date(b.updateTime).getTime() - new Date(a.updateTime).getTime()
);
});
}
},
actions: {
readConversation(id) {
const index = this.conversations.findIndex((item) => item.id === id);
if (index >= 0) {
updateChatConversation({
id: this.conversations[index].id,
unRead: 0
});
this.conversations.splice(index, 1);
}
},
async connectSocketIO(userId: number) {
console.log(
'---------------------------------connectSocketIO----------------------------------'
);
const options: ConnectionOptions = {
token: getToken() || '',
userId: userId,
isAdmin: true
};
const socket: Socket = io(SOCKET_URL, {
query: options,
transports: ['websocket', 'polling'],
timeout: 5000
});
socket.on('connect', () => {
this.socket = socket;
console.log(
'---------------------------------socket connect----------------------------------'
);
// 获取聊天列表
pageChatConversation({
keywords: '',
status: 1,
page: 1,
limit: 100,
onlyFake: true
}).then((res) => {
if (res?.list) {
this.conversations = res.list;
}
});
});
console.log(
'---------------------------------socket----------------------------------',
socket
);
console.log('收到socket消息>>>');
// 收到新消息
socket.on('message', (data: ChatMessage) => {
console.log('收到socket消息>>>');
const index = this.conversations.findIndex(
(item) =>
item.friendId === data.formUserId && item.userId === data.toUserId
);
let content = '';
if (data.type == 'image') {
content = '图片';
} else if (data.type === 'card') {
content = '卡片';
} else {
content = data.content;
}
if (index >= 0) {
this.conversations[index].unRead++;
this.conversations[index].content = content;
this.conversations[index].updateTime = Date.now();
} else {
this.conversations.push({
content: content,
friendInfo: data.formUserInfo,
userInfo: data.toUserInfo,
messages: [],
unRead: 1,
updateTime: Date.now(),
userId: data.toUserId,
friendId: data.formUserId
});
}
emitter.emit('message', data);
});
socket.on('connect_error', () => {
console.log('connect_error');
});
}
}
});

40
store/modules/params.ts

@ -1,40 +0,0 @@
/**
* store
*/
import { defineStore } from 'pinia';
export interface ParamsState {
title: string | null;
comments: string | null;
back: string | null;
redirect: string | null | undefined;
}
export const useParamsStore = defineStore({
id: 'params',
state: (): ParamsState => ({
// 标题
title: '操作成功',
// 描述
comments: '您的申请已提交',
// 当前页面路径
back: null,
// 跳转的页面
redirect: null
}),
getters: {},
actions: {
setTitle(value: string) {
this.title = value;
},
setComments(value: string) {
this.comments = value;
},
setBack(value: string) {
this.back = value;
},
setRedirect(value: string) {
this.redirect = value;
}
}
});

85
store/modules/tenant.ts

@ -1,85 +0,0 @@
/**
* store
*/
import { defineStore } from 'pinia';
import { formatMenus, toTreeData, formatTreeData } from 'ele-admin-pro';
import type { MenuItem } from 'ele-admin-pro';
import { USER_MENUS } from '@/config/setting';
import { getTenantInfo } from '@/api/layout';
import { Tenant } from '@/api/system/tenant/model';
import { Company } from '@/api/system/company/model';
// const EXTRA_MENUS: any = [];
export interface UserState {
tenant: Tenant | null;
company: Company | null;
menus: MenuItem[] | null | undefined;
}
export const useTenantStore = defineStore({
id: 'tenant',
state: (): UserState => ({
// 租户信息
tenant: null,
// 企业信息
company: null,
// 当前登录用户的菜单
menus: null
}),
getters: {},
actions: {
/**
*
*/
async fetchTenantInfo() {
const company = await getTenantInfo().catch(() => void 0);
if (!company) {
return {};
}
// 租户信息
this.company = company;
// 企业信息
if (company) {
this.company = company;
localStorage.setItem('TenantId', String(company.tenantId));
localStorage.setItem('TenantName', String(company.shortName));
localStorage.setItem('CompanyId', String(company.companyId));
}
// 用户菜单, 过滤掉按钮类型并转为 children 形式
const { menus, homePath } = formatMenus(
USER_MENUS ??
toTreeData({
data: USER_MENUS,
idField: 'menuId',
parentIdField: 'parentId'
})
);
this.menus = menus;
return { menus, homePath };
},
/**
*
*/
setInfo(value: Tenant) {
this.tenant = value;
},
/**
* badge
*/
setMenuBadge(path: string, value?: number | string, color?: string) {
this.menus = formatTreeData(this.menus, (m) => {
if (path === m.path) {
return {
...m,
meta: {
...m.meta,
badge: value,
badgeColor: color
}
};
}
return m;
});
}
}
});

707
store/modules/theme.ts

@ -1,707 +0,0 @@
/**
*
*/
import { defineStore } from 'pinia';
import {
changeColor,
screenWidth,
screenHeight,
contentWidth,
contentHeight,
WEAK_CLASS,
BODY_LIMIT_CLASS,
DISABLES_CLASS
} from 'ele-admin-pro/es';
import type {
TabItem,
HeadStyleType,
SideStyleType,
LayoutStyleType,
SideMenuStyleType,
TabStyleType,
TabRemoveOption
} from 'ele-admin-pro/es';
import {
TAB_KEEP_ALIVE,
KEEP_ALIVE_EXCLUDES,
THEME_STORE_NAME
} from '@/config/setting';
// import { getCache } from '@/api/system/cache';
/**
* state
*/
const DEFAULT_STATE: ThemeState = Object.freeze({
// 页签数据
tabs: [],
// 是否折叠侧栏
collapse: false,
// 是否折叠一级侧栏
sideNavCollapse: true,
// 内容区域是否全屏
bodyFullscreen: false,
// 是否开启页签栏
showTabs: true,
// 是否开启页脚
showFooter: true,
// 顶栏风格: light(亮色), dark(暗色), primary(主色)
headStyle: 'light',
// 侧栏风格: light(亮色), dark(暗色)
sideStyle: 'dark',
// 布局风格: side(默认), top(顶栏导航), mix(混合导航)
layoutStyle: 'side',
// 侧栏菜单风格: default(默认), mix(双排侧栏)
sideMenuStyle: 'default',
// 页签风格: default(默认), dot(圆点), card(卡片)
tabStyle: 'default',
// 路由切换动画
transitionName: 'fade',
// 是否固定顶栏
fixedHeader: true,
// 是否固定侧栏
fixedSidebar: true,
// 是否固定主体
fixedBody: true,
// 内容区域宽度铺满
bodyFull: true,
// logo 是否自适应宽度
logoAutoSize: false,
// 侧栏是否彩色图标
colorfulIcon: false,
// 侧栏是否只保持一个子菜单展开
sideUniqueOpen: false,
// 是否是色弱模式
weakMode: false,
// 是否是暗黑模式
darkMode: false,
// 主题色
color: '',
// 主页的组件名称
homeComponents: [],
// 刷新路由时的参数
routeReload: null,
// 屏幕宽度
screenWidth: screenWidth(),
// 屏幕高度
screenHeight: screenHeight(),
// 内容区域宽度
contentWidth: contentWidth(),
// 内容区域高度
contentHeight: contentHeight(),
// 是否开启响应式
styleResponsive: true
});
// 延时操作定时器
let disableTransitionTimer: number, updateContentSizeTimer: number;
/**
*
*/
function getCacheSetting(): any {
try {
const value = localStorage.getItem(THEME_STORE_NAME);
// 加载redis缓存
// getCache('theme').then((data) => {
// if (typeof data === 'object') {
// // 写入本地缓存
// localStorage.setItem(THEME_STORE_NAME, JSON.stringify(data));
// return data;
// }
// });
if (value) {
const cache = JSON.parse(value);
// 加载本地缓存
if (typeof cache === 'object') {
return cache;
}
}
} catch (e) {
console.error(e);
}
return {};
}
/**
*
*/
function cacheSetting(key: string, value: any) {
const cache = getCacheSetting();
if (cache[key] !== value) {
cache[key] = value;
// console.log(value);
// localStorage.setItem(THEME_STORE_NAME, JSON.stringify(cache));
// updateCacheTheme({
// key: 'theme',
// content: JSON.stringify(cache)
// }).then((res) => {
// console.log(res);
// });
}
}
/**
*
*/
function changeStyleResponsive(styleResponsive: boolean) {
if (styleResponsive) {
document.body.classList.remove(BODY_LIMIT_CLASS);
} else {
document.body.classList.add(BODY_LIMIT_CLASS);
}
}
/**
*
*/
function changeWeakMode(weakMode: boolean) {
if (weakMode) {
document.body.classList.add(WEAK_CLASS);
} else {
document.body.classList.remove(WEAK_CLASS);
}
}
/**
*
*/
function changeTheme(value?: string | null, dark?: boolean) {
return new Promise<void>((resolve, reject) => {
try {
changeColor(value, dark);
resolve();
} catch (e) {
reject(e);
}
});
}
/**
*
*/
function disableTransition() {
disableTransitionTimer && clearTimeout(disableTransitionTimer);
document.body.classList.add(DISABLES_CLASS);
disableTransitionTimer = setTimeout(() => {
document.body.classList.remove(DISABLES_CLASS);
}, 100) as unknown as number;
}
export const useThemeStore = defineStore({
id: 'theme',
state: (): ThemeState => {
const state = { ...DEFAULT_STATE };
// 读取本地缓存
const cache = getCacheSetting();
Object.keys(state).forEach((key) => {
if (typeof cache[key] !== 'undefined') {
state[key] = cache[key];
}
});
return state;
},
getters: {
// 需要 keep-alive 的组件
keepAliveInclude(): string[] {
if (!TAB_KEEP_ALIVE || !this.showTabs) {
return [];
}
const components = new Set<string>();
const { reloadPath, reloadHome } = this.routeReload || {};
this.tabs?.forEach((t) => {
const isAlive = t.meta?.keepAlive !== false;
const isExclude = KEEP_ALIVE_EXCLUDES.includes(t.path as string);
const isReload = reloadPath && reloadPath === t.fullPath;
if (isAlive && !isExclude && !isReload && t.components) {
t.components.forEach((c) => {
if (typeof c === 'string' && c) {
components.add(c);
}
});
}
});
if (!reloadHome) {
this.homeComponents?.forEach((c) => {
if (typeof c === 'string' && c) {
components.add(c);
}
});
}
return Array.from(components);
}
},
actions: {
setTabs(value: TabItem[]) {
this.tabs = value;
//cacheSetting('tabs', value);
},
setCollapse(value: boolean) {
this.collapse = value;
this.delayUpdateContentSize(800);
},
setSideNavCollapse(value: boolean) {
this.sideNavCollapse = value;
this.delayUpdateContentSize(800);
},
setBodyFullscreen(value: boolean) {
disableTransition();
this.bodyFullscreen = value;
this.delayUpdateContentSize(800);
},
setShowTabs(value: boolean) {
this.showTabs = value;
cacheSetting('showTabs', value);
this.delayUpdateContentSize();
},
setShowFooter(value: boolean) {
this.showFooter = value;
cacheSetting('showFooter', value);
this.delayUpdateContentSize();
},
setHeadStyle(value: HeadStyleType) {
this.headStyle = value;
cacheSetting('headStyle', value);
},
setSideStyle(value: SideStyleType) {
this.sideStyle = value;
cacheSetting('sideStyle', value);
},
setLayoutStyle(value: LayoutStyleType) {
disableTransition();
this.layoutStyle = value;
cacheSetting('layoutStyle', value);
this.delayUpdateContentSize();
},
setSideMenuStyle(value: SideMenuStyleType) {
disableTransition();
this.sideMenuStyle = value;
cacheSetting('sideMenuStyle', value);
this.delayUpdateContentSize();
},
setTabStyle(value: TabStyleType) {
this.tabStyle = value;
cacheSetting('tabStyle', value);
},
setTransitionName(value: string) {
this.transitionName = value;
cacheSetting('transitionName', value);
},
setFixedHeader(value: boolean) {
disableTransition();
this.fixedHeader = value;
cacheSetting('fixedHeader', value);
},
setFixedSidebar(value: boolean) {
disableTransition();
this.fixedSidebar = value;
cacheSetting('fixedSidebar', value);
},
setFixedBody(value: boolean) {
disableTransition();
this.fixedBody = value;
cacheSetting('fixedBody', value);
},
setBodyFull(value: boolean) {
this.bodyFull = value;
cacheSetting('bodyFull', value);
this.delayUpdateContentSize();
},
setLogoAutoSize(value: boolean) {
disableTransition();
this.logoAutoSize = value;
cacheSetting('logoAutoSize', value);
},
setColorfulIcon(value: boolean) {
this.colorfulIcon = value;
cacheSetting('colorfulIcon', value);
},
setSideUniqueOpen(value: boolean) {
this.sideUniqueOpen = value;
cacheSetting('sideUniqueOpen', value);
},
setStyleResponsive(value: boolean) {
changeStyleResponsive(value);
this.styleResponsive = value;
cacheSetting('styleResponsive', value);
},
/**
*
* @param value
*/
setWeakMode(value: boolean) {
return new Promise<void>((resolve) => {
changeWeakMode(value);
this.weakMode = value;
cacheSetting('weakMode', value);
resolve();
});
},
/**
*
* @param value
*/
setDarkMode(value: boolean) {
return new Promise<void>((resolve, reject) => {
changeTheme(this.color, value)
.then(() => {
this.darkMode = value;
cacheSetting('darkMode', value);
resolve();
})
.catch((e) => {
reject(e);
});
});
},
/**
*
* @param value
*/
setColor(value?: string) {
return new Promise<void>((resolve, reject) => {
changeTheme(value, this.darkMode)
.then(() => {
this.color = value;
cacheSetting('color', value);
resolve();
})
.catch((e) => {
reject(e);
});
});
},
/**
*
* @param components
*/
setHomeComponents(components: string[]) {
this.homeComponents = components;
},
/**
*
* @param option
*/
setRouteReload(option: RouteReloadOption | null) {
this.routeReload = option;
},
/**
*
*/
updateScreenSize() {
this.screenWidth = screenWidth();
this.screenHeight = screenHeight();
this.updateContentSize();
},
/**
*
*/
updateContentSize() {
this.contentWidth = contentWidth();
this.contentHeight = contentHeight();
},
/**
*
* @param delay
*/
delayUpdateContentSize(delay?: number) {
updateContentSizeTimer && clearTimeout(updateContentSizeTimer);
updateContentSizeTimer = setTimeout(() => {
this.updateContentSize();
}, delay ?? 100) as unknown as number;
},
/**
*
*/
resetSetting() {
return new Promise<void>((resolve, reject) => {
disableTransition();
this.showTabs = DEFAULT_STATE.showTabs;
this.showFooter = DEFAULT_STATE.showFooter;
this.headStyle = DEFAULT_STATE.headStyle;
this.sideStyle = DEFAULT_STATE.sideStyle;
this.layoutStyle = DEFAULT_STATE.layoutStyle;
this.sideMenuStyle = DEFAULT_STATE.sideMenuStyle;
this.tabStyle = DEFAULT_STATE.tabStyle;
this.transitionName = DEFAULT_STATE.transitionName;
this.fixedHeader = DEFAULT_STATE.fixedHeader;
this.fixedSidebar = DEFAULT_STATE.fixedSidebar;
this.fixedBody = DEFAULT_STATE.fixedBody;
this.bodyFull = DEFAULT_STATE.bodyFull;
this.logoAutoSize = DEFAULT_STATE.logoAutoSize;
this.colorfulIcon = DEFAULT_STATE.colorfulIcon;
this.sideUniqueOpen = DEFAULT_STATE.sideUniqueOpen;
this.styleResponsive = DEFAULT_STATE.styleResponsive;
this.weakMode = DEFAULT_STATE.weakMode;
this.darkMode = DEFAULT_STATE.darkMode;
this.color = DEFAULT_STATE.color;
localStorage.removeItem(THEME_STORE_NAME);
Promise.all([
changeStyleResponsive(this.styleResponsive),
changeWeakMode(this.weakMode),
changeTheme(this.color, this.darkMode)
])
.then(() => {
resolve();
})
.catch((e) => {
reject(e);
});
});
},
/**
*
*/
recoverTheme() {
// 关闭响应式布局
if (!this.styleResponsive) {
changeStyleResponsive(false);
}
// 恢复色弱模式
if (this.weakMode) {
changeWeakMode(true);
}
// 恢复主题色
if (this.color || this.darkMode) {
changeTheme(this.color, this.darkMode).catch((e) => {
console.error(e);
});
}
},
/**
* key
* @param data
*/
tabAdd(data: TabItem | TabItem[]) {
if (Array.isArray(data)) {
data.forEach((d) => {
this.tabAdd(d);
});
return;
}
const i = this.tabs.findIndex((d) => d.key === data.key);
if (i === -1) {
this.setTabs(this.tabs.concat([data]));
} else if (data.fullPath !== this.tabs[i].fullPath) {
this.setTabs(
this.tabs
.slice(0, i)
.concat([data])
.concat(this.tabs.slice(i + 1))
);
}
},
/**
*
* @param key key
*/
async tabRemove({
key,
active
}: TabRemoveOption): Promise<TabRemoveResult> {
const i = this.tabs.findIndex((t) => t.key === key || t.fullPath === key);
if (i === -1) {
return {};
}
const t = this.tabs[i];
if (!t.closable) {
return Promise.reject();
}
const path = this.tabs[i - 1]?.fullPath;
this.setTabs(this.tabs.filter((_d, j) => j !== i));
return t.key === active ? { path, home: !path } : {};
},
/**
*
*/
async tabRemoveLeft({
key,
active
}: TabRemoveOption): Promise<TabRemoveResult> {
let index = -1; // 选中页签的 index
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i].key === active) {
index = i;
}
if (this.tabs[i].key === key) {
if (i === 0) {
break;
}
const temp = this.tabs.filter((d, j) => !d.closable && j < i);
if (temp.length === i + 1) {
break;
}
const path = index === -1 ? void 0 : this.tabs[i].fullPath;
this.setTabs(temp.concat(this.tabs.slice(i)));
return { path };
}
}
return Promise.reject();
},
/**
*
*/
async tabRemoveRight({
key,
active
}: TabRemoveOption): Promise<TabRemoveResult> {
if (this.tabs.length) {
let index = -1; // 选中页签的 index
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i].key === active) {
index = i;
}
if (this.tabs[i].key === key) {
if (i === this.tabs.length - 1) {
return Promise.reject();
}
const temp = this.tabs.filter((d, j) => !d.closable && j > i);
if (temp.length === this.tabs.length - i - 1) {
return Promise.reject();
}
const path = index === -1 ? this.tabs[i].fullPath : void 0;
this.setTabs(
this.tabs
.slice(0, i + 1)
.concat(this.tabs.filter((d, j) => !d.closable && j > i))
);
return { path };
}
}
// 主页时关闭全部
const temp = this.tabs.filter((d) => !d.closable);
if (temp.length !== this.tabs.length) {
this.setTabs(temp);
return { home: index !== -1 };
}
}
return Promise.reject();
},
/**
*
*/
async tabRemoveOther({
key,
active
}: TabRemoveOption): Promise<TabRemoveResult> {
let index = -1; // 选中页签的 index
let path: string | undefined; // 关闭后跳转的 path
const temp = this.tabs.filter((d, i) => {
if (d.key === active) {
index = i;
}
if (d.key === key) {
path = d.fullPath;
}
return !d.closable || d.key === key;
});
if (temp.length === this.tabs.length) {
return Promise.reject();
}
this.setTabs(temp);
if (index === -1) {
return {};
}
return key === active ? {} : { path, home: !path };
},
/**
*
* @param active key
*/
async tabRemoveAll(active: string): Promise<TabRemoveResult> {
const t = this.tabs.find((d) => d.key === active);
const home = typeof t !== 'undefined' && t.closable === true; // 是否跳转主页
const temp = this.tabs.filter((d) => !d.closable);
if (temp.length === this.tabs.length) {
return Promise.reject();
}
this.setTabs(temp);
return { home };
},
/**
*
* @param data
*/
tabSetItem(data: TabItem) {
let i = -1;
if (data.key) {
i = this.tabs.findIndex((d) => d.key === data.key);
} else if (data.fullPath) {
i = this.tabs.findIndex((d) => d.fullPath === data.fullPath);
} else if (data.path) {
i = this.tabs.findIndex((d) => d.path === data.path);
}
if (i !== -1) {
const item = { ...this.tabs[i] };
if (data.title) {
item.title = data.title;
}
if (typeof data.closable === 'boolean') {
item.closable = data.closable;
}
if (data.components) {
item.components = data.components;
}
this.setTabs(
this.tabs
.slice(0, i)
.concat([item])
.concat(this.tabs.slice(i + 1))
);
}
}
}
});
/**
* State
*/
export interface ThemeState {
tabs: TabItem[];
collapse: boolean;
sideNavCollapse: boolean;
bodyFullscreen: boolean;
showTabs: boolean;
showFooter: boolean;
headStyle: HeadStyleType;
sideStyle: SideStyleType;
layoutStyle: LayoutStyleType;
sideMenuStyle: SideMenuStyleType;
tabStyle: TabStyleType;
transitionName: string;
fixedHeader: boolean;
fixedSidebar: boolean;
fixedBody: boolean;
bodyFull: boolean;
logoAutoSize: boolean;
colorfulIcon: boolean;
sideUniqueOpen: boolean;
weakMode: boolean;
darkMode: boolean;
color?: string | null;
homeComponents: string[];
routeReload: RouteReloadOption | null;
screenWidth: number;
screenHeight: number;
contentWidth: number;
contentHeight: number;
styleResponsive: boolean;
}
/**
*
*/
export interface RouteReloadOption {
// 是否是刷新主页
reloadHome?: boolean;
// 要刷新的页签路由地址
reloadPath?: string;
}
/**
*
*/
export interface TabRemoveResult {
// 关闭后要跳转的地址
path?: string;
// 关闭后是否跳转到主页
home?: boolean;
}

128
store/modules/user.ts

@ -1,128 +0,0 @@
/**
* store
*/
import { defineStore } from 'pinia';
import { formatMenus, toTreeData, formatTreeData } from 'ele-admin-pro/es';
import type { MenuItemType } from 'ele-admin-pro/es';
import type { User } from '@/api/system/user/model';
import { TOKEN_STORE_NAME, USER_MENUS } from '@/config/setting';
import { getUserInfo } from '@/api/layout';
import { initialization } from '@/api/system/tenant';
// import { isExternalLink } from 'ele-admin-pro';
// import { message } from 'ant-design-vue';
const EXTRA_MENUS: any = [];
export interface UserState {
info: User | null;
menus: MenuItemType[] | null | undefined;
authorities: (string | undefined)[];
roles: (string | undefined)[];
}
export const useUserStore = defineStore({
id: 'user',
state: (): UserState => ({
// 当前登录用户的信息
info: null,
// 当前登录用户的菜单
menus: null,
// 当前登录用户的权限
authorities: [],
// 当前登录用户的角色
roles: []
}),
getters: {},
actions: {
/**
*
*/
async fetchUserInfo() {
const result = await getUserInfo().catch(() => {});
if (!result) {
return {};
}
// 租户初始化
if (result.authorities?.length == 0) {
result.roles?.map((d) => {
if (d.roleCode === 'superAdmin') {
// const hide = message.loading('系统正在初始化...', 500);
initialization(d.roleId).then(() => {
// hide();
location.replace('/');
return;
});
}
});
}
// 用户信息
this.info = result;
// 缓存租户信息
localStorage.setItem('TenantId', `${this.info.tenantId}`);
// 缓存企业信息
if (this.info.companyInfo) {
localStorage.setItem(
'CompanyLogo',
`${this.info.companyInfo?.companyLogo}`
);
localStorage.setItem('PlanId', `${this.info.companyInfo?.planId}`);
localStorage.setItem(
'ModulesUrl',
`${this.info.companyInfo.modulesUrl}`
);
}
// 用户权限
this.authorities =
result.authorities
?.filter((d) => !!d.authority)
?.map((d) => d.authority) ?? [];
// 用户角色
this.roles = result.roles?.map((d) => d.roleCode) ?? [];
// 获取token
const token = localStorage.getItem(TOKEN_STORE_NAME);
// 用户菜单, 过滤掉按钮类型并转为 children 形式
const { menus, homePath } = formatMenus(
USER_MENUS ??
toTreeData({
data: result.authorities
?.filter((d) => d.menuType !== 1)
.map((d) => {
// 改造子模块的访问路径
if (d.modulesUrl) {
d.component = `${d.modulesUrl}${d.path}?token=${token}`;
}
return d;
}),
idField: 'menuId',
parentIdField: 'parentId'
}).concat(EXTRA_MENUS)
);
this.menus = menus;
// console.log('主控菜单', menus);
return { menus, homePath };
},
/**
*
*/
setInfo(value: User) {
this.info = value;
},
/**
* badge
*/
setMenuBadge(path: string, value?: number | string, color?: string) {
this.menus = formatTreeData(this.menus, (m) => {
if (path === m.path) {
return {
...m,
meta: {
...m.meta,
badge: value,
badgeColor: color
}
};
}
return m;
});
}
}
});

6
utils/domain.ts

@ -1,5 +1,3 @@
import { isNumber } from 'ele-admin-pro';
// 解析域名结构
export function getHost(): any {
const host = window.location.host;
@ -33,7 +31,7 @@ export function getDomainPart1(): any {
if (split[0] == '127') {
return undefined;
}
if (isNumber(split[0])) {
if (typeof (split[0])) {
return split[0];
}
return undefined;
@ -101,6 +99,6 @@ export function getProductCode(): string | null {
/**
*
*/
export function navSubDomain(path): string {
export function navSubDomain(path: string): string {
return `${window.location.protocol}//${path}.${getRootDomain()}`;
}

254
utils/page-tab-util.ts

@ -1,254 +0,0 @@
/**
*
*/
import { unref } from 'vue';
import type { RouteLocationNormalizedLoaded } from 'vue-router';
import type { TabItem, TabRemoveOption } from 'ele-admin-pro/es';
import { message } from 'ant-design-vue/es';
import router from '@/router';
import { useThemeStore } from '@/store/modules/theme';
import type { RouteReloadOption } from '@/store/modules/theme';
import { removeToken } from '@/utils/token-util';
import { setDocumentTitle } from '@/utils/document-title-util';
import {
HOME_PATH,
LAYOUT_PATH,
REDIRECT_PATH,
REPEATABLE_TABS
} from '@/config/setting';
const HOME_ROUTE = HOME_PATH || LAYOUT_PATH;
const BASE_URL = import.meta.env.BASE_URL;
/**
*
*/
export interface TabReloadOptions {
// 是否是主页
isHome?: boolean;
// 路由地址
fullPath?: string;
}
/**
*
*/
export function reloadPageTab(option?: TabReloadOptions) {
if (!option) {
// 刷新当前路由
const { path, fullPath, query } = unref(router.currentRoute);
if (path.includes(REDIRECT_PATH)) {
return;
}
const isHome = isHomeRoute(unref(router.currentRoute));
setRouteReload({
reloadHome: isHome,
reloadPath: isHome ? void 0 : fullPath
});
router.replace({
path: REDIRECT_PATH + path,
query
});
} else {
// 刷新指定页签
const { fullPath, isHome } = option;
setRouteReload({
reloadHome: isHome,
reloadPath: isHome ? void 0 : fullPath
});
router.replace(REDIRECT_PATH + fullPath);
}
}
/**
*
*/
export function finishPageTab() {
const key = getRouteTabKey();
removePageTab({ key, active: key });
}
/**
*
*/
export function removePageTab(option: TabRemoveOption) {
useThemeStore()
.tabRemove(option)
.then(({ path, home }) => {
if (path) {
router.push(path);
} else if (home) {
router.push(HOME_ROUTE);
}
})
.catch(() => {
message.error('当前页签不可关闭');
});
}
/**
*
*/
export function removeLeftPageTab(option: TabRemoveOption) {
useThemeStore()
.tabRemoveLeft(option)
.then(({ path }) => {
if (path) {
router.push(path);
}
})
.catch(() => {
message.error('左侧没有可关闭的页签');
});
}
/**
*
*/
export function removeRightPageTab(option: TabRemoveOption) {
useThemeStore()
.tabRemoveRight(option)
.then(({ path, home }) => {
if (path) {
router.push(path);
} else if (home) {
router.push(HOME_ROUTE);
}
})
.catch(() => {
message.error('右侧没有可关闭的页签');
});
}
/**
*
*/
export function removeOtherPageTab(option: TabRemoveOption) {
useThemeStore()
.tabRemoveOther(option)
.then(({ path, home }) => {
if (path) {
router.push(path);
} else if (home) {
router.push(HOME_ROUTE);
}
})
.catch(() => {
message.error('没有可关闭的页签');
});
}
/**
*
* @param active
*/
export function removeAllPageTab(active: string) {
useThemeStore()
.tabRemoveAll(active)
.then(({ home }) => {
if (home) {
router.push(HOME_ROUTE);
}
})
.catch(() => {
message.error('没有可关闭的页签');
});
}
/**
*
*/
export function cleanPageTabs() {
useThemeStore().setTabs([]);
}
/**
*
* @param data
*/
export function addPageTab(data: TabItem | TabItem[]) {
useThemeStore().tabAdd(data);
}
/**
*
* @param data
*/
export function setPageTab(data: TabItem) {
useThemeStore().tabSetItem(data);
}
/**
*
* @param title
*/
export function setPageTabTitle(title: string) {
setPageTab({ key: getRouteTabKey(), title });
setDocumentTitle(title);
}
/**
* key
*/
export function getRouteTabKey() {
const { path, fullPath, meta } = unref(router.currentRoute);
const isUnique = meta.tabUnique === false || REPEATABLE_TABS.includes(path);
return isUnique ? fullPath : path;
}
/**
*
* @param components
*/
export function setHomeComponents(components: string[]) {
useThemeStore().setHomeComponents(components);
}
/**
*
* @param option
*/
export function setRouteReload(option: RouteReloadOption | null) {
return useThemeStore().setRouteReload(option);
}
/**
*
* @param route
*/
export function isHomeRoute(route: RouteLocationNormalizedLoaded) {
const { path, matched } = route;
if (HOME_ROUTE === path) {
return true;
}
return (
matched[0] &&
matched[0].path === LAYOUT_PATH &&
matched[0].redirect === path
);
}
/**
*
* @param from
*/
export function goHomeRoute(from?: string) {
router.replace(from || HOME_ROUTE);
}
/**
* 退
* @param route 使
* @param from
*/
export function logout(route?: boolean, from?: string) {
removeToken();
if (route) {
router.push({
path: '/login',
query: from ? { from } : void 0
});
} else {
// 这样跳转避免再次登录重复注册动态路由
location.replace(BASE_URL + 'login' + (from ? '?from=' + from : ''));
}
}

60
utils/request.ts

@ -3,12 +3,8 @@
*/
import axios from 'axios';
import type { AxiosResponse } from 'axios';
import { unref } from 'vue';
import router from '@/router';
import { Modal } from 'ant-design-vue';
import { API_BASE_URL, TOKEN_HEADER_NAME, LAYOUT_PATH } from '@/config/setting';
import {API_BASE_URL, TENANT_ID, TOKEN_HEADER_NAME} from '~/config';
import { getToken, setToken } from './token-util';
import { logout } from './page-tab-util';
import type { ApiResult } from '@/api';
import { getHostname, getTenantId } from '@/utils/domain';
@ -20,8 +16,8 @@ const service = axios.create({
*
*/
service.interceptors.request.use(
(config) => {
const TENANT_ID = localStorage.getItem('TenantId');
(config) => {
const tid = localStorage.getItem('TenantId');
const token = getToken();
// 添加 token 到 header
if (token && config.headers) {
@ -35,15 +31,15 @@ service.interceptors.request.use(
config.headers.common['CompanyId'] = companyId;
}
// 通过网站域名获取租户ID
if (getHostname()) {
config.headers.common['Domain'] = getHostname();
}
// if (getHostname()) {
// config.headers.common['Domain'] = getHostname();
// }
// 解析二级域名获取租户ID
if (getTenantId()) {
config.headers.common['TenantId'] = getTenantId();
console.log('domain', getTenantId());
return config;
}
// if (getTenantId()) {
// config.headers.common['TenantId'] = getTenantId();
// console.log('domain', getTenantId());
// return config;
// }
if (TENANT_ID) {
config.headers.common['TenantId'] = TENANT_ID;
return config;
@ -62,23 +58,23 @@ service.interceptors.request.use(
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));
}
// 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) {

2
utils/token-util.ts

@ -1,7 +1,7 @@
/**
* token
*/
import { APP_SECRET, TOKEN_STORE_NAME } from '@/config/setting';
import { APP_SECRET, TOKEN_STORE_NAME } from '@/config';
import md5 from 'js-md5';
/**

9
utils/tool.ts

@ -13,3 +13,12 @@ export function escapeHtml(str: string) {
export function isArray(str: unknown) {
return Object.prototype.toString.call(str) === '[object Array]';
}
// 配置服务器接口
export function getBaseUrl() {
if (process.server) {
return "http://127.0.0.1:9001/api"
} else {
return "/api"
}
}

BIN
归档.zip

Binary file not shown.
Loading…
Cancel
Save