注册全局组件SVG,集成pinia,封装axios
This commit is contained in:
3
frontend/.env.development
Normal file
3
frontend/.env.development
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
NODE_ENV='development'
|
||||||
|
VITE_TITLE=""
|
||||||
|
VITE_URL="http://localhost:8081"
|
||||||
3
frontend/.env.production
Normal file
3
frontend/.env.production
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
NODE_ENV='production'
|
||||||
|
VITE_TITLE=""
|
||||||
|
VITE_URL="http://www.test.com"
|
||||||
3
frontend/.eslintignore
Normal file
3
frontend/.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// .eslintignore 配置, 防止校验打包的产物
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
@@ -9,14 +9,15 @@
|
|||||||
"build-staging": "vite build --mode staging",
|
"build-staging": "vite build --mode staging",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "vite build",
|
|
||||||
"type-check": "vue-tsc --build --force",
|
"type-check": "vue-tsc --build --force",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"axios": "^1.7.3",
|
||||||
"element-plus": "^2.7.8",
|
"element-plus": "^2.7.8",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.2.1",
|
||||||
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-router": "^4.3.3"
|
"vue-router": "^4.3.3"
|
||||||
},
|
},
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"typescript": "~5.4.0",
|
"typescript": "~5.4.0",
|
||||||
"vite": "^5.3.1",
|
"vite": "^5.3.1",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vue-tsc": "^2.0.21"
|
"vue-tsc": "^2.0.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ defineOptions({
|
|||||||
import { ElConfigProvider } from 'element-plus'
|
import { ElConfigProvider } from 'element-plus'
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
|
|
||||||
document.getElementById('loadingPage').remove()
|
document.getElementById('loadingPage')?.remove()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 主进程与渲染进程通信频道定义
|
* 主进程与渲染进程通信频道定义
|
||||||
* Definition of communication channels between main process and rendering process
|
* Definition of communication channels between main process and rendering process
|
||||||
@@ -7,7 +6,4 @@ const ipcApiRoute = {
|
|||||||
test: 'controller.example.test',
|
test: 'controller.example.test',
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { ipcApiRoute }
|
||||||
ipcApiRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
57
frontend/src/api/user/index.ts
Normal file
57
frontend/src/api/user/index.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import request from '@/utils/http'
|
||||||
|
import { useUserInfoStore } from '@/stores/user'
|
||||||
|
import { sm3Digest } from '@/assets/commjs/sm3.js'
|
||||||
|
import { sm2, encrypt } from '@/assets/commjs/sm2.js'
|
||||||
|
|
||||||
|
// 获取公钥
|
||||||
|
export const publicKey = (params?: any) => {
|
||||||
|
if (!params) {
|
||||||
|
const userInfo = useUserInfoStore()
|
||||||
|
params = {
|
||||||
|
loginName: encrypt(userInfo.loginName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: '/user/generateSm2Key',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pwdSm3 = async (pwd: any, loginName?: string) => {
|
||||||
|
let publicKeyStr = await publicKey(
|
||||||
|
loginName ? { loginName: encrypt(loginName) } : false,
|
||||||
|
)
|
||||||
|
let sm3Pwd = sm3Digest(pwd) //SM3加密
|
||||||
|
return sm2(sm3Pwd + '|' + pwd, publicKeyStr.data, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
//登录获取token
|
||||||
|
export const login = async (params: any) => {
|
||||||
|
params.password = await pwdSm3(params.password, params.username)
|
||||||
|
params.username = encrypt(params.username)
|
||||||
|
return request({
|
||||||
|
url: '/pqs-auth/oauth/token',
|
||||||
|
method: 'post',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取用户信息
|
||||||
|
export const getUserById = () => {
|
||||||
|
const userInfo = useUserInfoStore()
|
||||||
|
return request({
|
||||||
|
url: '/user-boot/user/getUserById?id=' + userInfo.userIndex,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新token
|
||||||
|
export function refreshToken(): Promise<any> {
|
||||||
|
const userInfo = useUserInfoStore()
|
||||||
|
return login({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: userInfo.refresh_token,
|
||||||
|
username: userInfo.loginName,
|
||||||
|
})
|
||||||
|
}
|
||||||
3734
frontend/src/assets/commjs/sm2.js
Normal file
3734
frontend/src/assets/commjs/sm2.js
Normal file
File diff suppressed because it is too large
Load Diff
257
frontend/src/assets/commjs/sm3.js
Normal file
257
frontend/src/assets/commjs/sm3.js
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
/**
|
||||||
|
* 国密摘要算法(SM3)
|
||||||
|
* @param str:raw string
|
||||||
|
* @return the 256-bit hex string produced by SM3 from a raw string
|
||||||
|
*/
|
||||||
|
function sm3Digest(str) {
|
||||||
|
//1. 转换为二进制数组
|
||||||
|
var binArr = str2bin(str2rstr_utf8(str));
|
||||||
|
//2. 填充
|
||||||
|
var groupNum = alignSM3(binArr, str.length);
|
||||||
|
//3. 迭代压缩
|
||||||
|
var v = new Array(8);//初始值
|
||||||
|
v[0] = 0x7380166f;
|
||||||
|
v[1] = 0x4914b2b9;
|
||||||
|
v[2] = 0x172442d7;
|
||||||
|
v[3] = 0xda8a0600;
|
||||||
|
v[4] = 0xa96f30bc;
|
||||||
|
v[5] = 0x163138aa;
|
||||||
|
v[6] = 0xe38dee4d;
|
||||||
|
v[7] = 0xb0fb0e4e;
|
||||||
|
//按 512bit 分组进行压缩
|
||||||
|
for (var i = 0; i < groupNum; i++) {
|
||||||
|
v = compress(v, binArr, i);
|
||||||
|
}
|
||||||
|
return word2str(v, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数组转换为字符串。数组长度不定,每个元素为 32bit 的数字。
|
||||||
|
* @param words:数组,每个元素为 32bit 的数字
|
||||||
|
* @param seperator:在每个数组元素转换得到的字符串之间的分隔符
|
||||||
|
*/
|
||||||
|
function word2str(words, seperator) {
|
||||||
|
var prefix = Array(8).join('0');
|
||||||
|
for (var i = 0; i < words.length; i++) {
|
||||||
|
//若 hex 不足 8 位,则高位补 0
|
||||||
|
words[i] = (prefix + (words[i] >>> 0).toString(16)).slice(-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return words.join(seperator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符串转换为二进制数组,默认字符串编码为 UTF-8,且范围在 0x00~0xFF 内。
|
||||||
|
* 若某些字符的编码超过此范围,则会只保留最低字节。加密可正常进行,但加密结果有误。
|
||||||
|
* 每个数组元素包含 4 个字符,即 32 bit。
|
||||||
|
* @param 字符串
|
||||||
|
* @return 数组,长度为(字符串长度 / 4),每个元素为 32bit 的数字
|
||||||
|
*/
|
||||||
|
function str2bin(str) {
|
||||||
|
var binary = new Array(str.length >> 2);
|
||||||
|
for (var i = 0; i < str.length * 8; i += 8) {
|
||||||
|
binary[i >> 5] |= (str.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
|
||||||
|
}
|
||||||
|
return binary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对明文的二进制串进行填充
|
||||||
|
* <pre>
|
||||||
|
* | 满足 mod 512 = 448 | 固定 64 位 |
|
||||||
|
* | 明文二进制 |填充部分|明文二进制串的长度的二进制表示|
|
||||||
|
* xxxxxxxxxxxx 10.....0 0...........................xx
|
||||||
|
* </pre>
|
||||||
|
* @param arr:数组,每个元素为 32bit 的数字
|
||||||
|
* @param strLen:明文字符串长度
|
||||||
|
* @return 数组,每个元素为 32bit 的数字,数组长度为 16 的倍数(包括 16)
|
||||||
|
*/
|
||||||
|
function alignSM3(arr, strLen) {
|
||||||
|
//在明文二进制串后面拼接 1000 0000
|
||||||
|
arr[strLen >> 2] |= 0x80 << (24 - strLen % 4 * 8);
|
||||||
|
var groupNum = ((strLen + 8) >> 6) + 1;//以 512bit 为一组,总的组数
|
||||||
|
var wordNum = groupNum * 16;//一个 word 32bit,总的 word 数
|
||||||
|
|
||||||
|
for (var i = (strLen >> 2) + 1; i < wordNum; i++) {
|
||||||
|
arr[i] = 0;
|
||||||
|
}
|
||||||
|
arr[wordNum - 1] = strLen * 8;//在末尾填上明文的二进制长度
|
||||||
|
|
||||||
|
return groupNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩函数中的置换函数
|
||||||
|
*/
|
||||||
|
function p0(x) {
|
||||||
|
return x ^ bitRol(x, 9) ^ bitRol(x, 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩函数中的置换函数
|
||||||
|
*/
|
||||||
|
function p1(x) {
|
||||||
|
return x ^ bitRol(x, 15) ^ bitRol(x, 23);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 循环左移
|
||||||
|
*/
|
||||||
|
function bitRol(input, n) {
|
||||||
|
return (input << n) | (input >>> (32 - n));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩函数
|
||||||
|
*/
|
||||||
|
function compress(v, binArr, i) {
|
||||||
|
//将消息分组扩展成 132 个字
|
||||||
|
var w1 = new Array(68);
|
||||||
|
var w2 = new Array(64);
|
||||||
|
for (var j = 0; j < 68; j++) {
|
||||||
|
if (j < 16) {
|
||||||
|
w1[j] = binArr[i * 16 + j];
|
||||||
|
} else {
|
||||||
|
w1[j] = p1(w1[j-16] ^ w1[j-9] ^ bitRol(w1[j-3], 15)) ^ bitRol(w1[j-13], 7) ^ w1[j-6];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var j = 0; j < 64; j++) {
|
||||||
|
w2[j] = w1[j] ^ w1[j+4];
|
||||||
|
}
|
||||||
|
|
||||||
|
//压缩
|
||||||
|
var a = v[0];
|
||||||
|
var b = v[1];
|
||||||
|
var c = v[2];
|
||||||
|
var d = v[3];
|
||||||
|
var e = v[4];
|
||||||
|
var f = v[5];
|
||||||
|
var g = v[6];
|
||||||
|
var h = v[7];
|
||||||
|
var ss1;
|
||||||
|
var ss2;
|
||||||
|
var tt1;
|
||||||
|
var tt2;
|
||||||
|
for (var j = 0; j < 64; j++) {
|
||||||
|
ss1 = bitRol(addAll(bitRol(a, 12) , e , bitRol(t(j), j)), 7);
|
||||||
|
ss2 = ss1 ^ bitRol(a, 12);
|
||||||
|
tt1 = addAll(ff(a, b, c, j) , d , ss2 , w2[j]);
|
||||||
|
tt2 = addAll(gg(e, f, g, j) , h , ss1 , w1[j]);
|
||||||
|
d = c;
|
||||||
|
c = bitRol(b, 9);
|
||||||
|
b = a;
|
||||||
|
a = tt1;
|
||||||
|
h = g;
|
||||||
|
g = bitRol(f, 19);
|
||||||
|
f = e;
|
||||||
|
e = p0(tt2);
|
||||||
|
}
|
||||||
|
v[0] ^= a;
|
||||||
|
v[1] ^= b;
|
||||||
|
v[2] ^= c;
|
||||||
|
v[3] ^= d;
|
||||||
|
v[4] ^= e;
|
||||||
|
v[5] ^= f;
|
||||||
|
v[6] ^= g;
|
||||||
|
v[7] ^= h;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常量 T 随 j 的不同而不同
|
||||||
|
*/
|
||||||
|
function t(j) {
|
||||||
|
if (0 <= j && j < 16) {
|
||||||
|
return 0x79CC4519;
|
||||||
|
} else if (j < 64) {
|
||||||
|
return 0x7A879D8A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布尔函数,随 j 的变化取不同的表达式
|
||||||
|
*/
|
||||||
|
function ff(x, y, z, j) {
|
||||||
|
if (0 <= j && j < 16) {
|
||||||
|
return x ^ y ^ z;
|
||||||
|
} else if (j < 64) {
|
||||||
|
return (x & y) | (x & z) | (y & z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布尔函数,随 j 的变化取不同的表达式
|
||||||
|
*/
|
||||||
|
function gg(x, y, z, j) {
|
||||||
|
if (0 <= j && j < 16) {
|
||||||
|
return x ^ y ^ z;
|
||||||
|
} else if (j < 64) {
|
||||||
|
return (x & y) | (~x & z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 两数相加
|
||||||
|
* 避免某些 js 引擎的 32 位加法的 bug
|
||||||
|
*/
|
||||||
|
function safe_add(x, y) {
|
||||||
|
var lsw = ( x & 0xFFFF ) + (y & 0xFFFF);
|
||||||
|
var msw = ( x >> 16 ) + (y >> 16) + (lsw >> 16);
|
||||||
|
return (msw << 16) | ( lsw & 0xFFFF );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将所有参数相加
|
||||||
|
*/
|
||||||
|
function addAll() {
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < arguments.length; i++) {
|
||||||
|
sum = safe_add(sum, arguments[i]);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTF-16 --> UTF-8
|
||||||
|
*/
|
||||||
|
function str2rstr_utf8(input) {
|
||||||
|
var output = "" ;
|
||||||
|
var i = -1 ;
|
||||||
|
var x, y ;
|
||||||
|
|
||||||
|
while(++ i < input.length) {
|
||||||
|
//按 UTF-16 解码
|
||||||
|
x = input.charCodeAt(i);
|
||||||
|
y = i + 1 < input.length ? input .charCodeAt (i + 1) : 0 ;
|
||||||
|
if( 0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF ) {
|
||||||
|
x = 0x10000 + ((x & 0x03FF) << 10 ) + (y & 0x03FF);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//按 UTF-8 编码
|
||||||
|
if( x <= 0x7F ) {
|
||||||
|
output += String.fromCharCode(x);
|
||||||
|
}
|
||||||
|
else if(x <= 0x7FF) {
|
||||||
|
output += String.fromCharCode(
|
||||||
|
0xC0 | ((x >>> 6 ) & 0x1F),
|
||||||
|
0x80 | ( x & 0x3F ));
|
||||||
|
} else if(x <= 0xFFFF) {
|
||||||
|
output += String.fromCharCode(
|
||||||
|
0xE0 | ((x >>> 12) & 0x0F ),
|
||||||
|
0x80 | ((x >>> 6 ) & 0x3F),
|
||||||
|
0x80 | ( x & 0x3F ));
|
||||||
|
} else if(x <= 0x1FFFFF) {
|
||||||
|
output += String.fromCharCode(
|
||||||
|
0xF0 | ((x >>> 18) & 0x07 ),
|
||||||
|
0x80 | ((x >>> 12) & 0x3F),
|
||||||
|
0x80 | ((x >>> 6 ) & 0x3F),
|
||||||
|
0x80 | ( x & 0x3F ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
sm3Digest
|
||||||
|
}
|
||||||
|
|
||||||
1
frontend/src/assets/icons/wind.svg
Normal file
1
frontend/src/assets/icons/wind.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg t="1723085514271" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1125" width="200" height="200"><path d="M765.22 393.6H184.15c-8.28 0-15-6.72-15-15s6.72-15 15-15h581.07c37.38 0 67.8-30.41 67.8-67.8s-30.41-67.8-67.8-67.8-67.8 30.41-67.8 67.8c0 8.28-6.72 15-15 15s-15-6.72-15-15c0-53.93 43.87-97.8 97.8-97.8s97.8 43.87 97.8 97.8-43.87 97.8-97.8 97.8zM783.99 809.79c-43.58 0-79.03-35.45-79.03-79.03 0-8.28 6.72-15 15-15s15 6.72 15 15c0 27.04 22 49.03 49.03 49.03s49.03-22 49.03-49.03-22-49.03-49.03-49.03H297.67c-8.28 0-15-6.72-15-15s6.72-15 15-15h486.32c43.58 0 79.03 35.45 79.03 79.03s-35.45 79.03-79.03 79.03z" fill="#333333" p-id="1126"></path><path d="M481.66 306.63H312.79c-8.28 0-15-6.72-15-15s6.72-15 15-15h168.87c8.28 0 15 6.72 15 15s-6.72 15-15 15zM227.46 681.49h-43c-8.28 0-15-6.72-15-15s6.72-15 15-15h43c8.28 0 15 6.72 15 15s-6.72 15-15 15zM777.81 598.8h-43c-8.28 0-15-6.72-15-15s6.72-15 15-15h43c8.28 0 15 6.72 15 15s-6.72 15-15 15zM383.78 761H241.87c-8.28 0-15-6.72-15-15s6.72-15 15-15h141.91c8.28 0 15 6.72 15 15s-6.72 15-15 15zM848.02 452.99H613.98c-8.28 0-15-6.72-15-15s6.72-15 15-15h234.04c8.28 0 15 6.72 15 15s-6.72 15-15 15z" fill="#333333" p-id="1127"></path><path d="M492 535.33m-243.33 0a243.33 243.33 0 1 0 486.66 0 243.33 243.33 0 1 0-486.66 0Z" fill="#FFFFFF" p-id="1128"></path><path d="M492 793.67c-69 0-133.88-26.87-182.67-75.66-48.79-48.79-75.66-113.67-75.66-182.67s26.87-133.88 75.66-182.67C358.12 303.88 423 277.01 492 277.01s133.88 26.87 182.67 75.66c48.79 48.79 75.66 113.67 75.66 182.67s-26.87 133.88-75.66 182.67C625.88 766.8 561 793.67 492 793.67zM492 307c-60.99 0-118.33 23.75-161.46 66.88-43.13 43.13-66.88 100.47-66.88 161.46s23.75 118.33 66.88 161.46c43.13 43.13 100.47 66.88 161.46 66.88s118.33-23.75 161.46-66.88 66.88-100.47 66.88-161.46-23.75-118.33-66.88-161.46S552.99 307 492 307z" fill="#333333" p-id="1129"></path><path d="M583.85 517.56l-58.52-11.99 24.71-97.1c5.39-21.17-20.94-35.62-35.9-19.71L393.3 517.17c-11.35 12.06-5.06 31.89 11.16 35.22l58.52 11.99-24.71 97.1c-5.39 21.17 20.94 35.62 35.9 19.71l120.84-128.41c11.35-12.06 5.06-31.89-11.16-35.22z" fill="#52E217" p-id="1130"></path><path d="M458.92 702.95c-5.94 0-11.96-1.5-17.55-4.57-14.68-8.06-21.77-24.38-17.64-40.6l20.83-81.86-43.12-8.83c-13.02-2.67-23.19-11.82-27.21-24.49s-0.97-26.02 8.13-35.7L503.2 378.49c11.48-12.2 29.05-14.97 43.73-6.92 14.68 8.06 21.77 24.38 17.64 40.6l-20.83 81.86 43.12 8.83c13.02 2.67 23.19 11.82 27.21 24.49s0.97 26.02-8.13 35.7L485.1 691.46c-7.1 7.54-16.53 11.48-26.17 11.49z m70.43-305.96c-1.33 0-2.84 0.5-4.3 2.04L404.21 527.44c-2.15 2.29-1.79 4.79-1.38 6.07 0.41 1.29 1.55 3.54 4.63 4.17l58.52 11.99c4.02 0.82 7.52 3.25 9.7 6.73 2.18 3.48 2.84 7.69 1.83 11.66l-24.71 97.1c-1.02 4.02 1.44 6.05 3 6.91 1.56 0.86 4.6 1.84 7.44-1.18l120.84-128.41c2.15-2.29 1.79-4.79 1.38-6.07-0.41-1.29-1.55-3.54-4.63-4.17l-58.52-11.99c-4.02-0.82-7.52-3.25-9.7-6.73a14.993 14.993 0 0 1-1.83-11.66l24.71-97.1c1.02-4.02-1.44-6.05-3-6.91-0.76-0.42-1.87-0.87-3.14-0.87z" fill="#333333" p-id="1131"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
19
frontend/src/components/index.ts
Normal file
19
frontend/src/components/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { App, Component } from 'vue'
|
||||||
|
|
||||||
|
// 当组件很多的时候,可以使用
|
||||||
|
import { SvgIcon } from './staticExtend/SvgIcon'
|
||||||
|
|
||||||
|
// 这个地方将合并到对象中
|
||||||
|
const Components: {
|
||||||
|
[propName: string]: Component
|
||||||
|
} = { SvgIcon }
|
||||||
|
|
||||||
|
// 批量注册全局组件
|
||||||
|
export default {
|
||||||
|
install: (app: App) => {
|
||||||
|
Object.keys(Components).forEach((key) => {
|
||||||
|
app.component(key, Components[key])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
2
frontend/src/components/staticExtend/SvgIcon/index.ts
Normal file
2
frontend/src/components/staticExtend/SvgIcon/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import SvgIcon from './src/SvgIcon.vue'
|
||||||
|
export { SvgIcon }
|
||||||
89
frontend/src/components/staticExtend/SvgIcon/src/SvgIcon.vue
Normal file
89
frontend/src/components/staticExtend/SvgIcon/src/SvgIcon.vue
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* @name SvgIcon
|
||||||
|
* @description svg图标组件
|
||||||
|
* 支持定义名称、颜色、大小、旋转
|
||||||
|
* @example <SvgIcon name="icon-name" color="#fff" size="20" spin />
|
||||||
|
*/
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
:class="['svg-icon', spin && 'svg-icon-spin']"
|
||||||
|
:style="getStyle"
|
||||||
|
>
|
||||||
|
<use :xlink:href="symbolId" :fill="color" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
<script setup lang='ts'>
|
||||||
|
defineOptions({
|
||||||
|
name: 'SvgIcon',
|
||||||
|
})
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type { CSSProperties } from 'vue'
|
||||||
|
// 定义组件对外暴露的props
|
||||||
|
const props = defineProps({
|
||||||
|
prefix: {
|
||||||
|
type: String,
|
||||||
|
default: 'icon',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 20,
|
||||||
|
},
|
||||||
|
spin: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
//计算属性获取symbolId
|
||||||
|
const symbolId = computed(() => {
|
||||||
|
return `#${props.prefix}-${props.name}`
|
||||||
|
})
|
||||||
|
|
||||||
|
//计算属性获取svg样式
|
||||||
|
const getStyle = computed((): CSSProperties => {
|
||||||
|
const { size } = props
|
||||||
|
let s = `${size}`
|
||||||
|
// 确保size为px单位
|
||||||
|
s = `${s.replace('px', '')}px`
|
||||||
|
return {
|
||||||
|
width: s,
|
||||||
|
height: s,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon-spin {
|
||||||
|
animation: loadingCircle 1.2s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旋转动画 */
|
||||||
|
@keyframes loadingCircle {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,2 +1 @@
|
|||||||
/*文件说明:本文件用来定义一些本地缓存的key*/
|
/*文件说明:本文件用来定义一些本地缓存的key*/
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
import {createApp} from 'vue'
|
|
||||||
// element-plus
|
// element-plus
|
||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
|
||||||
|
|
||||||
// 使用pinia
|
// 使用pinia
|
||||||
import { createPinia } from 'pinia'
|
import pinia from '@/stores'
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import Router from './router/index';
|
import Router from './router/index'
|
||||||
|
|
||||||
// 引入tailwindcss
|
// 引入tailwindcss
|
||||||
import '@/assets/styles/tailMain.css'
|
import '@/assets/styles/tailMain.css'
|
||||||
|
|
||||||
|
// 导入全局注册的组件
|
||||||
|
import 'virtual:svg-icons-register'
|
||||||
|
import registerGlobComp from '@/components'
|
||||||
|
|
||||||
//创建实例
|
//创建实例
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
const setupAll = async () => {
|
const setupAll = async () => {
|
||||||
app
|
app
|
||||||
.use(Router) // 使用路由
|
.use(Router) // 使用路由
|
||||||
.use(ElementPlus) // 使用ele-plus组件
|
.use(ElementPlus) // 使用ele-plus组件
|
||||||
.use(createPinia()) // 使用pinia
|
.use(pinia) // 使用pinia
|
||||||
|
.use(registerGlobComp) // 使用全局自定义组件
|
||||||
|
|
||||||
// 自动引入图标
|
|
||||||
Object.keys(ElementPlusIconsVue).forEach((key) => {
|
|
||||||
app.component(key, ElementPlusIconsVue[key]);
|
|
||||||
});
|
|
||||||
|
|
||||||
//待路由初始化完毕后,挂载app
|
//待路由初始化完毕后,挂载app
|
||||||
await Router.isReady()
|
await Router.isReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//挂载app
|
//挂载app
|
||||||
setupAll().then(() => {
|
setupAll().then(() => {
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,11 @@ const Router = createRouter({
|
|||||||
routes: routerMap,
|
routes: routerMap,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
Router.beforeEach((to, from, next) => {
|
Router.beforeEach((to, from, next) => {
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 路由加载后
|
// 路由加载后
|
||||||
Router.afterEach(() => {
|
Router.afterEach(() => {})
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
export default Router
|
export default Router
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ const constantRouterMap = [
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: Login
|
component: Login,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default constantRouterMap
|
export default constantRouterMap
|
||||||
|
|||||||
14
frontend/src/stores/constant/cacheKey.ts
Normal file
14
frontend/src/stores/constant/cacheKey.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 本地缓存Key
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
export const USER_INFO = 'userInfo'
|
||||||
|
|
||||||
|
// WEB端布局配置
|
||||||
|
export const STORE_CONFIG = 'storeConfig'
|
||||||
|
|
||||||
|
// 后台标签页
|
||||||
|
export const STORE_TAB_VIEW_CONFIG = 'storeTabViewConfig'
|
||||||
|
// 字典
|
||||||
|
export const DICT_DATA = 'dictData'
|
||||||
3
frontend/src/stores/index.ts
Normal file
3
frontend/src/stores/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createPinia } from 'pinia'
|
||||||
|
const pinia = createPinia()
|
||||||
|
export default pinia
|
||||||
95
frontend/src/stores/interface/index.ts
Normal file
95
frontend/src/stores/interface/index.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// 变量名对应含义请在 /stores/* 里边找
|
||||||
|
import type { RouteRecordRaw, RouteLocationNormalized } from 'vue-router'
|
||||||
|
|
||||||
|
export interface Layout {
|
||||||
|
showDrawer: boolean
|
||||||
|
shrink: boolean
|
||||||
|
layoutMode: string
|
||||||
|
mainAnimation: string
|
||||||
|
isDark: boolean
|
||||||
|
menuWidth: number
|
||||||
|
menuDefaultIcon: string
|
||||||
|
menuCollapse: boolean
|
||||||
|
menuUniqueOpened: boolean
|
||||||
|
menuShowTopBar: boolean
|
||||||
|
elementUiPrimary: string[]
|
||||||
|
tableHeaderBackground: string[]
|
||||||
|
tableHeaderColor: string[]
|
||||||
|
tableCurrent: string[]
|
||||||
|
menuBackground: string[]
|
||||||
|
menuColor: string[]
|
||||||
|
menuActiveBackground: string[]
|
||||||
|
menuActiveColor: string[]
|
||||||
|
menuTopBarBackground: string[]
|
||||||
|
headerBarTabColor: string[]
|
||||||
|
headerBarBackground: string[]
|
||||||
|
headerBarHoverBackground: string[]
|
||||||
|
headerBarTabActiveBackground: string[]
|
||||||
|
headerBarTabActiveColor: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavTabs {
|
||||||
|
activeIndex: number
|
||||||
|
activeRoute: RouteLocationNormalized | null
|
||||||
|
tabsView: RouteLocationNormalized[]
|
||||||
|
tabFullScreen: boolean
|
||||||
|
tabsViewRoutes: RouteRecordRaw[]
|
||||||
|
authNode: Map<string, string[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
export interface UserInfo {
|
||||||
|
access_token: string
|
||||||
|
token_type: string
|
||||||
|
refresh_token: string
|
||||||
|
expires_in: number
|
||||||
|
scope: string
|
||||||
|
nickname: string
|
||||||
|
userType: number
|
||||||
|
deptIndex: string
|
||||||
|
userIndex: string
|
||||||
|
client_id: string
|
||||||
|
headSculpture: any
|
||||||
|
jti: string
|
||||||
|
name: string
|
||||||
|
deptId: string
|
||||||
|
phone: string
|
||||||
|
email: string
|
||||||
|
limitIpStart: string
|
||||||
|
limitIpEnd: string
|
||||||
|
limitTime: string
|
||||||
|
casualUser: number
|
||||||
|
type: number
|
||||||
|
smsNotice: number
|
||||||
|
emailNotice: number
|
||||||
|
role: string[]
|
||||||
|
devCode: any
|
||||||
|
id: string
|
||||||
|
loginName: string
|
||||||
|
state: number
|
||||||
|
registerTime: string
|
||||||
|
loginTime: string
|
||||||
|
deptName: string
|
||||||
|
areaId: string
|
||||||
|
areaName: string
|
||||||
|
deptLevel: number
|
||||||
|
roleList: string[]
|
||||||
|
roleCode: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典数据
|
||||||
|
export interface DictData {
|
||||||
|
basic: BasicDictData[]
|
||||||
|
area: BasicDictData[]
|
||||||
|
areaTree: BasicDictData[]
|
||||||
|
userList: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BasicDictData {
|
||||||
|
name: string
|
||||||
|
id: string
|
||||||
|
code: string
|
||||||
|
value: null
|
||||||
|
sort: number | null
|
||||||
|
children?: BasicDictData[]
|
||||||
|
}
|
||||||
75
frontend/src/stores/user.ts
Normal file
75
frontend/src/stores/user.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { USER_INFO } from '@/stores/constant/cacheKey'
|
||||||
|
import type { UserInfo } from '@/stores/interface'
|
||||||
|
|
||||||
|
export const useUserInfoStore = defineStore('userInfo', {
|
||||||
|
// 行为
|
||||||
|
actions: {
|
||||||
|
dataFill(state: UserInfo) {
|
||||||
|
this.$state = { ...this.$state, ...state }
|
||||||
|
},
|
||||||
|
removeToken() {
|
||||||
|
this.access_token = ''
|
||||||
|
this.refresh_token = ''
|
||||||
|
},
|
||||||
|
setToken(token: string, type: 'auth' | 'refresh') {
|
||||||
|
const field = type == 'auth' ? 'token' : 'refresh_token'
|
||||||
|
this[field] = token
|
||||||
|
},
|
||||||
|
getToken(type: 'auth' | 'refresh' = 'auth') {
|
||||||
|
if (type === 'auth') {
|
||||||
|
if (this.token_type && this.access_token) {
|
||||||
|
return this.token_type + ' ' + this.access_token
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.refresh_token
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 状态
|
||||||
|
state: (): UserInfo => {
|
||||||
|
return {
|
||||||
|
access_token: '',
|
||||||
|
token_type: '',
|
||||||
|
refresh_token: '',
|
||||||
|
expires_in: 0,
|
||||||
|
scope: '',
|
||||||
|
nickname: '',
|
||||||
|
userType: 0,
|
||||||
|
deptIndex: '',
|
||||||
|
userIndex: '',
|
||||||
|
client_id: '',
|
||||||
|
headSculpture: '',
|
||||||
|
jti: '',
|
||||||
|
name: '',
|
||||||
|
deptId: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
limitIpStart: '',
|
||||||
|
limitIpEnd: '',
|
||||||
|
limitTime: '',
|
||||||
|
casualUser: 0,
|
||||||
|
type: 0,
|
||||||
|
smsNotice: 0,
|
||||||
|
emailNotice: 0,
|
||||||
|
role: [],
|
||||||
|
devCode: '',
|
||||||
|
id: '',
|
||||||
|
loginName: '',
|
||||||
|
state: 0,
|
||||||
|
registerTime: '',
|
||||||
|
loginTime: '',
|
||||||
|
deptName: '',
|
||||||
|
areaId: '',
|
||||||
|
areaName: '',
|
||||||
|
deptLevel: 0,
|
||||||
|
roleList: [],
|
||||||
|
roleCode: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persist: {
|
||||||
|
key: USER_INFO,
|
||||||
|
},
|
||||||
|
})
|
||||||
33
frontend/src/types/global.d.ts
vendored
Normal file
33
frontend/src/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
interface Window {
|
||||||
|
XEUtils: Record<string, any>
|
||||||
|
existLoading: boolean
|
||||||
|
lazy: number
|
||||||
|
unique: number
|
||||||
|
tokenRefreshing: boolean
|
||||||
|
requests: Function[]
|
||||||
|
eventSource: EventSource
|
||||||
|
loadLangHandle: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface anyObj {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface treeData {
|
||||||
|
id?: string
|
||||||
|
value?: string
|
||||||
|
name?: string
|
||||||
|
label?: string
|
||||||
|
children: treeData[]
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface ApiResponse<T = any> {
|
||||||
|
code: number
|
||||||
|
data: T
|
||||||
|
msg: string
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiPromise<T = any> = Promise<ApiResponse<T>>
|
||||||
229
frontend/src/utils/http/index.ts
Normal file
229
frontend/src/utils/http/index.ts
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
import type { AxiosRequestConfig, Method } from 'axios'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { ElLoading, ElNotification, type LoadingOptions } from 'element-plus'
|
||||||
|
import { refreshToken } from '@/api/user'
|
||||||
|
import router from '@/router/index'
|
||||||
|
import { useUserInfoStore } from '@/stores/user'
|
||||||
|
|
||||||
|
window.requests = []
|
||||||
|
window.tokenRefreshing = false
|
||||||
|
const pendingMap = new Map()
|
||||||
|
const loadingInstance: LoadingInstance = {
|
||||||
|
target: null,
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建`Axios`
|
||||||
|
* 默认开启`reductDataFormat(简洁响应)`,返回类型为`ApiPromise`
|
||||||
|
* 关闭`reductDataFormat`,返回类型则为`AxiosPromise`
|
||||||
|
*/
|
||||||
|
function createAxios<Data = any, T = ApiPromise<Data>>(
|
||||||
|
axiosConfig: AxiosRequestConfig,
|
||||||
|
options: Options = {},
|
||||||
|
loading: LoadingOptions = {},
|
||||||
|
): T {
|
||||||
|
const userInfo = useUserInfoStore()
|
||||||
|
|
||||||
|
const Axios = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_URL,
|
||||||
|
timeout: 1000 * 60 * 5,
|
||||||
|
headers: {},
|
||||||
|
responseType: 'json',
|
||||||
|
})
|
||||||
|
|
||||||
|
options = Object.assign(
|
||||||
|
{
|
||||||
|
CancelDuplicateRequest: true, // 是否开启取消重复请求, 默认为 true
|
||||||
|
loading: false, // 是否开启loading层效果, 默认为false
|
||||||
|
reductDataFormat: true, // 是否开启简洁的数据结构响应, 默认为true
|
||||||
|
showErrorMessage: true, // 是否开启接口错误信息展示,默认为true
|
||||||
|
showCodeMessage: true, // 是否开启code不为1时的信息提示, 默认为true
|
||||||
|
showSuccessMessage: false, // 是否开启code为1时的信息提示, 默认为false
|
||||||
|
anotherToken: '', // 当前请求使用另外的用户token
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 请求拦截
|
||||||
|
Axios.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
removePending(config)
|
||||||
|
options.CancelDuplicateRequest && addPending(config)
|
||||||
|
// 创建loading实例
|
||||||
|
if (options.loading) {
|
||||||
|
loadingInstance.count++
|
||||||
|
if (loadingInstance.count === 1) {
|
||||||
|
loadingInstance.target = ElLoading.service(loading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 自动携带token
|
||||||
|
if (config.headers) {
|
||||||
|
const token = userInfo.getToken()
|
||||||
|
if (token) {
|
||||||
|
;(config.headers as any).Authorization = token
|
||||||
|
} else {
|
||||||
|
config.headers.Authorization = 'Basic bmpjbnRlc3Q6bmpjbnBxcw=='
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截
|
||||||
|
Axios.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
removePending(response.config)
|
||||||
|
options.loading && closeLoading(options) // 关闭loading
|
||||||
|
|
||||||
|
if (
|
||||||
|
response.data.code === 'A0000' ||
|
||||||
|
response.data.type === 'application/json'
|
||||||
|
) {
|
||||||
|
return options.reductDataFormat ? response.data : response
|
||||||
|
} else if (response.data.code == 'A0202') {
|
||||||
|
if (!window.tokenRefreshing) {
|
||||||
|
window.tokenRefreshing = true
|
||||||
|
return refreshToken()
|
||||||
|
.then(res => {
|
||||||
|
userInfo.setToken(res.data.token, 'auth')
|
||||||
|
response.headers.Authorization = `${res.data.token}`
|
||||||
|
window.requests.forEach(cb => cb(res.data.token))
|
||||||
|
window.requests = []
|
||||||
|
return Axios(response.config)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
userInfo.removeToken()
|
||||||
|
router.push({ name: 'login' })
|
||||||
|
return Promise.reject(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
window.tokenRefreshing = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
// 用函数形式将 resolve 存入,等待刷新后再执行
|
||||||
|
window.requests.push((token: string) => {
|
||||||
|
response.headers.Authorization = `${token}`
|
||||||
|
resolve(Axios(response.config))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (response.data.code == 'A0024') {
|
||||||
|
userInfo.removeToken()
|
||||||
|
router.push({ name: 'login' })
|
||||||
|
return Promise.reject(response.data)
|
||||||
|
} else {
|
||||||
|
if (options.showCodeMessage) {
|
||||||
|
ElNotification({
|
||||||
|
type: 'error',
|
||||||
|
message: response.data.message || '未知错误',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Promise.reject(response.data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
error.config && removePending(error.config)
|
||||||
|
options.loading && closeLoading(options) // 关闭loading
|
||||||
|
return Promise.reject(error) // 错误继续返回给到具体页面
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return Axios(axiosConfig) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createAxios
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭Loading层实例
|
||||||
|
*/
|
||||||
|
function closeLoading(options: Options) {
|
||||||
|
if (options.loading && loadingInstance.count > 0) loadingInstance.count--
|
||||||
|
if (loadingInstance.count === 0) {
|
||||||
|
loadingInstance.target.close()
|
||||||
|
loadingInstance.target = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 储存每个请求的唯一cancel回调, 以此为标识
|
||||||
|
*/
|
||||||
|
function addPending(config: AxiosRequestConfig) {
|
||||||
|
const pendingKey = getPendingKey(config)
|
||||||
|
config.cancelToken =
|
||||||
|
config.cancelToken ||
|
||||||
|
new axios.CancelToken(cancel => {
|
||||||
|
if (!pendingMap.has(pendingKey)) {
|
||||||
|
pendingMap.set(pendingKey, cancel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除重复的请求
|
||||||
|
*/
|
||||||
|
function removePending(config: AxiosRequestConfig) {
|
||||||
|
const pendingKey = getPendingKey(config)
|
||||||
|
if (pendingMap.has(pendingKey)) {
|
||||||
|
const cancelToken = pendingMap.get(pendingKey)
|
||||||
|
cancelToken(pendingKey)
|
||||||
|
pendingMap.delete(pendingKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成每个请求的唯一key
|
||||||
|
*/
|
||||||
|
function getPendingKey(config: AxiosRequestConfig) {
|
||||||
|
let { data } = config
|
||||||
|
const { url, method, params, headers } = config
|
||||||
|
if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象
|
||||||
|
return [
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
headers && (headers as anyObj).Authorization ? (headers as anyObj).Authorization : '',
|
||||||
|
headers && (headers as anyObj)['ba-user-token'] ? (headers as anyObj)['ba-user-token'] : '',
|
||||||
|
JSON.stringify(params),
|
||||||
|
JSON.stringify(data),
|
||||||
|
].join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据请求方法组装请求数据/参数
|
||||||
|
*/
|
||||||
|
export function requestPayload(method: Method, data: anyObj) {
|
||||||
|
if (method == 'GET') {
|
||||||
|
return {
|
||||||
|
params: data,
|
||||||
|
}
|
||||||
|
} else if (method == 'POST') {
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoadingInstance {
|
||||||
|
target: any
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
// 是否开启取消重复请求, 默认为 true
|
||||||
|
CancelDuplicateRequest?: boolean
|
||||||
|
// 是否开启loading层效果, 默认为false
|
||||||
|
loading?: boolean
|
||||||
|
// 是否开启简洁的数据结构响应, 默认为true
|
||||||
|
reductDataFormat?: boolean
|
||||||
|
// 是否开启code不为A0000时的信息提示, 默认为true
|
||||||
|
showCodeMessage?: boolean
|
||||||
|
// 是否开启code为0时的信息提示, 默认为false
|
||||||
|
showSuccessMessage?: boolean
|
||||||
|
// 当前请求使用另外的用户token
|
||||||
|
anotherToken?: string
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
const Renderer = (window.require && window.require('electron')) || window.electron || {};
|
const Renderer =
|
||||||
|
(window.require && window.require('electron')) || window.electron || {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ipc
|
* ipc
|
||||||
* 官方api说明:https://www.electronjs.org/zh/docs/latest/api/ipc-renderer
|
* 官方api说明:https://www.electronjs.org/zh/docs/latest/api/ipc-renderer
|
||||||
*
|
*
|
||||||
* 属性/方法
|
* 属性/方法
|
||||||
* ipc.invoke(channel, param) - 发送异步消息(invoke/handle 模型)
|
* ipc.invoke(channel, param) - 发送异步消息(invoke/handle 模型)
|
||||||
* ipc.sendSync(channel, param) - 发送同步消息(send/on 模型)
|
* ipc.sendSync(channel, param) - 发送同步消息(send/on 模型)
|
||||||
@@ -20,14 +21,11 @@ const Renderer = (window.require && window.require('electron')) || window.electr
|
|||||||
/**
|
/**
|
||||||
* ipc
|
* ipc
|
||||||
*/
|
*/
|
||||||
const ipc = Renderer.ipcRenderer || undefined;
|
const ipc = Renderer.ipcRenderer || undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否为EE环境
|
* 是否为EE环境
|
||||||
*/
|
*/
|
||||||
const isEE = ipc ? true : false;
|
const isEE = ipc ? true : false
|
||||||
|
|
||||||
export {
|
|
||||||
Renderer, ipc, isEE
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export { Renderer, ipc, isEE }
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div></div>
|
|
||||||
</template>
|
|
||||||
<script setup lang='ts'>
|
|
||||||
defineOptions({
|
|
||||||
name: "componentName"
|
|
||||||
})
|
|
||||||
import {ref, reactive} from 'vue'
|
|
||||||
let select = ref(1)
|
|
||||||
|
|
||||||
defineExpose({ select })
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,32 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="loginContent" class="text-xl text-red-600">
|
<div ref="loginContent" class="text-xl text-red-600">
|
||||||
<!--用户名密码,暂时不做图形验证码-->
|
|
||||||
<h1 ref="loginTitle">123</h1>
|
<h1 ref="loginTitle">123</h1>
|
||||||
<Child ref="child"/>
|
<svg-icon name="wind" color="#fff" size="100" spin > </svg-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
defineOptions({
|
defineOptions(
|
||||||
name: "Login"
|
{
|
||||||
})
|
name: "CustomLogin"
|
||||||
import {ref, reactive,onMounted} from 'vue'
|
}
|
||||||
import Child from './Child.vue'
|
)
|
||||||
|
import {ref, onMounted} from 'vue'
|
||||||
let loginContent = ref()
|
let loginContent = ref()
|
||||||
let loginTitle = ref()
|
let loginTitle = ref()
|
||||||
let child = ref()
|
|
||||||
|
|
||||||
function showLog(){
|
|
||||||
console.log(child.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
showLog()
|
|
||||||
|
|
||||||
console.log(loginTitle.value);
|
|
||||||
console.log(child.value);
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log(loginTitle.value);
|
console.log(loginTitle.value);
|
||||||
console.log(child.value);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ["index.html", "./src/**/*.{html,js,ts,jsx,tsx,vue}"],
|
content: ['index.html', './src/**/*.{html,js,ts,jsx,tsx,vue}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": [
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"env.d.ts",
|
||||||
|
"src/**/*",
|
||||||
|
"src/**/*.vue"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"src/**/__tests__/*"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
"noEmit": false,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,36 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"declaration": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"allowJs": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["esnext", "DOM"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": false,
|
||||||
"types": [
|
"types": [
|
||||||
"node"
|
"node",
|
||||||
]
|
"element-plus/global"
|
||||||
}
|
],
|
||||||
|
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
|
||||||
|
"paths": { //路径映射,相对于baseUrl
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"allowSyntheticDefaultImports": true // 允许默认导入
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"vite.config.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules", "dist", "**/*.js"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,8 @@
|
|||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"noEmit": true,
|
"noEmit": false,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
|
|||||||
@@ -1,44 +1,56 @@
|
|||||||
import { defineConfig } from 'vite'
|
import {defineConfig, loadEnv} from 'vite'
|
||||||
|
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig((config) => {
|
||||||
plugins: [
|
// 根据当前工作目录中的 `mode` 加载 .env 文件
|
||||||
vue(),
|
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
|
||||||
],
|
const {command, mode} = config
|
||||||
// 基础配置
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
base: './',
|
return {
|
||||||
publicDir: 'public',
|
plugins: [
|
||||||
resolve: {
|
vue(),
|
||||||
alias: {
|
// svg图标配置,可以使用svg图标
|
||||||
'@': path.resolve(__dirname, 'src')
|
createSvgIconsPlugin({
|
||||||
}
|
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
|
||||||
},
|
symbolId: 'icon-[dir]-[name]',
|
||||||
css: {
|
}),
|
||||||
preprocessorOptions: {
|
],
|
||||||
less: {
|
// 基础配置
|
||||||
modifyVars: {
|
base: './',
|
||||||
'@border-color-base': '#dce3e8',
|
publicDir: 'public',
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
javascriptEnabled: true,
|
css: {
|
||||||
},
|
preprocessorOptions: {
|
||||||
},
|
less: {
|
||||||
},
|
modifyVars: {
|
||||||
build: {
|
'@border-color-base': '#dce3e8',
|
||||||
outDir: 'dist',
|
},
|
||||||
assetsDir: 'assets',
|
javascriptEnabled: true,
|
||||||
assetsInlineLimit: 4096,
|
},
|
||||||
cssCodeSplit: true,
|
},
|
||||||
brotliSize: false,
|
},
|
||||||
sourcemap: false,
|
build: {
|
||||||
minify: 'terser',
|
outDir: 'dist',
|
||||||
terserOptions: {
|
assetsDir: 'assets',
|
||||||
compress: {
|
assetsInlineLimit: 4096,
|
||||||
// 生产环境去除console及debug
|
cssCodeSplit: true,
|
||||||
drop_console: false,
|
sourcemap: false,
|
||||||
drop_debugger: true,
|
minify: 'terser',
|
||||||
},
|
terserOptions: {
|
||||||
},
|
compress: {
|
||||||
},
|
// 生产环境去除console及debug
|
||||||
|
drop_console: false,
|
||||||
|
drop_debugger: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user