mirror of
https://github.com/FatttSnake/Pinnacle-OA.git
synced 2026-04-05 15:01:23 +08:00
Added login, logout and getUserinfo (Include ui and server)
This commit is contained in:
@@ -22,7 +22,7 @@ module.exports = {
|
||||
rules: {
|
||||
"no-cond-assign": "error",
|
||||
"eqeqeq": "error",
|
||||
"indent": ["error", 4],
|
||||
"indent": ["error", 4, {"SwitchCase": 1}],
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
|
||||
2
ui/src/assets/svg/back-shape.svg
Normal file
2
ui/src/assets/svg/back-shape.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="473.5501403808594" height="514.205810546875"
|
||||
viewBox="0 0 473.5501403808594 514.205810546875"><g style="mix-blend-mode:passthrough"><g style="mix-blend-mode:passthrough" transform="matrix(-0.09544334560632706,-0.9953708648681641,0.9953532218933105,-0.09562418609857559,-475.4320658580816,596.4368332676222)"><path d="M207.21426391601562,514.205810546875L357.9022639160156,845.999810546875L56.52586391601562,845.999810546875L207.21426391601562,514.205810546875Z" fill="#5495F1" fill-opacity="1"/></g><g style="mix-blend-mode:passthrough" transform="matrix(0.6958200931549072,-0.7182161211967468,0.7182161211967468,0.6958200931549072,-42.65635399221446,124.80157307758964)"><path d="M204.50979614257812,112.7599334716797L272.49279614257813,317.36393347167973L136.52679614257812,317.36393347167973L204.50979614257812,112.7599334716797Z" fill="#FFFFFF" fill-opacity="0.6000000238418579"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 964 B |
2
ui/src/assets/svg/password.svg
Normal file
2
ui/src/assets/svg/password.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="27.428573608398438" height="32.000003814697266"
|
||||
viewBox="0 0 27.428573608398438 32.000003814697266"><g><path d="M22.8571,10.6667L25.9048,10.6667C26.7463,10.6667,27.4286,11.3489,27.4286,12.1905L27.4286,30.4762C27.4286,31.3178,26.7463,32,25.9048,32L1.52381,32C0.682233,32,0,31.3178,0,30.4762L0,12.1905C0,11.3489,0.682233,10.6667,1.52381,10.6667L4.57143,10.6667L4.57143,9.14286C4.57143,4.0934,8.66483,0,13.7143,0C18.7637,0,22.8571,4.0934,22.8571,9.14286L22.8571,10.6667ZM19.8095,10.6667L19.8095,9.14286C19.8095,5.77655,17.0806,3.04762,13.7143,3.04762C10.348,3.04762,7.61905,5.77655,7.61905,9.14286L7.61905,10.6667L19.8095,10.6667ZM12.1905,19.8095L12.1905,22.8571L15.2381,22.8571L15.2381,19.8095L12.1905,19.8095ZM6.09524,19.8095L6.09524,22.8571L9.14286,22.8571L9.14286,19.8095L6.09524,19.8095ZM18.2857,19.8095L18.2857,22.8571L21.3333,22.8571L21.3333,19.8095L18.2857,19.8095Z" fill-opacity="1"/></g></svg>
|
||||
|
After Width: | Height: | Size: 966 B |
@@ -1,4 +1,5 @@
|
||||
const PRODUCTION_NAME = 'Pinnacle OA'
|
||||
const TOKEN_NAME = 'JWT_TOKEN'
|
||||
const COLOR_PRODUCTION = '#00D4FF'
|
||||
const COLOR_BACKGROUND = '#D8D8D8'
|
||||
const COLOR_FONT_MAIN = '#4D4D4D'
|
||||
@@ -11,6 +12,7 @@ const SIZE_ICON_XL = '64px'
|
||||
|
||||
export {
|
||||
PRODUCTION_NAME,
|
||||
TOKEN_NAME,
|
||||
COLOR_PRODUCTION,
|
||||
COLOR_BACKGROUND,
|
||||
COLOR_FONT_MAIN,
|
||||
|
||||
@@ -5,18 +5,6 @@ import router from '@/router'
|
||||
import '@/assets/css/base.css'
|
||||
import '@/assets/css/common.css'
|
||||
|
||||
/*
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.matched.length === 0) {
|
||||
from.path ? next({ path: from.path }) : next('/')
|
||||
} else {
|
||||
if (to.meta.title) {
|
||||
document.title = PRODUCTION_NAME + ' - ' + to.meta.title
|
||||
}
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router).mount('#app')
|
||||
|
||||
273
ui/src/pages/Login.vue
Normal file
273
ui/src/pages/Login.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<div class="background" @keyup.enter="login">
|
||||
<div class="card-back">
|
||||
<div class="production-name">
|
||||
<span class="emphasize">{{ PRODUCTION_NAME() }}</span>
|
||||
<br />
|
||||
<span>自动化办公系统</span>
|
||||
</div>
|
||||
<img class="back-shape" :src="backShape" alt="back-shape" />
|
||||
</div>
|
||||
<div class="card-front">
|
||||
<div class="login-title">登录</div>
|
||||
<div class="input-box user-name-box">
|
||||
<div class="center-box" style="padding: 10px">
|
||||
<el-icon size="18">
|
||||
<icon-pinnacle-user />
|
||||
</el-icon>
|
||||
</div>
|
||||
<label for="user-name"></label
|
||||
><input
|
||||
type="text"
|
||||
name="user-name"
|
||||
id="user-name"
|
||||
v-model="userName"
|
||||
@keyup="userName = userName.replace(/\s+/g, '')"
|
||||
placeholder="用户名"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-box password-box">
|
||||
<div class="center-box" style="padding: 10px">
|
||||
<el-icon size="18">
|
||||
<icon-pinnacle-password />
|
||||
</el-icon>
|
||||
</div>
|
||||
<label for="password"></label
|
||||
><input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
v-model="password"
|
||||
@keyup="password = password.replace(/\s+/g, '')"
|
||||
placeholder="密码"
|
||||
/>
|
||||
</div>
|
||||
<div class="captcha-set">
|
||||
<div class="captcha-box">
|
||||
<div class="input-box" style="height: 100%">
|
||||
<label for="captcha"></label
|
||||
><input
|
||||
type="text"
|
||||
name="captcha"
|
||||
id="captcha"
|
||||
v-model="captcha"
|
||||
placeholder="验证码"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<img :src="captchaSrc" alt="Captcha" @click="getNewCaptcha" />
|
||||
</div>
|
||||
|
||||
<ElButton
|
||||
size="large"
|
||||
type="primary"
|
||||
:disabled="loggingIn"
|
||||
id="login-bt"
|
||||
@click="login"
|
||||
>
|
||||
<template #default
|
||||
><span style="font-size: clamp(2em, 1.5vw, 2.8em)"
|
||||
>登  录</span
|
||||
></template
|
||||
>
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { getCaptchaSrc, login, verifyCaptcha } from '@/utils/auth'
|
||||
import backShape from '@/assets/svg/back-shape.svg'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/el-message.css'
|
||||
import { PRODUCTION_NAME } from '@/constants/Common.constants'
|
||||
|
||||
export default {
|
||||
name: 'LoginPage',
|
||||
data() {
|
||||
return {
|
||||
backShape,
|
||||
captchaSrc: getCaptchaSrc(),
|
||||
userName: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
loggingIn: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
PRODUCTION_NAME() {
|
||||
return PRODUCTION_NAME
|
||||
},
|
||||
getNewCaptcha() {
|
||||
this.captchaSrc = getCaptchaSrc()
|
||||
},
|
||||
async login() {
|
||||
if (!this.userName) {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<strong>用户名</strong> 为空'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.password) {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<strong>密码</strong> 为空'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.captcha) {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<strong>验证码</strong> 为空'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!verifyCaptcha(this.captcha)) {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<strong>验证码</strong> 错误'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (await login(this.userName, this.password)) {
|
||||
ElMessage.success({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<strong>登录成功</strong>'
|
||||
})
|
||||
this.loggingIn = true
|
||||
const _this = this
|
||||
setTimeout(function () {
|
||||
_this.$router.push('/')
|
||||
}, 1500)
|
||||
} else {
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<strong>用户名</strong> 或 <strong>密码</strong> 错误'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.background {
|
||||
width: 100vw;
|
||||
min-width: 900px;
|
||||
height: 100vh;
|
||||
min-height: 500px;
|
||||
background: linear-gradient(to right, #5495f1, #82b5ff);
|
||||
}
|
||||
|
||||
.card-back {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 65vw;
|
||||
min-width: 800px;
|
||||
height: 25vw;
|
||||
min-height: 350px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
|
||||
.production-name {
|
||||
position: absolute;
|
||||
left: 6%;
|
||||
top: 8%;
|
||||
font-size: clamp(2em, 2vw, 2.8em);
|
||||
color: var(--font-main-color);
|
||||
}
|
||||
|
||||
.production-name .emphasize {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.back-shape {
|
||||
position: absolute;
|
||||
height: 80%;
|
||||
left: 30%;
|
||||
top: 10%;
|
||||
}
|
||||
|
||||
.card-front {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 25vw;
|
||||
min-width: 350px;
|
||||
height: 35vw;
|
||||
min-height: 500px;
|
||||
border-radius: 15px;
|
||||
background-color: white;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.login-title {
|
||||
margin-top: 10%;
|
||||
margin-left: 10%;
|
||||
font-size: clamp(2.8em, 2.8vw, 4em);
|
||||
letter-spacing: 0.25em;
|
||||
font-weight: bold;
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.input-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input-box input {
|
||||
flex: 1;
|
||||
font-size: 1.6em;
|
||||
background-color: transparent;
|
||||
padding-right: 10px;
|
||||
color: var(--font-main-color);
|
||||
}
|
||||
|
||||
.user-name-box,
|
||||
.password-box {
|
||||
margin: 10% auto -2% auto;
|
||||
height: 10%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.captcha-set {
|
||||
display: flex;
|
||||
margin: 15% auto 12% auto;
|
||||
height: 10%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.captcha-set img {
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.captcha-box {
|
||||
flex: 1;
|
||||
padding-right: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.captcha-box input {
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#login-bt {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -106,7 +106,7 @@
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="user-name">
|
||||
<span>用户名</span>
|
||||
<span>{{ username }}</span>
|
||||
</div>
|
||||
<div class="user-desc">
|
||||
<span>用户介绍</span>
|
||||
@@ -120,7 +120,7 @@
|
||||
<el-button style="width: 100%">个人档案</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button style="width: 100%">退出</el-button>
|
||||
<el-button @click="logout" style="width: 100%">退出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -155,9 +155,17 @@ import {
|
||||
SIZE_ICON_SM
|
||||
} from '@/constants/Common.constants.js'
|
||||
import _ from 'lodash'
|
||||
import { getUsername, logout } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'MainFrame',
|
||||
data() {
|
||||
return {
|
||||
routes: _.filter(_.get(this.$router, 'options.routes[0].children'), 'meta.title'),
|
||||
isCollapsed: false,
|
||||
username: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
SIZE_ICON_LG() {
|
||||
return SIZE_ICON_LG
|
||||
@@ -176,16 +184,16 @@ export default {
|
||||
},
|
||||
COLOR_FONT_MAIN() {
|
||||
return COLOR_FONT_MAIN
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
routes: _.filter(_.get(this.$router, 'options.routes[0].children'), 'meta.title'),
|
||||
isCollapsed: false
|
||||
},
|
||||
logout() {
|
||||
logout()
|
||||
this.$router.push({ name: 'Login' })
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.routes)
|
||||
getUsername().then((res) => {
|
||||
this.username = res.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { PRODUCTION_NAME } from '@/constants/Common.constants'
|
||||
import { getLoginStatus } from '@/utils/auth'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@@ -23,8 +25,41 @@ const router = createRouter({
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: async () => await import('@/pages/Login.vue'),
|
||||
name: 'Login',
|
||||
meta: {
|
||||
title: '登录'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.matched.length === 0) {
|
||||
from.path !== '' ? next({ path: from.path }) : next('/')
|
||||
} else {
|
||||
if (to.meta.title !== '') {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
document.title = `${PRODUCTION_NAME} - ${to.meta.title}`
|
||||
}
|
||||
|
||||
if (getLoginStatus()) {
|
||||
if (to.name === 'Login') {
|
||||
next('/')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
if (to.name === 'Login') {
|
||||
next()
|
||||
} else {
|
||||
next('/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import axios, { type AxiosError } from 'axios'
|
||||
import { getToken, removeToken } from '@/utils/common'
|
||||
import router from '@/router'
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: 'http://localhost:8621',
|
||||
timeout: 10000,
|
||||
withCredentials: false
|
||||
})
|
||||
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = getToken()
|
||||
if (token != null) {
|
||||
config.headers.set('token', token)
|
||||
}
|
||||
return config
|
||||
},
|
||||
async (error) => {
|
||||
return await Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
return response
|
||||
},
|
||||
async (error) => {
|
||||
if (error.response != null) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
console.log(`request error: ${error.response.code} - ${error.response.msg}`)
|
||||
switch (error.response.code) {
|
||||
case 30010:
|
||||
removeToken()
|
||||
await router.push({ name: 'Login' })
|
||||
}
|
||||
}
|
||||
return await Promise.reject(error?.response?.data)
|
||||
}
|
||||
)
|
||||
|
||||
const request = {
|
||||
async get<T = any>(url: string, data?: any): Promise<T> {
|
||||
return await request.request('GET', url, { params: data })
|
||||
},
|
||||
async post<T = any>(url: string, data?: any): Promise<T> {
|
||||
return await request.request('POST', url, { data })
|
||||
},
|
||||
async put<T = any>(url: string, data?: any): Promise<T> {
|
||||
return await request.request('PUT', url, { data })
|
||||
},
|
||||
async delete<T = any>(url: string, data?: any): Promise<T> {
|
||||
return await request.request('DELETE', url, { params: data })
|
||||
},
|
||||
async request<T = any>(method = 'GET', url: string, data?: any): Promise<T> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
service({ method, url, ...data })
|
||||
.then((res) => {
|
||||
resolve(res as unknown as Promise<T>)
|
||||
})
|
||||
.catch((e: Error | AxiosError) => {
|
||||
reject(e)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default request
|
||||
|
||||
61
ui/src/utils/auth.ts
Normal file
61
ui/src/utils/auth.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Captcha } from './common'
|
||||
import {
|
||||
getCaptcha,
|
||||
getLocalStorage,
|
||||
getToken,
|
||||
removeLocalStorage,
|
||||
setLocalStorage,
|
||||
setToken
|
||||
} from './common'
|
||||
import { TOKEN_NAME } from '@/constants/Common.constants'
|
||||
import _ from 'lodash'
|
||||
import request from '@/services'
|
||||
|
||||
let captcha: Captcha
|
||||
|
||||
async function login(username: string, passwd: string): Promise<boolean> {
|
||||
removeLocalStorage('username')
|
||||
await request.post('/login', { username, passwd }).then((res: any) => {
|
||||
const response = res.data
|
||||
if (response.code === 20010) {
|
||||
setToken(response.data.token)
|
||||
}
|
||||
})
|
||||
|
||||
return !_.isEmpty(getToken())
|
||||
}
|
||||
|
||||
function logout(): void {
|
||||
removeLocalStorage(TOKEN_NAME)
|
||||
removeLocalStorage('username')
|
||||
}
|
||||
|
||||
function getLoginStatus(): boolean {
|
||||
return getLocalStorage(TOKEN_NAME) != null
|
||||
}
|
||||
|
||||
async function getUsername(): Promise<string | null> {
|
||||
if (!_.isEmpty(getLocalStorage('username'))) {
|
||||
return getLocalStorage('username')
|
||||
}
|
||||
|
||||
let username = ''
|
||||
|
||||
await request.get('/userInfo').then((res) => {
|
||||
username = res.data.data.user.username
|
||||
})
|
||||
|
||||
setLocalStorage('username', username)
|
||||
return username
|
||||
}
|
||||
|
||||
function getCaptchaSrc(): string {
|
||||
captcha = getCaptcha(300, 150, 4)
|
||||
return captcha.base64Src
|
||||
}
|
||||
|
||||
function verifyCaptcha(value: string): boolean {
|
||||
return captcha.value === value.replace(/\s*/g, '').toUpperCase()
|
||||
}
|
||||
|
||||
export { login, logout, getLoginStatus, getUsername, getCaptchaSrc, verifyCaptcha }
|
||||
@@ -0,0 +1,153 @@
|
||||
import { TOKEN_NAME } from '@/constants/Common.constants'
|
||||
|
||||
interface Captcha {
|
||||
value: string
|
||||
base64Src: string
|
||||
}
|
||||
|
||||
function getQueryVariable(variable: string): string | null {
|
||||
const query = window.location.search.substring(1)
|
||||
const vars = query.split('&')
|
||||
for (let i = 0; i < vars.length; i++) {
|
||||
const pair = vars[i].split('=')
|
||||
if (pair[0] === variable) {
|
||||
return decodeURIComponent(pair[1].replace(/\+/g, ' '))
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function setCookie(
|
||||
name: string,
|
||||
value: string,
|
||||
daysToLive: number | null,
|
||||
path: string | null
|
||||
): void {
|
||||
let cookie = name + '=' + encodeURIComponent(value)
|
||||
|
||||
if (typeof daysToLive === 'number') {
|
||||
cookie = `${cookie}; max-age=${daysToLive * 24 * 60 * 60}`
|
||||
}
|
||||
|
||||
if (typeof path === 'string') {
|
||||
cookie += '; path=' + path
|
||||
}
|
||||
|
||||
document.cookie = cookie
|
||||
}
|
||||
|
||||
function setLocalStorage(name: string, value: string): void {
|
||||
localStorage.setItem(name, value)
|
||||
}
|
||||
|
||||
function setToken(token: string): void {
|
||||
setLocalStorage(TOKEN_NAME, token)
|
||||
}
|
||||
|
||||
function getCookie(name: string): string | null {
|
||||
const cookieArr = document.cookie.split(';')
|
||||
|
||||
for (let i = 0; i < cookieArr.length; i++) {
|
||||
const cookiePair = cookieArr[i].split('=')
|
||||
if (name === cookiePair[0].trim()) {
|
||||
return decodeURIComponent(cookiePair[1])
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function getLocalStorage(name: string): string | null {
|
||||
return localStorage.getItem(name)
|
||||
}
|
||||
|
||||
function getToken(): string | null {
|
||||
return getLocalStorage(TOKEN_NAME)
|
||||
}
|
||||
|
||||
function removeCookie(name: string): void {
|
||||
document.cookie = name + '=; max-age=0'
|
||||
}
|
||||
|
||||
function removeLocalStorage(name: string): void {
|
||||
localStorage.removeItem(name)
|
||||
}
|
||||
|
||||
function removeToken(): void {
|
||||
removeLocalStorage(TOKEN_NAME)
|
||||
}
|
||||
|
||||
function randomInt(start: number, end: number): number {
|
||||
if (start > end) {
|
||||
const t = start
|
||||
start = end
|
||||
end = t
|
||||
}
|
||||
start = Math.ceil(start)
|
||||
end = Math.floor(end)
|
||||
return start + Math.floor(Math.random() * (end - start))
|
||||
}
|
||||
|
||||
function randomFloat(start: number, end: number): number {
|
||||
return start + Math.random() * (end - start)
|
||||
}
|
||||
|
||||
function randomColor(start: number, end: number): string {
|
||||
return `rgb(${randomInt(start, end)},${randomInt(start, end)},${randomInt(start, end)})`
|
||||
}
|
||||
|
||||
function getCaptcha(width: number, high: number, num: number): Captcha {
|
||||
const CHARTS = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'.split('')
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
||||
|
||||
ctx.rect(0, 0, width, high)
|
||||
ctx.clip()
|
||||
|
||||
ctx.fillStyle = randomColor(200, 250)
|
||||
ctx.fillRect(0, 0, width, high)
|
||||
|
||||
for (let i = 0.05 * width * high; i > 0; i--) {
|
||||
ctx.fillStyle = randomColor(0, 256)
|
||||
ctx.fillRect(randomInt(0, width), randomInt(0, high), 1, 1)
|
||||
}
|
||||
|
||||
ctx.font = `${high - 4}px Consolas`
|
||||
ctx.fillStyle = randomColor(160, 200)
|
||||
let value = ''
|
||||
for (let i = 0; i < num; i++) {
|
||||
const x = ((width - 10) / num) * i + 5
|
||||
const y = high - 12
|
||||
const r = Math.PI * randomFloat(-0.12, 0.12)
|
||||
const ch = CHARTS[randomInt(0, CHARTS.length)]
|
||||
value += ch
|
||||
ctx.translate(x, y)
|
||||
ctx.rotate(r)
|
||||
ctx.fillText(ch, 0, 0)
|
||||
ctx.rotate(-r)
|
||||
ctx.translate(-x, -y)
|
||||
}
|
||||
|
||||
const base64Src = canvas.toDataURL('image/jpg')
|
||||
return {
|
||||
value,
|
||||
base64Src
|
||||
}
|
||||
}
|
||||
|
||||
export type { Captcha }
|
||||
|
||||
export {
|
||||
getQueryVariable,
|
||||
getCookie,
|
||||
getLocalStorage,
|
||||
getToken,
|
||||
setCookie,
|
||||
setLocalStorage,
|
||||
setToken,
|
||||
removeCookie,
|
||||
removeLocalStorage,
|
||||
removeToken,
|
||||
getCaptcha
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user