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的代码格式进行校验
|
eslintIntegration: false, //不让prettier使用eslint的代码格式进行校验
|
||||||
htmlWhitespaceSensitivity: 'ignore', // 指定HTML文件的全局空白区域敏感度 有效选项:"css"- 遵守CSS display属性的默认值。"strict" - 空格被认为是敏感的。"ignore" - 空格被认为是不敏感的。html 中空格也会占位,影响布局,prettier 格式化的时候可能会将文本换行,造成布局错乱
|
htmlWhitespaceSensitivity: 'ignore', // 指定HTML文件的全局空白区域敏感度 有效选项:"css"- 遵守CSS display属性的默认值。"strict" - 空格被认为是敏感的。"ignore" - 空格被认为是不敏感的。html 中空格也会占位,影响布局,prettier 格式化的时候可能会将文本换行,造成布局错乱
|
||||||
ignorePath: '.prettierignore', // 不使用prettier格式化的文件填写在项目的.prettierignore文件中
|
ignorePath: '.prettierignore', // 不使用prettier格式化的文件填写在项目的.prettierignore文件中
|
||||||
jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
|
|
||||||
jsxSingleQuote: false, // 在jsx中使用单引号代替双引号
|
jsxSingleQuote: false, // 在jsx中使用单引号代替双引号
|
||||||
// parser: 'babylon', // 格式化的解析器,默认是babylon
|
// parser: 'babylon', // 格式化的解析器,默认是babylon
|
||||||
requireConfig: false, // Require a 'prettierconfig' to format prettier
|
requireConfig: false, // Require a 'prettierconfig' to format prettier
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"@vueuse/core": "^6.3.2",
|
"@vueuse/core": "^6.3.2",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"chroma-js": "^2.1.2",
|
"chroma-js": "^2.1.2",
|
||||||
"dayjs": "^1.10.6",
|
"dayjs": "^1.10.7",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"naive-ui": "^2.18.1",
|
"naive-ui": "^2.18.1",
|
||||||
"pinia": "^2.0.0-rc.4",
|
"pinia": "^2.0.0-rc.4",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^13.1.0",
|
"@commitlint/cli": "^13.1.0",
|
||||||
"@commitlint/config-conventional": "^13.1.0",
|
"@commitlint/config-conventional": "^13.1.0",
|
||||||
"@iconify/json": "^1.1.399",
|
"@iconify/json": "^1.1.400",
|
||||||
"@iconify/vue": "^3.0.0",
|
"@iconify/vue": "^3.0.0",
|
||||||
"@types/chroma-js": "^2.1.3",
|
"@types/chroma-js": "^2.1.3",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"patch-package": "^6.4.7",
|
"patch-package": "^6.4.7",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.4.0",
|
"prettier": "^2.4.0",
|
||||||
"sass": "^1.39.0",
|
"sass": "^1.39.2",
|
||||||
"typescript": "^4.4.2",
|
"typescript": "^4.4.2",
|
||||||
"unplugin-icons": "^0.7.6",
|
"unplugin-icons": "^0.7.6",
|
||||||
"unplugin-vue-components": "^0.15.0",
|
"unplugin-vue-components": "^0.15.0",
|
||||||
|
@ -3,7 +3,7 @@ lockfileVersion: 5.3
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@commitlint/cli': ^13.1.0
|
'@commitlint/cli': ^13.1.0
|
||||||
'@commitlint/config-conventional': ^13.1.0
|
'@commitlint/config-conventional': ^13.1.0
|
||||||
'@iconify/json': ^1.1.399
|
'@iconify/json': ^1.1.400
|
||||||
'@iconify/vue': ^3.0.0
|
'@iconify/vue': ^3.0.0
|
||||||
'@types/chroma-js': ^2.1.3
|
'@types/chroma-js': ^2.1.3
|
||||||
'@types/nprogress': ^0.2.0
|
'@types/nprogress': ^0.2.0
|
||||||
@ -21,7 +21,7 @@ specifiers:
|
|||||||
commitizen: ^4.2.4
|
commitizen: ^4.2.4
|
||||||
cz-conventional-changelog: ^3.3.0
|
cz-conventional-changelog: ^3.3.0
|
||||||
cz-customizable: ^6.3.0
|
cz-customizable: ^6.3.0
|
||||||
dayjs: ^1.10.6
|
dayjs: ^1.10.7
|
||||||
dotenv: ^10.0.0
|
dotenv: ^10.0.0
|
||||||
eslint: ^7.32.0
|
eslint: ^7.32.0
|
||||||
eslint-config-airbnb-base: ^14.2.1
|
eslint-config-airbnb-base: ^14.2.1
|
||||||
@ -38,7 +38,7 @@ specifiers:
|
|||||||
postinstall-postinstall: ^2.1.0
|
postinstall-postinstall: ^2.1.0
|
||||||
prettier: ^2.4.0
|
prettier: ^2.4.0
|
||||||
qs: ^6.10.1
|
qs: ^6.10.1
|
||||||
sass: ^1.39.0
|
sass: ^1.39.2
|
||||||
smoothscroll-polyfill: ^0.4.4
|
smoothscroll-polyfill: ^0.4.4
|
||||||
typescript: ^4.4.2
|
typescript: ^4.4.2
|
||||||
unplugin-icons: ^0.7.6
|
unplugin-icons: ^0.7.6
|
||||||
@ -55,7 +55,7 @@ dependencies:
|
|||||||
'@vueuse/core': registry.nlark.com/@vueuse/core/6.3.2_vue@3.2.10
|
'@vueuse/core': registry.nlark.com/@vueuse/core/6.3.2_vue@3.2.10
|
||||||
axios: registry.nlark.com/axios/0.21.4
|
axios: registry.nlark.com/axios/0.21.4
|
||||||
chroma-js: registry.nlark.com/chroma-js/2.1.2
|
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
|
form-data: 4.0.0
|
||||||
naive-ui: registry.nlark.com/naive-ui/2.18.1_vue@3.2.10
|
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
|
pinia: registry.nlark.com/pinia/2.0.0-rc.4_typescript@4.4.2+vue@3.2.10
|
||||||
@ -67,7 +67,7 @@ dependencies:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@commitlint/cli': registry.nlark.com/@commitlint/cli/13.1.0
|
'@commitlint/cli': registry.nlark.com/@commitlint/cli/13.1.0
|
||||||
'@commitlint/config-conventional': registry.nlark.com/@commitlint/config-conventional/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
|
'@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/chroma-js': registry.nlark.com/@types/chroma-js/2.1.3
|
||||||
'@types/nprogress': registry.nlark.com/@types/nprogress/0.2.0
|
'@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
|
patch-package: registry.nlark.com/patch-package/6.4.7
|
||||||
postinstall-postinstall: registry.nlark.com/postinstall-postinstall/2.1.0
|
postinstall-postinstall: registry.nlark.com/postinstall-postinstall/2.1.0
|
||||||
prettier: registry.nlark.com/prettier/2.4.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
|
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
|
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: registry.nlark.com/vite/2.5.6
|
||||||
vite-plugin-html: registry.nlark.com/vite-plugin-html/2.1.0_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
|
version: 1.0.10
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
registry.nlark.com/@iconify/json/1.1.399:
|
registry.nlark.com/@iconify/json/1.1.400:
|
||||||
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}
|
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'
|
name: '@iconify/json'
|
||||||
version: 1.1.399
|
version: 1.1.400
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
registry.nlark.com/@iconify/vue/3.0.0_vue@3.2.10:
|
registry.nlark.com/@iconify/vue/3.0.0_vue@3.2.10:
|
||||||
@ -3461,10 +3461,10 @@ packages:
|
|||||||
engines: {node: '>=0.11'}
|
engines: {node: '>=0.11'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
registry.nlark.com/dayjs/1.10.6:
|
registry.nlark.com/dayjs/1.10.7:
|
||||||
resolution: {integrity: sha1-KIsqqC8thBimydTfWJjAc3rQKmM=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/dayjs/download/dayjs-1.10.6.tgz}
|
resolution: {integrity: sha1-LPX5Gt0oEWdIRAhmoKHSbzps5Gg=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/dayjs/download/dayjs-1.10.7.tgz}
|
||||||
name: dayjs
|
name: dayjs
|
||||||
version: 1.10.6
|
version: 1.10.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
registry.nlark.com/debug/2.6.9:
|
registry.nlark.com/debug/2.6.9:
|
||||||
@ -5176,10 +5176,10 @@ packages:
|
|||||||
tslib: registry.nlark.com/tslib/1.14.1
|
tslib: registry.nlark.com/tslib/1.14.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
registry.nlark.com/sass/1.39.0:
|
registry.nlark.com/sass/1.39.2:
|
||||||
resolution: {integrity: sha1-bGRpXRxDd2fI8aTkcSiOgx+B0DU=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/sass/download/sass-1.39.0.tgz}
|
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
|
name: sass
|
||||||
version: 1.39.0
|
version: 1.39.2
|
||||||
engines: {node: '>=8.9.0'}
|
engines: {node: '>=8.9.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5532,7 +5532,7 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: 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}
|
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
|
id: registry.nlark.com/unplugin-icons/0.7.6
|
||||||
name: unplugin-icons
|
name: unplugin-icons
|
||||||
@ -5553,7 +5553,7 @@ packages:
|
|||||||
vue-template-es2015-compiler:
|
vue-template-es2015-compiler:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
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
|
'@iconify/json-tools': registry.nlark.com/@iconify/json-tools/1.0.10
|
||||||
'@vue/compiler-sfc': registry.nlark.com/@vue/compiler-sfc/3.2.11
|
'@vue/compiler-sfc': registry.nlark.com/@vue/compiler-sfc/3.2.11
|
||||||
has-pkg: registry.nlark.com/has-pkg/0.0.1
|
has-pkg: registry.nlark.com/has-pkg/0.0.1
|
||||||
@ -5858,6 +5858,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@vue/composition-api': ^1.0.0-rc.1
|
'@vue/composition-api': ^1.0.0-rc.1
|
||||||
vue: ^3.0.0-0 || ^2.6.0
|
vue: ^3.0.0-0 || ^2.6.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vue/composition-api':
|
||||||
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: registry.nlark.com/vue/3.2.10
|
vue: registry.nlark.com/vue/3.2.10
|
||||||
dev: false
|
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 AppProviderContent from './AppProviderContent/index.vue';
|
||||||
|
import SystemLogo from './SystemLogo/index.vue';
|
||||||
import ExceptionSvg from './ExceptionSvg/index.vue';
|
import ExceptionSvg from './ExceptionSvg/index.vue';
|
||||||
|
import LoginBg from './LoginBg/index.vue';
|
||||||
import BannerSvg from './BannerSvg/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]',
|
set = '[object Set]',
|
||||||
map = '[object Map]'
|
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 { EnumAnimate } from './animate';
|
||||||
export { EnumNavMode, EnumNavTheme } from './theme';
|
export { EnumNavMode, EnumNavTheme } from './theme';
|
||||||
export { EnumRoutePaths } from './route';
|
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 { UserInfo } from './business';
|
||||||
export { ThemeSettings, NavMode, AnimateType } from './theme';
|
export { ThemeSettings, NavMode, AnimateType } from './theme';
|
||||||
|
export { RoutePathKey, LoginModuleType } from './common';
|
||||||
|
@ -8,10 +8,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useAppStore, useThemeStore } from '@/store';
|
import { useAppStore, useThemeStore } from '@/store';
|
||||||
|
import { useAppTitle } from '@/hooks';
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
const showTitle = computed(() => !app.menu.collapsed && theme.navStyle.mode !== 'vertical-mix');
|
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>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-center text-18px text-error">菜单</h3>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import BasicLayout from './BasicLayout/index.vue';
|
import BasicLayout from './BasicLayout/index.vue';
|
||||||
import BlankLayout from './BlankLayout/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 { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
import { constantRoutes, customRoutes } from './routes';
|
import { constantRoutes, customRoutes, RouteNameMap } from './routes';
|
||||||
import createRouterGuide from './permission';
|
import createRouterGuide from './permission';
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [...customRoutes, ...constantRoutes];
|
const routes: Array<RouteRecordRaw> = [...customRoutes, ...constantRoutes];
|
||||||
@ -19,3 +19,5 @@ export async function setupRouter(app: App) {
|
|||||||
createRouterGuide(router);
|
createRouterGuide(router);
|
||||||
await router.isReady();
|
await router.isReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { RouteNameMap };
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
import { BasicLayout, BlankLayout, BlankChildLayout } from '@/layouts';
|
import { BasicLayout, BlankLayout } from '@/layouts';
|
||||||
import { EnumRoutePaths } from '@/enum';
|
import { EnumRoutePaths } from '@/enum';
|
||||||
|
import type { RoutePathKey, LoginModuleType } from '@/interface';
|
||||||
type RouteKey = keyof typeof EnumRoutePaths;
|
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'),
|
name: RouteNameMap.get('login'),
|
||||||
path: EnumRoutePaths.login,
|
path: `${EnumRoutePaths.login}/:module(/${loginModuleRegExp}/)?`,
|
||||||
component: () => import('@/views/system/login/index.vue'),
|
component: () => import('@/views/system/login/index.vue'),
|
||||||
|
props: route => {
|
||||||
|
const moduleType: LoginModuleType = (route.params.module as LoginModuleType) || 'pwd-login';
|
||||||
|
return {
|
||||||
|
module: moduleType
|
||||||
|
};
|
||||||
|
},
|
||||||
meta: {
|
meta: {
|
||||||
fullPage: true
|
fullPage: true
|
||||||
}
|
}
|
||||||
@ -70,13 +80,13 @@ export const customRoutes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
name: 'root',
|
name: 'root',
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: { name: RouteNameMap.get('dashboard-analysis') },
|
redirect: { name: RouteNameMap.get('dashboard-analysis') }
|
||||||
component: BasicLayout,
|
},
|
||||||
children: [
|
|
||||||
{
|
{
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
component: BlankChildLayout,
|
component: BasicLayout,
|
||||||
|
redirect: { name: RouteNameMap.get('dashboard-analysis') },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: RouteNameMap.get('dashboard-analysis'),
|
name: RouteNameMap.get('dashboard-analysis'),
|
||||||
@ -93,7 +103,7 @@ export const customRoutes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
name: 'exception',
|
name: 'exception',
|
||||||
path: '/exception',
|
path: '/exception',
|
||||||
component: BlankChildLayout,
|
component: BasicLayout,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: RouteNameMap.get('exception-403'),
|
name: RouteNameMap.get('exception-403'),
|
||||||
@ -112,6 +122,4 @@ export const customRoutes: Array<RouteRecordRaw> = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
@ -16,6 +16,3 @@ html {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgba(0, 0, 0, 0.65);
|
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 '';
|
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 {
|
export {
|
||||||
isNumber,
|
isNumber,
|
||||||
isString,
|
isString,
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
<div class="p-10px">
|
<div class="p-10px">
|
||||||
<data-card :loading="loading" />
|
<data-card :loading="loading" />
|
||||||
<nav-card :loading="loading" />
|
<nav-card :loading="loading" />
|
||||||
|
<router-link :to="EnumRoutePaths['dashboard-workbench']">workbench</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { DataCard, NavCard } from './components';
|
import { DataCard, NavCard } from './components';
|
||||||
|
import { EnumRoutePaths } from '@/enum';
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
|
@ -1,6 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div></div>
|
<div>
|
||||||
|
<h2>工作台</h2>
|
||||||
|
<router-link :to="EnumRoutePaths['dashboard-analysis']">analysis</router-link>
|
||||||
|
<n-button @click="removeCurrent">去除</n-button>
|
||||||
|
</div>
|
||||||
</template>
|
</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>
|
<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>
|
<template>
|
||||||
<router-view />
|
<div></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup></script>
|
<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>
|
<template>
|
||||||
<div class="flex w-full h-full">
|
<div class="relative flex-center w-full h-full bg-[#DBE0F9]">
|
||||||
<div class="flex-center w-1/2 h-full">
|
<login-bg />
|
||||||
<h3>登录</h3>
|
<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>
|
||||||
<div class="flex-center w-1/2 h-full bg-primary bg-opacity-25">
|
<n-gradient-text type="primary" :size="28">{{ title }}</n-gradient-text>
|
||||||
<banner-svg class="w-400px h-400px" type="1" :color="theme.themeColor" />
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BannerSvg } from '@/components';
|
import type { Component, PropType } from 'vue';
|
||||||
import { useThemeStore } from '@/store';
|
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>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
Loading…
Reference in New Issue
Block a user