23 changed files with 722 additions and 289 deletions
@ -0,0 +1,274 @@ |
|||||
|
<template> |
||||
|
<!-- 首页幻灯片 --> |
||||
|
<div class="banner sm:pt-[60px] pt-[46px]" v-if="banner"> |
||||
|
<el-carousel :interval="5000" arrow="always" :height="screenWidth <= 768 ? `170px` : `750px`"> |
||||
|
<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="category md:w-screen-xl m-auto my-3 p-3 flex flex-col gap-xl"> |
||||
|
<div class="category-item bg-white rounded-lg p-3 hover:shadow"> |
||||
|
<div class="category-name text-lg text-gray-600 border-b border-gray-200 border-b-solid pb-2 mx-2"> |
||||
|
<el-icon> |
||||
|
<ElIconLink/> |
||||
|
</el-icon> |
||||
|
<text class="ml-2">栏目名称</text> |
||||
|
</div> |
||||
|
<div class="flex flex-wrap px-2 py-4"> |
||||
|
<div v-for="(item,index) in links" class="flex items-center"> |
||||
|
<a :href="item.url" target="_blank">{{ item.name }}</a> |
||||
|
<el-divider v-if="index + 1 != links.length" direction="vertical" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="category-item bg-white rounded-lg p-3 hover:shadow"> |
||||
|
<div class="category-name text-lg text-gray-600 border-b border-gray-200 border-b-solid pb-2 mx-2 flex items-center"> |
||||
|
<el-icon> |
||||
|
<ElIconLink/> |
||||
|
</el-icon> |
||||
|
<text class="ml-2">友情链接</text> |
||||
|
</div> |
||||
|
<div class="flex flex-wrap px-2 py-4"> |
||||
|
<div v-for="(item,index) in links" class="flex items-center"> |
||||
|
<a :href="item.url" target="_blank">{{ item.name }}</a> |
||||
|
<el-divider v-if="index + 1 != links.length" direction="vertical" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import type {ApiResult} from "~/api"; |
||||
|
import type {AdItem} from "~/api/cms/ad/model"; |
||||
|
import type {Link} from "~/api/cms/link/model"; |
||||
|
import {useServerRequest} from "~/composables/useServerRequest"; |
||||
|
import {useWebsite} from "~/composables/configState"; |
||||
|
import {getPath} from "~/utils/common"; |
||||
|
|
||||
|
const website = useWebsite(); |
||||
|
const banner = ref<any>(); |
||||
|
const links = ref<any>(); |
||||
|
const movieList = ref<any>(); |
||||
|
const screenWidth = window.innerWidth; |
||||
|
const screenHeight = window.innerHeight; |
||||
|
|
||||
|
// 获取数据 |
||||
|
const reload = async () => { |
||||
|
await nextTick() |
||||
|
const form = useServerRequest('/cms/navigation/getNavigationByPath',{query: {path: getPath()}}) |
||||
|
const getSlide = useServerRequest<ApiResult<AdItem[]>>('/cms/ad/side'); |
||||
|
const getLink = useServerRequest<ApiResult<Link[]>>('/oa/link?linkType=友情链接'); |
||||
|
const [{data: slide}, {data: link}] = await Promise.all([getSlide, getLink]); |
||||
|
console.log(slide.value) |
||||
|
banner.value = slide.value?.data; |
||||
|
links.value = link.value?.data; |
||||
|
} |
||||
|
reload(); |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.index { |
||||
|
padding-top: 20px; |
||||
|
|
||||
|
.banner { |
||||
|
.el-carousel__container { |
||||
|
height: 380px; |
||||
|
} |
||||
|
|
||||
|
.el-image { |
||||
|
height: 380px; |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 768px) { |
||||
|
.el-carousel__container { |
||||
|
height: 200px; |
||||
|
} |
||||
|
|
||||
|
.el-image { |
||||
|
height: 200px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.friendly-link { |
||||
|
border-bottom: #eee solid 1px; |
||||
|
padding: 10px; |
||||
|
font-size: 18px; |
||||
|
|
||||
|
> img { |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
|
||||
|
&__content { |
||||
|
padding: 20px; |
||||
|
|
||||
|
a { |
||||
|
padding-right: 15px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.demonstration { |
||||
|
color: var(--el-text-color-secondary); |
||||
|
} |
||||
|
|
||||
|
.col-pd { |
||||
|
li { |
||||
|
a { |
||||
|
font-size: 14px; |
||||
|
padding: 10px 0 10px; |
||||
|
border-bottom: dotted 1px #eeeeee; |
||||
|
|
||||
|
.badge { |
||||
|
display: inline-block; |
||||
|
margin-right: 10px; |
||||
|
width: 18px; |
||||
|
height: 18px; |
||||
|
text-align: center; |
||||
|
line-height: 18px; |
||||
|
border-radius: 2px; |
||||
|
font-size: 12px; |
||||
|
background-color: #eee; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.text-muted { |
||||
|
color: #999; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:nth-child(1) { |
||||
|
.badge { |
||||
|
background-color: #ff4a4a; |
||||
|
color: #fff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:nth-child(2) { |
||||
|
.badge { |
||||
|
background-color: #ff7701; |
||||
|
color: #fff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:nth-child(3) { |
||||
|
.badge { |
||||
|
background-color: #ffb400; |
||||
|
color: #fff; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.panel_hd { |
||||
|
border-bottom: #eeeeee solid 1px; |
||||
|
height: 46px; |
||||
|
margin-bottom: 15px; |
||||
|
|
||||
|
.title { |
||||
|
font-size: 18px; |
||||
|
line-height: 24px; |
||||
|
|
||||
|
img { |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__right { |
||||
|
li { |
||||
|
position: relative; |
||||
|
|
||||
|
&::before { |
||||
|
content: ''; |
||||
|
display: block; |
||||
|
width: 1px; |
||||
|
height: 10px; |
||||
|
background: #eee; |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
transform: translateY(-30%); |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
&:last-child::before { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
a { |
||||
|
padding: 0 10px; |
||||
|
color: #999; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.video-list { |
||||
|
&__block { |
||||
|
padding: 10px 0; |
||||
|
|
||||
|
&__img { |
||||
|
width: 100%; |
||||
|
height: 218px; |
||||
|
} |
||||
|
|
||||
|
.img-box { |
||||
|
position: relative; |
||||
|
height: 218px; |
||||
|
display: block; |
||||
|
|
||||
|
span { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
height: 30px; |
||||
|
line-height: 30px; |
||||
|
left: 0; |
||||
|
display: inline-block; |
||||
|
background-image: linear-gradient(transparent, rgba(0, 0, 0, .5)); |
||||
|
color: #fff; |
||||
|
font-size: 12px; |
||||
|
text-align: right; |
||||
|
padding-right: 10px; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__detail { |
||||
|
.title { |
||||
|
font-size: 14px; |
||||
|
color: #333; |
||||
|
padding-top: 10px; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
min-height: 19px; |
||||
|
font-size: 12px; |
||||
|
margin-bottom: 0; |
||||
|
margin-top: 5px; |
||||
|
color: #999; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media only screen and (max-width: 991px) { |
||||
|
.video-list { |
||||
|
&__block { |
||||
|
&__img, .img-box { |
||||
|
height: 170px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -1,274 +1,76 @@ |
|||||
<template> |
<template> |
||||
<!-- 首页幻灯片 --> |
|
||||
<div class="banner sm:pt-[60px] pt-[46px]" v-if="banner"> |
|
||||
<el-carousel :interval="5000" arrow="always" :height="screenWidth <= 768 ? `170px` : `750px`"> |
|
||||
<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> |
|
||||
|
<!-- Banner --> |
||||
|
<Banner :layout="layout" /> |
||||
|
|
||||
|
<!-- 简单模式(常规单页面) --> |
||||
|
<PageContainer :form="form" :layout="layout" /> |
||||
|
|
||||
<!-- 首页新闻栏目 --> |
|
||||
<div class="category md:w-3/4 m-auto my-3 p-3 flex flex-col gap-xl"> |
|
||||
<div class="category-item bg-white rounded-lg p-3 hover:shadow"> |
|
||||
<div class="category-name text-lg text-gray-600 border-b border-gray-200 border-b-solid pb-2 mx-2"> |
|
||||
<el-icon> |
|
||||
<ElIconLink/> |
|
||||
</el-icon> |
|
||||
<text class="ml-2">栏目名称</text> |
|
||||
</div> |
|
||||
<div class="flex flex-wrap px-2 py-4"> |
|
||||
<div v-for="(item,index) in links" class="flex items-center"> |
|
||||
<a :href="item.url" target="_blank">{{ item.name }}</a> |
|
||||
<el-divider v-if="index + 1 != links.length" direction="vertical" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="category-item bg-white rounded-lg p-3 hover:shadow"> |
|
||||
<div class="category-name text-lg text-gray-600 border-b border-gray-200 border-b-solid pb-2 mx-2 flex items-center"> |
|
||||
<el-icon> |
|
||||
<ElIconLink/> |
|
||||
</el-icon> |
|
||||
<text class="ml-2">友情链接</text> |
|
||||
</div> |
|
||||
<div class="flex flex-wrap px-2 py-4"> |
|
||||
<div v-for="(item,index) in links" class="flex items-center"> |
|
||||
<a :href="item.url" target="_blank">{{ item.name }}</a> |
|
||||
<el-divider v-if="index + 1 != links.length" direction="vertical" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
<!-- 高级模式(自定义组件) --> |
||||
|
<div class="flex flex-col" v-if="layout?.showLayout"> |
||||
|
{{ layout }} |
||||
</div> |
</div> |
||||
</template> |
|
||||
|
|
||||
|
</template> |
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import type {ApiResult} from "~/api"; |
import type {ApiResult} from "~/api"; |
||||
import type {AdItem} from "~/api/cms/ad/model"; |
|
||||
import type {Link} from "~/api/cms/link/model"; |
|
||||
import {useServerRequest} from "~/composables/useServerRequest"; |
import {useServerRequest} from "~/composables/useServerRequest"; |
||||
import {useWebsite} from "~/composables/configState"; |
|
||||
import {getPath} from "~/utils/common"; |
|
||||
|
|
||||
|
import {useConfigInfo, useForm, useToken, useWebsite} from "~/composables/configState"; |
||||
|
import type {BreadcrumbItem} from "~/types/global"; |
||||
|
import type {Navigation} from "~/api/cms/navigation/model"; |
||||
|
import {getIdBySpm} from "~/utils/common"; |
||||
|
import PageContainer from "~/components/PageContainer.vue"; |
||||
|
|
||||
|
// 引入状态管理 |
||||
|
const route = useRoute(); |
||||
const website = useWebsite(); |
const website = useWebsite(); |
||||
const banner = ref<any>(); |
|
||||
const links = ref<any>(); |
|
||||
const movieList = ref<any>(); |
|
||||
const screenWidth = window.innerWidth; |
|
||||
const screenHeight = window.innerHeight; |
|
||||
|
const layout = ref<any>(); |
||||
|
const config = useConfigInfo(); |
||||
|
const token = useToken(); |
||||
|
const form = useForm(); |
||||
|
const breadcrumb = ref<BreadcrumbItem>(); |
||||
|
|
||||
// 获取数据 |
|
||||
|
// 请求数据 |
||||
const reload = async () => { |
const reload = async () => { |
||||
await nextTick() |
|
||||
const form = useServerRequest('/cms/navigation/getNavigationByPath',{query: {path: getPath()}}) |
|
||||
const getSlide = useServerRequest<ApiResult<AdItem[]>>('/cms/ad/side'); |
|
||||
const getLink = useServerRequest<ApiResult<Link[]>>('/oa/link?linkType=友情链接'); |
|
||||
const [{data: slide}, {data: link}] = await Promise.all([getSlide, getLink]); |
|
||||
console.log(slide.value) |
|
||||
banner.value = slide.value?.data; |
|
||||
links.value = link.value?.data; |
|
||||
} |
|
||||
reload(); |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss"> |
|
||||
.index { |
|
||||
padding-top: 20px; |
|
||||
|
|
||||
.banner { |
|
||||
.el-carousel__container { |
|
||||
height: 380px; |
|
||||
} |
|
||||
|
|
||||
.el-image { |
|
||||
height: 380px; |
|
||||
} |
|
||||
|
|
||||
@media (max-width: 768px) { |
|
||||
.el-carousel__container { |
|
||||
height: 200px; |
|
||||
} |
|
||||
|
|
||||
.el-image { |
|
||||
height: 200px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.friendly-link { |
|
||||
border-bottom: #eee solid 1px; |
|
||||
padding: 10px; |
|
||||
font-size: 18px; |
|
||||
|
|
||||
> img { |
|
||||
margin-right: 10px; |
|
||||
} |
|
||||
|
|
||||
&__content { |
|
||||
padding: 20px; |
|
||||
|
|
||||
a { |
|
||||
padding-right: 15px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.demonstration { |
|
||||
color: var(--el-text-color-secondary); |
|
||||
} |
|
||||
|
|
||||
.col-pd { |
|
||||
li { |
|
||||
a { |
|
||||
font-size: 14px; |
|
||||
padding: 10px 0 10px; |
|
||||
border-bottom: dotted 1px #eeeeee; |
|
||||
|
|
||||
.badge { |
|
||||
display: inline-block; |
|
||||
margin-right: 10px; |
|
||||
width: 18px; |
|
||||
height: 18px; |
|
||||
text-align: center; |
|
||||
line-height: 18px; |
|
||||
border-radius: 2px; |
|
||||
font-size: 12px; |
|
||||
background-color: #eee; |
|
||||
color: #333; |
|
||||
} |
|
||||
|
|
||||
.text-muted { |
|
||||
color: #999; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&:nth-child(1) { |
|
||||
.badge { |
|
||||
background-color: #ff4a4a; |
|
||||
color: #fff; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&:nth-child(2) { |
|
||||
.badge { |
|
||||
background-color: #ff7701; |
|
||||
color: #fff; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&:nth-child(3) { |
|
||||
.badge { |
|
||||
background-color: #ffb400; |
|
||||
color: #fff; |
|
||||
} |
|
||||
|
// 存在spm(优先级高) |
||||
|
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/navigation/' + getIdBySpm(5)) |
||||
|
if (nav.value?.data) { |
||||
|
form.value = nav.value.data |
||||
|
}else{ |
||||
|
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/navigation/getNavigationByPath',{query: {path: route.path}}) |
||||
|
if(nav.value?.data){ |
||||
|
form.value = nav.value?.data; |
||||
} |
} |
||||
} |
} |
||||
} |
|
||||
|
|
||||
.panel_hd { |
|
||||
border-bottom: #eeeeee solid 1px; |
|
||||
height: 46px; |
|
||||
margin-bottom: 15px; |
|
||||
|
|
||||
.title { |
|
||||
font-size: 18px; |
|
||||
line-height: 24px; |
|
||||
|
|
||||
img { |
|
||||
margin-right: 10px; |
|
||||
} |
|
||||
|
// 页面布局 |
||||
|
if(form.value?.layout){ |
||||
|
layout.value = JSON.parse(form.value?.layout) |
||||
} |
} |
||||
|
|
||||
&__right { |
|
||||
li { |
|
||||
position: relative; |
|
||||
|
|
||||
&::before { |
|
||||
content: ''; |
|
||||
display: block; |
|
||||
width: 1px; |
|
||||
height: 10px; |
|
||||
background: #eee; |
|
||||
position: absolute; |
|
||||
top: 50%; |
|
||||
transform: translateY(-30%); |
|
||||
right: 0; |
|
||||
} |
|
||||
|
|
||||
&:last-child::before { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
a { |
|
||||
padding: 0 10px; |
|
||||
color: #999; |
|
||||
font-size: 14px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
// seo |
||||
|
useHead({ |
||||
|
title: `${form.value.title} - ${website.value.websiteName}`, |
||||
|
meta: [{ name: form.value.design?.keywords, content: form.value.design?.description }], |
||||
|
bodyAttrs: { |
||||
|
class: "page-container", |
||||
|
}, |
||||
|
script: [ |
||||
|
{ |
||||
|
children: `console.log(${JSON.stringify(form.value)})`, |
||||
|
}, |
||||
|
], |
||||
|
}); |
||||
|
// 面包屑 |
||||
|
breadcrumb.value = form.value |
||||
} |
} |
||||
|
|
||||
.video-list { |
|
||||
&__block { |
|
||||
padding: 10px 0; |
|
||||
|
|
||||
&__img { |
|
||||
width: 100%; |
|
||||
height: 218px; |
|
||||
} |
|
||||
|
|
||||
.img-box { |
|
||||
position: relative; |
|
||||
height: 218px; |
|
||||
display: block; |
|
||||
|
|
||||
span { |
|
||||
position: absolute; |
|
||||
bottom: 0; |
|
||||
width: 100%; |
|
||||
height: 30px; |
|
||||
line-height: 30px; |
|
||||
left: 0; |
|
||||
display: inline-block; |
|
||||
background-image: linear-gradient(transparent, rgba(0, 0, 0, .5)); |
|
||||
color: #fff; |
|
||||
font-size: 12px; |
|
||||
text-align: right; |
|
||||
padding-right: 10px; |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&__detail { |
|
||||
.title { |
|
||||
font-size: 14px; |
|
||||
color: #333; |
|
||||
padding-top: 10px; |
|
||||
} |
|
||||
|
watch( |
||||
|
() => route.path, |
||||
|
(path) => { |
||||
|
console.log(path,'=>Path') |
||||
|
|
||||
p { |
|
||||
min-height: 19px; |
|
||||
font-size: 12px; |
|
||||
margin-bottom: 0; |
|
||||
margin-top: 5px; |
|
||||
color: #999; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@media only screen and (max-width: 991px) { |
|
||||
.video-list { |
|
||||
&__block { |
|
||||
&__img, .img-box { |
|
||||
height: 170px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
||||
|
reload(); |
||||
|
}, |
||||
|
{ immediate: true } |
||||
|
); |
||||
|
</script> |
||||
|
@ -0,0 +1,178 @@ |
|||||
|
<template> |
||||
|
<div class="login flex justify-around mt-[120px] h-[550px]"> |
||||
|
<div class="flash"> |
||||
|
|
||||
|
</div> |
||||
|
<el-card class="m-2 w-screen-sm sm:w-[430px] flex justify-around"> |
||||
|
<el-space class="tabs pt-5 text-xl flex justify-center"> |
||||
|
<el-tabs v-model="activeName" class="demo-tabs"> |
||||
|
<el-tab-pane label="账号登录" name="first"> |
||||
|
<div class="custom-style my-4"> |
||||
|
<el-form :model="form" label-width="auto" class="w-[330px]"> |
||||
|
<el-form-item> |
||||
|
<el-input class="w-full" size="large" maxlength="11" placeholder="登录账号|手机号码" v-model="form.username" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-input type="password" size="large" placeholder="登录密码" v-model="form.password" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-space class="flex justify-between w-full"> |
||||
|
<el-input size="large" placeholder="图形验证码" v-model="form.code" /> |
||||
|
<el-image alt="" :src="captcha" @click="changeCaptcha" /> |
||||
|
</el-space> |
||||
|
</el-form-item> |
||||
|
|
||||
|
<el-form-item label="记住密码"> |
||||
|
<el-switch v-model="form.remember" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-button type="primary" size="large" class="w-full" @click="onSubmit">登录</el-button> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
</el-tab-pane> |
||||
|
<el-tab-pane label="短信登录" name="second"> |
||||
|
<div class="custom-style my-4"> |
||||
|
<el-form :model="form" label-width="auto" class="w-[330px]"> |
||||
|
<el-form-item> |
||||
|
<el-input class="w-full" size="large" placeholder="手机号码" v-model="form.username" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-input type="password" size="large" placeholder="验证码" v-model="form.password" /> |
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<el-button type="primary" size="large" class="w-full" @click="onSubmit">登录</el-button> |
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
</el-space> |
||||
|
<div class="clearfix flex justify-center"> |
||||
|
<el-divider> |
||||
|
其他登录方式 |
||||
|
</el-divider> |
||||
|
</div> |
||||
|
<div class="clearfix flex justify-center"> |
||||
|
<el-button circle :icon="ElIconUserFilled"></el-button> |
||||
|
</div> |
||||
|
<!-- <div class="clearfix flex justify-center">--> |
||||
|
<!-- <el-space>--> |
||||
|
<!-- <a class=" text-gray-400 cursor-pointer">忘记账号</a>--> |
||||
|
<!-- <a class=" text-gray-400 cursor-pointer">忘记密码</a>--> |
||||
|
<!-- <a class=" text-gray-400 cursor-pointer">登录异常帮助文档</a>--> |
||||
|
<!-- </el-space>--> |
||||
|
<!-- </div>--> |
||||
|
|
||||
|
|
||||
|
<!-- <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">--> |
||||
|
<!-- <el-tab-pane label="登录" name="first">登录</el-tab-pane>--> |
||||
|
<!-- <el-tab-pane label="注册" name="second">注册</el-tab-pane>--> |
||||
|
<!-- </el-tabs>--> |
||||
|
</el-card> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script setup lang="ts"> |
||||
|
import {useConfigInfo, useToken, useUserInfo, useWebsite} from "~/composables/configState"; |
||||
|
import useFormData from '@/utils/use-form-data'; |
||||
|
import type { User } from '@/api/system/user/model'; |
||||
|
import { ref } from 'vue' |
||||
|
import {getCaptcha} from "~/api/passport/login"; |
||||
|
import {useServerRequest} from "~/composables/useServerRequest"; |
||||
|
import type {ApiResult} from "~/api"; |
||||
|
import type {Website} from "~/api/cms/website/model"; |
||||
|
import type {CaptchaResult, LoginResult} from "~/api/passport/login/model"; |
||||
|
|
||||
|
// 配置信息 |
||||
|
const runtimeConfig = useRuntimeConfig(); |
||||
|
const website = useWebsite() |
||||
|
const config = useConfigInfo(); |
||||
|
const token = useToken(); |
||||
|
const userInfo = useUserInfo(); |
||||
|
|
||||
|
const value = ref('登录') |
||||
|
const options = ['登录', '注册'] |
||||
|
const activeName = ref('first') |
||||
|
|
||||
|
// 验证码 base64 数据 |
||||
|
const captcha = ref(''); |
||||
|
// 验证码内容, 实际项目去掉 |
||||
|
const text = ref(''); |
||||
|
// 是否显示图形验证码弹窗 |
||||
|
const visible = ref(false); |
||||
|
// 图形验证码 |
||||
|
const imgCode = ref(''); |
||||
|
// 发送验证码按钮loading |
||||
|
const codeLoading = ref(false); |
||||
|
// 验证码倒计时时间 |
||||
|
const countdownTime = ref(0); |
||||
|
// 验证码倒计时定时器 |
||||
|
let countdownTimer: number | null = null; |
||||
|
|
||||
|
// 配置信息 |
||||
|
const { form } = useFormData<User>({ |
||||
|
username: '', |
||||
|
phone: '', |
||||
|
password: '', |
||||
|
code: '', |
||||
|
smsCode: '', |
||||
|
remember: true, |
||||
|
isAdmin: true |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
const navigateTo = (url: string) => { |
||||
|
window.location.href = url; |
||||
|
} |
||||
|
|
||||
|
/* 获取图形验证码 */ |
||||
|
const changeCaptcha = async () => { |
||||
|
const {data: captchaInfo } = await useServerRequest<ApiResult<CaptchaResult>>('/captcha',{baseURL: runtimeConfig.public.apiServer}); |
||||
|
const captchaData = captchaInfo.value?.data |
||||
|
if(captchaData){ |
||||
|
captcha.value = captchaData.base64; |
||||
|
} |
||||
|
|
||||
|
// 已经登录跳转首页 |
||||
|
if(token.value && token.value.length > 0){ |
||||
|
navigateTo('/') |
||||
|
return; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* 执行登录 |
||||
|
*/ |
||||
|
const onSubmit = async () => { |
||||
|
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/login',{baseURL: runtimeConfig.public.apiServer,method: "post",body: form}) |
||||
|
console.log(response.value); |
||||
|
if (response.value?.code == 0) { |
||||
|
if(response.value.data){ |
||||
|
const access_token = response.value.data?.access_token |
||||
|
const user = response.value.data?.user |
||||
|
if(access_token){ |
||||
|
token.value = access_token; |
||||
|
} |
||||
|
if(user){ |
||||
|
userInfo.value = user |
||||
|
} |
||||
|
} |
||||
|
ElMessage.success(response.value.message) |
||||
|
// setTimeout(() => { |
||||
|
// navigateTo('/') |
||||
|
// return; |
||||
|
// },1000) |
||||
|
} |
||||
|
if(response.value?.code !== 0){ |
||||
|
ElMessage.error(response.value?.message) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
changeCaptcha(); |
||||
|
</script> |
||||
|
<style lang="less"> |
||||
|
body{ |
||||
|
background: url("https://oss.wsdns.cn/20240616/4fe7e2e00b7e43e7a5f189fe11b21196.jpeg"); |
||||
|
background-size: 100%; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,134 @@ |
|||||
|
<template> |
||||
|
<div class="login-layout mt-[100px] m-auto w-screen-xl"> |
||||
|
<el-container class="mt-[100px] m-auto p-"> |
||||
|
<el-aside width="170px" class="bg-white p-3 hover:shadow"> |
||||
|
<div class="flex justify-center pb-4"> |
||||
|
<img src="https://oss.wsdns.cn/20240328/797a8e323bba4fc6827abf9d8b98ba45.png" class="w-[80px] h-[80px] rounded-full" /> |
||||
|
</div> |
||||
|
<div class="menu"> |
||||
|
<el-menu |
||||
|
default-active="1" |
||||
|
style="border: none" |
||||
|
> |
||||
|
<el-menu-item v-for="(item,index) in activities" :index="`${index+1}`" style="border-bottom: 1px solid #f3f3f3"> |
||||
|
<span class="text-center">{{ item.name }}</span> |
||||
|
</el-menu-item> |
||||
|
</el-menu> |
||||
|
</div> |
||||
|
</el-aside> |
||||
|
<div shadow="hover" class="flash bg-white hover:shadow h-[500px] w-full ml-6"> |
||||
|
={{ userInfo }}= |
||||
|
</div> |
||||
|
</el-container> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script setup lang="ts"> |
||||
|
import {useConfigInfo, useToken, useUserInfo, useWebsite} from "~/composables/configState"; |
||||
|
import useFormData from '@/utils/use-form-data'; |
||||
|
import type { User } from '@/api/system/user/model'; |
||||
|
import { ref } from 'vue' |
||||
|
import {getCaptcha} from "~/api/passport/login"; |
||||
|
import {useServerRequest} from "~/composables/useServerRequest"; |
||||
|
import type {ApiResult} from "~/api"; |
||||
|
import type {Website} from "~/api/cms/website/model"; |
||||
|
import type {CaptchaResult, LoginResult} from "~/api/passport/login/model"; |
||||
|
|
||||
|
// 配置信息 |
||||
|
const runtimeConfig = useRuntimeConfig(); |
||||
|
const website = useWebsite() |
||||
|
const config = useConfigInfo(); |
||||
|
const token = useToken(); |
||||
|
const userInfo = useUserInfo(); |
||||
|
|
||||
|
const value = ref('登录') |
||||
|
const options = ['登录', '注册'] |
||||
|
const activeName = ref('first') |
||||
|
|
||||
|
const activities = [ |
||||
|
{ |
||||
|
name: '账号信息' |
||||
|
}, |
||||
|
{ |
||||
|
name: '实名认证' |
||||
|
}, |
||||
|
{ |
||||
|
name: '订单列表' |
||||
|
}, |
||||
|
{ |
||||
|
name: '退出登录' |
||||
|
}, |
||||
|
] |
||||
|
const activeNames = ref(['1']) |
||||
|
const handleChange = (val: string[]) => { |
||||
|
console.log(val) |
||||
|
} |
||||
|
// 验证码 base64 数据 |
||||
|
const captcha = ref(''); |
||||
|
// 验证码内容, 实际项目去掉 |
||||
|
const text = ref(''); |
||||
|
// 是否显示图形验证码弹窗 |
||||
|
const visible = ref(false); |
||||
|
// 图形验证码 |
||||
|
const imgCode = ref(''); |
||||
|
// 发送验证码按钮loading |
||||
|
const codeLoading = ref(false); |
||||
|
// 验证码倒计时时间 |
||||
|
const countdownTime = ref(0); |
||||
|
// 验证码倒计时定时器 |
||||
|
let countdownTimer: number | null = null; |
||||
|
|
||||
|
// 配置信息 |
||||
|
const { form } = useFormData<User>({ |
||||
|
username: '', |
||||
|
phone: '', |
||||
|
password: '', |
||||
|
code: '', |
||||
|
smsCode: '', |
||||
|
remember: true, |
||||
|
isAdmin: true |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
const navigateTo = (url: string) => { |
||||
|
window.location.href = url; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 执行登录 |
||||
|
*/ |
||||
|
const onSubmit = async () => { |
||||
|
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/login',{baseURL: runtimeConfig.public.apiServer,method: "post",body: form}) |
||||
|
if (response.value?.code == 0) { |
||||
|
if(response.value.data){ |
||||
|
const access_token = response.value.data?.access_token |
||||
|
const user = response.value.data?.user |
||||
|
if(access_token){ |
||||
|
token.value = access_token; |
||||
|
} |
||||
|
if(user){ |
||||
|
userInfo.value = user; |
||||
|
} |
||||
|
} |
||||
|
ElMessage.success(response.value.message) |
||||
|
setTimeout(() => { |
||||
|
navigateTo('/') |
||||
|
return; |
||||
|
},1000) |
||||
|
} |
||||
|
console.log(response.value?.data,'>>>>>sfd') |
||||
|
ElMessage.error(response.value?.message) |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
useHead({ |
||||
|
title: `用户中心 - ${config.value?.siteName}`, |
||||
|
meta: [{ name: website.value.keywords, content: website.value.comments }] |
||||
|
}); |
||||
|
|
||||
|
</script> |
||||
|
<style lang="less"> |
||||
|
body{ |
||||
|
background: url("https://oss.wsdns.cn/20240328/797a8e323bba4fc6827abf9d8b98ba45.png"); |
||||
|
background-size: 100%; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,29 @@ |
|||||
|
import { reactive } from 'vue'; |
||||
|
|
||||
|
/** |
||||
|
* 表单数据 hook |
||||
|
* @param initValue 默认值 |
||||
|
*/ |
||||
|
export default function <T extends object>(initValue?: T) { |
||||
|
const form = reactive<T>({ ...initValue } as T); |
||||
|
|
||||
|
const resetFields = () => { |
||||
|
Object.keys(form).forEach((key) => { |
||||
|
form[key] = initValue ? initValue[key] : void 0; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const assignFields = (data: object) => { |
||||
|
Object.keys(form).forEach((key) => { |
||||
|
form[key] = data[key]; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
form, |
||||
|
// 重置为初始值
|
||||
|
resetFields, |
||||
|
// 赋值不改变字段
|
||||
|
assignFields |
||||
|
}; |
||||
|
} |
Loading…
Reference in new issue