登录优化
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_API_BASE='https://demo.leapy.cn'
|
||||||
VITE_WS_URL='wss://demo.leapy.cn/ws'
|
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_API_BASE='https://demo.leapy.cn'
|
||||||
VITE_WS_URL='wss://demo.leapy.cn/ws'
|
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",
|
"element-plus": "2.13.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"image-conversion": "^2.1.1",
|
"image-conversion": "^2.1.1",
|
||||||
|
"jsencrypt": "^3.5.4",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
|
|
@ -37,6 +38,7 @@
|
||||||
"xgplayer-hls": "^3.0.23"
|
"xgplayer-hls": "^3.0.23"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.3",
|
||||||
"@vitejs/plugin-vue": "^6.0.3",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,6 @@ export default {
|
||||||
//布局 分栏:column | 通栏:header | 经典:menu | 功能坞:dock
|
//布局 分栏:column | 通栏:header | 经典:menu | 功能坞:dock
|
||||||
//dock将关闭标签和面包屑栏
|
//dock将关闭标签和面包屑栏
|
||||||
APP_LAYOUT: 'column',
|
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 api from "@/api"
|
||||||
import sRouter from './system'
|
import sRouter from './system'
|
||||||
import {treeFilter, filterAsyncRouter, flatAsyncRoutes} from '@/utils/route'
|
import {treeFilter, filterAsyncRouter, flatAsyncRoutes} from '@/utils/route'
|
||||||
import {beforeEach, afterEach} from '@/utils/route'
|
import {beforeEach, afterEach, makeMenu} from '@/utils/route'
|
||||||
import i18n from "@/locales"
|
import i18n from "@/locales"
|
||||||
|
|
||||||
//系统路由
|
//系统路由
|
||||||
|
|
@ -59,7 +59,7 @@ router.beforeEach(async (to, from, next) => {
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
const res = await api.auth.menu()
|
const res = await api.auth.menu()
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
tools.data.set("MENU", tools.makeMenu(res.data.menus, 0))
|
tools.data.set("MENU", makeMenu(res.data.menus, 0))
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
tools.data.set("PERMISSIONS", res.data.buttons)
|
tools.data.set("PERMISSIONS", res.data.buttons)
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
|
|
@ -72,7 +72,7 @@ router.beforeEach(async (to, from, next) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
let menu = [...userMenu, ...apiMenu]
|
let menu = [...userMenu, ...apiMenu]
|
||||||
var menuRouter = filterAsyncRouter(menu)
|
let menuRouter = filterAsyncRouter(menu);
|
||||||
menuRouter = flatAsyncRoutes(menuRouter)
|
menuRouter = flatAsyncRoutes(menuRouter)
|
||||||
menuRouter.forEach(item => {
|
menuRouter.forEach(item => {
|
||||||
router.addRoute("layout", item)
|
router.addRoute("layout", item)
|
||||||
|
|
|
||||||
|
|
@ -112,3 +112,29 @@ export function getMenu() {
|
||||||
})
|
})
|
||||||
return [...userMenu, ...apiMenu]
|
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 = {
|
const tools = {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -11,7 +13,9 @@ const tools = {
|
||||||
},
|
},
|
||||||
get(cacheKey: string) {
|
get(cacheKey: string) {
|
||||||
try {
|
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) {
|
if (cacheValue) {
|
||||||
let nowTime = new Date().getTime()
|
let nowTime = new Date().getTime()
|
||||||
if (nowTime > cacheValue.expireIn && cacheValue.expireIn !== 0) {
|
if (nowTime > cacheValue.expireIn && cacheValue.expireIn !== 0) {
|
||||||
|
|
@ -48,36 +52,36 @@ const tools = {
|
||||||
return [null, err]
|
return [null, err]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
crypto: {
|
md5: function (data: string) {
|
||||||
//MD5加密
|
|
||||||
MD5(data: string) {
|
|
||||||
return CryptoJS.MD5(data).toString()
|
return CryptoJS.MD5(data).toString()
|
||||||
},
|
},
|
||||||
|
aes: {
|
||||||
|
encrypt(message: string, secretKey: string): string {
|
||||||
|
return CryptoJS.AES.encrypt(message, secretKey).toString()
|
||||||
},
|
},
|
||||||
makeMenu: function (menus, pid = 0) {
|
decrypt(ciphertext: string, secretKey: string) {
|
||||||
const arr = [];
|
CryptoJS.AES.decrypt(ciphertext, secretKey).toString(CryptoJS.enc.Utf8)
|
||||||
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);
|
rsa: {
|
||||||
if (children.length > 0) {
|
encrypt(message: string): string {
|
||||||
tmp['children'] = children
|
const encryptor = new JSEncrypt()
|
||||||
|
encryptor.setPublicKey(config.RSA_PUBLIC_KEY)
|
||||||
|
const encrypted = encryptor.encrypt(message)
|
||||||
|
if (!encrypted) {
|
||||||
|
throw new Error('RSA encrypt failed')
|
||||||
}
|
}
|
||||||
arr.push(tmp)
|
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) {
|
screen: function (element) {
|
||||||
var isFull = !!(document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement);
|
var isFull = !!(document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement);
|
||||||
|
|
@ -129,16 +133,13 @@ const tools = {
|
||||||
}
|
}
|
||||||
return fmt;
|
return fmt;
|
||||||
},
|
},
|
||||||
randomUUIDString: function (len = 16) {
|
|
||||||
return crypto.randomUUID().replace(/-/g, '').slice(0, len)
|
|
||||||
},
|
|
||||||
makeTreeData: function (data, pid = 0, key = "id", parent = "parent_id") {
|
makeTreeData: function (data, pid = 0, key = "id", parent = "parent_id") {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
for (let item of data) {
|
for (let item of data) {
|
||||||
if (item[parent] == pid) {
|
if (item[parent] == pid) {
|
||||||
// 数据格式处理
|
// 数据格式处理
|
||||||
const tmp = item;
|
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) {
|
if (children.length > 0) {
|
||||||
tmp['children'] = children
|
tmp['children'] = children
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +148,9 @@ const tools = {
|
||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
},
|
},
|
||||||
|
randomUUIDString: function (len = 16) {
|
||||||
|
return crypto.randomUUID().replace(/-/g, '').slice(0, len)
|
||||||
|
},
|
||||||
getBrowser(userAgent: string) {
|
getBrowser(userAgent: string) {
|
||||||
// 检测浏览器类型和版本
|
// 检测浏览器类型和版本
|
||||||
if (/Opera|OPR/.test(userAgent)) {
|
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
|
isLogin.value = true
|
||||||
const data = {
|
const data = {
|
||||||
username: form.value.username,
|
username: tools.rsa.encrypt(form.value.username),
|
||||||
password: tools.crypto.MD5(form.value.password),
|
password: tools.rsa.encrypt(tools.md5(form.value.password)),
|
||||||
uuid: form.value.uuid,
|
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))
|
const [res, err] = await tools.go(api.auth.login(data))
|
||||||
isLogin.value = false
|
isLogin.value = false
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,13 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "node",
|
||||||
"lib": [
|
"types": ["node"],
|
||||||
"ESNext",
|
"baseUrl": "./",
|
||||||
"DOM"
|
|
||||||
],
|
|
||||||
"jsx": "preserve",
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"types": [
|
|
||||||
"node",
|
|
||||||
"vite/client"
|
|
||||||
],
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["src/*"]
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true
|
||||||
"ignoreDeprecations": "6.0"
|
}
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts",
|
|
||||||
"src/**/*.tsx",
|
|
||||||
"src/**/*.vue"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ interface ImportMetaEnv {
|
||||||
readonly VITE_APP_TITLE: string
|
readonly VITE_APP_TITLE: string
|
||||||
readonly VITE_WS_URL: string,
|
readonly VITE_WS_URL: string,
|
||||||
readonly VITE_APP_ENV: string
|
readonly VITE_APP_ENV: string
|
||||||
|
readonly VITE_RSA_PUBLIC_KEY: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue