mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 23:40:24 +08:00
refactor: 登录页调整
This commit is contained in:
@@ -15,6 +15,7 @@ import LockScreen from '@/views/layout/lockScreen/index.vue';
|
||||
import Setings from '@/views/layout/navBars/breadcrumb/setings.vue';
|
||||
import Watermark from '@/common/utils/wartermark';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import { getThemeConfig } from './common/utils/storage';
|
||||
|
||||
const setingsRef = ref();
|
||||
const route = useRoute();
|
||||
@@ -42,10 +43,14 @@ onMounted(() => {
|
||||
mittBus.on('openSetingsDrawer', () => {
|
||||
openSetingsDrawer();
|
||||
});
|
||||
|
||||
// 获取缓存中的布局配置
|
||||
if (getLocal('themeConfig')) {
|
||||
themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') });
|
||||
const tc = getThemeConfig();
|
||||
if (tc) {
|
||||
themeConfigStores.setThemeConfig({ themeConfig: tc });
|
||||
document.documentElement.style.cssText = getLocal('themeConfigStyle');
|
||||
|
||||
themeConfigStores.switchDark(tc.isDark);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 458 KiB |
1
mayfly_go_web/src/assets/image/login-bg-main.svg
Normal file
1
mayfly_go_web/src/assets/image/login-bg-main.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 13 KiB |
19
mayfly_go_web/src/assets/image/login-bg-split.svg
Normal file
19
mayfly_go_web/src/assets/image/login-bg-split.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
@@ -1,46 +1,42 @@
|
||||
import { nextTick } from 'vue';
|
||||
import loadingCss from '@/theme/loading.scss?inline';
|
||||
import '@/theme/loading.scss';
|
||||
|
||||
// 定义方法
|
||||
/**
|
||||
* 页面全局 Loading
|
||||
* @method start 创建 loading
|
||||
* @method done 移除 loading
|
||||
*/
|
||||
export const NextLoading = {
|
||||
// 载入 css
|
||||
setCss: () => {
|
||||
let link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = loadingCss;
|
||||
link.crossOrigin = 'anonymous';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
},
|
||||
// 创建 loading
|
||||
start: () => {
|
||||
const bodys: any = document.body;
|
||||
const div = document.createElement('div');
|
||||
const bodys: Element = document.body;
|
||||
const div = <HTMLElement>document.createElement('div');
|
||||
div.setAttribute('class', 'loading-next');
|
||||
const htmls = `
|
||||
<div class="loading-next-box">
|
||||
<div class="loading-next-box-warp">
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-warp">
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
div.innerHTML = htmls;
|
||||
bodys.insertBefore(div, bodys.childNodes[0]);
|
||||
},
|
||||
// 移除 loading
|
||||
done: () => {
|
||||
done: (time: number = 1000) => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const el = document.querySelector('.loading-next');
|
||||
el && el.parentNode?.removeChild(el);
|
||||
}, 1000);
|
||||
const el = <HTMLElement>document.querySelector('.loading-next');
|
||||
el?.parentNode?.removeChild(el);
|
||||
}, time);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,6 +21,14 @@ export function saveUser(userinfo: any) {
|
||||
setLocal(UserKey, userinfo);
|
||||
}
|
||||
|
||||
export function saveThemeConfig(themeConfig: any) {
|
||||
setLocal('themeConfig', themeConfig);
|
||||
}
|
||||
|
||||
export function getThemeConfig() {
|
||||
return getLocal('themeConfig');
|
||||
}
|
||||
|
||||
// 获取是否开启水印
|
||||
export function getUseWatermark() {
|
||||
return getLocal('useWatermark');
|
||||
|
||||
@@ -43,9 +43,12 @@ function del() {
|
||||
|
||||
const watermark = {
|
||||
use: () => {
|
||||
const userinfo = getUser();
|
||||
if (!userinfo) {
|
||||
del();
|
||||
}
|
||||
setTimeout(() => {
|
||||
const userinfo = getUser();
|
||||
if (userinfo && getUseWatermark()) {
|
||||
if (getUseWatermark()) {
|
||||
set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`);
|
||||
} else {
|
||||
del();
|
||||
|
||||
@@ -29,7 +29,6 @@ const router = createRouter({
|
||||
|
||||
// 前端控制路由:初始化方法,防止刷新时丢失
|
||||
export function initAllFun() {
|
||||
NextLoading.start(); // 界面 loading 动画开始执行
|
||||
const token = getToken(); // 获取浏览器缓存 token 值
|
||||
if (!token) {
|
||||
// 无 token 停止执行下一步
|
||||
@@ -48,7 +47,6 @@ export function initAllFun() {
|
||||
|
||||
// 后端控制路由:执行路由数据初始化
|
||||
export async function initBackEndControlRoutesFun() {
|
||||
NextLoading.start(); // 界面 loading 动画开始执行
|
||||
const token = getToken(); // 获取浏览器缓存 token 值
|
||||
if (!token) {
|
||||
// 无 token 停止执行下一步
|
||||
@@ -232,14 +230,19 @@ export function resetRoute() {
|
||||
}
|
||||
|
||||
export async function initRouter() {
|
||||
// 初始化方法执行
|
||||
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
|
||||
if (!isRequestRoutes) {
|
||||
// 未开启后端控制路由
|
||||
initAllFun();
|
||||
} else if (isRequestRoutes) {
|
||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
await initBackEndControlRoutesFun();
|
||||
NextLoading.start(); // 界面 loading 动画开始执行
|
||||
try {
|
||||
// 初始化方法执行
|
||||
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
|
||||
if (!isRequestRoutes) {
|
||||
// 未开启后端控制路由
|
||||
initAllFun();
|
||||
} else if (isRequestRoutes) {
|
||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
await initBackEndControlRoutesFun();
|
||||
}
|
||||
} finally {
|
||||
NextLoading.done();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +300,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
// 路由加载后
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
NextLoading.done();
|
||||
});
|
||||
|
||||
// 导出路由
|
||||
|
||||
@@ -140,5 +140,17 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
setThemeConfig(data: ThemeConfigState) {
|
||||
this.themeConfig = data.themeConfig;
|
||||
},
|
||||
// 切换暗模式
|
||||
switchDark(isDark: boolean) {
|
||||
this.themeConfig.isDark = isDark;
|
||||
const body = document.documentElement as HTMLElement;
|
||||
if (isDark) {
|
||||
body.setAttribute('class', 'dark');
|
||||
this.themeConfig.editorTheme = 'vs-dark';
|
||||
} else {
|
||||
body.setAttribute('class', '');
|
||||
this.themeConfig.editorTheme = 'SolarizedLight';
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -83,3 +83,42 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 登录页动画
|
||||
------------------------------- */
|
||||
@keyframes loginLeft {
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
50%,
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
@keyframes loginTop {
|
||||
0% {
|
||||
top: -100%;
|
||||
}
|
||||
50%,
|
||||
100% {
|
||||
top: 100%;
|
||||
}
|
||||
}
|
||||
@keyframes loginRight {
|
||||
0% {
|
||||
right: -100%;
|
||||
}
|
||||
50%,
|
||||
100% {
|
||||
right: 100%;
|
||||
}
|
||||
}
|
||||
@keyframes loginBottom {
|
||||
0% {
|
||||
bottom: -100%;
|
||||
}
|
||||
50%,
|
||||
100% {
|
||||
bottom: 100%;
|
||||
}
|
||||
}
|
||||
@@ -335,8 +335,14 @@
|
||||
/* Set padding to ensure the height is 32px */
|
||||
// padding: 6px 12px;
|
||||
background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
|
||||
}
|
||||
.el-popper.is-customized .el-popper__arrow::before {
|
||||
}
|
||||
.el-popper.is-customized .el-popper__arrow::before {
|
||||
background: linear-gradient(45deg, #b2e68d, #bce689);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-dialog {
|
||||
border-radius: 6px; /* 设置圆角 */
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
|
||||
}
|
||||
@@ -213,7 +213,7 @@ onUnmounted(() => {
|
||||
}
|
||||
.layout-lock-screen-img {
|
||||
@extend .layout-lock-screen-fixed;
|
||||
background: url('@/assets/image/bg-login.png') no-repeat;
|
||||
background: url('@/assets/image/login-bg-main.svg') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
z-index: 9999991;
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="copy-config">
|
||||
<!-- <div class="copy-config">
|
||||
<el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false"> </el-alert>
|
||||
<el-button
|
||||
size="small"
|
||||
@@ -385,7 +385,7 @@
|
||||
@click="onCopyConfigClick($event.target)"
|
||||
>一键复制配置
|
||||
</el-button>
|
||||
</div>
|
||||
</div> -->
|
||||
</el-scrollbar>
|
||||
</el-drawer>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<crop />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<el-dropdown trigger="click" :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link" style="cursor: pointer">
|
||||
<img :src="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
{{ userInfo.name || userInfo.username }}
|
||||
@@ -75,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||
import { ref, computed, reactive, onMounted, nextTick } from 'vue';
|
||||
import { ref, computed, reactive, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
@@ -83,11 +83,12 @@ import { resetRoute } from '@/router/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { clearUser, clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage';
|
||||
import { clearSession, removeLocal } from '@/common/utils/storage';
|
||||
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
|
||||
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import openApi from '@/common/openApi';
|
||||
import { saveThemeConfig, getThemeConfig } from '@/common/utils/storage';
|
||||
|
||||
const router = useRouter();
|
||||
const searchRef = ref();
|
||||
@@ -99,7 +100,8 @@ const state = reactive({
|
||||
disabledSize: '',
|
||||
});
|
||||
const { userInfo } = storeToRefs(useUserInfo());
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const themeConfigStore = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(themeConfigStore);
|
||||
|
||||
// 设置分割样式
|
||||
const layoutUserFlexNum = computed(() => {
|
||||
@@ -164,16 +166,8 @@ const onHandleCommandClick = (path: string) => {
|
||||
};
|
||||
|
||||
const switchDark = (isDark: boolean) => {
|
||||
themeConfig.value.isDark = isDark;
|
||||
setLocal('themeConfig', themeConfig.value);
|
||||
const body = document.documentElement as HTMLElement;
|
||||
if (isDark) {
|
||||
body.setAttribute('class', 'dark');
|
||||
themeConfig.value.editorTheme = 'vs-dark';
|
||||
} else {
|
||||
body.setAttribute('class', '');
|
||||
themeConfig.value.editorTheme = 'SolarizedLight';
|
||||
}
|
||||
themeConfigStore.switchDark(isDark);
|
||||
saveThemeConfig(themeConfig.value);
|
||||
};
|
||||
|
||||
// // 菜单搜索点击
|
||||
@@ -185,7 +179,7 @@ const onSearchClick = () => {
|
||||
const onComponentSizeChange = (size: string) => {
|
||||
removeLocal('themeConfig');
|
||||
themeConfig.value.globalComponentSize = size;
|
||||
setLocal('themeConfig', themeConfig.value);
|
||||
saveThemeConfig(themeConfig.value);
|
||||
// proxy.$ELEMENT.size = size;
|
||||
initComponentSize();
|
||||
window.location.reload();
|
||||
@@ -193,7 +187,7 @@ const onComponentSizeChange = (size: string) => {
|
||||
|
||||
// 初始化全局组件大小
|
||||
const initComponentSize = () => {
|
||||
switch (getLocal('themeConfig').globalComponentSize) {
|
||||
switch (getThemeConfig().globalComponentSize) {
|
||||
case '':
|
||||
state.disabledSize = '';
|
||||
break;
|
||||
@@ -211,12 +205,10 @@ const initComponentSize = () => {
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
if (getLocal('themeConfig')) {
|
||||
const isDark = themeConfig.value.isDark;
|
||||
state.isDark = isDark;
|
||||
switchDark(isDark);
|
||||
|
||||
const themeConfig = getThemeConfig();
|
||||
if (themeConfig) {
|
||||
initComponentSize();
|
||||
state.isDark = themeConfig.isDark;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,68 +1,81 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-logo">
|
||||
<span>{{ themeConfig.globalViceTitle }}</span>
|
||||
<div class="login-container flex">
|
||||
<div class="login-left">
|
||||
<div class="login-left-logo">
|
||||
<img :src="logoMini" />
|
||||
<div class="login-left-logo-text">
|
||||
<span>mayfly-go</span>
|
||||
<!-- <span class="login-left-logo-text-msg">mayfly-go</span> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-left-img">
|
||||
<img :src="loginBgImg" />
|
||||
</div>
|
||||
<img :src="loginBgSplitImg" class="login-left-waves" />
|
||||
</div>
|
||||
<div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
|
||||
<div class="login-content-main">
|
||||
<h4 class="login-content-title">mayfly-go</h4>
|
||||
<el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
|
||||
<el-tab-pane label="账号密码登录" name="account" :disabled="tabsActiveName === 'account'">
|
||||
<transition name="el-zoom-in-center">
|
||||
<Account v-show="isTabPaneShow" ref="loginForm" />
|
||||
</transition>
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'">
|
||||
<transition name="el-zoom-in-center">
|
||||
<Mobile v-show="!isTabPaneShow" />
|
||||
</transition>
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
<div class="mt20" v-show="oauth2LoginConfig.enable">
|
||||
<el-button link size="small">第三方登录: </el-button>
|
||||
<el-tooltip :content="oauth2LoginConfig.name" placement="top-start">
|
||||
<el-button link size="small" type="primary" @click="oauth2Login">
|
||||
<el-icon :size="18">
|
||||
<Link />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<div class="login-right flex">
|
||||
<div class="login-right-warp flex-margin">
|
||||
<span class="login-right-warp-one"></span>
|
||||
<span class="login-right-warp-two"></span>
|
||||
<div class="login-right-warp-mian">
|
||||
<div class="login-right-warp-main-title">mayfly-go</div>
|
||||
<div class="login-right-warp-main-form">
|
||||
<div v-if="!state.isScan">
|
||||
<el-tabs v-model="state.tabsActiveName">
|
||||
<el-tab-pane label="账号密码登录" name="account">
|
||||
<Account ref="loginForm" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="mt20" v-show="state.oauth2LoginConfig.enable">
|
||||
<el-button link size="small">第三方登录: </el-button>
|
||||
<el-tooltip :content="state.oauth2LoginConfig.name" placement="top-start">
|
||||
<el-button link size="small" type="primary" @click="oauth2Login">
|
||||
<el-icon :size="18">
|
||||
<Link />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="login-copyright">
|
||||
<div class="mb5 login-copyright-company">mayfly</div>
|
||||
<div class="login-copyright-msg">mayfly</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted, h, ref } from 'vue';
|
||||
import Account from '@/views/login/component/AccountLogin.vue';
|
||||
<script setup lang="ts" name="loginIndex">
|
||||
import { ref, defineAsyncComponent, onMounted, reactive, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import logoMini from '@/assets/image/logo.svg';
|
||||
import loginBgImg from '@/assets/image/login-bg-main.svg';
|
||||
import loginBgSplitImg from '@/assets/image/login-bg-split.svg';
|
||||
import openApi from '@/common/openApi';
|
||||
import config from '@/common/config';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
// 引入组件
|
||||
const Account = defineAsyncComponent(() => import('./component/AccountLogin.vue'));
|
||||
|
||||
const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
|
||||
|
||||
// 定义变量内容
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
|
||||
const state = reactive({
|
||||
tabsActiveName: 'account',
|
||||
isTabPaneShow: true,
|
||||
isScan: false,
|
||||
oauth2LoginConfig: {
|
||||
name: 'OAuth2登录',
|
||||
enable: false,
|
||||
},
|
||||
});
|
||||
|
||||
const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
|
||||
|
||||
const { isTabPaneShow, tabsActiveName, oauth2LoginConfig: oauth2LoginConfig } = toRefs(state);
|
||||
|
||||
// 切换密码、手机登录
|
||||
const onTabsClick = () => {
|
||||
state.isTabPaneShow = !state.isTabPaneShow;
|
||||
};
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return themeConfig.value;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
state.oauth2LoginConfig = await openApi.oauth2LoginConfig();
|
||||
@@ -94,76 +107,178 @@ const oauth2Login = () => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('@/assets/image/bg-login.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.login-logo {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
color: var(--el-color-primary);
|
||||
letter-spacing: 2px;
|
||||
width: 90%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.login-content {
|
||||
width: 500px;
|
||||
padding: 20px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) translate3d(0, 0, 0);
|
||||
background-color: rgba(255, 255, 255, 0.99);
|
||||
box-shadow: 0 2px 12px 0 var(--el-color-primary-light-5);
|
||||
border-radius: 4px;
|
||||
transition: height 0.2s linear;
|
||||
height: 490px;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.login-content-main {
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
|
||||
.login-content-title {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
text-align: center;
|
||||
letter-spacing: 4px;
|
||||
margin: 15px 0 30px;
|
||||
white-space: nowrap;
|
||||
background: var(--bg-main-color);
|
||||
.login-left {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background-color: rgba(211, 239, 255, 1);
|
||||
margin-right: 100px;
|
||||
.login-left-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 80px;
|
||||
z-index: 1;
|
||||
animation: logoAnimation 0.3s ease;
|
||||
img {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
.login-left-logo-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
span {
|
||||
margin-left: 10px;
|
||||
font-size: 28px;
|
||||
color: #26a59a;
|
||||
}
|
||||
.login-left-logo-text-msg {
|
||||
font-size: 12px;
|
||||
color: #32a99e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-content-mobile {
|
||||
height: 418px;
|
||||
}
|
||||
|
||||
.login-copyright {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 30px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
|
||||
.login-copyright-company {
|
||||
white-space: nowrap;
|
||||
.login-left-img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 52%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
animation: error-num 0.6s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.login-copyright-msg {
|
||||
@extend .login-copyright-company;
|
||||
.login-left-waves {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -100px;
|
||||
}
|
||||
}
|
||||
.login-right {
|
||||
width: 700px;
|
||||
.login-right-warp {
|
||||
border: 1px solid var(--el-color-primary-light-3);
|
||||
border-radius: 3px;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-main-color);
|
||||
.login-right-warp-one,
|
||||
.login-right-warp-two {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
.login-right-warp-one {
|
||||
&::before {
|
||||
filter: hue-rotate(0deg);
|
||||
top: 0px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, var(--el-color-primary));
|
||||
animation: loginLeft 3s linear infinite;
|
||||
}
|
||||
&::after {
|
||||
filter: hue-rotate(60deg);
|
||||
top: -100%;
|
||||
right: 2px;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, transparent, var(--el-color-primary));
|
||||
animation: loginTop 3s linear infinite;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
}
|
||||
.login-right-warp-two {
|
||||
&::before {
|
||||
filter: hue-rotate(120deg);
|
||||
bottom: 2px;
|
||||
right: -100%;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(270deg, transparent, var(--el-color-primary));
|
||||
animation: loginRight 3s linear infinite;
|
||||
animation-delay: 1.4s;
|
||||
}
|
||||
&::after {
|
||||
filter: hue-rotate(300deg);
|
||||
bottom: -100%;
|
||||
left: 0px;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background: linear-gradient(360deg, transparent, var(--el-color-primary));
|
||||
animation: loginBottom 3s linear infinite;
|
||||
animation-delay: 2.1s;
|
||||
}
|
||||
}
|
||||
.login-right-warp-mian {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
.login-right-warp-main-title {
|
||||
height: 130px;
|
||||
line-height: 130px;
|
||||
font-size: 27px;
|
||||
text-align: center;
|
||||
letter-spacing: 3px;
|
||||
animation: logoAnimation 0.3s ease;
|
||||
animation-delay: 0.3s;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.login-right-warp-main-form {
|
||||
flex: 1;
|
||||
padding: 0 50px 50px;
|
||||
.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-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: 1px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,19 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var client = &http.Client{}
|
||||
|
||||
// 默认超时
|
||||
const DefTimeout = 60
|
||||
|
||||
type RequestWrapper struct {
|
||||
client http.Client
|
||||
url string
|
||||
method string
|
||||
timeout int
|
||||
@@ -34,7 +35,7 @@ type MultipartFile struct {
|
||||
|
||||
// 创建一个请求
|
||||
func NewRequest(url string) *RequestWrapper {
|
||||
return &RequestWrapper{url: url}
|
||||
return &RequestWrapper{url: url, client: http.Client{}}
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) Url(url string) *RequestWrapper {
|
||||
@@ -55,15 +56,13 @@ func (r *RequestWrapper) Timeout(timeout int) *RequestWrapper {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) GetByParam(paramMap map[string]string) *ResponseWrapper {
|
||||
func (r *RequestWrapper) GetByQuery(queryMap map[string]any) *ResponseWrapper {
|
||||
var params string
|
||||
for k, v := range paramMap {
|
||||
for k, v := range queryMap {
|
||||
if params != "" {
|
||||
params += "&"
|
||||
} else {
|
||||
params += "?"
|
||||
}
|
||||
params += k + "=" + v
|
||||
params += k + "=" + stringx.AnyToStr(v)
|
||||
}
|
||||
r.url += "?" + params
|
||||
return r.Get()
|
||||
@@ -72,7 +71,7 @@ func (r *RequestWrapper) GetByParam(paramMap map[string]string) *ResponseWrapper
|
||||
func (r *RequestWrapper) Get() *ResponseWrapper {
|
||||
r.method = "GET"
|
||||
r.body = nil
|
||||
return request(r)
|
||||
return sendRequest(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostJson(body string) *ResponseWrapper {
|
||||
@@ -83,18 +82,18 @@ func (r *RequestWrapper) PostJson(body string) *ResponseWrapper {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header["Content-type"] = "application/json"
|
||||
return request(r)
|
||||
return sendRequest(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostObj(body any) *ResponseWrapper {
|
||||
marshal, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return createRequestError(errors.New("解析json obj错误"))
|
||||
return &ResponseWrapper{err: errors.New("解析json obj错误")}
|
||||
}
|
||||
return r.PostJson(string(marshal))
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostParams(params string) *ResponseWrapper {
|
||||
func (r *RequestWrapper) PostForm(params string) *ResponseWrapper {
|
||||
buf := bytes.NewBufferString(params)
|
||||
r.method = "POST"
|
||||
r.body = buf
|
||||
@@ -102,7 +101,7 @@ func (r *RequestWrapper) PostParams(params string) *ResponseWrapper {
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header["Content-type"] = "application/x-www-form-urlencoded"
|
||||
return request(r)
|
||||
return sendRequest(r)
|
||||
}
|
||||
|
||||
func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[string]string) *ResponseWrapper {
|
||||
@@ -115,7 +114,7 @@ func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[strin
|
||||
if uploadFile.FilePath != "" {
|
||||
file, err := os.Open(uploadFile.FilePath)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
return &ResponseWrapper{err: err}
|
||||
}
|
||||
defer file.Close()
|
||||
reader = file
|
||||
@@ -125,18 +124,18 @@ func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[strin
|
||||
|
||||
part, err := writer.CreateFormFile(uploadFile.FieldName, uploadFile.FileName)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
return &ResponseWrapper{err: err}
|
||||
}
|
||||
_, err = io.Copy(part, reader)
|
||||
io.Copy(part, reader)
|
||||
}
|
||||
// 如果有其他参数,则写入body
|
||||
for k, v := range reqParams {
|
||||
if err := writer.WriteField(k, v); err != nil {
|
||||
return createRequestError(err)
|
||||
return &ResponseWrapper{err: err}
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
return createRequestError(err)
|
||||
return &ResponseWrapper{err: err}
|
||||
}
|
||||
|
||||
r.method = "POST"
|
||||
@@ -145,74 +144,26 @@ func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[strin
|
||||
r.header = make(map[string]string)
|
||||
}
|
||||
r.header["Content-type"] = writer.FormDataContentType()
|
||||
return request(r)
|
||||
return sendRequest(r)
|
||||
}
|
||||
|
||||
type ResponseWrapper struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) IsSuccess() bool {
|
||||
return r.StatusCode == 200
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) BodyToObj(objPtr any) error {
|
||||
_ = json.Unmarshal(r.Body, &objPtr)
|
||||
return r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) BodyToString() (string, error) {
|
||||
return string(r.Body), r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) BodyToMap() (map[string]any, error) {
|
||||
var res map[string]any
|
||||
err := json.Unmarshal(r.Body, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, r.getError()
|
||||
}
|
||||
|
||||
func (r *ResponseWrapper) getError() error {
|
||||
if !r.IsSuccess() {
|
||||
return errors.New(string(r.Body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func request(rw *RequestWrapper) *ResponseWrapper {
|
||||
wrapper := &ResponseWrapper{StatusCode: 0, Header: make(http.Header)}
|
||||
func sendRequest(rw *RequestWrapper) *ResponseWrapper {
|
||||
respWrapper := &ResponseWrapper{}
|
||||
timeout := rw.timeout
|
||||
if timeout > 0 {
|
||||
client.Timeout = time.Duration(timeout) * time.Second
|
||||
rw.client.Timeout = time.Duration(timeout) * time.Second
|
||||
} else {
|
||||
timeout = DefTimeout
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(rw.method, rw.url, rw.body)
|
||||
if err != nil {
|
||||
return createRequestError(err)
|
||||
respWrapper.err = fmt.Errorf("创建请求错误-%s", err.Error())
|
||||
return respWrapper
|
||||
}
|
||||
setRequestHeader(req, rw.header)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
wrapper.Body = []byte(fmt.Sprintf("执行HTTP请求错误-%s", err.Error()))
|
||||
return wrapper
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
wrapper.Body = []byte(fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error()))
|
||||
return wrapper
|
||||
}
|
||||
wrapper.StatusCode = resp.StatusCode
|
||||
wrapper.Body = body
|
||||
wrapper.Header = resp.Header
|
||||
|
||||
return wrapper
|
||||
resp, err := rw.client.Do(req)
|
||||
return &ResponseWrapper{resp: resp, err: err}
|
||||
}
|
||||
|
||||
func setRequestHeader(req *http.Request, header map[string]string) {
|
||||
@@ -222,6 +173,73 @@ func setRequestHeader(req *http.Request, header map[string]string) {
|
||||
}
|
||||
}
|
||||
|
||||
func createRequestError(err error) *ResponseWrapper {
|
||||
return &ResponseWrapper{0, []byte(fmt.Sprintf("创建HTTP请求错误-%s", err.Error())), make(http.Header)}
|
||||
type ResponseWrapper struct {
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
// 将响应体通过json解析转为指定结构体
|
||||
func (r *ResponseWrapper) BodyToObj(objPtr any) error {
|
||||
bodyBytes, err := r.BodyBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bodyBytes, &objPtr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析响应体-json解析失败-%s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将响应体转为strings
|
||||
func (r *ResponseWrapper) BodyToString() (string, error) {
|
||||
bodyBytes, err := r.BodyBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bodyBytes), nil
|
||||
}
|
||||
|
||||
// 将响应体通过json解析转为map
|
||||
func (r *ResponseWrapper) BodyToMap() (map[string]any, error) {
|
||||
var res map[string]any
|
||||
return res, r.BodyToObj(&res)
|
||||
}
|
||||
|
||||
// 获取响应体的字节数组
|
||||
func (r *ResponseWrapper) BodyBytes() ([]byte, error) {
|
||||
resp, err := r.GetHttpResp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应体数据失败-%s", err.Error())
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
// 获取http响应结果结构体
|
||||
func (r *ResponseWrapper) GetHttpResp() (*http.Response, error) {
|
||||
if r.err != nil {
|
||||
return nil, fmt.Errorf("请求失败-%s", r.err.Error())
|
||||
}
|
||||
if r.resp == nil {
|
||||
return nil, errors.New("请求失败-响应结构体为空,请检查请求url等信息")
|
||||
}
|
||||
|
||||
statusCode := r.resp.StatusCode
|
||||
if isFailureStatusCode(statusCode) {
|
||||
logx.Warnf("请求响应状态码为为失败状态: %v", statusCode)
|
||||
}
|
||||
|
||||
return r.resp, nil
|
||||
}
|
||||
|
||||
func isFailureStatusCode(statusCode int) bool {
|
||||
return statusCode < http.StatusOK || statusCode >= http.StatusBadRequest
|
||||
}
|
||||
|
||||
34
server/pkg/httpclient/httpclient_test.go
Normal file
34
server/pkg/httpclient/httpclient_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestStruct struct {
|
||||
Id uint64
|
||||
Username string
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
res, err := NewRequest("www.baidu.com").Get().BodyToString()
|
||||
fmt.Println(err)
|
||||
fmt.Println(res)
|
||||
}
|
||||
|
||||
func TestGetBodyToMap(t *testing.T) {
|
||||
res, err := NewRequest("http://go.mayfly.run/api/syslogs?pageNum=1&pageSize=10").Get().BodyToMap()
|
||||
fmt.Println(err)
|
||||
fmt.Println(res["msg"])
|
||||
fmt.Println(res["code"])
|
||||
}
|
||||
|
||||
func TestGetQueryBodyToMap(t *testing.T) {
|
||||
res, err := NewRequest("http://go.mayfly.run/api/syslogs").
|
||||
Header("Authorization", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTUzOTQ5NTIsImlkIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.pGrczVZqk5nlId-FZPkjW_O5Sw3-2yjgzACp_j4JEXY").
|
||||
GetByQuery(map[string]any{"pageNum": 1, "pageSize": 10}).
|
||||
BodyToMap()
|
||||
fmt.Println(err)
|
||||
fmt.Println(res["msg"])
|
||||
fmt.Println(res["code"])
|
||||
}
|
||||
Reference in New Issue
Block a user