After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 37 KiB |
@ -0,0 +1,3 @@ |
|||
$drak-blue: #292838; |
|||
|
|||
$light-gray: #666; |
@ -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> |
@ -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> |
@ -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); |
@ -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); |
|||
}; |
@ -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); |
|||
}; |
|||
|
@ -0,0 +1,11 @@ |
|||
module.exports = { |
|||
apps: [ |
|||
{ |
|||
name: '网宿软件', |
|||
port: '10198', |
|||
exec_mode: 'cluster', |
|||
instances: 'max', |
|||
script: './.output/server/index.mjs' |
|||
} |
|||
] |
|||
} |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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); |
|||
}); |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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>`;
|
|||
} |
|||
} |
After Width: | Height: | Size: 37 KiB |
@ -1,6 +0,0 @@ |
|||
/** |
|||
* pinia |
|||
*/ |
|||
import { createPinia } from 'pinia'; |
|||
|
|||
export default createPinia(); |
@ -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'); |
|||
}); |
|||
} |
|||
} |
|||
}); |
@ -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; |
|||
} |
|||
} |
|||
}); |
@ -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; |
|||
}); |
|||
} |
|||
} |
|||
}); |
@ -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; |
|||
} |
@ -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; |
|||
}); |
|||
} |
|||
} |
|||
}); |
@ -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 : '')); |
|||
} |
|||
} |