mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-17 17:26:38 +08:00
feat(projects): 登录页面实现
This commit is contained in:
parent
5c01006306
commit
f1e7cf608e
@ -12,7 +12,6 @@ module.exports = {
|
||||
eslintIntegration: false, //不让prettier使用eslint的代码格式进行校验
|
||||
htmlWhitespaceSensitivity: 'ignore', // 指定HTML文件的全局空白区域敏感度 有效选项:"css"- 遵守CSS display属性的默认值。"strict" - 空格被认为是敏感的。"ignore" - 空格被认为是不敏感的。html 中空格也会占位,影响布局,prettier 格式化的时候可能会将文本换行,造成布局错乱
|
||||
ignorePath: '.prettierignore', // 不使用prettier格式化的文件填写在项目的.prettierignore文件中
|
||||
jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
|
||||
jsxSingleQuote: false, // 在jsx中使用单引号代替双引号
|
||||
// parser: 'babylon', // 格式化的解析器,默认是babylon
|
||||
requireConfig: false, // Require a 'prettierconfig' to format prettier
|
||||
|
@ -21,7 +21,7 @@
|
||||
"@vueuse/core": "^6.3.2",
|
||||
"axios": "^0.21.4",
|
||||
"chroma-js": "^2.1.2",
|
||||
"dayjs": "^1.10.6",
|
||||
"dayjs": "^1.10.7",
|
||||
"form-data": "^4.0.0",
|
||||
"naive-ui": "^2.18.1",
|
||||
"pinia": "^2.0.0-rc.4",
|
||||
@ -33,7 +33,7 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^13.1.0",
|
||||
"@commitlint/config-conventional": "^13.1.0",
|
||||
"@iconify/json": "^1.1.399",
|
||||
"@iconify/json": "^1.1.400",
|
||||
"@iconify/vue": "^3.0.0",
|
||||
"@types/chroma-js": "^2.1.3",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
@ -60,7 +60,7 @@
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.4.0",
|
||||
"sass": "^1.39.0",
|
||||
"sass": "^1.39.2",
|
||||
"typescript": "^4.4.2",
|
||||
"unplugin-icons": "^0.7.6",
|
||||
"unplugin-vue-components": "^0.15.0",
|
||||
|
@ -3,7 +3,7 @@ lockfileVersion: 5.3
|
||||
specifiers:
|
||||
'@commitlint/cli': ^13.1.0
|
||||
'@commitlint/config-conventional': ^13.1.0
|
||||
'@iconify/json': ^1.1.399
|
||||
'@iconify/json': ^1.1.400
|
||||
'@iconify/vue': ^3.0.0
|
||||
'@types/chroma-js': ^2.1.3
|
||||
'@types/nprogress': ^0.2.0
|
||||
@ -21,7 +21,7 @@ specifiers:
|
||||
commitizen: ^4.2.4
|
||||
cz-conventional-changelog: ^3.3.0
|
||||
cz-customizable: ^6.3.0
|
||||
dayjs: ^1.10.6
|
||||
dayjs: ^1.10.7
|
||||
dotenv: ^10.0.0
|
||||
eslint: ^7.32.0
|
||||
eslint-config-airbnb-base: ^14.2.1
|
||||
@ -38,7 +38,7 @@ specifiers:
|
||||
postinstall-postinstall: ^2.1.0
|
||||
prettier: ^2.4.0
|
||||
qs: ^6.10.1
|
||||
sass: ^1.39.0
|
||||
sass: ^1.39.2
|
||||
smoothscroll-polyfill: ^0.4.4
|
||||
typescript: ^4.4.2
|
||||
unplugin-icons: ^0.7.6
|
||||
@ -55,7 +55,7 @@ dependencies:
|
||||
'@vueuse/core': registry.nlark.com/@vueuse/core/6.3.2_vue@3.2.10
|
||||
axios: registry.nlark.com/axios/0.21.4
|
||||
chroma-js: registry.nlark.com/chroma-js/2.1.2
|
||||
dayjs: registry.nlark.com/dayjs/1.10.6
|
||||
dayjs: registry.nlark.com/dayjs/1.10.7
|
||||
form-data: 4.0.0
|
||||
naive-ui: registry.nlark.com/naive-ui/2.18.1_vue@3.2.10
|
||||
pinia: registry.nlark.com/pinia/2.0.0-rc.4_typescript@4.4.2+vue@3.2.10
|
||||
@ -67,7 +67,7 @@ dependencies:
|
||||
devDependencies:
|
||||
'@commitlint/cli': registry.nlark.com/@commitlint/cli/13.1.0
|
||||
'@commitlint/config-conventional': registry.nlark.com/@commitlint/config-conventional/13.1.0
|
||||
'@iconify/json': registry.nlark.com/@iconify/json/1.1.399
|
||||
'@iconify/json': registry.nlark.com/@iconify/json/1.1.400
|
||||
'@iconify/vue': registry.nlark.com/@iconify/vue/3.0.0_vue@3.2.10
|
||||
'@types/chroma-js': registry.nlark.com/@types/chroma-js/2.1.3
|
||||
'@types/nprogress': registry.nlark.com/@types/nprogress/0.2.0
|
||||
@ -94,9 +94,9 @@ devDependencies:
|
||||
patch-package: registry.nlark.com/patch-package/6.4.7
|
||||
postinstall-postinstall: registry.nlark.com/postinstall-postinstall/2.1.0
|
||||
prettier: registry.nlark.com/prettier/2.4.0
|
||||
sass: registry.nlark.com/sass/1.39.0
|
||||
sass: registry.nlark.com/sass/1.39.2
|
||||
typescript: registry.nlark.com/typescript/4.4.2
|
||||
unplugin-icons: registry.nlark.com/unplugin-icons/0.7.6_5d72f6392975d02ee45d0acbc066efa3
|
||||
unplugin-icons: registry.nlark.com/unplugin-icons/0.7.6_ab88f3ed6cd34af3ce86467cf77018e8
|
||||
unplugin-vue-components: registry.nlark.com/unplugin-vue-components/0.15.0_vite@2.5.6+vue@3.2.10
|
||||
vite: registry.nlark.com/vite/2.5.6
|
||||
vite-plugin-html: registry.nlark.com/vite-plugin-html/2.1.0_vite@2.5.6
|
||||
@ -2345,10 +2345,10 @@ packages:
|
||||
version: 1.0.10
|
||||
dev: true
|
||||
|
||||
registry.nlark.com/@iconify/json/1.1.399:
|
||||
resolution: {integrity: sha1-hdBstQTkqkRkdYzo9n//lH1VKDU=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/@iconify/json/download/@iconify/json-1.1.399.tgz}
|
||||
registry.nlark.com/@iconify/json/1.1.400:
|
||||
resolution: {integrity: sha1-ZlZgMgOxo4klDH396+BrL9+LGwE=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/@iconify/json/download/@iconify/json-1.1.400.tgz}
|
||||
name: '@iconify/json'
|
||||
version: 1.1.399
|
||||
version: 1.1.400
|
||||
dev: true
|
||||
|
||||
registry.nlark.com/@iconify/vue/3.0.0_vue@3.2.10:
|
||||
@ -3461,10 +3461,10 @@ packages:
|
||||
engines: {node: '>=0.11'}
|
||||
dev: false
|
||||
|
||||
registry.nlark.com/dayjs/1.10.6:
|
||||
resolution: {integrity: sha1-KIsqqC8thBimydTfWJjAc3rQKmM=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/dayjs/download/dayjs-1.10.6.tgz}
|
||||
registry.nlark.com/dayjs/1.10.7:
|
||||
resolution: {integrity: sha1-LPX5Gt0oEWdIRAhmoKHSbzps5Gg=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/dayjs/download/dayjs-1.10.7.tgz}
|
||||
name: dayjs
|
||||
version: 1.10.6
|
||||
version: 1.10.7
|
||||
dev: false
|
||||
|
||||
registry.nlark.com/debug/2.6.9:
|
||||
@ -5176,10 +5176,10 @@ packages:
|
||||
tslib: registry.nlark.com/tslib/1.14.1
|
||||
dev: true
|
||||
|
||||
registry.nlark.com/sass/1.39.0:
|
||||
resolution: {integrity: sha1-bGRpXRxDd2fI8aTkcSiOgx+B0DU=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/sass/download/sass-1.39.0.tgz}
|
||||
registry.nlark.com/sass/1.39.2:
|
||||
resolution: {integrity: sha1-FoGWQ3j1jXb8ZKalAmGb1ayZ9mA=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/sass/download/sass-1.39.2.tgz?cache=0&sync_timestamp=1631232791563&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsass%2Fdownload%2Fsass-1.39.2.tgz}
|
||||
name: sass
|
||||
version: 1.39.0
|
||||
version: 1.39.2
|
||||
engines: {node: '>=8.9.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@ -5532,7 +5532,7 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
registry.nlark.com/unplugin-icons/0.7.6_5d72f6392975d02ee45d0acbc066efa3:
|
||||
registry.nlark.com/unplugin-icons/0.7.6_ab88f3ed6cd34af3ce86467cf77018e8:
|
||||
resolution: {integrity: sha1-CLYc+b2imJyKfcbU6oBStluwkKA=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/unplugin-icons/download/unplugin-icons-0.7.6.tgz}
|
||||
id: registry.nlark.com/unplugin-icons/0.7.6
|
||||
name: unplugin-icons
|
||||
@ -5553,7 +5553,7 @@ packages:
|
||||
vue-template-es2015-compiler:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@iconify/json': registry.nlark.com/@iconify/json/1.1.399
|
||||
'@iconify/json': registry.nlark.com/@iconify/json/1.1.400
|
||||
'@iconify/json-tools': registry.nlark.com/@iconify/json-tools/1.0.10
|
||||
'@vue/compiler-sfc': registry.nlark.com/@vue/compiler-sfc/3.2.11
|
||||
has-pkg: registry.nlark.com/has-pkg/0.0.1
|
||||
@ -5858,6 +5858,9 @@ packages:
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.0.0-rc.1
|
||||
vue: ^3.0.0-0 || ^2.6.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
dependencies:
|
||||
vue: registry.nlark.com/vue/3.2.10
|
||||
dev: false
|
||||
|
BIN
src/assets/img/common/logo-fill.png
Normal file
BIN
src/assets/img/common/logo-fill.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 705 KiB |
39
src/components/common/LoginBg/components/CornerBottom.vue
Normal file
39
src/components/common/LoginBg/components/CornerBottom.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
height="896"
|
||||
width="967.8852157128662"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
id="path-2"
|
||||
opacity="1"
|
||||
fill-rule="evenodd"
|
||||
d="M896,448 C1142.6325445712241,465.5747656464056 695.2579309733121,896 448,896 C200.74206902668806,896 5.684341886080802e-14,695.2579309733121 0,448.0000000000001 C0,200.74206902668806 200.74206902668791,5.684341886080802e-14 447.99999999999994,0 C695.2579309733121,0 475,418 896,448Z"
|
||||
/>
|
||||
<linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
|
||||
<stop offset="0" :stop-color="startColor" stop-opacity="1" />
|
||||
<stop offset="1" :stop-color="endColor" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1" />
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
startColor: {
|
||||
type: String,
|
||||
default: '#28aff0'
|
||||
},
|
||||
endColor: {
|
||||
type: String,
|
||||
default: '#120fc4'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped></style>
|
33
src/components/common/LoginBg/components/CornerTop.vue
Normal file
33
src/components/common/LoginBg/components/CornerTop.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<svg height="1337" width="1337">
|
||||
<defs>
|
||||
<path
|
||||
id="path-1"
|
||||
opacity="1"
|
||||
fill-rule="evenodd"
|
||||
d="M1337,668.5 C1337,1037.455193874239 1037.455193874239,1337 668.5,1337 C523.6725684305388,1337 337,1236 370.50000000000006,1094 C434.03835568300906,824.6732385973953 6.906089672974592e-14,892.6277623047779 0,668.5000000000001 C0,299.5448061257611 299.5448061257609,1.1368683772161603e-13 668.4999999999999,0 C1037.455193874239,0 1337,299.544806125761 1337,668.5Z"
|
||||
/>
|
||||
<linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
|
||||
<stop offset="0" :stop-color="startColor" stop-opacity="1" />
|
||||
<stop offset="1" :stop-color="endColor" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g opacity="1">
|
||||
<use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
startColor: {
|
||||
type: String,
|
||||
default: '#583ed5'
|
||||
},
|
||||
endColor: {
|
||||
type: String,
|
||||
default: '#17d7fa'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped></style>
|
4
src/components/common/LoginBg/components/index.ts
Normal file
4
src/components/common/LoginBg/components/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import CornerTop from './CornerTop.vue';
|
||||
import CornerBottom from './CornerBottom.vue';
|
||||
|
||||
export { CornerTop, CornerBottom };
|
15
src/components/common/LoginBg/index.vue
Normal file
15
src/components/common/LoginBg/index.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="absolute-lt w-full h-full overflow-hidden">
|
||||
<div class="absolute -right-300px -top-900px">
|
||||
<corner-top />
|
||||
</div>
|
||||
<div class="absolute -left-200px -bottom-400px">
|
||||
<corner-bottom />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CornerTop, CornerBottom } from './components';
|
||||
</script>
|
||||
<style scoped></style>
|
19
src/components/common/SystemLogo/index.vue
Normal file
19
src/components/common/SystemLogo/index.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<img :src="logoSrc" alt="" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import logo from '@/assets/img/common/logo.png';
|
||||
import logoFill from '@/assets/img/common/logo-fill.png';
|
||||
|
||||
const props = defineProps({
|
||||
fill: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const logoSrc = computed(() => (props.fill ? logoFill : logo));
|
||||
</script>
|
||||
<style scoped></style>
|
@ -1,6 +1,7 @@
|
||||
import AppProviderContent from './AppProviderContent/index.vue';
|
||||
import SystemLogo from './SystemLogo/index.vue';
|
||||
import ExceptionSvg from './ExceptionSvg/index.vue';
|
||||
import LoginBg from './LoginBg/index.vue';
|
||||
import BannerSvg from './BannerSvg/index.vue';
|
||||
import CountTo from './CountTo/index.vue';
|
||||
|
||||
export { AppProviderContent, ExceptionSvg, BannerSvg, CountTo };
|
||||
export { AppProviderContent, SystemLogo, ExceptionSvg, LoginBg, BannerSvg };
|
||||
|
3
src/components/custom/index.ts
Normal file
3
src/components/custom/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import CountTo from './CountTo/index.vue';
|
||||
|
||||
export { CountTo };
|
@ -1 +1,2 @@
|
||||
export { AppProviderContent, ExceptionSvg, BannerSvg, CountTo } from './common';
|
||||
export { AppProviderContent, SystemLogo, ExceptionSvg, LoginBg, BannerSvg } from './common';
|
||||
export { CountTo } from './custom';
|
||||
|
@ -19,3 +19,12 @@ export enum EnumDataType {
|
||||
set = '[object Set]',
|
||||
map = '[object Map]'
|
||||
}
|
||||
|
||||
/** 登录模块 */
|
||||
export enum EnumLoginModule {
|
||||
'pwd-login' = '账密登录',
|
||||
'code-login' = '手机验证码登录',
|
||||
'register' = '注册',
|
||||
'reset-pwd' = '重置密码',
|
||||
'bind-wechat' = '微信绑定'
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { ContentType, EnumDataType } from './common';
|
||||
export { ContentType, EnumDataType, EnumLoginModule } from './common';
|
||||
export { EnumAnimate } from './animate';
|
||||
export { EnumNavMode, EnumNavTheme } from './theme';
|
||||
export { EnumRoutePaths } from './route';
|
||||
|
4
src/hooks/business/index.ts
Normal file
4
src/hooks/business/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import useCountDown from './useCountDown';
|
||||
import useSmsCode from './useSmsCode';
|
||||
|
||||
export { useCountDown, useSmsCode };
|
46
src/hooks/business/useCountDown.ts
Normal file
46
src/hooks/business/useCountDown.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
/**
|
||||
* 倒计时
|
||||
* @param second - 倒计时的时间(s)
|
||||
*/
|
||||
export default function useCountDown(second: number) {
|
||||
if (second <= 0 && second % 1 !== 0) {
|
||||
throw Error('倒计时的时间应该为一个正整数!');
|
||||
}
|
||||
const counts = ref(0);
|
||||
const isCounting = computed(() => Boolean(counts.value));
|
||||
|
||||
let intervalId: any;
|
||||
|
||||
/**
|
||||
* 开始计时
|
||||
* @param updateSecond - 更改初时传入的倒计时时间
|
||||
*/
|
||||
function start(updateSecond: number = second) {
|
||||
if (!counts.value) {
|
||||
counts.value = updateSecond;
|
||||
intervalId = setInterval(() => {
|
||||
counts.value -= 1;
|
||||
if (counts.value <= 0) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止计时
|
||||
*/
|
||||
function stop() {
|
||||
intervalId = clearInterval(intervalId);
|
||||
counts.value = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
counts,
|
||||
isCounting,
|
||||
start,
|
||||
stop
|
||||
};
|
||||
}
|
15
src/hooks/business/useSmsCode.ts
Normal file
15
src/hooks/business/useSmsCode.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { computed } from 'vue';
|
||||
import useCountDown from './useCountDown';
|
||||
|
||||
export default function useSmsCode() {
|
||||
const { counts, start, isCounting } = useCountDown(60);
|
||||
const initLabel = '获取验证码';
|
||||
const countingLabel = (second: number) => `${second}秒后重新获取`;
|
||||
const label = computed(() => (isCounting.value ? countingLabel(counts.value) : initLabel));
|
||||
|
||||
return {
|
||||
label,
|
||||
start,
|
||||
isCounting
|
||||
};
|
||||
}
|
5
src/hooks/common/index.ts
Normal file
5
src/hooks/common/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import useAppTitle from './useAppTitle';
|
||||
import useCreateContext from './useCreateContext';
|
||||
import useRouterChange from './useRouterChange';
|
||||
|
||||
export { useAppTitle, useCreateContext, useRouterChange };
|
6
src/hooks/common/useAppTitle.ts
Normal file
6
src/hooks/common/useAppTitle.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/** 项目名称 */
|
||||
export default function useAppTitle() {
|
||||
const title = import.meta.env.VITE_APP_TITLE as string;
|
||||
|
||||
return title;
|
||||
}
|
20
src/hooks/common/useCreateContext.ts
Normal file
20
src/hooks/common/useCreateContext.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { provide, inject } from 'vue';
|
||||
import type { InjectionKey } from 'vue';
|
||||
|
||||
/** 创建共享上下文状态 */
|
||||
export default function useCreateContext<T>(contextName: string = 'context') {
|
||||
const injectKey: InjectionKey<T> = Symbol(contextName);
|
||||
|
||||
function useProvider(shareState: T) {
|
||||
provide(injectKey, shareState);
|
||||
}
|
||||
|
||||
function useContext() {
|
||||
return inject(injectKey);
|
||||
}
|
||||
|
||||
return {
|
||||
useProvider,
|
||||
useContext
|
||||
};
|
||||
}
|
42
src/hooks/common/useRouterChange.ts
Normal file
42
src/hooks/common/useRouterChange.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { useRouter } from 'vue-router';
|
||||
import { EnumRoutePaths } from '@/enum';
|
||||
import { RouteNameMap } from '@/router';
|
||||
import type { LoginModuleType } from '@/interface';
|
||||
|
||||
export default function useRouterChange() {
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* 跳转登录页面(通过vue路由)
|
||||
* @param module - 展示的登录模块
|
||||
* @param redirectUrl - 登录后重定向的页面路径
|
||||
*/
|
||||
function toLogin(module: LoginModuleType = 'pwd-login', redirectUrl?: string) {
|
||||
router.push({
|
||||
name: RouteNameMap.get('login'),
|
||||
params: {
|
||||
module
|
||||
},
|
||||
query: {
|
||||
redirectUrl
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 跳转登录页面(通过window.location)
|
||||
* @param module - 展示的登录模块
|
||||
* @param redirectUrl - 登录后重定向的页面路径
|
||||
*/
|
||||
function toLoginByLocation(module: LoginModuleType = 'pwd-login', redirectUrl?: string) {
|
||||
let href = `${window.location.origin + EnumRoutePaths.login}/${module}`;
|
||||
if (redirectUrl) {
|
||||
href += redirectUrl;
|
||||
}
|
||||
window.location.href = href;
|
||||
}
|
||||
|
||||
return {
|
||||
toLogin,
|
||||
toLoginByLocation
|
||||
};
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export { useAppTitle, useCreateContext, useRouterChange } from './common';
|
||||
export { useCountDown, useSmsCode } from './business';
|
@ -0,0 +1,4 @@
|
||||
import { EnumRoutePaths, EnumLoginModule } from '@/enum';
|
||||
|
||||
export type RoutePathKey = keyof typeof EnumRoutePaths;
|
||||
export type LoginModuleType = keyof typeof EnumLoginModule;
|
@ -1,2 +1,3 @@
|
||||
export { UserInfo } from './business';
|
||||
export { ThemeSettings, NavMode, AnimateType } from './theme';
|
||||
export { RoutePathKey, LoginModuleType } from './common';
|
||||
|
@ -8,10 +8,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useAppStore, useThemeStore } from '@/store';
|
||||
import { useAppTitle } from '@/hooks';
|
||||
|
||||
const app = useAppStore();
|
||||
const theme = useThemeStore();
|
||||
const showTitle = computed(() => !app.menu.collapsed && theme.navStyle.mode !== 'vertical-mix');
|
||||
const title = import.meta.env.VITE_APP_TITLE as string;
|
||||
const title = useAppTitle();
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="text-center text-18px text-error">菜单</h3>
|
||||
<div class="flex-center h-48px">
|
||||
<router-link to="/login" class="text-primary text-18px">登录页</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import BasicLayout from './BasicLayout/index.vue';
|
||||
import BlankLayout from './BlankLayout/index.vue';
|
||||
import BlankChildLayout from './BlankChildLayout/index.vue';
|
||||
|
||||
export { BasicLayout, BlankLayout, BlankChildLayout };
|
||||
export { BasicLayout, BlankLayout };
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
|
||||
import type { App } from 'vue';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { constantRoutes, customRoutes } from './routes';
|
||||
import { constantRoutes, customRoutes, RouteNameMap } from './routes';
|
||||
import createRouterGuide from './permission';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [...customRoutes, ...constantRoutes];
|
||||
@ -19,3 +19,5 @@ export async function setupRouter(app: App) {
|
||||
createRouterGuide(router);
|
||||
await router.isReady();
|
||||
}
|
||||
|
||||
export { RouteNameMap };
|
||||
|
@ -1,11 +1,15 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { BasicLayout, BlankLayout, BlankChildLayout } from '@/layouts';
|
||||
import { BasicLayout, BlankLayout } from '@/layouts';
|
||||
import { EnumRoutePaths } from '@/enum';
|
||||
|
||||
type RouteKey = keyof typeof EnumRoutePaths;
|
||||
import type { RoutePathKey, LoginModuleType } from '@/interface';
|
||||
import { getLoginModuleRegExp } from '@/utils';
|
||||
|
||||
/** 路由名称 */
|
||||
export const RouteNameMap = new Map<RouteKey, RouteKey>((Object.keys(EnumRoutePaths) as RouteKey[]).map(v => [v, v]));
|
||||
export const RouteNameMap = new Map<RoutePathKey, RoutePathKey>(
|
||||
(Object.keys(EnumRoutePaths) as RoutePathKey[]).map(v => [v, v])
|
||||
);
|
||||
|
||||
const loginModuleRegExp = getLoginModuleRegExp();
|
||||
|
||||
/**
|
||||
* 固定不变的路由
|
||||
@ -21,8 +25,14 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
// 登录
|
||||
{
|
||||
name: RouteNameMap.get('login'),
|
||||
path: EnumRoutePaths.login,
|
||||
path: `${EnumRoutePaths.login}/:module(/${loginModuleRegExp}/)?`,
|
||||
component: () => import('@/views/system/login/index.vue'),
|
||||
props: route => {
|
||||
const moduleType: LoginModuleType = (route.params.module as LoginModuleType) || 'pwd-login';
|
||||
return {
|
||||
module: moduleType
|
||||
};
|
||||
},
|
||||
meta: {
|
||||
fullPage: true
|
||||
}
|
||||
@ -70,13 +80,13 @@ export const customRoutes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
name: 'root',
|
||||
path: '/',
|
||||
redirect: { name: RouteNameMap.get('dashboard-analysis') },
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
redirect: { name: RouteNameMap.get('dashboard-analysis') }
|
||||
},
|
||||
{
|
||||
name: 'dashboard',
|
||||
path: '/dashboard',
|
||||
component: BlankChildLayout,
|
||||
component: BasicLayout,
|
||||
redirect: { name: RouteNameMap.get('dashboard-analysis') },
|
||||
children: [
|
||||
{
|
||||
name: RouteNameMap.get('dashboard-analysis'),
|
||||
@ -93,7 +103,7 @@ export const customRoutes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
name: 'exception',
|
||||
path: '/exception',
|
||||
component: BlankChildLayout,
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
{
|
||||
name: RouteNameMap.get('exception-403'),
|
||||
@ -112,6 +122,4 @@ export const customRoutes: Array<RouteRecordRaw> = [
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
@ -16,6 +16,3 @@ html {
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
svg {
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -1,5 +1,13 @@
|
||||
export function getStorageToken() {
|
||||
import type { LoginModuleType } from '@/interface';
|
||||
|
||||
export function getToken() {
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getStorageUserInfo() {}
|
||||
export function getUserInfo() {}
|
||||
|
||||
/** 获取登录模块的正则字符串 */
|
||||
export function getLoginModuleRegExp() {
|
||||
const arr: LoginModuleType[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
|
||||
return arr.join('|');
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { getStorageToken, getStorageUserInfo } from './auth';
|
||||
export { getToken, getUserInfo, getLoginModuleRegExp } from './auth';
|
||||
export {
|
||||
isNumber,
|
||||
isString,
|
||||
|
@ -2,12 +2,14 @@
|
||||
<div class="p-10px">
|
||||
<data-card :loading="loading" />
|
||||
<nav-card :loading="loading" />
|
||||
<router-link :to="EnumRoutePaths['dashboard-workbench']">workbench</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { DataCard, NavCard } from './components';
|
||||
import { EnumRoutePaths } from '@/enum';
|
||||
|
||||
const loading = ref(true);
|
||||
|
||||
|
@ -1,6 +1,21 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<div>
|
||||
<h2>工作台</h2>
|
||||
<router-link :to="EnumRoutePaths['dashboard-analysis']">analysis</router-link>
|
||||
<n-button @click="removeCurrent">去除</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { NButton } from 'naive-ui';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { EnumRoutePaths } from '@/enum';
|
||||
import { RouteNameMap } from '@/router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function removeCurrent() {
|
||||
router.removeRoute(RouteNameMap.get('dashboard-workbench')!);
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@ -1,65 +0,0 @@
|
||||
<template>
|
||||
<div class="min-h-500px">
|
||||
<n-space>
|
||||
<n-button v-for="item in actions" :key="item.key" type="primary" @click="handleClick(item.key)">
|
||||
{{ item.label }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-space>
|
||||
<n-button>Default</n-button>
|
||||
<n-button type="primary">Primary</n-button>
|
||||
<n-button type="info">Info</n-button>
|
||||
<n-button type="success">Success</n-button>
|
||||
<n-button type="warning">Warning</n-button>
|
||||
<n-button type="error">Error</n-button>
|
||||
</n-space>
|
||||
<router-link class="text-primary" to="/system">system</router-link>
|
||||
<div>
|
||||
<span class="text-primary">primary</span>
|
||||
<span class="text-info">info</span>
|
||||
<span class="text-success">success</span>
|
||||
<span class="text-warning">warning</span>
|
||||
<span class="text-error">error</span>
|
||||
</div>
|
||||
<p v-for="i in 100" :key="i">{{ i }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NButton, NSpace, useDialog, useNotification, useMessage } from 'naive-ui';
|
||||
|
||||
type ActionType = 'dialog' | 'notification' | 'message';
|
||||
interface Action {
|
||||
key: ActionType;
|
||||
label: string;
|
||||
}
|
||||
|
||||
defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const dialog = useDialog();
|
||||
const notification = useNotification();
|
||||
const message = useMessage();
|
||||
|
||||
const actions: Action[] = [
|
||||
{ key: 'dialog', label: 'dialog' },
|
||||
{ key: 'notification', label: 'notification' },
|
||||
{ key: 'message', label: 'message' }
|
||||
];
|
||||
function handleClick(type: ActionType) {
|
||||
if (type === 'dialog') {
|
||||
dialog.info({ content: '弹窗示例!' });
|
||||
}
|
||||
if (type === 'notification') {
|
||||
notification.info({ content: '通知示例!' });
|
||||
}
|
||||
if (type === 'message') {
|
||||
message.info('消息示例!');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<router-view />
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
67
src/views/system/login/components/CodeLogin/index.vue
Normal file
67
src/views/system/login/components/CodeLogin/index.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="pt-24px">
|
||||
<n-form ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||
<n-form-item path="phone">
|
||||
<n-input v-model:value="model.phone" placeholder="手机号码" />
|
||||
</n-form-item>
|
||||
<n-form-item path="code">
|
||||
<div class="flex-y-center w-full">
|
||||
<n-input v-model:value="model.code" placeholder="验证码" />
|
||||
<div class="w-18px"></div>
|
||||
<n-button size="large" :disabled="isCounting" @click="handleSmsCode">{{ label }}</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-space :vertical="true" size="large">
|
||||
<n-button type="primary" size="large" :block="true" :round="true" @click="handleSubmit">确定</n-button>
|
||||
<n-button size="large" :block="true" :round="true" @click="toLogin('pwd-login')">返回</n-button>
|
||||
</n-space>
|
||||
</n-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { NForm, NFormItem, NInput, NSpace, NButton, useMessage } from 'naive-ui';
|
||||
import type { FormInst } from 'naive-ui';
|
||||
import { useRouterChange, useSmsCode } from '@/hooks';
|
||||
|
||||
const message = useMessage();
|
||||
const { toLogin } = useRouterChange();
|
||||
const { label, isCounting, start } = useSmsCode();
|
||||
|
||||
const formRef = ref<(HTMLElement & FormInst) | null>(null);
|
||||
const model = reactive({
|
||||
phone: '',
|
||||
code: ''
|
||||
});
|
||||
const rules = {
|
||||
phone: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入手机号'
|
||||
},
|
||||
code: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入验证码'
|
||||
}
|
||||
};
|
||||
|
||||
function handleSmsCode() {
|
||||
start();
|
||||
}
|
||||
|
||||
function handleSubmit(e: MouseEvent) {
|
||||
if (!formRef.value) return;
|
||||
e.preventDefault();
|
||||
|
||||
formRef.value.validate(errors => {
|
||||
if (!errors) {
|
||||
message.success('验证成功');
|
||||
} else {
|
||||
message.error('验证失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
72
src/views/system/login/components/PwdLogin/index.vue
Normal file
72
src/views/system/login/components/PwdLogin/index.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="pt-24px">
|
||||
<n-form ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||
<n-form-item path="phone">
|
||||
<n-input v-model:value="model.phone" placeholder="手机号码" />
|
||||
</n-form-item>
|
||||
<n-form-item path="pwd">
|
||||
<n-input v-model:value="model.pwd" placeholder="密码" />
|
||||
</n-form-item>
|
||||
<n-space :vertical="true" size="large">
|
||||
<div class="flex-y-center justify-between">
|
||||
<n-checkbox v-model:checked="rememberMe">记住我</n-checkbox>
|
||||
<span class="text-primary cursor-pointer" @click="toLogin('reset-pwd')">忘记密码?</span>
|
||||
</div>
|
||||
<n-button type="primary" size="large" :block="true" :round="true" @click="handleSubmit">确定</n-button>
|
||||
<div class="flex-y-center justify-between">
|
||||
<n-button class="flex-1" :block="true" @click="toLogin('code-login')">
|
||||
{{ EnumLoginModule['code-login'] }}
|
||||
</n-button>
|
||||
<div class="w-12px"></div>
|
||||
<n-button class="flex-1" :block="true" @click="toLogin('register')">{{ EnumLoginModule.register }}</n-button>
|
||||
</div>
|
||||
</n-space>
|
||||
</n-form>
|
||||
<other-login />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { NForm, NFormItem, NInput, NSpace, NCheckbox, NButton, useMessage } from 'naive-ui';
|
||||
import type { FormInst } from 'naive-ui';
|
||||
import { EnumLoginModule } from '@/enum';
|
||||
import { useRouterChange } from '@/hooks';
|
||||
import { OtherLogin } from '../common';
|
||||
|
||||
const { toLogin } = useRouterChange();
|
||||
const message = useMessage();
|
||||
|
||||
const formRef = ref<(HTMLElement & FormInst) | null>(null);
|
||||
const model = reactive({
|
||||
phone: '',
|
||||
pwd: ''
|
||||
});
|
||||
const rules = {
|
||||
phone: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入手机号'
|
||||
},
|
||||
pwd: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入密码'
|
||||
}
|
||||
};
|
||||
const rememberMe = ref(false);
|
||||
|
||||
function handleSubmit(e: MouseEvent) {
|
||||
if (!formRef.value) return;
|
||||
e.preventDefault();
|
||||
|
||||
formRef.value.validate(errors => {
|
||||
if (!errors) {
|
||||
message.success('验证成功');
|
||||
} else {
|
||||
message.error('验证失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
87
src/views/system/login/components/Register/index.vue
Normal file
87
src/views/system/login/components/Register/index.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="pt-24px">
|
||||
<n-form ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||
<n-form-item path="phone">
|
||||
<n-input v-model:value="model.phone" placeholder="手机号码" />
|
||||
</n-form-item>
|
||||
<n-form-item path="code">
|
||||
<div class="flex-y-center w-full">
|
||||
<n-input v-model:value="model.code" placeholder="验证码" />
|
||||
<div class="w-18px"></div>
|
||||
<n-button size="large" :disabled="isCounting" @click="handleSmsCode">{{ label }}</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item path="pwd">
|
||||
<n-input v-model:value="model.pwd" placeholder="密码" />
|
||||
</n-form-item>
|
||||
<n-form-item path="confirmPwd">
|
||||
<n-input v-model:value="model.confirmPwd" placeholder="确认密码" />
|
||||
</n-form-item>
|
||||
<n-space :vertical="true" size="large">
|
||||
<n-checkbox v-model:checked="agreement">我已经仔细阅读并接受用户协议和隐私政策</n-checkbox>
|
||||
<n-button type="primary" size="large" :block="true" :round="true" @click="handleSubmit">确定</n-button>
|
||||
<n-button size="large" :block="true" :round="true" @click="toLogin('pwd-login')">返回</n-button>
|
||||
</n-space>
|
||||
</n-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { NForm, NFormItem, NInput, NSpace, NCheckbox, NButton, useMessage } from 'naive-ui';
|
||||
import type { FormInst } from 'naive-ui';
|
||||
import { useRouterChange, useSmsCode } from '@/hooks';
|
||||
|
||||
const message = useMessage();
|
||||
const { toLogin } = useRouterChange();
|
||||
const { label, isCounting, start } = useSmsCode();
|
||||
|
||||
const formRef = ref<(HTMLElement & FormInst) | null>(null);
|
||||
const model = reactive({
|
||||
phone: '',
|
||||
code: '',
|
||||
pwd: '',
|
||||
confirmPwd: ''
|
||||
});
|
||||
const rules = {
|
||||
phone: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入手机号'
|
||||
},
|
||||
code: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入验证码'
|
||||
},
|
||||
pwd: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入密码'
|
||||
},
|
||||
confirmPwd: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入确认密码'
|
||||
}
|
||||
};
|
||||
const agreement = ref(false);
|
||||
|
||||
function handleSmsCode() {
|
||||
start();
|
||||
}
|
||||
|
||||
function handleSubmit(e: MouseEvent) {
|
||||
if (!formRef.value) return;
|
||||
e.preventDefault();
|
||||
|
||||
formRef.value.validate(errors => {
|
||||
if (!errors) {
|
||||
message.success('验证成功');
|
||||
} else {
|
||||
message.error('验证失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
85
src/views/system/login/components/ResetPwd/index.vue
Normal file
85
src/views/system/login/components/ResetPwd/index.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="pt-24px">
|
||||
<n-form ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
||||
<n-form-item path="phone">
|
||||
<n-input v-model:value="model.phone" placeholder="手机号码" />
|
||||
</n-form-item>
|
||||
<n-form-item path="code">
|
||||
<div class="flex-y-center w-full">
|
||||
<n-input v-model:value="model.code" placeholder="验证码" />
|
||||
<div class="w-18px"></div>
|
||||
<n-button size="large" :disabled="isCounting" @click="handleSmsCode">{{ label }}</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item path="pwd">
|
||||
<n-input v-model:value="model.pwd" placeholder="密码" />
|
||||
</n-form-item>
|
||||
<n-form-item path="confirmPwd">
|
||||
<n-input v-model:value="model.confirmPwd" placeholder="确认密码" />
|
||||
</n-form-item>
|
||||
<n-space :vertical="true" size="large">
|
||||
<n-button type="primary" size="large" :block="true" :round="true" @click="handleSubmit">确定</n-button>
|
||||
<n-button size="large" :block="true" :round="true" @click="toLogin('pwd-login')">返回</n-button>
|
||||
</n-space>
|
||||
</n-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { NForm, NFormItem, NInput, NSpace, NButton, useMessage } from 'naive-ui';
|
||||
import type { FormInst } from 'naive-ui';
|
||||
import { useRouterChange, useSmsCode } from '@/hooks';
|
||||
|
||||
const message = useMessage();
|
||||
const { toLogin } = useRouterChange();
|
||||
const { label, isCounting, start } = useSmsCode();
|
||||
|
||||
const formRef = ref<(HTMLElement & FormInst) | null>(null);
|
||||
const model = reactive({
|
||||
phone: '',
|
||||
code: '',
|
||||
pwd: '',
|
||||
confirmPwd: ''
|
||||
});
|
||||
const rules = {
|
||||
phone: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入手机号'
|
||||
},
|
||||
code: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入验证码'
|
||||
},
|
||||
pwd: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入密码'
|
||||
},
|
||||
confirmPwd: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入确认密码'
|
||||
}
|
||||
};
|
||||
|
||||
function handleSmsCode() {
|
||||
start();
|
||||
}
|
||||
|
||||
function handleSubmit(e: MouseEvent) {
|
||||
if (!formRef.value) return;
|
||||
e.preventDefault();
|
||||
|
||||
formRef.value.validate(errors => {
|
||||
if (!errors) {
|
||||
message.success('验证成功');
|
||||
} else {
|
||||
message.error('验证失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<n-space :vertical="true">
|
||||
<n-divider class="!mb-0 text-14px text-[#666]">其他登录方式</n-divider>
|
||||
<div class="flex-center">
|
||||
<icon-mdi-wechat class="text-22px text-[#888] hover:text-[#52BF5E] cursor-pointer" />
|
||||
</div>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NSpace, NDivider } from 'naive-ui';
|
||||
</script>
|
||||
<style scoped></style>
|
3
src/views/system/login/components/common/index.ts
Normal file
3
src/views/system/login/components/common/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import OtherLogin from './OtherLogin/index.vue';
|
||||
|
||||
export { OtherLogin };
|
7
src/views/system/login/components/index.ts
Normal file
7
src/views/system/login/components/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import PwdLogin from './PwdLogin/index.vue';
|
||||
import CodeLogin from './CodeLogin/index.vue';
|
||||
import Register from './Register/index.vue';
|
||||
import ResetPwd from './ResetPwd/index.vue';
|
||||
import BindWechat from './BindWechat/index.vue';
|
||||
|
||||
export { PwdLogin, CodeLogin, Register, ResetPwd, BindWechat };
|
17
src/views/system/login/composable.ts
Normal file
17
src/views/system/login/composable.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { useCreateContext } from '@/hooks';
|
||||
import { LoginModuleType } from '@/interface';
|
||||
|
||||
interface ShareState {
|
||||
activeModule: Ref<LoginModuleType>;
|
||||
handleLoginModule(module: LoginModuleType): void;
|
||||
}
|
||||
|
||||
const { useContext, useProvider } = useCreateContext<ShareState>();
|
||||
|
||||
export function useLoginContext() {
|
||||
return {
|
||||
useContext,
|
||||
useProvider
|
||||
};
|
||||
}
|
@ -1,18 +1,53 @@
|
||||
<template>
|
||||
<div class="flex w-full h-full">
|
||||
<div class="flex-center w-1/2 h-full">
|
||||
<h3>登录</h3>
|
||||
<div class="relative flex-center w-full h-full bg-[#DBE0F9]">
|
||||
<login-bg />
|
||||
<div class="w-400px p-40px bg-white rounded-20px z-10">
|
||||
<header class="flex-y-center justify-between">
|
||||
<div class="w-70px h-70px rounded-35px overflow-hidden">
|
||||
<system-logo class="w-full h-full" :fill="true" />
|
||||
</div>
|
||||
<div class="flex-center w-1/2 h-full bg-primary bg-opacity-25">
|
||||
<banner-svg class="w-400px h-400px" type="1" :color="theme.themeColor" />
|
||||
<n-gradient-text type="primary" :size="28">{{ title }}</n-gradient-text>
|
||||
</header>
|
||||
<main class="pt-24px">
|
||||
<div v-for="item in modules" v-show="module === item.key" :key="item.key">
|
||||
<h3 class="text-18px text-primary font-medium">{{ item.label }}</h3>
|
||||
<component :is="item.component" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BannerSvg } from '@/components';
|
||||
import { useThemeStore } from '@/store';
|
||||
import type { Component, PropType } from 'vue';
|
||||
import { NGradientText } from 'naive-ui';
|
||||
import { SystemLogo, LoginBg } from '@/components';
|
||||
import { useAppTitle } from '@/hooks';
|
||||
import { EnumLoginModule } from '@/enum';
|
||||
import type { LoginModuleType } from '@/interface';
|
||||
import { PwdLogin, CodeLogin, Register, ResetPwd, BindWechat } from './components';
|
||||
|
||||
const theme = useThemeStore();
|
||||
interface LoginModule {
|
||||
key: LoginModuleType;
|
||||
label: string;
|
||||
component: Component;
|
||||
}
|
||||
|
||||
defineProps({
|
||||
module: {
|
||||
type: String as PropType<LoginModuleType>,
|
||||
default: 'pwd-login'
|
||||
}
|
||||
});
|
||||
|
||||
const title = useAppTitle();
|
||||
|
||||
const modules: LoginModule[] = [
|
||||
{ key: 'pwd-login', label: EnumLoginModule['pwd-login'], component: PwdLogin },
|
||||
{ key: 'code-login', label: EnumLoginModule['code-login'], component: CodeLogin },
|
||||
{ key: 'register', label: EnumLoginModule.register, component: Register },
|
||||
{ key: 'reset-pwd', label: EnumLoginModule['reset-pwd'], component: ResetPwd },
|
||||
{ key: 'bind-wechat', label: EnumLoginModule['bind-wechat'], component: BindWechat }
|
||||
];
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
Loading…
Reference in New Issue
Block a user