Browse Source

新增:控制台->无感登录到后台管理

master
科技小王子 8 months ago
parent
commit
8fb6f5e487
  1. 7
      components/AppFooter.vue
  2. 49
      components/AppHeader.vue
  3. 120
      pages/console/index.vue
  4. 9
      pages/index.vue
  5. 7
      pages/passport/login.vue
  6. 1
      types/global.d.ts
  7. 8
      utils/common.ts

7
components/AppFooter.vue

@ -1,6 +1,9 @@
<template> <template>
<footer class="overflow-hidden" v-if="!getPath().startsWith('/login')">
<div class="sm:h-[100px] h-[50px]"></div>
<footer class="overflow-hidden">
<!-- 间隔 -->
<div class="sm:h-[3px] h-[15px]" v-if="getPath().startsWith('/passport/login')"></div>
<div class="sm:h-[30px] h-[15px]" v-else></div>
<div class=" bg-white my-3 w-3/4 m-auto" v-if="config.copyrightForDemoData"> <div class=" bg-white my-3 w-3/4 m-auto" v-if="config.copyrightForDemoData">
<el-alert :title="config.copyrightForDemoData" center show-icon type="warning" /> <el-alert :title="config.copyrightForDemoData" center show-icon type="warning" />
</div> </div>

49
components/AppHeader.vue

@ -58,33 +58,34 @@
</nav> </nav>
</div> </div>
<div class="header__right items-center"> <div class="header__right items-center">
<div class="sm:flex hidden" v-if="config.showSearchTools">
<el-space class="sm:flex hidden" v-if="config.showSearchTools">
<el-input <el-input
class="w-20 mr-4"
class="w-20"
placeholder="站内搜索" placeholder="站内搜索"
:suffix-icon="ElIconSearch" :suffix-icon="ElIconSearch"
v-model="searchValue" v-model="searchValue"
@keyup.enter.native="handleSearch" @keyup.enter.native="handleSearch"
/> />
</div>
<ClientOnly>
<template v-if="token">
<el-dropdown @command="handleCommand">
<el-avatar class="cursor-pointer" :src="userInfo?.avatar" :size="30" />
<el-button circle :icon="ElIconUserFilled" color="#155FAA"></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<template v-else>
<el-button v-if="config.showSearchIcon" circle :icon="ElIconSearch" @click="navigateTo('/search')"></el-button>
<el-button v-if="config.showLoginButton" circle :icon="ElIconUserFilled" @click="goLogin"></el-button>
</template>
</ClientOnly>
<el-button v-if="token" @click="openSpmUrl(`https://${website.tenantId}.websoft.top/token-login`,website,website.websiteId)">控制台</el-button>
<ClientOnly>
<template v-if="token">
<el-dropdown @command="handleCommand">
<el-avatar class="cursor-pointer" :src="userInfo?.avatar" :size="30" />
<el-button circle :icon="ElIconUserFilled" color="#155FAA"></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<template v-else>
<!-- <el-button v-if="config.showSearchIcon" circle :icon="ElIconSearch" @click="navigateTo('/search')"></el-button>-->
<el-button v-if="config.showLoginButton" circle :icon="ElIconUserFilled" @click="goLogin"></el-button>
</template>
</ClientOnly>
</el-space>
</div> </div>
</div> </div>
<Passport :visible="showPassport" @done="reload" /> <Passport :visible="showPassport" @done="reload" />
@ -106,7 +107,7 @@
import {useServerRequest} from "~/composables/useServerRequest"; import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import type {User} from "~/api/system/user/model"; import type {User} from "~/api/system/user/model";
import {navigateTo} from "#imports";
import {navigateTo, openSpmUrl} from "#imports";
const route = useRoute(); const route = useRoute();
// //
const runtimeConfig = useRuntimeConfig(); const runtimeConfig = useRuntimeConfig();
@ -139,11 +140,11 @@
function logOut() { function logOut() {
token.value = '' token.value = ''
navigateTo('/login')
navigateTo('/passport/login')
} }
function goLogin() { function goLogin() {
navigateTo('/login')
navigateTo('/passport/login')
} }
const handleSearch = (key: string, keyPath: string) => { const handleSearch = (key: string, keyPath: string) => {

120
pages/console/index.vue

@ -0,0 +1,120 @@
<template>
<div class="login-layout mt-[100px] m-auto sm:w-screen-xl w-full">
<div class="mt-[100px] m-auto flex sm:flex-row flex-col sm:p-0 p-3">
<el-card shadow="hover" class="sm:w-[250px] sm:flex sm:mb-0 mb-5 justify-center w-full">
<div class="flex justify-center pb-4 flex-col justify-center items-center">
<el-avatar :src="userInfo?.avatar" :size="70" />
<text class="text-gray-400 py-1 text-sm">更换头像</text>
</div>
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="onDone" />
</el-card>
<el-card shadow="hover" class="flash bg-white hover:shadow w-full sm:ml-6 ml-0">
<template #header>账号信息</template>
<el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:px-6 sm:py-2">
<el-form-item label="手机号码">
<el-input disabled v-model="form.mobile" />
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="form.nickname" />
</el-form-item>
<el-form-item label="邮箱账号">
<el-input v-model="form.email" placeholder="邮箱账号" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex">
<el-radio value="1"></el-radio>
<el-radio value="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="个人签名">
<el-input v-model="form.comments" type="textarea" placeholder="个人签名" :rows="4" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import {useConfigInfo, useToken, useWebsite} from "~/composables/configState";
import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model';
import { ref } from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import UserMenu from "~/components/UserMenu.vue";
//
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite()
const config = useConfigInfo();
const userInfo = ref<User>();
const activeIndex = ref('');
//
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true,
isAdmin: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
const onDone = (index: string) => {
activeIndex.value = index;
}
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user',{baseURL: runtimeConfig.public.apiServer})
if(response.value?.data){
userInfo.value = response.value?.data;
assignFields(response.value?.data);
}
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
console.log(path,'=>Path')
reload();
},
{ immediate: true }
);
</script>
<style lang="less">
body{
background: url("https://oss.wsdns.cn/20240328/797a8e323bba4fc6827abf9d8b98ba45.png");
background-size: 100%;
}
</style>

9
pages/index.vue

@ -19,6 +19,7 @@ import type {BreadcrumbItem} from "~/types/global";
import type {Navigation} from "~/api/cms/navigation/model"; import type {Navigation} from "~/api/cms/navigation/model";
import {getIdBySpm} from "~/utils/common"; import {getIdBySpm} from "~/utils/common";
import PageContainer from "~/components/PageContainer.vue"; import PageContainer from "~/components/PageContainer.vue";
import {navigateTo} from "#imports";
// //
const route = useRoute(); const route = useRoute();
@ -47,6 +48,14 @@ const reload = async () => {
layout.value = JSON.parse(form.value?.layout) layout.value = JSON.parse(form.value?.layout)
} }
// ()
if(!token.value || token.value == ''){
if (config.value.MustLogin) {
navigateTo('/passport/login');
return false;
}
}
// seo // seo
useHead({ useHead({
title: `${form.value.title} - ${website.value.websiteName}`, title: `${form.value.title} - ${website.value.websiteName}`,

7
pages/login.vue → pages/passport/login.vue

@ -10,7 +10,7 @@
<div class="custom-style my-4"> <div class="custom-style my-4">
<el-form :model="form" label-width="auto" class="w-[330px]"> <el-form :model="form" label-width="auto" class="w-[330px]">
<el-form-item> <el-form-item>
<el-input class="w-full" size="large" maxlength="11" placeholder="请输入手机号码" v-model="form.username">
<el-input class="w-full" size="large" maxlength="11" placeholder="请输入账号|手机号码" v-model="form.username">
<template #prepend>+86</template> <template #prepend>+86</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@ -165,7 +165,7 @@ const changeCaptcha = async () => {
// //
if(token.value && token.value.length > 0){ if(token.value && token.value.length > 0){
navigateTo('/')
navigateTo('/user')
return; return;
} }
}; };
@ -188,6 +188,7 @@ const onSubmit = async () => {
} }
if(response.value?.code != 0){ if(response.value?.code != 0){
ElMessage.error(response.value?.message) ElMessage.error(response.value?.message)
await changeCaptcha()
} }
} }
@ -207,6 +208,7 @@ const onSubmitBySms = async () => {
} }
if(response.value?.code != 0){ if(response.value?.code != 0){
ElMessage.error(response.value?.message) ElMessage.error(response.value?.message)
await changeCaptcha()
} }
} }
@ -222,6 +224,7 @@ const doLogin = async (data: any) => {
user.value.gradeId = data.user.gradeId; user.value.gradeId = data.user.gradeId;
user.value.gradeName = data.user.gradeName; user.value.gradeName = data.user.gradeName;
user.value.avatar = data.user.avatar; user.value.avatar = data.user.avatar;
localStorage.setItem('UserId',data.user.userId);
} }
setTimeout(() => { setTimeout(() => {
navigateTo('/user') navigateTo('/user')

1
types/global.d.ts

@ -19,6 +19,7 @@ export interface Config {
wxQrcode?: string; wxQrcode?: string;
wxQrcodeText?: string; wxQrcodeText?: string;
copyrightForDemoData?: string; copyrightForDemoData?: string;
MustLogin?: boolean;
} }
export interface BreadcrumbItem { export interface BreadcrumbItem {

8
utils/common.ts

@ -106,12 +106,12 @@ export function openSpmUrl(path: string, d?: any, id = 0): void {
const spm = ref<string>(''); const spm = ref<string>('');
const token = ref<string>(); const token = ref<string>();
const url = ref<string>(); const url = ref<string>();
const tid = getTenantId() || 0;
const tid = d?.tenantId || 0;
const pid = d?.parentId || 0; const pid = d?.parentId || 0;
const cid = d?.categoryId || 0; const cid = d?.categoryId || 0;
const uid = localStorage.getItem('UserId') || 0; const uid = localStorage.getItem('UserId') || 0;
const timestamp = ref(Date.now() / 1000); const timestamp = ref(Date.now() / 1000);
token.value = `&token=`;
token.value = `&token=${localStorage.getItem('token')}`;
// TODO 不传data // TODO 不传data
// if (!d) { // if (!d) {
// window.open(`${domain}${path}`); // window.open(`${domain}${path}`);
@ -119,11 +119,11 @@ export function openSpmUrl(path: string, d?: any, id = 0): void {
// } // }
// TODO 封装租户ID和店铺ID // TODO 封装租户ID和店铺ID
spm.value = `?spm=c.${uid}.${tid}.${pid}.${cid}.${id}.${timestamp.value}`;
spm.value = `?spm=c.${uid}.${tid}.${pid}.${cid}.${id}.${timestamp.value}${token.value}`;
// TODO 含http直接跳转 // TODO 含http直接跳转
if (path.slice(0, 4) == 'http') { if (path.slice(0, 4) == 'http') {
window.open(`${path}${spm.value}`);
window.location.href = `${path}${spm.value}`;
return; return;
} }

Loading…
Cancel
Save