refactor(重构前端文件目录,移除多余的菜单文件): 前端对接完成后端登录功能

This commit is contained in:
H0nGzA1
2023-01-30 19:39:43 +08:00
parent bb1f6dd128
commit cb3201c624
91 changed files with 117 additions and 13479 deletions

View File

@@ -0,0 +1,118 @@
<template>
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">401</div>
<div class="left-item-animation left-item-title">{{ $t('message.noAccess.accessTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.noAccess.accessMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onSetAuth">{{ $t('message.noAccess.accessBtn') }}</el-button>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { Session } from '/@/utils/storage';
export default defineComponent({
name: '401',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const onSetAuth = () => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
// 清除缓存/token等
Session.clear();
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
};
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
onSetAuth,
initTagViewHeight,
};
},
});
</script>
<style scoped lang="scss">
.error {
height: 100%;
background-color: var(--el-color-white);
display: flex;
.error-flex {
margin: auto;
display: flex;
height: 350px;
width: 900px;
.left {
flex: 1;
height: 100%;
align-items: center;
display: flex;
.left-item {
.left-item-animation {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.left-item-num {
color: var(--el-color-info);
font-size: 55px;
}
.left-item-title {
font-size: 20px;
color: var(--el-text-color-primary);
margin: 15px 0 5px 0;
animation-delay: 0.1s;
}
.left-item-msg {
color: var(--el-text-color-secondary);
font-size: 12px;
margin-bottom: 30px;
animation-delay: 0.2s;
}
.left-item-btn {
animation-delay: 0.2s;
}
}
}
.right {
flex: 1;
opacity: 0;
animation-name: error-img;
animation-duration: 2s;
animation-fill-mode: forwards;
img {
width: 100%;
height: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">404</div>
<div class="left-item-animation left-item-title">{{ $t('message.notFound.foundTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.notFound.foundMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onGoHome">{{ $t('message.notFound.foundBtn') }}</el-button>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
export default defineComponent({
name: '404',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const router = useRouter();
const onGoHome = () => {
router.push('/');
};
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
onGoHome,
initTagViewHeight,
};
},
});
</script>
<style scoped lang="scss">
.error {
height: 100%;
background-color: var(--el-color-white);
display: flex;
.error-flex {
margin: auto;
display: flex;
height: 350px;
width: 900px;
.left {
flex: 1;
height: 100%;
align-items: center;
display: flex;
.left-item {
.left-item-animation {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.left-item-num {
color: var(--el-color-info);
font-size: 55px;
}
.left-item-title {
font-size: 20px;
color: var(--el-text-color-primary);
margin: 15px 0 5px 0;
animation-delay: 0.1s;
}
.left-item-msg {
color: var(--el-text-color-secondary);
font-size: 12px;
margin-bottom: 30px;
animation-delay: 0.2s;
}
.left-item-btn {
animation-delay: 0.2s;
}
}
}
.right {
flex: 1;
opacity: 0;
animation-name: error-img;
animation-duration: 2s;
animation-fill-mode: forwards;
img {
width: 100%;
height: 100%;
}
}
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
import { request } from "/@/utils/service";
// import request from "/@/utils/request";
export function getCaptcha() {
return request({
url: '/api/captcha',
method: 'get',
});
}
export function login(params: object) {
return request({
url: '/api/login/',
method: 'post',
data: params
});
}
export function getUserInfo() {
return request({
url: '/api/system/user/user_info/',
method: 'get',
});
}

View File

@@ -0,0 +1,212 @@
<template>
<el-form size="large" class="login-content-form">
<el-form-item class="login-animation1">
<el-input type="text" :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.username" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-User /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item class="login-animation2">
<el-input
:type="isShowPassword ? 'text' : 'password'"
:placeholder="$t('message.account.accountPlaceholder2')"
v-model="ruleForm.password"
autocomplete="off"
>
<template #prefix>
<el-icon class="el-input__icon"><ele-Unlock /></el-icon>
</template>
<template #suffix>
<i
class="iconfont el-input__icon login-content-password"
:class="isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
@click="isShowPassword = !isShowPassword"
>
</i>
</template>
</el-input>
</el-form-item>
<el-form-item class="login-animation3">
<el-col :span="15">
<el-input
type="text"
maxlength="4"
:placeholder="$t('message.account.accountPlaceholder3')"
v-model="ruleForm.captcha"
clearable
autocomplete="off"
>
<template #prefix>
<el-icon class="el-input__icon"><ele-Position /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<el-button class="login-content-captcha">
<el-image :src="ruleForm.captchaImgBase" @click="refreshCaptcha" />
<!-- TODO 完成点击刷新验证码 -->
</el-button>
</el-col>
</el-form-item>
<el-form-item class="login-animation4">
<el-button type="primary" class="login-content-submit" round @click="loginClick" :loading="loading.signIn">
<span>{{ $t('message.account.accountBtnText') }}</span>
</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import Cookies from 'js-cookie';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
import { initBackEndControlRoutes } from '/@/router/backEnd';
import { Session } from '/@/utils/storage';
import { formatAxis } from '/@/utils/formatTime';
import { NextLoading } from '/@/utils/loading';
import * as loginApi from '/@/views/system/login/api';
import { useUserInfo } from '/@/stores/userInfo';
export default defineComponent({
name: 'loginAccount',
setup() {
const { t } = useI18n();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { userInfos } = storeToRefs(useUserInfo());
const route = useRoute();
const router = useRouter();
const state = reactive({
isShowPassword: false,
ruleForm: {
username: 'superadmin',
password: 'admin123456',
captcha: '',
captchaKey: '',
captchaImgBase: '',
},
loading: {
signIn: false,
},
});
// 时间获取
const currentTime = computed(() => {
return formatAxis(new Date());
});
const getCaptcha = async () => {
loginApi.getCaptcha().then((ret: any) => {
state.ruleForm.captchaImgBase = ret.image_base;
state.ruleForm.captchaKey = ret.key;
});
};
const refreshCaptcha = async () => {
loginApi.getCaptcha().then((ret: any) => {
state.ruleForm.captchaImgBase = ret.image_base;
state.ruleForm.captchaKey = ret.key;
});
};
const loginClick = async () => {
loginApi.login(state.ruleForm).then((ret: any) => {
console.log(ret);
Session.set('token', ret.access);
Cookies.set('username', ret.name);
if (!themeConfig.value.isRequestRoutes) {
// 前端控制路由2、请注意执行顺序
initFrontEndControlRoutes();
loginSuccess();
} else {
// 模拟后端控制路由isRequestRoutes 为 true则开启后端控制路由
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
initBackEndControlRoutes();
// 执行完 initBackEndControlRoutes再执行 signInSuccess
loginSuccess();
}
});
};
const getUserInfo = () => {
loginApi.getUserInfo().then((ret: any) => {
console.log('getUserInfo');
console.log(ret);
});
};
// 登录成功后的跳转
const loginSuccess = () => {
//登录成功获取用户信息
getUserInfo();
// 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
if (route.query?.redirect) {
router.push({
path: <string>route.query?.redirect,
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
});
} else {
router.push('/');
}
// 登录成功提示
// 关闭 loading
state.loading.signIn = true;
const signInText = t('message.signInText');
ElMessage.success(`${currentTimeInfo}${signInText}`);
// 添加 loading防止第一次进入界面时出现短暂空白
NextLoading.start();
};
onMounted(() => {
getCaptcha();
});
return {
refreshCaptcha,
loginClick,
loginSuccess,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.login-content-form {
margin-top: 20px;
@for $i from 1 through 4 {
.login-animation#{$i} {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
animation-delay: calc($i/10) + s;
}
}
.login-content-password {
display: inline-block;
width: 20px;
cursor: pointer;
&:hover {
color: #909399;
}
}
.login-content-captcha {
width: 100%;
padding: 0;
font-weight: bold;
letter-spacing: 5px;
}
.login-content-submit {
width: 100%;
letter-spacing: 2px;
font-weight: 300;
margin-top: 15px;
}
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<el-form size="large" class="login-content-form">
<el-form-item class="login-animation1">
<el-input type="text" :placeholder="$t('message.mobile.placeholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
<template #prefix>
<i class="iconfont icon-dianhua el-input__icon"></i>
</template>
</el-input>
</el-form-item>
<el-form-item class="login-animation2">
<el-col :span="15">
<el-input type="text" maxlength="4" :placeholder="$t('message.mobile.placeholder2')" v-model="ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-Position /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<el-button class="login-content-code">{{ $t('message.mobile.codeText') }}</el-button>
</el-col>
</el-form-item>
<el-form-item class="login-animation3">
<el-button round type="primary" class="login-content-submit">
<span>{{ $t('message.mobile.btnText') }}</span>
</el-button>
</el-form-item>
<div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div>
</el-form>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
// 定义接口来定义对象的类型
interface LoginMobileState {
userName: any;
code: string | number | undefined;
}
// 定义对象与类型
const ruleForm: LoginMobileState = {
userName: '',
code: '',
};
export default defineComponent({
name: 'loginMobile',
setup() {
const state = reactive({ ruleForm });
return {
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.login-content-form {
margin-top: 20px;
@for $i from 1 through 4 {
.login-animation#{$i} {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
animation-delay: calc($i/10) + s;
}
}
.login-content-code {
width: 100%;
padding: 0;
}
.login-content-submit {
width: 100%;
letter-spacing: 2px;
font-weight: 300;
margin-top: 15px;
}
.login-msg {
color: var(--el-text-color-placeholder);
}
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<div class="login-scan-container">
<div ref="qrcodeRef"></div>
<div class="font12 mt20 login-msg">{{ $t('message.scan.text') }}</div>
</div>
</template>
<script lang="ts">
import { ref, defineComponent, onMounted } from 'vue';
import QRCode from 'qrcodejs2-fixes';
export default defineComponent({
name: 'loginScan',
setup() {
const qrcodeRef = ref<HTMLElement | null>(null);
// 初始化生成二维码
const initQrcode = () => {
(qrcodeRef.value as HTMLElement).innerHTML = '';
new QRCode(qrcodeRef.value, {
text: `https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi`,
width: 260,
height: 260,
colorDark: '#000000',
colorLight: '#ffffff',
});
};
// 页面加载时
onMounted(() => {
initQrcode();
});
return { qrcodeRef };
},
});
</script>
<style scoped lang="scss">
.login-scan-animation {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.login-scan-container {
padding: 20px;
display: flex;
flex-direction: column;
text-align: center;
@extend .login-scan-animation;
animation-delay: 0.1s;
:deep(img) {
margin: auto;
}
.login-msg {
color: var(--el-text-color-placeholder);
@extend .login-scan-animation;
animation-delay: 0.2s;
}
}
</style>

View File

@@ -0,0 +1,200 @@
<template>
<div class="login-container">
<div class="login-icon-group">
<div class="login-icon-group-title">
<img :src="logoMini" />
<div class="login-icon-group-title-text font25">{{ getThemeConfig.globalViceTitle }}</div>
</div>
<img :src="loginIconTwo" class="login-icon-group-icon" />
</div>
<div class="login-content">
<div class="login-content-main">
<h4 class="login-content-title ml15">{{ getThemeConfig.globalTitle }}后台管理系统</h4>
<div v-if="!isScan">
<el-tabs v-model="tabsActiveName">
<el-tab-pane :label="$t('message.label.one1')" name="account">
<Account />
</el-tab-pane>
<el-tab-pane :label="$t('message.label.two2')" name="mobile">
<Mobile />
</el-tab-pane>
</el-tabs>
</div>
<Scan v-if="isScan" />
<div class="login-content-main-sacn" @click="isScan = !isScan">
<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import logoMini from '/@/assets/logo-mini.svg';
import loginIconTwo from '/@/assets/login-icon-two.svg';
import { NextLoading } from '/@/utils/loading';
import Account from '/@/views/system/login/component/account.vue';
import Mobile from '/@/views/system/login/component/mobile.vue';
import Scan from '/@/views/system/login/component/scan.vue';
// 定义接口来定义对象的类型
interface LoginState {
tabsActiveName: string;
isScan: boolean;
}
export default defineComponent({
name: 'loginIndex',
components: { Account, Mobile, Scan },
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<LoginState>({
tabsActiveName: 'account',
isScan: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 页面加载时
onMounted(() => {
NextLoading.done();
});
return {
logoMini,
loginIconTwo,
getThemeConfig,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.login-container {
width: 100%;
height: 100%;
position: relative;
background: var(--el-color-white);
.login-icon-group {
width: 100%;
height: 100%;
position: relative;
.login-icon-group-title {
position: absolute;
top: 50px;
left: 80px;
display: flex;
align-items: center;
img {
width: 30px;
height: 30px;
}
&-text {
padding-left: 15px;
color: var(--el-color-primary);
}
}
&::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 60%;
overflow: hidden;
height: 80%;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='1200' height='770' xmlns='http://www.w3.org/2000/svg' fill='none'%3E%3Cg%3E%3Cpath id='svg_1' d='M58.4 47.77C104.6 59.51 135.26 67.37 162.11 78.04C188.97 88.72 226.33 102.69 265.92 123.55C305.51 144.4 366.96 167.09 441.43 121.52C515.9 75.95 546.48 61.01 577.69 46.27C608.9 31.53 625.86 23.69 680.26 12.28C734.65 0.87 837.29 10.7 867.29 21.8C897.29 32.9 935.51 51.9 962.21 95.45C988.9 139.01 972.91 177.36 951.37 221.39C929.83 265.43 883.49 306 890.44 337.33C897.4 368.66 974.73 412.18 974.73 411.47C974.73 412.18 1066.36 457.62 1106.36 491.06C1146.36 524.5 1178.8 563.36 1184.03 579.63C1189.26 595.9 1200.4 622.49 1181.55 676.88C1162.71 731.26 1127.16 764.32 1115.31 778.64C1103.45 792.96 5.34 783.61 4.32 784.63C3.3 785.65 -172.34 2.38 1.13 35.04L58.4 47.77L58.4 47.77Z' fill='%23409eff'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background: var(--el-color-primary-light-5);
transition: all 0.3s ease;
}
&::after {
content: '';
width: 150px;
height: 300px;
position: absolute;
right: 0;
top: 0;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='150' height='300' xmlns='http://www.w3.org/2000/svg' fill='none'%3E%3Cg%3E%3Cpath id='svg_1' d='M-0.56 -0.28C41.94 36.17 67.73 18.94 93.33 33.96C118.93 48.98 107.58 73.56 101.94 89.76C96.29 105.96 50.09 217.83 47.87 231.18C45.64 244.52 46.02 255.2 64.4 270.05C82.79 284.91 121.99 292.31 111.98 289.81C101.97 287.32 153.96 301.48 151.83 299.9C149.69 298.32 149.98 -1.36 149.71 -1.18C149.98 -1.36 -43.06 -36.74 -0.56 -0.28L-0.56 -0.28Z' fill='%23409eff'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background: var(--el-color-primary-light-5);
transition: all 0.3s ease;
}
&-icon {
width: 60%;
height: 70%;
position: absolute;
left: 0;
bottom: 0;
}
}
.login-content {
width: 500px;
padding: 20px;
position: absolute;
right: 200px;
top: 50%;
transform: translateY(-50%) translate3d(0, 0, 0);
background-color: var(--el-color-white);
border: 5px solid var(--el-color-primary-light-8);
border-radius: 5px;
overflow: hidden;
z-index: 1;
height: 460px;
.login-content-main {
margin: 0 auto;
width: 80%;
.login-content-title {
color: var(--el-text-color-primary);
font-weight: 500;
font-size: 22px;
text-align: center;
letter-spacing: 4px;
margin: 15px 0 30px;
white-space: nowrap;
z-index: 5;
position: relative;
transition: all 0.3s ease;
}
}
.login-content-main-sacn {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 50px;
overflow: hidden;
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-text-color-primary);
&-delta {
position: absolute;
width: 35px;
height: 70px;
z-index: 2;
top: 2px;
right: 21px;
background: var(--el-color-white);
transform: rotate(-45deg);
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
}
i {
width: 47px;
height: 50px;
display: inline-block;
font-size: 48px;
position: absolute;
right: 2px;
top: -1px;
}
}
}
}
</style>