登录优化
This commit is contained in:
parent
7bcdc963b9
commit
3b6d02e21e
|
|
@ -7,3 +7,14 @@ VITE_APP_ENV='development'
|
|||
# 开发环境
|
||||
VITE_API_BASE='https://demo.leapy.cn'
|
||||
VITE_WS_URL='wss://demo.leapy.cn/ws'
|
||||
|
||||
# SM2公钥
|
||||
VITE_RSA_PUBLIC_KEY='-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgSGuTW9sIHkDCOcXe0Gk
|
||||
euU82C9vGDaL569ChxdG3Ab4BCu1LqtGARMBeTW99amHbRbJE4/dzafUsGEh2rOq
|
||||
/yb/qwKixtk1PWfrcqTuHJ8vmuj9MCQ0EIirKzc7lvGDUoIQuGAyQMQOTx2/iiDW
|
||||
Kk50n1wtA4j8CITNuvZIXcfyKcPNtrgvBnhIuBVuZ7+X8oEjiO4nknVN2HrgeDK7
|
||||
aQ4B43MR9rraqaupOv2l8Ua1nwMI3BtBdhQgXkjzMruHehL5+Bq4EHY01mCccUWv
|
||||
7YoL2R9Idu8KKxvMxypO1SffMGj3ViE4TvAQHU+eRrnXWDv2c7WCXSzSZUPfXalO
|
||||
EwIDAQAB
|
||||
-----END PUBLIC KEY-----'
|
||||
|
|
|
|||
|
|
@ -7,3 +7,14 @@ VITE_APP_ENV='production'
|
|||
# 生产环境
|
||||
VITE_API_BASE='https://demo.leapy.cn'
|
||||
VITE_WS_URL='wss://demo.leapy.cn/ws'
|
||||
|
||||
# SM2公钥
|
||||
VITE_RSA_PUBLIC_KEY='-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgSGuTW9sIHkDCOcXe0Gk
|
||||
euU82C9vGDaL569ChxdG3Ab4BCu1LqtGARMBeTW99amHbRbJE4/dzafUsGEh2rOq
|
||||
/yb/qwKixtk1PWfrcqTuHJ8vmuj9MCQ0EIirKzc7lvGDUoIQuGAyQMQOTx2/iiDW
|
||||
Kk50n1wtA4j8CITNuvZIXcfyKcPNtrgvBnhIuBVuZ7+X8oEjiO4nknVN2HrgeDK7
|
||||
aQ4B43MR9rraqaupOv2l8Ua1nwMI3BtBdhQgXkjzMruHehL5+Bq4EHY01mCccUWv
|
||||
7YoL2R9Idu8KKxvMxypO1SffMGj3ViE4TvAQHU+eRrnXWDv2c7WCXSzSZUPfXalO
|
||||
EwIDAQAB
|
||||
-----END PUBLIC KEY-----'
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"element-plus": "2.13.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"image-conversion": "^2.1.1",
|
||||
"jsencrypt": "^3.5.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
"xgplayer-hls": "^3.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/node": "^25.0.3",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"eslint": "^9.39.2",
|
||||
|
|
|
|||
|
|
@ -22,4 +22,6 @@ export default {
|
|||
//布局 分栏:column | 通栏:header | 经典:menu | 功能坞:dock
|
||||
//dock将关闭标签和面包屑栏
|
||||
APP_LAYOUT: 'column',
|
||||
// sm2公钥
|
||||
RSA_PUBLIC_KEY: import.meta.env.VITE_RSA_PUBLIC_KEY
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import tools from '@/utils/tools'
|
|||
import api from "@/api"
|
||||
import sRouter from './system'
|
||||
import {treeFilter, filterAsyncRouter, flatAsyncRoutes} from '@/utils/route'
|
||||
import {beforeEach, afterEach} from '@/utils/route'
|
||||
import {beforeEach, afterEach, makeMenu} from '@/utils/route'
|
||||
import i18n from "@/locales"
|
||||
|
||||
//系统路由
|
||||
|
|
@ -59,7 +59,7 @@ router.beforeEach(async (to, from, next) => {
|
|||
/* @ts-ignore */
|
||||
const res = await api.auth.menu()
|
||||
/* @ts-ignore */
|
||||
tools.data.set("MENU", tools.makeMenu(res.data.menus, 0))
|
||||
tools.data.set("MENU", makeMenu(res.data.menus, 0))
|
||||
/* @ts-ignore */
|
||||
tools.data.set("PERMISSIONS", res.data.buttons)
|
||||
/* @ts-ignore */
|
||||
|
|
@ -72,7 +72,7 @@ router.beforeEach(async (to, from, next) => {
|
|||
})
|
||||
|
||||
let menu = [...userMenu, ...apiMenu]
|
||||
var menuRouter = filterAsyncRouter(menu)
|
||||
let menuRouter = filterAsyncRouter(menu);
|
||||
menuRouter = flatAsyncRoutes(menuRouter)
|
||||
menuRouter.forEach(item => {
|
||||
router.addRoute("layout", item)
|
||||
|
|
|
|||
|
|
@ -112,3 +112,29 @@ export function getMenu() {
|
|||
})
|
||||
return [...userMenu, ...apiMenu]
|
||||
}
|
||||
|
||||
export function makeMenu(menus, pid = 0) {
|
||||
const arr = [];
|
||||
for (let item of menus) {
|
||||
if (item.pid === pid) {
|
||||
// 数据格式处理
|
||||
const tmp = {
|
||||
name: item['name'],
|
||||
path: item['type'] == 0 ? '/' + item['path'] : item['path'],
|
||||
component: item['path'],
|
||||
meta: {
|
||||
'title': item['title'],
|
||||
'icon': item['icon'],
|
||||
'hidden': item['hidden'],
|
||||
'type': item['type'] == 0 ? 'menu' : 'link'
|
||||
}
|
||||
};
|
||||
const children = makeMenu(menus, item.menu_id);
|
||||
if (children.length > 0) {
|
||||
tmp['children'] = children
|
||||
}
|
||||
arr.push(tmp)
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import CryptoJS from 'crypto-js';
|
||||
import CryptoJS from 'crypto-js'
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
import config from "@/config"
|
||||
|
||||
const tools = {
|
||||
data: {
|
||||
|
|
@ -11,7 +13,9 @@ const tools = {
|
|||
},
|
||||
get(cacheKey: string) {
|
||||
try {
|
||||
const cacheValue = JSON.parse(tools.base64.decrypt(localStorage.getItem(cacheKey)))
|
||||
const data = localStorage.getItem(cacheKey);
|
||||
if (!data) return null
|
||||
const cacheValue = JSON.parse(tools.base64.decrypt(data))
|
||||
if (cacheValue) {
|
||||
let nowTime = new Date().getTime()
|
||||
if (nowTime > cacheValue.expireIn && cacheValue.expireIn !== 0) {
|
||||
|
|
@ -48,36 +52,36 @@ const tools = {
|
|||
return [null, err]
|
||||
}
|
||||
},
|
||||
crypto: {
|
||||
//MD5加密
|
||||
MD5(data: string) {
|
||||
return CryptoJS.MD5(data).toString()
|
||||
},
|
||||
md5: function (data: string) {
|
||||
return CryptoJS.MD5(data).toString()
|
||||
},
|
||||
makeMenu: function (menus, pid = 0) {
|
||||
const arr = [];
|
||||
for (let item of menus) {
|
||||
if (item.pid === pid) {
|
||||
// 数据格式处理
|
||||
const tmp = {
|
||||
name: item['name'],
|
||||
path: item['type'] == 0 ? '/' + item['path'] : item['path'],
|
||||
component: item['path'],
|
||||
meta: {
|
||||
'title': item['title'],
|
||||
'icon': item['icon'],
|
||||
'hidden': item['hidden'],
|
||||
'type': item['type'] == 0 ? 'menu' : 'link'
|
||||
}
|
||||
};
|
||||
const children = this.makeMenu(menus, item.menu_id);
|
||||
if (children.length > 0) {
|
||||
tmp['children'] = children
|
||||
}
|
||||
arr.push(tmp)
|
||||
}
|
||||
aes: {
|
||||
encrypt(message: string, secretKey: string): string {
|
||||
return CryptoJS.AES.encrypt(message, secretKey).toString()
|
||||
},
|
||||
decrypt(ciphertext: string, secretKey: string) {
|
||||
CryptoJS.AES.decrypt(ciphertext, secretKey).toString(CryptoJS.enc.Utf8)
|
||||
}
|
||||
},
|
||||
rsa: {
|
||||
encrypt(message: string): string {
|
||||
const encryptor = new JSEncrypt()
|
||||
encryptor.setPublicKey(config.RSA_PUBLIC_KEY)
|
||||
const encrypted = encryptor.encrypt(message)
|
||||
if (!encrypted) {
|
||||
throw new Error('RSA encrypt failed')
|
||||
}
|
||||
return encrypted;
|
||||
},
|
||||
decrypt(cipherText: string, privateKey: string): string {
|
||||
const decryptor = new JSEncrypt()
|
||||
decryptor.setPrivateKey(privateKey)
|
||||
const decrypted = decryptor.decrypt(cipherText)
|
||||
if (!decrypted) {
|
||||
throw new Error('RSA decrypt failed')
|
||||
}
|
||||
return decrypted
|
||||
}
|
||||
return arr
|
||||
},
|
||||
screen: function (element) {
|
||||
var isFull = !!(document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement);
|
||||
|
|
@ -129,16 +133,13 @@ const tools = {
|
|||
}
|
||||
return fmt;
|
||||
},
|
||||
randomUUIDString: function (len = 16) {
|
||||
return crypto.randomUUID().replace(/-/g, '').slice(0, len)
|
||||
},
|
||||
makeTreeData: function (data, pid = 0, key = "id", parent = "parent_id") {
|
||||
const arr = [];
|
||||
for (let item of data) {
|
||||
if (item[parent] == pid) {
|
||||
// 数据格式处理
|
||||
const tmp = item;
|
||||
const children = tools.makeTreeData(data, item[key], key, parent);
|
||||
const children = this.makeTreeData(data, item[key], key, parent);
|
||||
if (children.length > 0) {
|
||||
tmp['children'] = children
|
||||
}
|
||||
|
|
@ -147,6 +148,9 @@ const tools = {
|
|||
}
|
||||
return arr
|
||||
},
|
||||
randomUUIDString: function (len = 16) {
|
||||
return crypto.randomUUID().replace(/-/g, '').slice(0, len)
|
||||
},
|
||||
getBrowser(userAgent: string) {
|
||||
// 检测浏览器类型和版本
|
||||
if (/Opera|OPR/.test(userAgent)) {
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
<template>
|
||||
<el-main class="pi-page">
|
||||
<el-page-header @back="onBack" :content="title" class="header">
|
||||
<template #extra>
|
||||
<div class="flex items-center">
|
||||
<el-popconfirm title="确定删除吗?" @confirm="del">
|
||||
<template #reference>
|
||||
<el-button type="danger" circle icon="el-icon-delete" :disabled="!info.mess_id"/>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</el-page-header>
|
||||
<section v-if="info.mess_id" class="intro">
|
||||
<el-text>发布人:{{ info.nickname }}</el-text>
|
||||
<el-text v-time="info.create_time"></el-text>
|
||||
</section>
|
||||
<section v-if="info.mess_id" v-html="info.content"/>
|
||||
<section v-if="!info.mess_id" class="empty">
|
||||
<img src="@/assets/images/404.png">
|
||||
</section>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {getCurrentInstance, onMounted, ref} from "vue";
|
||||
import api from "@/api"
|
||||
import useTabs from "@/utils/useTabs"
|
||||
import {useRoute} from "vue-router"
|
||||
|
||||
defineOptions({
|
||||
name: "messageDetail"
|
||||
})
|
||||
|
||||
const {proxy} = getCurrentInstance()
|
||||
const route = useRoute()
|
||||
let info = ref({})
|
||||
let title = ref("消息不存在")
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
const res = await api.system.message.detail({id: route.query.id})
|
||||
if (res.data) {
|
||||
info.value = res.data
|
||||
title.value = info.value.title
|
||||
}
|
||||
}
|
||||
|
||||
function onBack() {
|
||||
useTabs.close()
|
||||
}
|
||||
|
||||
async function del() {
|
||||
const res = await api.system.message.remove({id: route.query.id})
|
||||
proxy.$message.success(res.msg)
|
||||
onBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pi-page {
|
||||
background: var(--el-bg-color);
|
||||
margin: 20px auto;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -186,11 +186,11 @@ async function login() {
|
|||
}
|
||||
isLogin.value = true
|
||||
const data = {
|
||||
username: form.value.username,
|
||||
password: tools.crypto.MD5(form.value.password),
|
||||
username: tools.rsa.encrypt(form.value.username),
|
||||
password: tools.rsa.encrypt(tools.md5(form.value.password)),
|
||||
uuid: form.value.uuid,
|
||||
code: form.value.code
|
||||
};
|
||||
code: tools.rsa.encrypt(form.value.code)
|
||||
}
|
||||
// 登录接口
|
||||
const [res, err] = await tools.go(api.auth.login(data))
|
||||
isLogin.value = false
|
||||
|
|
|
|||
|
|
@ -1,33 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"jsx": "preserve",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"types": [
|
||||
"node",
|
||||
"vite/client"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"types": ["node"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
]
|
||||
"allowSyntheticDefaultImports": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ interface ImportMetaEnv {
|
|||
readonly VITE_APP_TITLE: string
|
||||
readonly VITE_WS_URL: string,
|
||||
readonly VITE_APP_ENV: string
|
||||
readonly VITE_RSA_PUBLIC_KEY: string
|
||||
}
|
||||
|
||||
interface Document {
|
||||
|
|
|
|||
Loading…
Reference in New Issue