调整登录页面,rsa登录修改为固定公钥,新增邮箱登录页面

This commit is contained in:
文永达 2023-09-22 01:08:45 +08:00
parent 5ed643de18
commit f474b4439f
34 changed files with 1232 additions and 270 deletions

View File

@ -143,7 +143,7 @@
<h5>Loading...</h5>
</div>
</div>
<script type="module" src="/src/main.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
</html>

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -7,9 +7,9 @@
import useUserStore from './store/modules/user'
import useAppStore from './store/modules/app'
import { ElConfigProvider } from 'element-plus'
import zh from 'element-plus/lib/locale/lang/zh-cn' //
import en from 'element-plus/lib/locale/lang/en' //
import tw from 'element-plus/lib/locale/lang/zh-tw' //
import zh from 'element-plus/es/locale/lang/zh-cn' //
import en from 'element-plus/es/locale/lang/en' //
import tw from 'element-plus/es/locale/lang/zh-tw' //
import defaultSettings from '@/settings'
const { proxy } = getCurrentInstance()

View File

@ -96,16 +96,16 @@ div:focus {
text-align: center;
}
.link-type,
.link-type:focus {
color: var(--el-color-primary);
cursor: pointer;
// .link-type,
// .link-type:focus {
// color: var(--el-color-primary);
// cursor: pointer;
&:hover {
// color: rgb(32, 160, 255);
opacity: 0.3;
}
}
// &:hover {
// // color: rgb(32, 160, 255);
// opacity: 0.3;
// }
// }
/** 基础通用 **/
.pt5 {

View File

@ -10,18 +10,18 @@
background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
// color: #fff;
}
// .title {
// margin: 0px auto 30px auto;
// text-align: center;
// // color: #fff;
// }
.login-form {
border-radius: 6px;
background: #ffffff;
// background-color: hsla(0, 0%, 100%, 0.3);
width: var(--base-login-width);
padding: 25px 15px 5px 15px;
width: calc(100% - 30px - 70px);
margin: 0 auto;
padding: 15px;
position: relative;
.input-icon {
@ -31,19 +31,19 @@
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
// .login-tip {
// font-size: 13px;
// text-align: center;
// color: #bfbfbf;
// }
.login-code {
width: 33%;
width: 34%;
height: 40px;
float: right;
img {
width: 100%;
// width: 100%;
cursor: pointer;
vertical-align: middle;
}
@ -68,6 +68,12 @@
}
.langSet {
position: absolute;
right: 20px;
top: 10px;
left: 20px;
top: 13px;
}
.link-wrapper {
height: 30px;
line-height: 30px;
text-align: right;
}

View File

@ -0,0 +1,506 @@
<template>
<div class="drag-verify-container">
<div :style="dragVerifyImgStyle">
<img ref="checkImg" crossOrigin="anonymous" :src="imgsrc" @load="checkimgLoaded" style="width: 100%" alt="" />
<canvas ref="maincanvas" class="main-canvas"></canvas>
<canvas ref="movecanvas" :class="{ goFirst: isOk, goKeep: isKeep }" class="move-canvas"></canvas>
<div class="refresh" v-if="showRefresh && !isPassing">
<el-icon @click="refreshimg">
<Refresh />
</el-icon>
</div>
<div class="tips success" v-if="showTips && isPassing">{{ successPassTimeTip }}</div>
<div class="tips danger" v-if="showTips && !isPassing && showErrorTip">{{ failTip }}</div>
</div>
<div
ref="dragVerify"
class="drag_verify"
:style="dragVerifyStyle"
@mousemove="dragMoving"
@mouseup="dragFinish"
@mouseleave="dragFinish"
@touchmove="dragMoving"
@touchend="dragFinish">
<div class="dv_progress_bar" :class="{ goFirst2: isOk }" ref="progressBar" :style="progressBarStyle">
{{ successMessage }}
</div>
<div class="dv_text" :style="textStyle" ref="message">
{{ message }}
</div>
<div
class="dv_handler dv_handler_bg"
:class="{ goFirst: isOk }"
@mousedown="dragStart"
@touchstart="dragStart"
ref="handler"
:style="handlerStyle">
<el-icon>
<DArrowRight />
</el-icon>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dragVerifyImgChip',
props: {
isPassing: {
type: Boolean,
default: false
},
width: {
type: Number,
default: 250
},
height: {
type: Number,
default: 40
},
text: {
type: String,
default: 'swiping to the right side'
},
successText: {
type: String,
default: 'success'
},
background: {
type: String,
default: '#eee'
},
progressBarBg: {
type: String,
default: '#76c61d'
},
completedBg: {
type: String,
default: '#76c61d'
},
circle: {
type: Boolean,
default: false
},
radius: {
type: String,
default: '4px'
},
handlerIcon: {
type: String
},
successIcon: {
type: String
},
handlerBg: {
type: String,
default: '#fff'
},
textSize: {
type: String,
default: '14px'
},
textColor: {
type: String,
default: '#333'
},
imgsrc: {
type: String
},
barWidth: {
type: Number,
default: 40
},
barRadius: {
type: Number,
default: 8
},
showRefresh: {
type: Boolean,
default: false
},
refreshIcon: {
type: String
},
showTips: {
type: Boolean,
default: true
},
successTip: {
type: String,
default: '验证通过超过80%用户'
},
failTip: {
type: String,
default: '验证未通过,拖动滑块将悬浮图像正确合并'
},
diffWidth: {
type: Number,
default: 20
}
},
mounted: function () {
const dragEl = this.$refs.dragVerify
dragEl.style.setProperty('--textColor', this.textColor)
dragEl.style.setProperty('--width', Math.floor(this.width / 2) + 'px')
dragEl.style.setProperty('--pwidth', -Math.floor(this.width / 2) + 'px')
},
computed: {
handlerStyle: function () {
return {
width: this.height + 'px',
height: this.height + 'px',
background: this.handlerBg
}
},
message: function () {
return this.isPassing ? '' : this.text
},
successMessage: function () {
return this.isPassing ? this.successText : ''
},
dragVerifyStyle: function () {
return {
width: this.width + 'px',
height: this.height + 'px',
lineHeight: this.height + 'px',
background: this.background,
borderRadius: this.circle ? this.height / 2 + 'px' : this.radius
}
},
dragVerifyImgStyle: function () {
return {
width: this.width + 'px',
position: 'relative',
overflow: 'hidden'
}
},
progressBarStyle: function () {
return {
background: this.progressBarBg,
height: this.height + 'px',
borderRadius: this.circle ? this.height / 2 + 'px 0 0 ' + this.height / 2 + 'px' : this.radius
}
},
textStyle: function () {
return {
height: this.height + 'px',
width: this.width + 'px',
fontSize: this.textSize
}
}
},
data() {
return {
isMoving: false,
x: 0,
isOk: false,
isKeep: false,
clipBarx: 0,
showErrorTip: false,
passTime: 0,
startTime: 0,
successPassTimeTip: this.successTip
}
},
methods: {
draw: function (ctx, x, y, operation) {
var l = this.barWidth
var r = this.barRadius
const PI = Math.PI
ctx.beginPath()
ctx.moveTo(x, y)
ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI)
ctx.lineTo(x + l, y)
ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI)
ctx.lineTo(x + l, y + l)
ctx.lineTo(x, y + l)
ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true)
ctx.lineTo(x, y)
ctx.lineWidth = 2
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)'
ctx.stroke()
ctx[operation]()
ctx.globalCompositeOperation = 'destination-over'
},
checkimgLoaded: function () {
//
var barWidth = this.barWidth
var imgHeight = this.$refs.checkImg.height
var imgWidth = this.$refs.checkImg.width
var halfWidth = Math.floor(this.width / 2)
var refreshHeigth = 25
var tipHeight = 20
var x = halfWidth + Math.ceil(Math.random() * (halfWidth - barWidth - this.barRadius - 5))
var y = refreshHeigth + Math.floor(Math.random() * (imgHeight - barWidth - refreshHeigth - tipHeight))
this.$refs.maincanvas.setAttribute('width', imgWidth)
this.$refs.maincanvas.setAttribute('height', imgHeight)
this.$refs.maincanvas.style.display = 'block'
var canvasCtx = this.$refs.maincanvas.getContext('2d')
this.draw(canvasCtx, x, y, 'fill')
this.clipBarx = x
var moveCanvas = this.$refs.movecanvas
moveCanvas.setAttribute('width', imgWidth)
moveCanvas.setAttribute('height', imgHeight)
this.$refs.movecanvas.style.display = 'block'
const L = barWidth + this.barRadius * 2 + 3 //
var moveCtx = this.$refs.movecanvas.getContext('2d')
moveCtx.clearRect(0, 0, imgWidth, imgHeight)
this.draw(moveCtx, x, y, 'clip')
moveCtx.drawImage(this.$refs.checkImg, 0, 0, imgWidth, imgHeight)
var y = y - this.barRadius * 2 - 1
const ImageData = moveCtx.getImageData(x, y, L, L)
moveCanvas.setAttribute('width', L)
moveCanvas.setAttribute('height', imgHeight)
moveCtx.putImageData(ImageData, 0, y)
},
dragStart: function (e) {
//
this.startTime = new Date().getTime()
if (!this.isPassing) {
this.isMoving = true
this.x = e.pageX || e.touches[0].pageX
}
this.showBar = true
this.showErrorTip = false
this.$emit('handlerMove')
},
dragMoving: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.touches[0].pageX) - this.x
var handler = this.$refs.handler
handler.style.left = _x + 'px'
this.$refs.progressBar.style.width = _x + this.height / 2 + 'px'
this.$refs.movecanvas.style.left = _x + 'px'
}
},
dragFinish: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.changedTouches[0].pageX) - this.x
if (Math.abs(_x - this.clipBarx) > this.diffWidth) {
this.isOk = true
var that = this
setTimeout(function () {
that.$refs.handler.style.left = '0'
that.$refs.progressBar.style.width = '0'
that.$refs.movecanvas.style.left = '0'
that.isOk = false
}, 500)
//
this.passTime = 0
this.$emit('passfail')
this.showErrorTip = true
} else {
//
const endTime = new Date().getTime()
this.passTime = (endTime - this.startTime) / 1000
this.successPassTimeTip = '验证通过,用时' + this.passTime + 's'
this.passVerify()
}
this.isMoving = false
}
},
passVerify: function () {
this.$emit('update:isPassing', true)
this.isMoving = false
var handler = this.$refs.handler
handler.children[0].className = this.successIcon
this.$refs.progressBar.style.background = this.completedBg
this.$refs.message.style['-webkit-text-fill-color'] = 'unset'
this.$refs.message.style.animation = 'slidetounlock2 3s infinite'
this.$refs.progressBar.style.color = '#fff'
this.$refs.progressBar.style.fontSize = this.textSize
this.isKeep = true
setTimeout(() => {
this.$refs.movecanvas.style.left = this.clipBarx + 'px'
setTimeout(() => {
this.isKeep = false
this.$refs.maincanvas.style.display = 'none'
this.$refs.movecanvas.style.display = 'none'
}, 200)
}, 100)
this.$emit('passcallback')
},
reset: function () {
this.reImg()
this.checkimgLoaded()
},
reImg: function () {
this.$emit('update:isPassing', false)
const oriData = this.$options.data()
for (const key in oriData) {
if (Object.prototype.hasOwnProperty.call(oriData, key)) {
this[key] = oriData[key]
}
}
var handler = this.$refs.handler
var message = this.$refs.message
handler.style.left = '0'
this.$refs.progressBar.style.width = '0'
handler.children[0].className = this.handlerIcon
message.style['-webkit-text-fill-color'] = 'transparent'
message.style.animation = 'slidetounlock 3s infinite'
message.style.color = this.background
this.$refs.movecanvas.style.left = '0px'
},
refreshimg: function () {
this.$emit('refresh')
}
},
watch: {
imgsrc: {
immediate: false,
handler: function () {
this.reImg()
}
}
}
}
</script>
<style scoped>
.drag_verify {
position: relative;
background-color: #e8e8e8;
text-align: center;
overflow: hidden;
}
.drag_verify .dv_handler {
position: absolute;
top: 0px;
left: 0px;
cursor: move;
}
.drag_verify .dv_handler i {
color: #666;
padding-left: 0;
font-size: 16px;
}
.drag_verify .dv_handler .el-icon-circle-check {
color: #6c6;
margin-top: 9px;
}
.drag_verify .dv_progress_bar {
position: absolute;
height: 34px;
width: 0px;
}
.drag_verify .dv_text {
position: absolute;
top: 0px;
color: transparent;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
background: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, var(--textColor)),
color-stop(0.4, var(--textColor)),
color-stop(0.5, #fff),
color-stop(0.6, var(--textColor)),
color-stop(1, var(--textColor))
);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-size-adjust: none;
animation: slidetounlock 3s infinite;
}
.drag_verify .dv_text * {
-webkit-text-fill-color: var(--textColor);
}
.goFirst {
left: 0px !important;
transition: left 0.5s;
}
.goKeep {
transition: left 0.2s;
}
.goFirst2 {
width: 0px !important;
transition: width 0.5s;
}
.drag-verify-container {
position: relative;
line-height: 0;
}
.refresh {
position: absolute;
right: 5px;
top: 5px;
cursor: pointer;
font-size: 20px;
z-index: 200;
}
.tips {
position: absolute;
bottom: 0;
height: 20px;
line-height: 20px;
text-align: center;
width: 100%;
font-size: 12px;
z-index: 200;
}
.tips.success {
background: rgba(255, 255, 255, 0.6);
color: green;
}
.tips.danger {
background: rgba(0, 0, 0, 0.6);
color: yellow;
}
.main-canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.move-canvas {
position: absolute;
top: 0;
left: 0;
}
</style>
<style>
@-webkit-keyframes slidetounlock {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--width) 0;
}
}
@-webkit-keyframes slidetounlock2 {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--pwidth) 0;
}
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div class="title-container">
<img class="logo" src="/favicon.ico" />
<div class="title">
<span class="white">企业仓库</span>
<span class="blue">&nbsp;管理系统</span>
</div>
<el-tag class="tag" type="info" effect="plain" disable-transitions>BETA</el-tag>
</div>
</template>
<script setup name="LoginFormTitle" lang="ts"></script>
<style rel="stylesheet/scss" lang="scss" scoped>
.title-container {
height: 90px;
vertical-align: bottom;
.logo {
height: 50px;
cursor: pointer;
float: left;
}
.title {
font-size: 26px;
font-weight: 700;
width: 320px;
line-height: 50px;
float: left;
}
.title .white {
color: rgba(255, 255, 255, 1);
font-size: 32px;
}
.title .blue {
color: rgba(64, 158, 255, 1);
font-size: 42px;
}
.tag {
margin: 5px 0;
}
}
</style>

View File

@ -4,12 +4,14 @@
"password": "密码",
"captcha": "验证码",
"btnLogin": "登录",
"btnLoginLoading": "登录中...",
"rememberMe": "记住密码",
"loginSuccess": "登录成功",
"loginTimeOut": "登录状态已过期,请重新登录",
"reLogin": "重新登录",
"invalidSession": "无效的会话,或者会话已过期,请重新登录。",
"otherLoginWay": "其他登录方式",
"register": "注册"
"register": "注册",
"toLoginByEmail": "邮箱登录"
}
}

View File

@ -5,7 +5,7 @@ import ElementPlus from 'element-plus'
import 'dayjs/locale/zh-cn'
import '@/assets/styles/index.scss' // global css
import App from './App'
import App from './App.vue'
import router from './router'
import directive from './directive' // directive
// 注册指令
@ -28,17 +28,17 @@ import { getDicts } from '@/api/system/dict/data'
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, download } from '@/utils/ruoyi'
// 分页组件
import Pagination from '@/components/Pagination'
import Pagination from '@/components/Pagination/index.vue'
// 自定义表格工具组件
import RightToolbar from '@/components/RightToolbar'
import RightToolbar from '@/components/RightToolbar/index.vue'
// 文件上传组件
import FileUpload from '@/components/FileUpload'
import FileUpload from '@/components/FileUpload/index.vue'
// 图片上传组件
import ImageUpload from '@/components/ImageUpload'
import ImageUpload from '@/components/ImageUpload/index.vue'
// 图片预览组件
import ImagePreview from '@/components/ImagePreview'
import ImagePreview from '@/components/ImagePreview/index.vue'
// 字典标签组件
import DictTag from '@/components/DictTag'
import DictTag from '@/components/DictTag/index.vue'
// el-date-picker 快捷选项
import dateOptions from '@/utils/dateOptions'

View File

@ -7,9 +7,9 @@ import { isHttp } from '@/utils/validate'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
NProgress.configure({ showSpinner: false });
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/socialLogin'];
const whiteList = ['/login', '/loginByEmail', '/auth-redirect', '/bind', '/register', '/socialLogin']
router.beforeEach((to, from, next) => {
NProgress.start()
@ -22,22 +22,29 @@ router.beforeEach((to, from, next) => {
} else {
if (useUserStore().roles.length === 0) {
// 判断当前用户是否已拉取完user_info信息
useUserStore().getInfo().then(() => {
usePermissionStore().generateRoutes().then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
useUserStore()
.getInfo()
.then(() => {
usePermissionStore()
.generateRoutes()
.then((accessRoutes) => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach((route) => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
})
}).catch(err => {
useUserStore().logOut().then(() => {
ElMessage.error(err != undefined ? err : '登录失败')
next({ path: '/' })
.catch((err) => {
useUserStore()
.logOut()
.then(() => {
ElMessage.error(err != undefined ? err : '登录失败')
next({ path: '/' })
})
})
})
} else {
next()
}
@ -56,4 +63,4 @@ router.beforeEach((to, from, next) => {
router.afterEach(() => {
NProgress.done()
})
})

View File

@ -39,6 +39,11 @@ export const constantRoutes = [
component: () => import('@/views/login'),
hidden: true
},
{
path: '/loginByEmail',
component: () => import('@/views/loginByEmail'),
hidden: true
},
{
path: '/sociallogin',
component: () => import('@/views/socialLogin'),

View File

@ -21,27 +21,26 @@ const useUserStore = defineStore('user', {
// 登录
login(userInfo) {
return new Promise((resolve, reject) => {
getRsaKey().then((response) => {
const publicKey = response.data.publicKey
const username = userInfo.username.trim()
const password = encryptByPublicKey(userInfo.password, publicKey)
const code = userInfo.code
const uuid = userInfo.uuid
login(username, password, code, uuid)
.then((res) => {
if (res.code == 200) {
setToken(res.data)
this.token = res.data
resolve() //then处理
} else {
console.log('login error ', res)
reject(res) //catch处理
}
})
.catch((error) => {
reject(error)
})
})
// getRsaKey().then((response) => {
const username = userInfo.username.trim()
const password = encryptByPublicKey(userInfo.password)
const code = userInfo.code
const uuid = userInfo.uuid
login(username, password, code, uuid)
.then((res) => {
if (res.code == 200) {
setToken(res.data)
this.token = res.data
resolve() //then处理
} else {
console.log('login error ', res)
reject(res) //catch处理
}
})
.catch((error) => {
reject(error)
})
// })
})
},
/**

View File

@ -1,5 +1,5 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
import jsrsasign from 'jsrsasign'
import { KEYUTIL, KJUR } from 'jsrsasign'
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALj0zjON+EVdBsnMcR4Uj+jOYgp5ZipftQZ1utW8KvVioz+RSaotF1JHt59q9SC/mZcWWpbpcEqQ3WyyyCC33msCAwEAAQ=='
@ -7,6 +7,17 @@ const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALj0zjON+EVdBsnMcR4Uj+jOYgp5Z
const privateKey =
'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAuPTOM434RV0GycxxHhSP6M5iCnlmKl+1BnW61bwq9WKjP5FJqi0XUke3n2r1IL+ZlxZalulwSpDdbLLIILfeawIDAQABAkB5PYAtq1KjpWddwPYlkbUEFsWNuCaQgExZ/7KJiN9gGjo/UfUZ3W39Orb8PITIYf1NbasReqgddAcsfJNyoDWBAiEA7K89DyTmbjNSmekdD3rejRDdMzzXYtcbo69ZjHoowMUCIQDIDN8eg6PcWk4kiRcRYcNEfriUJR7Fg07ellSPv821bwIhAJA5TEyxIJUgQwI0cZfgOELfdtrlBR5ek6IPlNKsEa89AiBbMVroexPQWC41A3VLjChKagXUKpO7b98dIqRLnyCz6wIgP3qpvnO4IOxY7f5XarfCVyIHZJAMt/R1f16P5OkKv+A='
const publicPem = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo7yRx+lfHxy/c7lZFtZn
A3GBeQ+9nxBuOcSz/I8l0dP5JZsWVE+jMtmoxnFrTgCsnGuDHMNpSlexUc3IVcsp
bC4tSWh/r4Gy9KsIw0tIY0pulKsN/VhV//7dajMV/Dg+Rd5bwGGjWrlWOp2EPVYB
HI66uZ3PpEQe3FbEdaLkJRTENbywgGcGo1tqUpqY5BDGAu9IiXwZe4hdM2uniFS2
RKcYDovCiJE1lA7yyx9HEUd9PhYMhPMLvIIdPWW2Gqxsi3IZgG25hER0+o3zICgq
G/2VbodBM65JTLBY+KnY6H3o80b0v2qwOB7TktVtWQR6Lz8Ud+qrlgeAzvZb5Fmp
DQIDAQAB
-----END PUBLIC KEY-----`
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt()
@ -21,7 +32,9 @@ export function decrypt(txt) {
return encryptor.decrypt(txt) // 对数据进行解密
}
export const encryptByPublicKey = (txt, publicKey) => {
const pubKey = jsrsasign.KEYUTIL.getKey(publicKey)
return jsrsasign.KJUR.crypto.Cipher.encrypt(txt, pubKey)
export const encryptByPublicKey = (txt: string, publicKey: string = publicPem) => {
const pubKey = KEYUTIL.getKey(publicKey)
// const ciphertext = KJUR.crypto.Cipher.encrypt(txt, pubKey, 'RSA')
const ciphertext = pubKey.encrypt(txt)
return btoa(atob(ciphertext).padStart(128, '\0'))
}

View File

@ -0,0 +1,11 @@
import { ComponentCustomProperties, ComponentInternalInstance, getCurrentInstance } from 'vue'
interface UseCurrentInstance {
globalProperties: ComponentCustomProperties & Record<string, any>
}
export default function useCurrentInstance(): UseCurrentInstance {
const { appContext } = getCurrentInstance() as ComponentInternalInstance
const globalProperties = appContext.config.globalProperties
return {
globalProperties
}
}

View File

@ -154,19 +154,6 @@
</el-card>
</el-col>
</el-row>
<el-dialog title="618爆款" v-model="open" :close-on-click-modal="false" :show-close="false">
<div class="promo" v-for="item in promoteList2" :key="item.id">
<h3>
<el-link type="success" target="_black" :href="item.link" v-html="item.content + '☛☛点我进入☚☚'"></el-link>
</h3>
<span v-html="item.tip"></span>
</div>
<template #footer>
<el-button type="primary" @click="goTarget('https://curl.qcloud.com/4yEoRquq')">主会场</el-button>
<el-button @click="open = false">没兴趣</el-button>
</template>
</el-dialog>
</div>
</template>
@ -177,7 +164,6 @@ const version = defaultSettings.version
function goTarget(url) {
window.open(url, '__blank')
}
const open = ref(true)
const promoteList2 = ref([
{
id: 1,

View File

@ -1,56 +1,71 @@
<template>
<starBackground></starBackground>
<starBackground />
<div class="login">
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">{{ defaultSettings.title }}</h3>
<LangSelect title="多语言设置" class="langSet" />
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" :placeholder="$t('login.account')">
<template #prefix>
<svg-icon name="user" class="input-icon" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" auto-complete="off" :placeholder="$t('login.password')" @keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="password" class="input-icon" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaOnOff != 'off'">
<el-input v-model="loginForm.code" auto-complete="off" :placeholder="$t('login.captcha')" style="width: 63%" @keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="validCode" class="input-icon" />
</template>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
<div class="login-wrapper">
<LoginFormTitle />
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="link-wrapper">
<LangSelect title="多语言设置" class="langSet" />
<el-text type="primary" style="margin: 0 10px">
<router-link :to="'/register'">{{ $t('login.register') }}</router-link>
</el-text>
<el-text type="primary">
<router-link :to="'/loginByEmail'">{{ $t('login.toLoginByEmail') }}</router-link>
</el-text>
</div>
</el-form-item>
<div style="display: flex; justify-content: space-between">
<el-checkbox v-model="loginForm.rememberMe">{{ $t('login.rememberMe') }}</el-checkbox>
<span style="font-size: 12px">
<span @click="handleForgetPwd()" class="forget-pwd">忘记密码</span>
<router-link class="link-type" :to="'/register'">{{ $t('login.register') }}</router-link>
</span>
</div>
<el-form-item style="width: 100%">
<el-button :loading="loading" size="default" type="primary" style="width: 100%" @click.prevent="handleLogin">
<span v-if="!loading">{{ $t('login.btnLogin') }}</span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
<div class="other-login" v-if="defaultSettings.showOtherLogin">
<el-divider>{{ $t('login.otherLoginWay') }}</el-divider>
<span @click="onAuth('GITHUB')" title="github"><svg-icon name="github" className="login-icon"></svg-icon></span>
<span @click="onAuth('GITEE')" title="gitee"><svg-icon name="gitee" className="login-icon"></svg-icon></span>
</div>
</el-form>
<el-divider style="margin: 12px 0" />
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="on" :placeholder="$t('login.account')">
<template #prefix>
<svg-icon name="user" class="el-input__icon input-icon" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
size="large"
auto-complete="off"
:placeholder="$t('login.password')"
@keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="password" class="el-input__icon input-icon" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaOnOff != 'off'">
<el-input
v-model="loginForm.code"
size="large"
auto-complete="off"
:placeholder="$t('login.captcha')"
style="width: 66%"
@keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="validCode" class="el-input__icon input-icon" />
</template>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<div class="link-wrapper" style="text-align: left">
<el-checkbox v-model="loginForm.rememberMe">{{ $t('login.rememberMe') }}</el-checkbox>
</div>
<el-form-item>
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
<span v-if="!loading">{{ $t('login.btnLogin') }}</span>
<span v-else>{{ $t('login.btnLoginLoading') }}</span>
</el-button>
</el-form-item>
<div class="other-login" v-if="defaultSettings.showOtherLogin">
<el-divider>{{ $t('login.otherLoginWay') }}</el-divider>
<span @click="onAuth('GITHUB')" title="github"><svg-icon name="github" className="login-icon"></svg-icon></span>
<span @click="onAuth('GITEE')" title="gitee"><svg-icon name="gitee" className="login-icon"></svg-icon></span>
</div>
</el-form>
</div>
<!-- 底部 -->
<div class="el-login-footer">
@ -67,7 +82,7 @@ import defaultSettings from '@/settings'
import starBackground from '@/views/components/starBackground.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import useUserStore from '@/store/modules/user'
import LoginFormTitle from '@/components/LoginFormTitle/index.vue'
const userStore = useUserStore()
const router = useRouter()
const route = useRoute()
@ -167,17 +182,17 @@ getCookie()
<style lang="scss" scoped>
@import '@/assets/styles/login.scss';
.forget-pwd {
color: #ccc;
margin-right: 10px;
cursor: pointer;
}
.login-icon {
width: 30px;
height: 30px;
margin-right: 20px;
cursor: pointer;
}
// .forget-pwd {
// color: #ccc;
// margin-right: 10px;
// cursor: pointer;
// }
// .login-icon {
// width: 30px;
// height: 30px;
// margin-right: 20px;
// cursor: pointer;
// }
.other-login {
padding: 0px 10px 5px;
}

344
src/views/loginByEmail.vue Normal file
View File

@ -0,0 +1,344 @@
<template>
<starBackground />
<div class="login">
<div class="login-wrapper">
<LoginFormTitle />
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="link-wrapper">
<LangSelect title="多语言设置" class="langSet" />
<!-- <el-text type="primary" style="margin: 0 10px">-->
<!-- <router-link :to="'/register'">{{ $t('login.register') }}</router-link>-->
<!-- </el-text>-->
<el-text type="primary">
<router-link :to="'/login'">{{ $t('loginByEmail.toLogin') }}</router-link>
</el-text>
</div>
<el-divider style="margin: 12px 0" />
<el-form-item prop="email">
<el-input v-model="loginForm.email" type="text" size="large" auto-complete="off" :placeholder="$t('loginByEmail.email')">
<template #prefix>
<svg-icon name="email" class="el-input__icon input-icon" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<!-- style="width: 66%" -->
<el-input v-model="loginForm.code" size="large" auto-complete="off" :placeholder="$t('login.captcha')" @keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="validCode" class="el-input__icon input-icon" />
</template>
<template #append>
<el-button @click="getDragVerify" :loading="checkDragVerify.loading">
{{ checkDragVerify.text }}
</el-button>
</template>
</el-input>
<!-- <el-button class="login-code-email" color="#626aef" size="large" @click="getDragVerify" :loading="checkDragVerify.loading">
{{ checkDragVerify.text }}
</el-button> -->
</el-form-item>
<el-form-item>
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
<span v-if="!loading">{{ $t('login.btnLogin') }}</span>
<span v-else>{{ $t('login.btnLoginLoading') }}</span>
</el-button>
</el-form-item>
</el-form>
</div>
<div class="el-login-footer">
<div v-html="defaultSettings.copyright"></div>
</div>
<!-- 滑块验证 -->
<el-dialog v-model="dragVerify" destroy-on-close width="440px" top="30vh">
<DragVerifyImgChip
:imgsrc="verifyImg.src"
v-model:isPassing="isPassing"
text="请按住滑块拖动"
successText="验证通过"
:width="400"
:showRefresh="true"
@refresh="refreshImg"
@passcallback="verifyPass"
style="border: 1px solid rgb(238, 238, 238)">
</DragVerifyImgChip>
</el-dialog>
</div>
</template>
<script setup name="loginByEmail" lang="ts">
// import { getMailCode, getEmailVerifyImg, getRsaKey } from '@/api/system/login'
import defaultSettings from '@/settings'
import starBackground from '@/views/components/starBackground.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import LoginFormTitle from '@/components/LoginFormTitle/index.vue'
import DragVerifyImgChip from '@/components/DragVerifyImgChip/index.vue'
import { useI18n } from 'vue-i18n'
import useCurrentInstance from '@/utils/useCurrentInstance'
import userUserStore from '@/store/modules/user'
import { encryptByPublicKey } from '@/utils/jsencrypt'
const { globalProperties } = useCurrentInstance()
const userStore = userUserStore()
const router = useRouter()
const route = useRoute()
interface checkDragVerify {
loading: boolean
duration: number
timer: any
text: string
}
interface loginForm {
email: string
code?: string
uuid?: string
}
const { t } = useI18n()
const seconds = 30
const checkEmail = (_rule, value, cb) => {
//
const regEmail = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
if (regEmail.test(value)) {
//
return cb()
}
cb(new Error('请输入合法的邮箱'))
}
const loginRules = {
code: [{ required: true, trigger: 'change', message: t('loginByEmail.loginRules.dragVerifyRequired') }],
email: [
{ required: true, trigger: 'blur', message: t('loginByEmail.loginRules.emailRequired') },
{ validator: checkEmail, trigger: 'blur', message: t('loginByEmail.loginRules.emailFormat') }
]
}
const loginRef = ref()
const loading = ref<boolean>(false)
const dragVerify = ref(false)
// const verifyImg = ref('https://picsum.photos/979/547' + '/?image=' + Math.round(Math.random() * 20))
// const /v.jpg
const isPassing = ref(false)
const loginForm = ref<loginForm>({
email: '',
code: '',
uuid: ''
})
const checkDragVerify = reactive<checkDragVerify>({
loading: false,
duration: seconds,
timer: null,
text: t('loginByEmail.getDragVerify')
})
interface verifyImg {
src: string
array: Array<string>
flag: number
}
const verifyImg: verifyImg = reactive({
src: '',
array: [],
flag: 1
})
//
function getVerifyImg() {
// getEmailVerifyImg().then((res) => {
// verifyImg.array = res.data
// if (verifyImg.array.length === 0) {
// verifyImg.array.push('/v.jpg')
// }
// verifyImg.src = verifyImg.array[0]
// // resolve(verifyImg)
// })
verifyImg.array = [
'/verifyimg/10-979x547.jpg',
'/verifyimg/11-979x547.jpg',
'/verifyimg/12-979x547.jpg',
'/verifyimg/13-979x547.jpg',
'/verifyimg/14-979x547.jpg',
'/verifyimg/16-979x547.jpg',
'/verifyimg/17-979x547.jpg',
'/verifyimg/18-979x547.jpg',
'/verifyimg/19-979x547.jpg',
'/verifyimg/20-979x547.jpg',
'/verifyimg/28-979x547.jpg',
'/verifyimg/29-979x547.jpg',
'/verifyimg/33-979x547.jpg',
'/verifyimg/37-979x547.jpg'
]
verifyImg.src = verifyImg.array[0]
}
//#region
// const verifyImgArrayFlag = ref(0)
// undefined
// o10
//#endregion
/**
* @description: 刷新图片
*/
function refreshImg() {
//
// 11
if (verifyImg.flag < verifyImg.array.length) {
verifyImg.src = verifyImg.array[verifyImg.flag]
verifyImg.flag++
} else {
verifyImg.flag = 0
verifyImg.src = verifyImg.array[verifyImg.flag]
verifyImg.flag++
}
// verifyImg.value = 'https://picsum.photos/979/547' + '/?image=' + Math.round(Math.random() * 20)
// verifyImgArray.value.shift();
}
const redirect = ref()
redirect.value = route.query.redirect
interface roles {
id: number
name: string
}
interface LoginUser {
createBy: string
createTime: string
delFlag: string
userId: number
userName: string
nickName: string
phonenumber?: string
remark?: string
email: string
password: string
sex: string
status: string
loginIp: string
loginDate: string
deptId: number
}
// const currentLoginUser = ref<LoginUser>()
let currentLoginCo: any[] = reactive([])
/**
* @description: 登录
*/
function handleLogin() {
loginRef.value.validate((valid) => {
if (valid) {
loading.value = true
// action
userStore
.loginByEmail(loginForm.value)
.then((res) => {
if (res !== undefined && res.code === 300) {
//
roleSelectDialog.visible = true
const currentCoSysTypes = res.data.co.map((item) => item.coSysType)
globalProperties.getDicts('co_sys_type').then((response) => {
const roles: roles[] = []
//
currentCoSysTypes
.map((item) => response.data.filter((i) => i.dictValue === item)[0])
.forEach((element) => {
roles.push({
id: element.dictValue,
name: element.dictLabel
})
// currentLoginUser.value = res.data.user
})
roleSelectDialog.roles = roles
})
currentLoginCo = reactive(res.data.co)
return
}
globalProperties.$modal.msgSuccess(globalProperties.$t('login.loginSuccess'))
router.push({ path: redirect.value || '/' })
})
.catch((error) => {
console.error(error)
globalProperties.$modal.msgError(error.msg)
loading.value = false
// //
// verifyPass()
})
}
})
}
interface roleSelectDialog {
visible: boolean
roles: roles[]
}
const roleSelectDialog: roleSelectDialog = reactive({
visible: false,
roles: []
})
/**
* @description: 企业类型选择弹窗关闭
*/
function roleSelectDialogClose(): void {
loading.value = false
loginForm.value.code = ''
globalProperties.$modal.msg(t('roleSelectDialog.close'))
}
/**
* @description: 企业类型选择弹窗回调
* @param {roles} role 企业类型信息
*/
function roleSelectDialogCallback(role: roles): void {
loading.value = false
const { coSysType, coId } = currentLoginCo.find((item) => item.coSysType === role.id)
userStore
.loginByCoSysType({ coId: coId, cosysType: coSysType }) // , user: currentLoginUser.value
.then(() => {
router.push({ path: redirect.value || '/' })
})
.catch((error) => {
console.error(error)
globalProperties.$modal.msgError(error.msg)
loading.value = false
//
// if (captchaOnOff.value) {
// getCode()
// loginForm.value.code = ''
// }
})
}
/**
* @description: 获取滑动验证
*/
function getDragVerify() {
loginRef.value.validateField('email', async (valid) => {
if (valid) {
isPassing.value = false
dragVerify.value = true
}
})
}
/**
* @description: 验证通过
*/
function verifyPass() {
checkDragVerify.loading = true
checkDragVerify.timer && clearInterval(checkDragVerify.timer)
checkDragVerify.timer = setInterval(() => {
const tmp = checkDragVerify.duration--
checkDragVerify.text = `${tmp}` + t('loginByEmail.checkDragVerifyDuration')
if (tmp <= 0) {
clearInterval(checkDragVerify.timer)
checkDragVerify.timer = null
checkDragVerify.duration = seconds
checkDragVerify.text = t('loginByEmail.getDragVerify')
checkDragVerify.loading = false
}
}, 1000)
// getRsaKey().then((response) => {
getMailCode({ toUser: encryptByPublicKey(loginForm.value.email) }).then((res) => {
// console.log(res)
})
// })
setTimeout(() => {
dragVerify.value = false
}, 1500)
}
getVerifyImg()
</script>
<style lang="scss" scoped>
@import '@/assets/styles/login.scss';
</style>

View File

@ -1,62 +1,88 @@
<template>
<starBackground></starBackground>
<div class="register">
<el-form ref="registerFormRef" :model="registerForm" :rules="registerRules" class="register-form">
<h3 class="title">{{ title }}</h3>
<el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" auto-complete="off" placeholder="账号">
<template #prefix>
<svg-icon name="user" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="registerForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter="handleRegister">
<template #prefix>
<svg-icon name="password" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input v-model="registerForm.confirmPassword" type="password" auto-complete="off" placeholder="确认密码" @keyup.enter="handleRegister">
<template #prefix>
<svg-icon name="password" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaOnOff">
<el-input v-model="registerForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleRegister">
<template #prefix>
<svg-icon name="validCode" />
</template>
</el-input>
<div class="register-code ml10">
<img :src="codeUrl" @click="getCode" class="register-code-img" />
<div class="login">
<div class="login-wrapper">
<LoginFormTitle />
<el-form ref="registerFormRef" :model="registerForm" :rules="registerRules" class="login-form">
<div class="link-wrapper">
<LangSelect title="多语言设置" class="langSet" />
<el-text type="primary">
<router-link :to="'/login'">使用已有账户登录</router-link>
</el-text>
</div>
</el-form-item>
<el-form-item style="width: 100%">
<el-button :loading="loading" type="primary" size="large" style="width: 100%" @click.prevent="handleRegister">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
<div style="float: right">
<router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
</div>
</el-form-item>
</el-form>
<el-divider style="margin: 12px 0" />
<el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
<template #prefix>
<svg-icon name="user" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
size="large"
auto-complete="off"
placeholder="密码"
@keyup.enter="handleRegister">
<template #prefix>
<svg-icon name="password" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
size="large"
auto-complete="off"
placeholder="确认密码"
@keyup.enter="handleRegister">
<template #prefix>
<svg-icon name="password" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaOnOff">
<el-input
v-model="registerForm.code"
size="large"
auto-complete="off"
placeholder="验证码"
style="width: 66%"
@keyup.enter="handleRegister">
<template #prefix>
<svg-icon name="validCode" />
</template>
</el-input>
<div class="login-code ml10">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-form-item>
<el-button :loading="loading" type="primary" size="large" style="width: 100%" @click.prevent="handleRegister">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 底部 -->
<div class="el-register-footer">
<div class="el-login-footer">
<div v-html="defaultSettings.copyright"></div>
</div>
</div>
</template>
<script setup name="register">
<script setup name="register" lang="ts">
import starBackground from '@/views/components/starBackground.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import LoginFormTitle from '@/components/LoginFormTitle/index.vue'
import { getCodeImg, register } from '@/api/system/login'
import defaultSettings from '@/settings'
import { ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance()
const { proxy } = getCurrentInstance() as any
const router = useRouter()
const codeUrl = ref('')
const registerForm = reactive({
@ -70,7 +96,7 @@ const registerForm = reactive({
const registerFormRef = ref(null)
const loading = ref(false)
const captchaOnOff = ref(true)
const equalToPassword = (rule, value, callback) => {
const equalToPassword = (_rule, value, callback) => {
if (registerForm.password !== value) {
callback(new Error('两次输入的密码不一致'))
} else {
@ -102,18 +128,11 @@ const registerRules = reactive({
],
code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
})
const copyRight = computed(() => {
return defaultSettings.copyright
})
const title = computed(() => {
return defaultSettings.title
})
function getCode() {
getCodeImg().then((res) => {
codeUrl.value = 'data:image/gif;base64,' + res.data.img
registerForm.uuid = res.data.uuid
// this.$forceUpdate()
})
}
function handleRegister() {
@ -147,63 +166,5 @@ getCode()
</script>
<style rel="stylesheet/scss" lang="scss">
.register {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background: radial-gradient(220% 105% at top center, #1b2947 10%, #4b76a7 40%, #81acae 65%, #f7f7b6);
}
.title {
margin: 0px auto 30px auto;
text-align: center;
// color: #fff;
}
.register-form {
border-radius: 6px;
background: #fff;
width: var(--base-login-width);
padding: 25px 15px 5px 15px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.register-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.register-code {
width: 33%;
height: 38px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-register-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.register-code-img {
height: 38px;
}
@import '@/assets/styles/login.scss';
</style>

62
tsconfig.json Normal file
View File

@ -0,0 +1,62 @@
{
"compilerOptions": {
//
"isolatedModules": true,
// ECMAScript
"target": "esnext",
//
"module": "esnext",
//
"moduleResolution": "node",
//
// --strict --noImplicitAny, --noImplicitThis, --alwaysStrict
// --strictNullChecks --strictFunctionTypes--strictPropertyInitialization
"strict": true,
"noLib": false,
"forceConsistentCasingInFileNames": true,
//
"allowSyntheticDefaultImports": true,
"strictFunctionTypes": false,
// jsx
"jsx": "preserve",
"allowJs": true,
"checkJs": false,
// .map
"sourceMap": true,
"esModuleInterop": true,
"resolveJsonModule": true,
// 使
"noUnusedLocals": false,
"noUnusedParameters": true,
"experimentalDecorators": true,
"lib": ["dom", "esnext", "DOM.Iterable"],
//implicitly has an 'any' type
"noImplicitAny": false,
// *.d.ts
"skipLibCheck": true,
//
"types": ["vite/client", "element-plus/global"],
"removeComments": true,
//
"baseUrl": "./",
//
"paths": {
"@/*": ["src/*"],
"/@/*": ["src/*"],
"#/*": ["src/types/*"],
"/#/*": ["src/types/*"]
},
"outDir": "./"
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"vite.config.ts",
"auto-imports.d.ts",
"components.d.ts",
"src/api/article/article.js"
],
"exclude": ["node_modules", "dist", ".vscode", ".idea"]
}