Browse Source

新增:会员登录功能

master
科技小王子 8 months ago
parent
commit
3b74e4acbf
  1. 25
      api/passport/login/index.ts
  2. 4
      components/AppFooter.vue
  3. 9
      components/AppHeader.vue
  4. 2
      components/Banner.vue
  5. 2
      components/PageContainer.vue
  6. 12
      composables/configState.ts
  7. 4
      layouts/default.vue
  8. 5
      nuxt.config.ts
  9. 2
      pages/_____bak/[custom].vue
  10. 2
      pages/_____bak/a/index.vue
  11. 2
      pages/_____bak/customs/index.vue
  12. 274
      pages/_____bak/index_.vue
  13. 2
      pages/_____bak/page/[id].vue
  14. 2
      pages/article/[id].vue
  15. 2
      pages/article/detail/[id].vue
  16. 2
      pages/docs/index.vue
  17. 322
      pages/index.vue
  18. 178
      pages/login.vue
  19. 2
      pages/product/[id].vue
  20. 2
      pages/product/detail/[id].vue
  21. 134
      pages/user/index.vue
  22. 1
      types/global.d.ts
  23. 29
      utils/use-form-data.ts

25
api/passport/login/index.ts

@ -1,5 +1,5 @@
import request from '@/utils/request';
import { setToken } from '@/utils/token-util';
// import { setToken } from '@/utils/token-util';
import type { ApiResult } from '@/api';
import type {
LoginParam,
@ -18,7 +18,7 @@ export async function login(data: LoginParam) {
data
);
if (res.data.code === 0) {
setToken(res.data.data?.access_token, data.remember);
// setToken(res.data.data?.access_token, data.remember);
if (res.data.data?.user) {
const user = res.data.data?.user;
localStorage.setItem('TenantId', String(user.tenantId));
@ -49,15 +49,16 @@ export async function loginBySms(data: LoginParam) {
data
);
if (res.data.code === 0) {
setToken(res.data.data?.access_token, data.remember);
if (res.data.data?.user) {
const user = res.data.data?.user;
localStorage.setItem('TenantId', String(user.tenantId));
localStorage.setItem('Phone', String(user.phone));
localStorage.setItem('UserId', String(user.userId));
localStorage.setItem('MerchantId', String(user.merchantId));
localStorage.setItem('MerchantName', String(user.merchantName));
}
console.log(res.data,'登录成功')
// setToken(res.data.data?.access_token, data.remember);
// if (res.data.data?.user) {
// const user = res.data.data?.user;
// localStorage.setItem('TenantId', String(user.tenantId));
// localStorage.setItem('Phone', String(user.phone));
// localStorage.setItem('UserId', String(user.userId));
// localStorage.setItem('MerchantId', String(user.merchantId));
// localStorage.setItem('MerchantName', String(user.merchantName));
// }
return res.data.message;
}
@ -87,7 +88,7 @@ export async function remoteLogin(data: LoginParam) {
data
);
if (res.data.code === 0) {
setToken(res.data.data?.access_token, data.remember);
// setToken(res.data.data?.access_token, data.remember);
return res.data.message;
}
return Promise.reject(new Error(res.data.message));

4
components/AppFooter.vue

@ -6,7 +6,7 @@
</div>
<div class="w-full flex flex-col sm:bg-black justify-between">
<!-- PC版 -->
<div class="sub-menu w-full md:w-3/4 m-auto flex justify-between py-10 text-center p-2 hidden-sm-and-down">
<div class="sub-menu w-full md:w-screen-xl m-auto flex justify-between py-10 text-center p-2 hidden-sm-and-down">
<!-- 底部菜单 -->
<div class="left flex flex-col sm:flex-row sm:flex-wrap justify-between gap-3xl sm:w-9/12 flex-wrap w-full">
@ -54,7 +54,7 @@
<!-- </div>-->
<!-- 版权信息 -->
<div class="w-full md:w-3/4 w-full m-auto flex sm:flex-row flex-col-reverse sm:justify-between justify-center items-center sm:py-10 pt-6 pb-6 text-center p-2">
<div class="w-full md:w-screen-xl w-full m-auto flex sm:flex-row flex-col-reverse sm:justify-between justify-center items-center sm:py-10 pt-6 pb-6 text-center p-2">
<div class="text-gray-400 sm:gap-xl leading-7 flex flex-col sm:flex-row">
<span>Copyright © {{ new Date().getFullYear() }} {{ config?.copyright }}</span>
<a class="text-gray-400 hover:text-gray-400" href="https://beian.miit.gov.cn/" target="_blank"> 备案号{{ config?.icpNo }}</a>

9
components/AppHeader.vue

@ -1,6 +1,6 @@
<template>
<header class="header bg-white z-100 top-0 w-full" :class="affix ? 'absolute' : 'fixed'">
<div class="flex items-center between md:w-3/4 m-auto px-2">
<div class="flex items-center between md:w-screen-xl m-auto px-2">
<div class="header___left flex items-center">
<div class="logo mt-1 sm:w-[170px] h-7 w-auto py-2 flex items-center">
<nuxt-link v-if="config?.siteLogo" to="/">
@ -129,10 +129,13 @@
}
}
function logOut() {}
function logOut() {
token.value = ''
navigateTo('/login')
}
function goLogin() {
showLogin.value = true;
navigateTo('/login')
}
const reload = () => {

2
components/Banner.vue

@ -3,7 +3,7 @@
<template v-if="layout && layout.showBanner">
<el-image :src="layout?.photo || config.subpageBanner" :class="layout?.style" class="sm:h-auto"></el-image>
<div class="banner-bar absolute top-0 w-full mt-[60px] sm:flex hidden">
<div class="banner-text py-12 md:w-3/4 m-auto opacity-90 flex flex-col justify-center">
<div class="banner-text py-12 md:w-screen-xl m-auto opacity-90 flex flex-col justify-center">
<div class="keywords my-4 text-3xl">{{ layout?.name }}</div>
<div class="description mb-4 mt-1 text-xl max-w-3xl text-gray-600">{{ layout?.description }}</div>
<div class="buy-btn">

2
components/PageContainer.vue

@ -1,5 +1,5 @@
<template>
<div class="container md:w-3/4 m-auto">
<div class="container md:w-screen-xl m-auto">
<div class="flex flex-col" v-if="!layout?.showLayout">
<Breadcrumb :data="form" />
<div :class="layout?.style" class="page-main w-full bg-white rounded-lg">

12
composables/configState.ts

@ -2,6 +2,7 @@ import { useState } from '#imports';
import type { Config } from '~/types/global';
import type {Website} from "~/api/cms/website/model";
import type {Navigation} from "~/api/cms/navigation/model";
import type {User} from "~/api/system/user/model";
// 网站信息
export const useWebsite = () =>
@ -39,7 +40,16 @@ export const useProductAffix = () =>
});
// 登录凭证
export const useToken = () => useState('token', () => null);
export const useToken = () => useState('token', () => '');
// 用户信息
export const useUserInfo = () => useState('token', () => {
userId: 0;
nickname: '昵称';
username: '用户名';
phone: '手机号码';
email: '';
});
// 是否显示登录弹窗
export const useShowLogin = () => useState('showLogin',() => false)

4
layouts/default.vue

@ -32,10 +32,10 @@
//
onMounted(() => {
//onMountedlocalwindow
// getLoacl();
getLoacl();
window.onbeforeunload = () => {
//setLocal
// setLocal(); //
setLocal(); //
};
});

5
nuxt.config.ts

@ -28,9 +28,10 @@ export default defineNuxtConfig({
},
runtimeConfig: {
public: {
// 开发环境需要修改租户ID和接口地址
// 开发环境配置
tenantId: '5',
apiDev: 'http://127.0.0.1:9001/api',
// apiDev: 'http://127.0.0.1:9001/api',
apiDev: 'https://modules.gxwebsoft.com/api',
// 以下一般不需要修改
apiBase: 'https://modules.gxwebsoft.com/api',

2
pages/_____bak/[custom].vue

@ -3,7 +3,7 @@
<Banner :data="form" />
<!-- 容器 -->
<div class="container md:w-3/4 m-auto">
<div class="container md:w-screen-xl m-auto">
<div class="flex flex-col">
<Breadcrumb :data="form" />
<div :class="form.design?.style" class="page-main w-full bg-white rounded-lg">

2
pages/_____bak/a/index.vue

@ -2,7 +2,7 @@
<!-- Banner -->
<Banner :data="form" />
<div v-if="form" class="flex flex-col w-full md:w-3/4 m-auto my-3">
<div v-if="form" class="flex flex-col w-full md:w-screen-xl m-auto my-3">
<Breadcrumb :data="form" title="文章详情" />

2
pages/_____bak/customs/index.vue

@ -3,7 +3,7 @@
<Banner :data="form" />
{{ form }}--
<!-- 容器 -->
<div class="container md:w-3/4 m-auto" v-if="form">
<div class="container md:w-screen-xl m-auto" v-if="form">
<div class="flex flex-col">
<Breadcrumb :data="form" />
<div :class="form.design?.styles" class="page-main w-full bg-white rounded-lg">

274
pages/_____bak/index_.vue

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

2
pages/_____bak/page/[id].vue

@ -3,7 +3,7 @@
<Banner :data="form" />
<!-- 容器 -->
<div class="container md:w-3/4 m-auto" v-if="form">
<div class="container md:w-screen-xl m-auto" v-if="form">
<div class="flex flex-col">
<Breadcrumb :data="form" />
<div :class="form.design?.styles" class="page-main w-full bg-white rounded-lg">

2
pages/article/[id].vue

@ -2,7 +2,7 @@
<!-- Banner -->
<Banner :layout="layout" />
<div class="container flex flex-col w-full md:w-3/4 m-auto my-3">
<div class="container flex flex-col w-full md:w-screen-xl m-auto my-3">
<Breadcrumb :data="form" />

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

@ -2,7 +2,7 @@
<!-- Banner -->
<Banner :data="form" />
<div v-if="form" class="flex flex-col w-full md:w-3/4 m-auto my-3">
<div v-if="form" class="flex flex-col w-full md:w-screen-xl m-auto my-3">
<Breadcrumb :data="layout" title="文章详情" />

2
pages/docs/index.vue

@ -3,7 +3,7 @@
<Banner :data="form" />
<!-- 容器 -->
<div class="container md:w-3/4 m-auto">
<div class="container md:w-screen-xl m-auto">
<div class="flex flex-col">
<Breadcrumb :data="form" />
<div :class="form.design?.style" class="page-main w-full bg-white rounded-lg">

322
pages/index.vue

@ -1,274 +1,76 @@
<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" />
<!-- 首页新闻栏目 -->
<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>
<!-- 简单模式(常规单页面) -->
<PageContainer :form="form" :layout="layout" />
<!-- 高级模式(自定义组件) -->
<div class="flex flex-col" v-if="layout?.showLayout">
{{ layout }}
</div>
</template>
</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";
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 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 () => {
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;
}
}
}
// 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;
}
}
//
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
// 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
}
.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;
}
}
watch(
() => route.path,
(path) => {
console.log(path,'=>Path')
&: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>
reload();
},
{ immediate: true }
);
</script>

178
pages/login.vue

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

2
pages/product/[id].vue

@ -2,7 +2,7 @@
<!-- Banner -->
<Banner :data="form" />
<div class="container flex flex-col w-full md:w-3/4 m-auto my-3">
<div class="container flex flex-col w-full md:w-screen-xl m-auto my-3">
<Breadcrumb :data="form" :title="form?.categoryName || '文章列表'" />

2
pages/product/detail/[id].vue

@ -23,7 +23,7 @@
</div>
</el-affix>
<div id="buynow" class="flex flex-col w-full md:w-3/4 m-auto md:pt-[60px]" v-if="goods">
<div id="buynow" class="flex flex-col w-full md:w-screen-xl m-auto md:pt-[60px]" v-if="goods">
<Breadcrumb :data="goods" />

134
pages/user/index.vue

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

1
types/global.d.ts

@ -15,6 +15,7 @@ export interface Config {
showSearchIcon?: string;
showLoginButton?: string;
showAdminIcon?: string;
loginBgImg?: string;
wxQrcode?: string;
wxQrcodeText?: string;
copyrightForDemoData?: string;

29
utils/use-form-data.ts

@ -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…
Cancel
Save