Merge branch 'dev' of https://gitee.com/xlsea/ruoyi-plus-soybean into flow
This commit is contained in:
commit
2b5735ab34
4
.env
4
.env
@ -2,9 +2,9 @@
|
|||||||
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
|
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
|
||||||
VITE_BASE_URL=/
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
VITE_APP_TITLE=RuoYi-Vue-Plus
|
VITE_APP_TITLE=RuoYi Plus Soybean
|
||||||
|
|
||||||
VITE_APP_DESC=RuoYi-Vue-Plus多租户管理系统
|
VITE_APP_DESC=RuoYi Plus Soybean 后台管理系统
|
||||||
|
|
||||||
# the prefix of the icon name
|
# the prefix of the icon name
|
||||||
VITE_ICON_PREFIX=icon
|
VITE_ICON_PREFIX=icon
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<html lang="zh-cmn-Hans">
|
<html lang="zh-cmn-Hans">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.png" />
|
<link rel="icon" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
<title>%VITE_APP_TITLE%</title>
|
<title>%VITE_APP_TITLE%</title>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 124 KiB |
4
public/favicon.svg
Normal file
4
public/favicon.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 476.22 476.22">
|
||||||
|
<path fill="#0e42d2" d="M389.98,400.6c-64.58,76.59-176.66,93.98-261.1,38.54-57.75-37.91-92.29-103.74-87.69-173.54,1.28-19.35,9.54-58.77,33.33-61.44,20.49-2.29,43.2,14.41,62.99,21.14,64.93,22.1,150.88,21.39,214.51-4.81,17.24-7.1,39.23-23.9,58.05-13.31,25.61,14.41,27.13,67.07,24.38,92.75-3.94,36.78-20.9,72.7-44.47,100.65ZM150.91,269c-17.4-4.01-34.19-9.87-50.5-17.04-2.56-1.12-15.17-8.19-16.64-6.83-23.51,92.25,48.81,179.87,140.64,188.01,3.68.33,9,.72,12.64.79,3.76.07,7.18-.41,10.86-.78-.24-3.7-1.05-7.05-.77-10.85,1.3-17.71,28.61-26.36,26.88-43.25-.89-8.76-14.85-11.03-22.02-11.55-8.23-.6-17.94,1.77-25.24-3.11-5.39-3.61-5.08-10.32-7.37-15.76-4.74-11.26-15.86-17.54-27.79-18.47-22.55-1.77-68.52,7.5-81.87-16.63-11.77-21.27,12.24-35.1,29.25-40.42,3.38-1.06,7.52-1.31,10.88-2.55,1.03-.38,1.28.27,1.04-1.56ZM391.83,245.21c-1.03-.58-12.51,5.5-14.65,6.42-20.82,8.92-42.01,16.02-64.2,20.87-10.31,2.25-21.7,3.15-31.66,5.65-19.17,4.82-17.59,22.78-.42,29.11,10.27,3.78,25.34,3.94,36.56,5.97,23.56,4.26,56.74,11.28,53.56,41.93-.31,2.98-2.13,5.86-2.26,8.2-.08,1.48.18,1.21,1.5,1.13,5.18-9.87,11.46-19.09,15.7-29.46,7.55-18.44,12.19-42.75,11.17-62.68-.19-3.67-2.95-25.81-5.31-27.15Z"/>
|
||||||
|
<path fill="#0e42d2" d="M278.22,21.54c4.79,5.69,9.27,14.39,11.84,21.37.67,1.8,1.49,7.86,2.59,8.61,1.49,1.01,17.78-3.63,21.39-4.05,71.76-8.37,101.88,65.14,44.46,110.3-41.92,32.98-84.43,32.67-135.62,31.41-47.74-1.18-111.25-13.52-129.97-64.78-19.66-53.81,31.01-83.19,77.15-58.5,2,1.07,11.58,7.79,12.54,7.59,1.57-.32,1.05-4.62,1.34-6.14,3.75-19.76,11.37-39.47,27.8-52.04,20.82-15.93,49.31-14.16,66.47,6.23ZM256.71,71.1c1.07-24.31-18.03-42.71-30.8-12.52-5.88,13.9-7.77,32.56-3.36,47,1.73.46,1.24-.41,1.7-.95,10.15-11.8,19.02-25.11,32.46-33.52ZM340.18,123.96c10.12-10.37,17.92-27.48.94-35.85-29-14.31-83.74,17.89-87.91,49.24-1.6,11.97,2.2,14.36,13.71,14.53,21.84.32,57.86-12.13,73.26-27.92ZM173.64,117.92c-4.77-6.36-9.81-11.55-16.53-15.93-7.07-4.62-24.6-13.3-29.47-2.16-3.94,9.02,6.8,23.05,13.43,28.86,12.36,10.83,29.63,16.76,45.66,19.42-.28-11.02-6.62-21.58-13.08-30.19Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 124 KiB |
1
src/assets/svg-icon/login-background.svg
Normal file
1
src/assets/svg-icon/login-background.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 192 KiB |
4
src/assets/svg-icon/logo.svg
Normal file
4
src/assets/svg-icon/logo.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 476.22 476.22">
|
||||||
|
<path fill="#0e42d2" d="M389.98,400.6c-64.58,76.59-176.66,93.98-261.1,38.54-57.75-37.91-92.29-103.74-87.69-173.54,1.28-19.35,9.54-58.77,33.33-61.44,20.49-2.29,43.2,14.41,62.99,21.14,64.93,22.1,150.88,21.39,214.51-4.81,17.24-7.1,39.23-23.9,58.05-13.31,25.61,14.41,27.13,67.07,24.38,92.75-3.94,36.78-20.9,72.7-44.47,100.65ZM150.91,269c-17.4-4.01-34.19-9.87-50.5-17.04-2.56-1.12-15.17-8.19-16.64-6.83-23.51,92.25,48.81,179.87,140.64,188.01,3.68.33,9,.72,12.64.79,3.76.07,7.18-.41,10.86-.78-.24-3.7-1.05-7.05-.77-10.85,1.3-17.71,28.61-26.36,26.88-43.25-.89-8.76-14.85-11.03-22.02-11.55-8.23-.6-17.94,1.77-25.24-3.11-5.39-3.61-5.08-10.32-7.37-15.76-4.74-11.26-15.86-17.54-27.79-18.47-22.55-1.77-68.52,7.5-81.87-16.63-11.77-21.27,12.24-35.1,29.25-40.42,3.38-1.06,7.52-1.31,10.88-2.55,1.03-.38,1.28.27,1.04-1.56ZM391.83,245.21c-1.03-.58-12.51,5.5-14.65,6.42-20.82,8.92-42.01,16.02-64.2,20.87-10.31,2.25-21.7,3.15-31.66,5.65-19.17,4.82-17.59,22.78-.42,29.11,10.27,3.78,25.34,3.94,36.56,5.97,23.56,4.26,56.74,11.28,53.56,41.93-.31,2.98-2.13,5.86-2.26,8.2-.08,1.48.18,1.21,1.5,1.13,5.18-9.87,11.46-19.09,15.7-29.46,7.55-18.44,12.19-42.75,11.17-62.68-.19-3.67-2.95-25.81-5.31-27.15Z"/>
|
||||||
|
<path fill="#0e42d2" d="M278.22,21.54c4.79,5.69,9.27,14.39,11.84,21.37.67,1.8,1.49,7.86,2.59,8.61,1.49,1.01,17.78-3.63,21.39-4.05,71.76-8.37,101.88,65.14,44.46,110.3-41.92,32.98-84.43,32.67-135.62,31.41-47.74-1.18-111.25-13.52-129.97-64.78-19.66-53.81,31.01-83.19,77.15-58.5,2,1.07,11.58,7.79,12.54,7.59,1.57-.32,1.05-4.62,1.34-6.14,3.75-19.76,11.37-39.47,27.8-52.04,20.82-15.93,49.31-14.16,66.47,6.23ZM256.71,71.1c1.07-24.31-18.03-42.71-30.8-12.52-5.88,13.9-7.77,32.56-3.36,47,1.73.46,1.24-.41,1.7-.95,10.15-11.8,19.02-25.11,32.46-33.52ZM340.18,123.96c10.12-10.37,17.92-27.48.94-35.85-29-14.31-83.74,17.89-87.91,49.24-1.6,11.97,2.2,14.36,13.71,14.53,21.84.32,57.86-12.13,73.26-27.92ZM173.64,117.92c-4.77-6.36-9.81-11.55-16.53-15.93-7.07-4.62-24.6-13.3-29.47-2.16-3.94,9.02,6.8,23.05,13.43,28.86,12.36,10.83,29.63,16.76,45.66,19.42-.28-11.02-6.62-21.58-13.08-30.19Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@ -3,7 +3,7 @@ defineOptions({ name: 'SystemLogo' });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img src="@/assets/imgs/logo.png" />
|
<icon-local-logo />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { useAttrs } from 'vue';
|
import { onMounted, useAttrs } from 'vue';
|
||||||
import type { TreeOption, TreeSelectProps } from 'naive-ui';
|
import type { TreeOption, TreeSelectProps } from 'naive-ui';
|
||||||
import { useLoading } from '@sa/hooks';
|
import { useLoading } from '@sa/hooks';
|
||||||
import { fetchGetMenuList } from '@/service/api/system';
|
import { fetchGetMenuList } from '@/service/api/system';
|
||||||
import { handleTree } from '@/utils/common';
|
import { handleTree } from '@/utils/common';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
defineOptions({ name: 'MenuTreeSelect' });
|
defineOptions({ name: 'MenuTreeSelect' });
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
immediate?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>();
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
immediate: true
|
||||||
|
});
|
||||||
|
|
||||||
const value = defineModel<CommonType.IdType | null>('value', { required: false });
|
const value = defineModel<CommonType.IdType | null>('value', { required: false });
|
||||||
const options = defineModel<Api.System.MenuList>('options', { required: false, default: [] });
|
const options = defineModel<Api.System.MenuList>('options', { required: false, default: [] });
|
||||||
@ -35,7 +39,19 @@ async function getMenuList() {
|
|||||||
endLoading();
|
endLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuList();
|
onMounted(() => {
|
||||||
|
if (props.immediate) {
|
||||||
|
getMenuList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderLabel({ option }: { option: TreeOption }) {
|
||||||
|
let label = String(option.menuName);
|
||||||
|
if (label?.startsWith('route.') || label?.startsWith('menu.')) {
|
||||||
|
label = $t(label as App.I18n.I18nKey);
|
||||||
|
}
|
||||||
|
return <div>{label}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
function renderPrefix({ option }: { option: TreeOption }) {
|
function renderPrefix({ option }: { option: TreeOption }) {
|
||||||
const renderLocalIcon = String(option.icon).startsWith('local-icon-');
|
const renderLocalIcon = String(option.icon).startsWith('local-icon-');
|
||||||
@ -55,6 +71,8 @@ function renderPrefix({ option }: { option: TreeOption }) {
|
|||||||
label-field="menuName"
|
label-field="menuName"
|
||||||
:options="options"
|
:options="options"
|
||||||
:default-expanded-keys="[0]"
|
:default-expanded-keys="[0]"
|
||||||
|
:render-tag="renderLabel"
|
||||||
|
:render-label="renderLabel"
|
||||||
:render-prefix="renderPrefix"
|
:render-prefix="renderPrefix"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type { TreeOption, TreeSelectInst, TreeSelectProps } from 'naive-ui';
|
|||||||
import { useBoolean } from '@sa/hooks';
|
import { useBoolean } from '@sa/hooks';
|
||||||
import { fetchGetMenuTreeSelect } from '@/service/api/system';
|
import { fetchGetMenuTreeSelect } from '@/service/api/system';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
defineOptions({ name: 'MenuTree' });
|
defineOptions({ name: 'MenuTree' });
|
||||||
|
|
||||||
@ -60,6 +61,14 @@ watch([expandAll, options], ([newVal]) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderLabel({ option }: { option: TreeOption }) {
|
||||||
|
let label = option.label;
|
||||||
|
if (label?.startsWith('route.') || label?.startsWith('menu.')) {
|
||||||
|
label = $t(label as App.I18n.I18nKey);
|
||||||
|
}
|
||||||
|
return <div>{label}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
function renderPrefix({ option }: { option: TreeOption }) {
|
function renderPrefix({ option }: { option: TreeOption }) {
|
||||||
const renderLocalIcon = String(option.icon).startsWith('local-icon-');
|
const renderLocalIcon = String(option.icon).startsWith('local-icon-');
|
||||||
let icon = renderLocalIcon ? undefined : String(option.icon ?? 'material-symbols:buttons-alt-outline-rounded');
|
let icon = renderLocalIcon ? undefined : String(option.icon ?? 'material-symbols:buttons-alt-outline-rounded');
|
||||||
@ -163,6 +172,7 @@ defineExpose({
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
check-strategy="all"
|
check-strategy="all"
|
||||||
|
:render-label="renderLabel"
|
||||||
:render-prefix="renderPrefix"
|
:render-prefix="renderPrefix"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -17,8 +17,8 @@ withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RouterLink to="/" class="w-full flex-center nowrap-hidden">
|
<RouterLink to="/" class="w-full flex-center nowrap-hidden">
|
||||||
<SystemLogo class="w-32px text-primary" />
|
<SystemLogo class="text-30px text-primary" />
|
||||||
<h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold transition duration-300 ease-in-out">
|
<h2 v-show="showTitle" class="pl-12px text-16px text-primary font-bold transition duration-300 ease-in-out">
|
||||||
{{ $t('system.title') }}
|
{{ $t('system.title') }}
|
||||||
</h2>
|
</h2>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const local: App.I18n.Schema = {
|
const local: App.I18n.Schema = {
|
||||||
system: {
|
system: {
|
||||||
title: 'RuoYi Vue Plus',
|
title: 'RuoYi Plus Soybean',
|
||||||
updateTitle: 'System Version Update Notification',
|
updateTitle: 'System Version Update Notification',
|
||||||
updateContent: 'A new version of the system has been detected. Do you want to refresh the page immediately?',
|
updateContent: 'A new version of the system has been detected. Do you want to refresh the page immediately?',
|
||||||
updateConfirm: 'Refresh immediately',
|
updateConfirm: 'Refresh immediately',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const local: App.I18n.Schema = {
|
const local: App.I18n.Schema = {
|
||||||
system: {
|
system: {
|
||||||
title: 'RuoYi Vue Plus',
|
title: 'RuoYi Plus Soybean',
|
||||||
updateTitle: '系统版本更新通知',
|
updateTitle: '系统版本更新通知',
|
||||||
updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
|
updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
|
||||||
updateConfirm: '立即刷新',
|
updateConfirm: '立即刷新',
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { getRgb } from '@sa/color';
|
|||||||
import { DARK_CLASS } from '@/constants/app';
|
import { DARK_CLASS } from '@/constants/app';
|
||||||
import { localStg } from '@/utils/storage';
|
import { localStg } from '@/utils/storage';
|
||||||
import { toggleHtmlClass } from '@/utils/common';
|
import { toggleHtmlClass } from '@/utils/common';
|
||||||
import systemLogo from '@/assets/imgs/logo.png';
|
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
import '@/styles/scss/loading.scss';
|
||||||
|
|
||||||
export function setupLoading() {
|
export function setupLoading() {
|
||||||
const app = document.getElementById('app');
|
const app = document.getElementById('app');
|
||||||
@ -21,12 +21,11 @@ export function setupLoading() {
|
|||||||
|
|
||||||
const loading = `
|
const loading = `
|
||||||
<div class="fixed-center flex-col bg-layout" style="${primaryColor}">
|
<div class="fixed-center flex-col bg-layout" style="${primaryColor}">
|
||||||
<div class="w-120px h-120px my-36px">
|
<div class="my-52px h-120px w-120px">
|
||||||
<div class="relative h-full animate-spin">
|
<!-- From Uiverse.io by SchawnnahJ -->
|
||||||
<img src="${systemLogo}" width="120" />
|
<div class="loader"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-28px font-500 text-primary">${$t('system.title')}</h2>
|
<h2 class="text-30px text-primary-400 font-500">${$t('system.title')}</h2>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
if (app) {
|
if (app) {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useRoute } from 'vue-router';
|
|
||||||
import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||||
import { BACKEND_ERROR_CODE, REQUEST_CANCELED_CODE, createFlatRequest } from '@sa/axios';
|
import { BACKEND_ERROR_CODE, REQUEST_CANCELED_CODE, createFlatRequest } from '@sa/axios';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
@ -52,7 +51,6 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
|||||||
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
||||||
},
|
},
|
||||||
async onBackendFail(response, instance) {
|
async onBackendFail(response, instance) {
|
||||||
const route = useRoute();
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const responseCode = String(response.data.code);
|
const responseCode = String(response.data.code);
|
||||||
|
|
||||||
@ -68,10 +66,6 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
|
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
|
||||||
if (route.name === 'login') {
|
|
||||||
handleLogout();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
|
// const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
|
||||||
// if (logoutCodes.includes(responseCode)) {
|
// if (logoutCodes.includes(responseCode)) {
|
||||||
// handleLogout();
|
// handleLogout();
|
||||||
@ -86,19 +80,23 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
|
|||||||
// prevent the user from refreshing the page
|
// prevent the user from refreshing the page
|
||||||
window.addEventListener('beforeunload', handleLogout);
|
window.addEventListener('beforeunload', handleLogout);
|
||||||
|
|
||||||
window.$dialog?.warning({
|
if (!window.location.pathname?.startsWith('/login')) {
|
||||||
title: '系统提示',
|
window.$dialog?.warning({
|
||||||
content: '登录状态已过期,您可以继续留在该页面,或者重新登录',
|
title: '系统提示',
|
||||||
positiveText: '重新登录',
|
content: '登录状态已过期,您可以继续留在该页面,或者重新登录',
|
||||||
negativeText: '取消',
|
positiveText: '重新登录',
|
||||||
maskClosable: false,
|
negativeText: '取消',
|
||||||
closeOnEsc: false,
|
maskClosable: false,
|
||||||
onPositiveClick() {
|
closeOnEsc: false,
|
||||||
logoutAndCleanup();
|
onPositiveClick() {
|
||||||
}
|
logoutAndCleanup();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
request.cancelAllRequest();
|
request.cancelAllRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
|
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
|
||||||
|
|||||||
105
src/styles/scss/loading.scss
Normal file
105
src/styles/scss/loading.scss
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
@use 'sass:math';
|
||||||
|
|
||||||
|
$base-size: 100px;
|
||||||
|
$em-to-px: math.div($base-size, 2.5);
|
||||||
|
$loader-color-1: rgb(var(--error-color) / 75%);
|
||||||
|
$loader-color-2: rgb(var(--primary-color) / 75%);
|
||||||
|
$loader-color-3: rgb(var(--success-color) / 75%);
|
||||||
|
$loader-color-4: rgb(var(--warning-color) / 75%);
|
||||||
|
|
||||||
|
@function loader-size($em-value) {
|
||||||
|
@return $em-to-px * $em-value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* From Uiverse.io by SchawnnahJ */
|
||||||
|
.loader {
|
||||||
|
position: relative;
|
||||||
|
width: loader-size(2.5);
|
||||||
|
height: loader-size(2.5);
|
||||||
|
transform: rotate(165deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader:before,
|
||||||
|
.loader:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
display: block;
|
||||||
|
width: loader-size(0.5);
|
||||||
|
height: loader-size(0.5);
|
||||||
|
border-radius: loader-size(0.25);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader:before {
|
||||||
|
animation: before8 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader:after {
|
||||||
|
animation: after6 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes before8 {
|
||||||
|
0% {
|
||||||
|
width: loader-size(0.5);
|
||||||
|
box-shadow:
|
||||||
|
loader-size(1) loader-size(-0.5) $loader-color-1,
|
||||||
|
loader-size(-1) loader-size(0.5) $loader-color-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
35% {
|
||||||
|
width: loader-size(2.5);
|
||||||
|
box-shadow:
|
||||||
|
0 loader-size(-0.5) $loader-color-1,
|
||||||
|
0 loader-size(0.5) $loader-color-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
width: loader-size(0.5);
|
||||||
|
box-shadow:
|
||||||
|
loader-size(-1) loader-size(-0.5) $loader-color-1,
|
||||||
|
loader-size(1) loader-size(0.5) $loader-color-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
loader-size(1) loader-size(-0.5) $loader-color-1,
|
||||||
|
loader-size(-1) loader-size(0.5) $loader-color-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes after6 {
|
||||||
|
0% {
|
||||||
|
height: loader-size(0.5);
|
||||||
|
box-shadow:
|
||||||
|
loader-size(0.5) loader-size(1) $loader-color-3,
|
||||||
|
loader-size(-0.5) loader-size(-1) $loader-color-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
35% {
|
||||||
|
height: loader-size(2.5);
|
||||||
|
box-shadow:
|
||||||
|
loader-size(0.5) 0 $loader-color-3,
|
||||||
|
loader-size(-0.5) 0 $loader-color-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
height: loader-size(0.5);
|
||||||
|
box-shadow:
|
||||||
|
loader-size(0.5) loader-size(-1) $loader-color-3,
|
||||||
|
loader-size(-0.5) loader-size(1) $loader-color-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
loader-size(0.5) loader-size(1) $loader-color-3,
|
||||||
|
loader-size(-0.5) loader-size(-1) $loader-color-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - #{math.div(loader-size(2.5), 2)});
|
||||||
|
left: calc(50% - #{math.div(loader-size(2.5), 2)});
|
||||||
|
}
|
||||||
4
src/typings/components.d.ts
vendored
4
src/typings/components.d.ts
vendored
@ -36,6 +36,7 @@ declare module 'vue' {
|
|||||||
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
|
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
|
||||||
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
|
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
|
||||||
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
||||||
|
IconLocalLogo: typeof import('~icons/local/logo')['default']
|
||||||
'IconMaterialSymbols:add': typeof import('~icons/material-symbols/add')['default']
|
'IconMaterialSymbols:add': typeof import('~icons/material-symbols/add')['default']
|
||||||
'IconMaterialSymbols:deleteOutline': typeof import('~icons/material-symbols/delete-outline')['default']
|
'IconMaterialSymbols:deleteOutline': typeof import('~icons/material-symbols/delete-outline')['default']
|
||||||
'IconMaterialSymbols:downloadRounded': typeof import('~icons/material-symbols/download-rounded')['default']
|
'IconMaterialSymbols:downloadRounded': typeof import('~icons/material-symbols/download-rounded')['default']
|
||||||
@ -47,6 +48,7 @@ declare module 'vue' {
|
|||||||
IconMaterialSymbolsAddRounded: typeof import('~icons/material-symbols/add-rounded')['default']
|
IconMaterialSymbolsAddRounded: typeof import('~icons/material-symbols/add-rounded')['default']
|
||||||
IconMaterialSymbolsDeleteOutline: typeof import('~icons/material-symbols/delete-outline')['default']
|
IconMaterialSymbolsDeleteOutline: typeof import('~icons/material-symbols/delete-outline')['default']
|
||||||
IconMaterialSymbolsDriveFileRenameOutlineOutline: typeof import('~icons/material-symbols/drive-file-rename-outline-outline')['default']
|
IconMaterialSymbolsDriveFileRenameOutlineOutline: typeof import('~icons/material-symbols/drive-file-rename-outline-outline')['default']
|
||||||
|
'IconMdi:github': typeof import('~icons/mdi/github')['default']
|
||||||
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
|
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
|
||||||
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
|
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
|
||||||
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
|
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
|
||||||
@ -54,6 +56,7 @@ declare module 'vue' {
|
|||||||
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
|
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
|
||||||
'IconQuill:collapse': typeof import('~icons/quill/collapse')['default']
|
'IconQuill:collapse': typeof import('~icons/quill/collapse')['default']
|
||||||
'IconQuill:expand': typeof import('~icons/quill/expand')['default']
|
'IconQuill:expand': typeof import('~icons/quill/expand')['default']
|
||||||
|
'IconSimpleIcons:gitee': typeof import('~icons/simple-icons/gitee')['default']
|
||||||
IconUilSearch: typeof import('~icons/uil/search')['default']
|
IconUilSearch: typeof import('~icons/uil/search')['default']
|
||||||
JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
|
JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
|
||||||
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
||||||
@ -73,7 +76,6 @@ declare module 'vue' {
|
|||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NCard: typeof import('naive-ui')['NCard']
|
NCard: typeof import('naive-ui')['NCard']
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
|
||||||
NCode: typeof import('naive-ui')['NCode']
|
NCode: typeof import('naive-ui')['NCode']
|
||||||
NCollapse: typeof import('naive-ui')['NCollapse']
|
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
import { getPaletteColorByNumber, mixColor } from '@sa/color';
|
|
||||||
import { loginModuleRecord } from '@/constants/app';
|
import { loginModuleRecord } from '@/constants/app';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import loginBackground from '@/assets/svg-icon/login-background.svg';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import PwdLogin from './modules/pwd-login.vue';
|
import PwdLogin from './modules/pwd-login.vue';
|
||||||
import CodeLogin from './modules/code-login.vue';
|
import CodeLogin from './modules/code-login.vue';
|
||||||
@ -36,29 +36,23 @@ const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
||||||
|
|
||||||
const bgThemeColor = computed(() =>
|
|
||||||
themeStore.darkMode ? getPaletteColorByNumber(themeStore.themeColor, 600) : themeStore.themeColor
|
|
||||||
);
|
|
||||||
|
|
||||||
const bgColor = computed(() => {
|
|
||||||
const COLOR_WHITE = '#ffffff';
|
|
||||||
|
|
||||||
const ratio = themeStore.darkMode ? 0.5 : 0.2;
|
|
||||||
|
|
||||||
return mixColor(COLOR_WHITE, themeStore.themeColor, ratio);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative size-full flex-center overflow-hidden" :style="{ backgroundColor: bgColor }">
|
<div class="relative size-full flex flex-wrap overflow-hidden">
|
||||||
<WaveBg :theme-color="bgThemeColor" />
|
<div class="hidden h-full w-50% bg-primary-100 lg:block dark:bg-primary-800">
|
||||||
<NCard :bordered="false" class="relative z-4 w-auto rd-12px">
|
<div class="size-full flex-center">
|
||||||
<div class="w-400px lt-sm:w-300px">
|
<img class="w-60% sm:w-80%" :src="loginBackground" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex-col-center px-24px py-32px lg:w-50%">
|
||||||
|
<div class="mx-auto max-w-464px w-full">
|
||||||
<header class="flex-y-center justify-between">
|
<header class="flex-y-center justify-between">
|
||||||
<SystemLogo class="w-64px text-primary lt-sm:text-48px" />
|
<div class="flex-y-center gap-16px">
|
||||||
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
|
<SystemLogo class="text-42px text-primary" />
|
||||||
<div class="i-flex-col">
|
<h3 class="text-32px text-primary font-500">{{ $t('system.title') }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="flex-y-center">
|
||||||
<ThemeSchemaSwitch
|
<ThemeSchemaSwitch
|
||||||
:theme-schema="themeStore.themeScheme"
|
:theme-schema="themeStore.themeScheme"
|
||||||
:show-tooltip="false"
|
:show-tooltip="false"
|
||||||
@ -75,15 +69,14 @@ const bgColor = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="pt-24px">
|
<main class="pt-24px">
|
||||||
<h3 class="text-18px text-primary font-medium">{{ $t(activeModule.label) }}</h3>
|
<div>
|
||||||
<div class="pt-24px">
|
|
||||||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
||||||
<component :is="activeModule.component" />
|
<component :is="activeModule.component" />
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -121,63 +121,89 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm
|
<div>
|
||||||
ref="formRef"
|
<div class="mb-12px text-30px text-black font-500 dark:text-white">登录到您的账户</div>
|
||||||
:model="model"
|
<div class="pb-24px text-18px text-#858585">欢迎回来!请输入您的账户信息</div>
|
||||||
:rules="rules"
|
<NForm
|
||||||
size="large"
|
ref="formRef"
|
||||||
:show-label="false"
|
:model="model"
|
||||||
@keyup.enter="() => !authStore.loginLoading && handleSubmit()"
|
:rules="rules"
|
||||||
>
|
size="large"
|
||||||
<NFormItem v-if="tenantEnabled" path="tenantId">
|
:show-label="false"
|
||||||
<NSelect
|
@keyup.enter="() => !authStore.loginLoading && handleSubmit()"
|
||||||
v-model:value="model.tenantId"
|
>
|
||||||
placeholder="请选择租户"
|
<NFormItem v-if="tenantEnabled" path="tenantId">
|
||||||
:options="tenantOption"
|
<NSelect
|
||||||
:loading="tenantLoading"
|
v-model:value="model.tenantId"
|
||||||
/>
|
placeholder="请选择租户"
|
||||||
</NFormItem>
|
:options="tenantOption"
|
||||||
<NFormItem path="username">
|
:loading="tenantLoading"
|
||||||
<NInput v-model:value="model.username" :placeholder="$t('page.login.common.userNamePlaceholder')" />
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="password">
|
<NFormItem path="username">
|
||||||
<NInput
|
<NInput v-model:value="model.username" :placeholder="$t('page.login.common.userNamePlaceholder')" />
|
||||||
v-model:value="model.password"
|
</NFormItem>
|
||||||
type="password"
|
<NFormItem path="password">
|
||||||
show-password-on="click"
|
<NInput
|
||||||
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
v-model:value="model.password"
|
||||||
/>
|
type="password"
|
||||||
</NFormItem>
|
show-password-on="click"
|
||||||
<NFormItem v-if="captchaEnabled" path="code">
|
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
||||||
<div class="w-full flex-y-center gap-16px">
|
/>
|
||||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
</NFormItem>
|
||||||
<NSpin :show="codeLoading" :size="28" class="h-42px">
|
<NFormItem v-if="captchaEnabled" path="code">
|
||||||
<NButton :focusable="false" class="login-code h-42px w-116px" @click="handleFetchCaptchaCode">
|
<div class="w-full flex-y-center gap-16px">
|
||||||
<img v-if="codeUrl" :src="codeUrl" />
|
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
||||||
<NEmpty v-else :show-icon="false" description="暂无验证码" />
|
<NSpin :show="codeLoading" :size="28" class="h-52px">
|
||||||
</NButton>
|
<NButton :focusable="false" class="login-code h-52px w-136px" @click="handleFetchCaptchaCode">
|
||||||
</NSpin>
|
<img v-if="codeUrl" :src="codeUrl" />
|
||||||
</div>
|
<NEmpty v-else :show-icon="false" description="暂无验证码" />
|
||||||
</NFormItem>
|
</NButton>
|
||||||
<NSpace vertical :size="16" class="mb-8px">
|
</NSpin>
|
||||||
<div class="mx-6px flex-y-center justify-between">
|
</div>
|
||||||
<NCheckbox v-model:checked="remberMe">{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
</NFormItem>
|
||||||
<NSpace :size="1">
|
<NSpace vertical :size="16" class="mb-8px">
|
||||||
<ButtonIcon class="color-#44b549" icon="ic:outline-wechat" @click="handleSocialLogin('wechat_open')" />
|
<div class="mx-6px mb-10px flex-y-center justify-between">
|
||||||
<ButtonIcon local-icon="topiam" @click="handleSocialLogin('topiam')" />
|
<NCheckbox v-model:checked="remberMe" size="large">{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
||||||
<ButtonIcon local-icon="maxkey" @click="handleSocialLogin('maxkey')" />
|
<NA type="primary" class="text-18px" @click="toggleLoginModule('reset-pwd')">
|
||||||
<ButtonIcon class="color-#c71d23" icon="simple-icons:gitee" @click="handleSocialLogin('gitee')" />
|
{{ $t('page.login.pwdLogin.forgetPassword') }}
|
||||||
<ButtonIcon class="color-#010409" icon="mdi:github" @click="handleSocialLogin('github')" />
|
</NA>
|
||||||
</NSpace>
|
</div>
|
||||||
</div>
|
<NButton type="primary" size="large" block :loading="authStore.loginLoading" @click="handleSubmit">
|
||||||
<NButton type="primary" size="large" block :loading="authStore.loginLoading" @click="handleSubmit">
|
{{ $t('common.login') }}
|
||||||
{{ $t('common.login') }}
|
</NButton>
|
||||||
|
<NButton v-if="registerEnabled" size="large" block @click="toggleLoginModule('register')">
|
||||||
|
{{ $t('page.login.common.register') }}
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</NForm>
|
||||||
|
|
||||||
|
<NDivider>
|
||||||
|
<div class="color-#858585">{{ $t('page.login.pwdLogin.otherAccountLogin') }}</div>
|
||||||
|
</NDivider>
|
||||||
|
|
||||||
|
<div class="w-full flex-y-center gap-16px">
|
||||||
|
<NButton class="flex-1" @click="handleSocialLogin('gitee')">
|
||||||
|
<template #icon>
|
||||||
|
<icon-simple-icons:gitee class="color-#c71d23" />
|
||||||
|
</template>
|
||||||
|
<span class="ml-6px">Gitee</span>
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton v-if="registerEnabled" size="large" block @click="toggleLoginModule('register')">
|
<NButton class="flex-1" @click="handleSocialLogin('github')">
|
||||||
|
<template #icon>
|
||||||
|
<icon-mdi:github class="color-#010409" />
|
||||||
|
</template>
|
||||||
|
<span class="ml-6px">GitHub</span>
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-32px w-full text-center text-18px text-#858585">
|
||||||
|
您还没有账户?
|
||||||
|
<NA type="primary" class="text-18px" @click="toggleLoginModule('register')">
|
||||||
{{ $t('page.login.common.register') }}
|
{{ $t('page.login.common.register') }}
|
||||||
</NButton>
|
</NA>
|
||||||
</NSpace>
|
</div>
|
||||||
</NForm>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -188,7 +214,34 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 40px;
|
height: 52px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection),
|
||||||
|
:deep(.n-input) {
|
||||||
|
--n-height: 52px !important;
|
||||||
|
--n-font-size: 16px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
padding: 0 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-checkbox) {
|
||||||
|
--n-size: 18px !important;
|
||||||
|
--n-font-size: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-button) {
|
||||||
|
--n-height: 52px !important;
|
||||||
|
--n-font-size: 18px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-divider) {
|
||||||
|
--n-font-size: 16px !important;
|
||||||
|
--n-font-weight: 400 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -103,56 +103,64 @@ handleFetchCaptchaCode();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm
|
<div>
|
||||||
ref="formRef"
|
<div class="mb-12px text-30px text-black font-500 dark:text-white">注册新账户</div>
|
||||||
:model="model"
|
<div class="pb-24px text-18px text-#858585">欢迎注册!请输入您的账户信息</div>
|
||||||
:rules="rules"
|
<NForm
|
||||||
size="large"
|
ref="formRef"
|
||||||
:show-label="false"
|
:model="model"
|
||||||
@keyup.enter="() => !registerLoading && handleSubmit()"
|
:rules="rules"
|
||||||
>
|
size="large"
|
||||||
<NFormItem v-if="tenantEnabled" path="tenantId">
|
:show-label="false"
|
||||||
<NSelect v-model:value="model.tenantId" :options="tenantOption" :enabled="tenantEnabled" />
|
@keyup.enter="() => !registerLoading && handleSubmit()"
|
||||||
</NFormItem>
|
>
|
||||||
<NFormItem path="username">
|
<NFormItem v-if="tenantEnabled" path="tenantId">
|
||||||
<NInput v-model:value="model.username" :placeholder="$t('page.login.common.userNamePlaceholder')" />
|
<NSelect v-model:value="model.tenantId" :options="tenantOption" :enabled="tenantEnabled" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="password">
|
<NFormItem path="username">
|
||||||
<NInput
|
<NInput v-model:value="model.username" :placeholder="$t('page.login.common.userNamePlaceholder')" />
|
||||||
v-model:value="model.password"
|
</NFormItem>
|
||||||
type="password"
|
<NFormItem path="password">
|
||||||
show-password-on="click"
|
<NInput
|
||||||
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
v-model:value="model.password"
|
||||||
/>
|
type="password"
|
||||||
</NFormItem>
|
show-password-on="click"
|
||||||
<NFormItem path="confirmPassword">
|
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
||||||
<NInput
|
/>
|
||||||
v-model:value="model.confirmPassword"
|
</NFormItem>
|
||||||
type="password"
|
<NFormItem path="confirmPassword">
|
||||||
show-password-on="click"
|
<NInput
|
||||||
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
v-model:value="model.confirmPassword"
|
||||||
/>
|
type="password"
|
||||||
</NFormItem>
|
show-password-on="click"
|
||||||
<NFormItem v-if="captchaEnabled" path="code">
|
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
||||||
<div class="w-full flex-y-center gap-16px">
|
/>
|
||||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
</NFormItem>
|
||||||
<NSpin :show="codeLoading" :size="28" class="h-42px">
|
<NFormItem v-if="captchaEnabled" path="code">
|
||||||
<NButton :focusable="false" class="login-code h-42px w-116px" @click="handleFetchCaptchaCode">
|
<div class="w-full flex-y-center gap-16px">
|
||||||
<img v-if="codeUrl" :src="codeUrl" />
|
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
||||||
<NEmpty v-else :show-icon="false" description="暂无验证码" />
|
<NSpin :show="codeLoading" :size="28" class="h-52px">
|
||||||
</NButton>
|
<NButton :focusable="false" class="login-code h-52px w-136px" @click="handleFetchCaptchaCode">
|
||||||
</NSpin>
|
<img v-if="codeUrl" :src="codeUrl" />
|
||||||
</div>
|
<NEmpty v-else :show-icon="false" description="暂无验证码" />
|
||||||
</NFormItem>
|
</NButton>
|
||||||
<NSpace vertical :size="18" class="w-full">
|
</NSpin>
|
||||||
<NButton type="primary" size="large" block :loading="registerLoading" @click="handleSubmit">
|
</div>
|
||||||
{{ $t('page.login.common.register') }}
|
</NFormItem>
|
||||||
</NButton>
|
<NSpace vertical :size="18" class="w-full pt-6px">
|
||||||
<NButton size="large" block @click="toggleLoginModule('pwd-login')">
|
<NButton type="primary" size="large" block :loading="registerLoading" @click="handleSubmit">
|
||||||
{{ $t('page.login.common.back') }}
|
{{ $t('page.login.common.register') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NForm>
|
</NForm>
|
||||||
|
|
||||||
|
<div class="mt-32px w-full text-center text-18px text-#858585">
|
||||||
|
您已有账户?
|
||||||
|
<NA type="primary" class="text-18px" @click="toggleLoginModule('pwd-login')">
|
||||||
|
{{ $t('common.login') }}
|
||||||
|
</NA>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@ -166,4 +174,21 @@ handleFetchCaptchaCode();
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection),
|
||||||
|
:deep(.n-input) {
|
||||||
|
--n-height: 52px !important;
|
||||||
|
--n-font-size: 16px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
padding: 0 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-button) {
|
||||||
|
--n-height: 52px !important;
|
||||||
|
--n-font-size: 18px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -45,38 +45,59 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
<div>
|
||||||
<NFormItem path="phone">
|
<div class="mb-12px text-30px text-black font-500 dark:text-white">{{ $t('page.login.resetPwd.title') }}</div>
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<div class="pb-24px text-18px text-#858585">请输入您的手机号,我们将发送验证码到您的手机</div>
|
||||||
</NFormItem>
|
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
|
||||||
<NFormItem path="code">
|
<NFormItem path="phone">
|
||||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="password">
|
<NFormItem path="code">
|
||||||
<NInput
|
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
||||||
v-model:value="model.password"
|
</NFormItem>
|
||||||
type="password"
|
<NFormItem path="password">
|
||||||
show-password-on="click"
|
<NInput
|
||||||
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
v-model:value="model.password"
|
||||||
/>
|
type="password"
|
||||||
</NFormItem>
|
show-password-on="click"
|
||||||
<NFormItem path="confirmPassword">
|
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
||||||
<NInput
|
/>
|
||||||
v-model:value="model.confirmPassword"
|
</NFormItem>
|
||||||
type="password"
|
<NFormItem path="confirmPassword">
|
||||||
show-password-on="click"
|
<NInput
|
||||||
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
v-model:value="model.confirmPassword"
|
||||||
/>
|
type="password"
|
||||||
</NFormItem>
|
show-password-on="click"
|
||||||
<NSpace vertical :size="18" class="w-full">
|
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
||||||
<NButton type="primary" size="large" round block @click="handleSubmit">
|
/>
|
||||||
{{ $t('common.confirm') }}
|
</NFormItem>
|
||||||
</NButton>
|
<NSpace vertical :size="18" class="w-full">
|
||||||
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
<NButton type="primary" size="large" block @click="handleSubmit">
|
||||||
{{ $t('page.login.common.back') }}
|
{{ $t('page.login.resetPwd.title') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
</NSpace>
|
<NButton size="large" block @click="toggleLoginModule('pwd-login')">
|
||||||
</NForm>
|
{{ $t('page.login.common.back') }}
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</NForm>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
:deep(.n-base-selection),
|
||||||
|
:deep(.n-input) {
|
||||||
|
--n-height: 52px !important;
|
||||||
|
--n-font-size: 16px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
padding: 0 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-button) {
|
||||||
|
--n-height: 52px !important;
|
||||||
|
--n-font-size: 18px !important;
|
||||||
|
--n-border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useLoading } from '@sa/hooks';
|
import { getRgb } from '@sa/color';
|
||||||
|
import { DARK_CLASS } from '@/constants/app';
|
||||||
import { fetchSocialLoginCallback } from '@/service/api';
|
import { fetchSocialLoginCallback } from '@/service/api';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
import { localStg } from '@/utils/storage';
|
||||||
|
import { toggleHtmlClass } from '@/utils/common';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
const { loading, startLoading, endLoading } = useLoading(true);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收Route传递的参数
|
* 接收Route传递的参数
|
||||||
@ -56,7 +58,6 @@ const callbackByCode = async (data: Api.Auth.SocialLoginForm) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await processResponse();
|
await processResponse();
|
||||||
endLoading();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const loginByCode = async (data: Api.Auth.SocialLoginForm) => {
|
const loginByCode = async (data: Api.Auth.SocialLoginForm) => {
|
||||||
@ -67,11 +68,9 @@ const loginByCode = async (data: Api.Auth.SocialLoginForm) => {
|
|||||||
} catch {
|
} catch {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
endLoading();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
startLoading();
|
|
||||||
// 如果域名不相等 则重定向处理
|
// 如果域名不相等 则重定向处理
|
||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
if (domain !== host) {
|
if (domain !== host) {
|
||||||
@ -99,17 +98,28 @@ const init = async () => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await init();
|
await init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const themeColor = localStg.get('themeColor') || '#2080f0';
|
||||||
|
const darkMode = localStg.get('darkMode') || false;
|
||||||
|
const { r, g, b } = getRgb(themeColor);
|
||||||
|
|
||||||
|
if (darkMode) {
|
||||||
|
toggleHtmlClass(DARK_CLASS).add();
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryColor = `--primary-color: ${r} ${g} ${b}`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="fixed-center flex-col bg-layout">
|
<div class="fixed-center flex-col bg-layout" :style="primaryColor">
|
||||||
<div class="my-36px h-120px w-120px">
|
<div class="my-52px h-120px w-120px">
|
||||||
<div class="relative h-full" :class="{ 'animate-spin': loading }">
|
<!-- From Uiverse.io by SchawnnahJ -->
|
||||||
<img src="@/assets/imgs/logo.png" width="120" />
|
<div class="loader"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-28px text-primary font-500">{{ msg }}</h2>
|
<h2 class="text-30px text-primary-400 font-500">{{ $t('system.title') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
@use '@/styles/scss/loading.scss';
|
||||||
|
</style>
|
||||||
|
|||||||
@ -255,12 +255,10 @@ function onCreate() {
|
|||||||
<NForm ref="formRef" :model="model" :rules="rules">
|
<NForm ref="formRef" :model="model" :rules="rules">
|
||||||
<NGrid responsive="screen" item-responsive>
|
<NGrid responsive="screen" item-responsive>
|
||||||
<NFormItemGi :span="24" :label="$t('page.system.menu.parentId')" path="pid">
|
<NFormItemGi :span="24" :label="$t('page.system.menu.parentId')" path="pid">
|
||||||
<NTreeSelect
|
<MenuTreeSelect
|
||||||
v-model:value="model.parentId"
|
v-model:value="model.parentId"
|
||||||
|
:immediate="false"
|
||||||
:options="treeData as []"
|
:options="treeData as []"
|
||||||
label-field="menuName"
|
|
||||||
key-field="menuId"
|
|
||||||
:default-expanded-keys="[0]"
|
|
||||||
:placeholder="$t('page.system.menu.form.parentId.required')"
|
:placeholder="$t('page.system.menu.form.parentId.required')"
|
||||||
/>
|
/>
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user