init
This commit is contained in:
14
frontend/.editorconfig
Normal file
14
frontend/.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
# https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
4
frontend/.env.development
Normal file
4
frontend/.env.development
Normal file
@@ -0,0 +1,4 @@
|
||||
VITE_TITLE="NPQS9100-自动检测平台工具箱"
|
||||
|
||||
VITE_RSA_PUBLIC_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"
|
||||
VITE_RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="
|
||||
3
frontend/.env.production
Normal file
3
frontend/.env.production
Normal file
@@ -0,0 +1,3 @@
|
||||
VITE_TITLE="NPQS9100-自动检测平台工具箱"
|
||||
VITE_RSA_PUBLIC_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"
|
||||
VITE_RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="
|
||||
6
frontend/.gitignore
vendored
Normal file
6
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
package-lock.json
|
||||
105
frontend/index.html
Normal file
105
frontend/index.html
Normal file
@@ -0,0 +1,105 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<title></title>
|
||||
<!-- 优化:vue渲染未完成之前,先加一个css动画 -->
|
||||
<style>
|
||||
#loadingPage {
|
||||
background-color: #dedede;
|
||||
font-size: 12px;
|
||||
}
|
||||
.base {
|
||||
height: 9em;
|
||||
left: 50%;
|
||||
margin: -7.5em;
|
||||
padding: 3em;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 9em;
|
||||
transform: rotateX(45deg) rotateZ(45deg);
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.cube,
|
||||
.cube:after,
|
||||
.cube:before {
|
||||
content: '';
|
||||
float: left;
|
||||
height: 3em;
|
||||
position: absolute;
|
||||
width: 3em;
|
||||
}
|
||||
/* Top */
|
||||
.cube {
|
||||
background-color: #06cf68;
|
||||
position: relative;
|
||||
transform: translateZ(3em);
|
||||
transform-style: preserve-3d;
|
||||
transition: .25s;
|
||||
box-shadow: 13em 13em 1.5em rgba(0, 0, 0, 0.1);
|
||||
animation: anim 1s infinite;
|
||||
}
|
||||
.cube:after {
|
||||
background-color: #05a151;
|
||||
transform: rotateX(-90deg) translateY(3em);
|
||||
transform-origin: 100% 100%;
|
||||
}
|
||||
.cube:before {
|
||||
background-color: #026934;
|
||||
transform: rotateY(90deg) translateX(3em);
|
||||
transform-origin: 100% 0;
|
||||
}
|
||||
.cube:nth-child(1) {
|
||||
animation-delay: 0.05s;
|
||||
}
|
||||
.cube:nth-child(2) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.cube:nth-child(3) {
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
.cube:nth-child(4) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.cube:nth-child(5) {
|
||||
animation-delay: 0.25s;
|
||||
}
|
||||
.cube:nth-child(6) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.cube:nth-child(7) {
|
||||
animation-delay: 0.35s;
|
||||
}
|
||||
.cube:nth-child(8) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.cube:nth-child(9) {
|
||||
animation-delay: 0.45s;
|
||||
}
|
||||
@keyframes anim {
|
||||
50% {
|
||||
transform: translateZ(0.5em);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loadingPage">
|
||||
<div class='base'>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
<div class='cube'></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
38
frontend/package.json
Normal file
38
frontend/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "ee",
|
||||
"version": "4.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite --host --port 8080",
|
||||
"serve": "vite --host --port 8080",
|
||||
"build-staging": "vite build --mode staging",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^6.1.0",
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"axios": "^1.12.2",
|
||||
"jsencrypt": "^3.5.4",
|
||||
"node-forge": "^1.3.1",
|
||||
"pinia": "^3.0.3",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"store2": "^2.14.4",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.2",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.16.0",
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/compiler-sfc": "^3.5.22",
|
||||
"less": "^4.4.2",
|
||||
"less-loader": "^12.3.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-pxtorem": "^6.1.0",
|
||||
"terser": "^5.44.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.4.0",
|
||||
"vite-plugin-compression": "^0.5.1"
|
||||
}
|
||||
}
|
||||
15
frontend/src/App.vue
Normal file
15
frontend/src/App.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<router-view/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {onMounted} from 'vue';
|
||||
|
||||
onMounted(() => {
|
||||
const loadingElement = document.getElementById('loadingPage');
|
||||
if (loadingElement) {
|
||||
(loadingElement as HTMLElement).remove();
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
84
frontend/src/api/index.ts
Normal file
84
frontend/src/api/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
/**
|
||||
* Definition of communication channel between main process and rendering process
|
||||
* format:controller/filename/method
|
||||
* Definition of communication channels between main process and rendering process
|
||||
*/
|
||||
const ipcApiRoute = {
|
||||
example: {
|
||||
test: 'controller/example/test',
|
||||
},
|
||||
framework: {
|
||||
checkForUpdater: 'controller/framework/checkForUpdater',
|
||||
downloadApp: 'controller/framework/downloadApp',
|
||||
jsondbOperation: 'controller/framework/jsondbOperation',
|
||||
sqlitedbOperation: 'controller/framework/sqlitedbOperation',
|
||||
uploadFile: 'controller/framework/uploadFile',
|
||||
checkHttpServer: 'controller/framework/checkHttpServer',
|
||||
doHttpRequest: 'controller/framework/doHttpRequest',
|
||||
doSocketRequest: 'controller/framework/doSocketRequest',
|
||||
ipcInvokeMsg: 'controller/framework/ipcInvokeMsg',
|
||||
ipcSendSyncMsg: 'controller/framework/ipcSendSyncMsg',
|
||||
ipcSendMsg: 'controller/framework/ipcSendMsg',
|
||||
startJavaServer: 'controller/framework/startJavaServer',
|
||||
closeJavaServer: 'controller/framework/closeJavaServer',
|
||||
someJob: 'controller/framework/someJob',
|
||||
timerJobProgress: 'controller/framework/timerJobProgress',
|
||||
createPool: 'controller/framework/createPool',
|
||||
createPoolNotice: 'controller/framework/createPoolNotice',
|
||||
someJobByPool: 'controller/framework/someJobByPool',
|
||||
hello: 'controller/framework/hello',
|
||||
openSoftware: 'controller/framework/openSoftware',
|
||||
},
|
||||
|
||||
// os
|
||||
os: {
|
||||
messageShow: 'controller/os/messageShow',
|
||||
messageShowConfirm: 'controller/os/messageShowConfirm',
|
||||
selectFolder: 'controller/os/selectFolder',
|
||||
selectPic: 'controller/os/selectPic',
|
||||
openDirectory: 'controller/os/openDirectory',
|
||||
loadViewContent: 'controller/os/loadViewContent',
|
||||
removeViewContent: 'controller/os/removeViewContent',
|
||||
createWindow: 'controller/os/createWindow',
|
||||
getWCid: 'controller/os/getWCid',
|
||||
sendNotification: 'controller/os/sendNotification',
|
||||
initPowerMonitor: 'controller/os/initPowerMonitor',
|
||||
getScreen: 'controller/os/getScreen',
|
||||
autoLaunch: 'controller/os/autoLaunch',
|
||||
setTheme: 'controller/os/setTheme',
|
||||
getTheme: 'controller/os/getTheme',
|
||||
window1ToWindow2: 'controller/os/window1ToWindow2',
|
||||
window2ToWindow1: 'controller/os/window2ToWindow1',
|
||||
},
|
||||
|
||||
// effect
|
||||
effect: {
|
||||
selectFile: 'controller/effect/selectFile',
|
||||
loginWindow: 'controller/effect/loginWindow',
|
||||
restoreWindow: 'controller/effect/restoreWindow',
|
||||
},
|
||||
|
||||
// cross
|
||||
cross: {
|
||||
crossInfo: 'controller/cross/info',
|
||||
getCrossUrl: 'controller/cross/getUrl',
|
||||
killCrossServer: 'controller/cross/killServer',
|
||||
createCrossServer: 'controller/cross/createServer',
|
||||
requestApi: 'controller/cross/requestApi',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize Channel
|
||||
* Format: Custom (recommended to add a prefix)
|
||||
*/
|
||||
const specialIpcRoute = {
|
||||
appUpdater: 'custom/app/updater', // updater channel
|
||||
}
|
||||
|
||||
export {
|
||||
ipcApiRoute,
|
||||
specialIpcRoute
|
||||
}
|
||||
|
||||
15
frontend/src/assets/global.less
Normal file
15
frontend/src/assets/global.less
Normal file
@@ -0,0 +1,15 @@
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* 滚动条 */
|
||||
::-webkit-scrollbar{width:8px;height:4px}
|
||||
::-webkit-scrollbar-button{width:10px;height:0}
|
||||
::-webkit-scrollbar-track{background:0 0}
|
||||
::-webkit-scrollbar-thumb{background: #ecf3fb; border-radius: 4px;-webkit-transition:.3s;transition:.3s}
|
||||
::-webkit-scrollbar-thumb:hover{background-color:#1890ff}
|
||||
::-webkit-scrollbar-thumb:active{background-color:#1890ff}
|
||||
BIN
frontend/src/assets/login.png
Normal file
BIN
frontend/src/assets/login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/src/assets/logo.png
Normal file
BIN
frontend/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
17
frontend/src/assets/theme.less
Normal file
17
frontend/src/assets/theme.less
Normal file
@@ -0,0 +1,17 @@
|
||||
@import 'ant-design-vue/dist/antd.less';
|
||||
|
||||
// 可自定义主题颜色
|
||||
//@primary-color: #07C160; // 全局主色
|
||||
@link-color: #1890ff; // 链接色
|
||||
@success-color: #52c41a; // 成功色
|
||||
@warning-color: #faad14; // 警告色
|
||||
@error-color: #f5222d; // 错误色
|
||||
@font-size-base: 14px; // 主字号
|
||||
@heading-color: rgba(0, 0, 0, 0.85); // 标题色
|
||||
@text-color: rgba(0, 0, 0, 0.65); // 主文本色
|
||||
@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
|
||||
@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
|
||||
@border-radius-base: 4px; // 组件/浮层圆角
|
||||
@border-color-base: #dce3e8; // 边框色
|
||||
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 浮层阴影
|
||||
|
||||
22
frontend/src/components/global/iconFont.ts
Normal file
22
frontend/src/components/global/iconFont.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createFromIconfontCN } from '@ant-design/icons-vue'
|
||||
import { h, VNode } from 'vue'
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: 'https://at.alicdn.com/t/font_2456157_4ovzopz659q.js',
|
||||
extraCommonProps: {
|
||||
type: 'icon-fengche',
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const DynamicIconFont = (props: Props): VNode => {
|
||||
return h(IconFont, { type: props.type || 'icon-fengche' })
|
||||
}
|
||||
|
||||
export default DynamicIconFont
|
||||
19
frontend/src/components/global/index.ts
Normal file
19
frontend/src/components/global/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import iconFont from './iconFont';
|
||||
|
||||
// Use import.meta.globEager to dynamically import all .vue files in the directory
|
||||
const modules: { [key: string]: { default: any } } = import.meta.glob('./*.vue', { eager: true });
|
||||
|
||||
// Create a map of component names to their default exports
|
||||
const map: { [key: string]: any } = {};
|
||||
Object.keys(modules).forEach(file => {
|
||||
const moduleName = file.replace('./', '').replace('.vue', '');
|
||||
map[moduleName] = modules[file].default;
|
||||
});
|
||||
|
||||
// Combine the dynamically imported components with the iconFont component
|
||||
const globalComponents = {
|
||||
...map,
|
||||
iconFont,
|
||||
};
|
||||
|
||||
export default globalComponents;
|
||||
112
frontend/src/layouts/AppSider.vue
Normal file
112
frontend/src/layouts/AppSider.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<a-layout has-sider id="app-layout-sider">
|
||||
<a-layout-sider
|
||||
v-model:collapsed="collapsed" collapsible
|
||||
theme="light"
|
||||
class="layout-sider"
|
||||
>
|
||||
<div class="logo">
|
||||
<img src="@/assets/logo.png" class="pic-logo" />
|
||||
<h4>PQS9100工具箱</h4>
|
||||
</div>
|
||||
<a-menu
|
||||
theme="light"
|
||||
mode="inline"
|
||||
:selectedKeys="[current]"
|
||||
@click="menuHandle"
|
||||
>
|
||||
<a-menu-item v-for="(menuInfo, index) in menu" :key="index">
|
||||
<component :is="menuInfo.icon"></component>
|
||||
<span>{{ menuInfo.title }}</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<a-layout :style="{ marginLeft: '200px' }">
|
||||
<a-layout-content class="layout-content">
|
||||
<router-view />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref} from 'vue';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {FileProtectOutlined} from "@ant-design/icons-vue"; // 定义菜单项的类型
|
||||
|
||||
// 定义菜单项的类型
|
||||
interface MenuItem {
|
||||
icon: any;
|
||||
title: string;
|
||||
pageName: string;
|
||||
}
|
||||
|
||||
// 定义菜单的类型
|
||||
interface Menu {
|
||||
[key: string]: MenuItem;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const collapsed = ref<boolean>(false);
|
||||
const current = ref<string>('menu_1');
|
||||
const menu = ref<Menu>({
|
||||
'menu_1': {
|
||||
icon: FileProtectOutlined,
|
||||
title: '设备激活',
|
||||
pageName: 'Activate'
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
menuHandle(null);
|
||||
});
|
||||
|
||||
const menuHandle = (e: any): void => {
|
||||
console.log('sider menu e:', e);
|
||||
if (e) {
|
||||
current.value = e.key;
|
||||
}
|
||||
console.log('sider menu current:', current.value);
|
||||
|
||||
const linkInfo = menu.value[current.value];
|
||||
console.log('[home] load linkInfo:', linkInfo);
|
||||
router.push({ name: linkInfo.pageName});
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
// 嵌套
|
||||
#app-layout-sider {
|
||||
height: 100%;
|
||||
.logo {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
padding:10px;
|
||||
user-select: none;
|
||||
}
|
||||
.pic-logo {
|
||||
height: 32px;
|
||||
margin: 10px;
|
||||
}
|
||||
.layout-sider {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
overflow: auto;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.menu-item {
|
||||
.ant-menu-item {
|
||||
background-color: #fff;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
.layout-content {
|
||||
width: 96%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
frontend/src/layouts/index.ts
Normal file
5
frontend/src/layouts/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import AppSider from '@/layouts/AppSider.vue'
|
||||
|
||||
export {
|
||||
AppSider
|
||||
}
|
||||
30
frontend/src/main.ts
Normal file
30
frontend/src/main.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as AntIcon from '@ant-design/icons-vue';
|
||||
import Antd from 'ant-design-vue';
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import './assets/global.less';
|
||||
import './assets/theme.less';
|
||||
import components from './components/global';
|
||||
import Router from './router/index';
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
// components
|
||||
type ComponentsType = typeof components;
|
||||
for (const componentName in components) {
|
||||
if (Object.prototype.hasOwnProperty.call(components, componentName)) {
|
||||
const component = components[componentName as keyof ComponentsType];
|
||||
app.component(componentName, component);
|
||||
}
|
||||
}
|
||||
|
||||
// icon
|
||||
const whiteList = ['createFromIconfontCN', 'getTwoToneColor', 'setTwoToneColor', 'default']
|
||||
const iconKeys = Object.keys(AntIcon) as Array<keyof typeof AntIcon>;
|
||||
iconKeys.forEach(key => {
|
||||
if (!whiteList.includes(key as typeof whiteList[number])) {
|
||||
app.component(key.toString(), AntIcon[key]);
|
||||
}
|
||||
});
|
||||
|
||||
app.use(Antd).use(Router).mount('#app')
|
||||
9
frontend/src/router/index.ts
Normal file
9
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||
import routerMap from './routerMap'
|
||||
|
||||
const Router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: routerMap as RouteRecordRaw[],
|
||||
})
|
||||
|
||||
export default Router
|
||||
21
frontend/src/router/routerMap.ts
Normal file
21
frontend/src/router/routerMap.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 基础路由
|
||||
* @type { *[] }
|
||||
*/
|
||||
|
||||
const constantRouterMap = [
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('@/layouts/AppSider.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/activate',
|
||||
name: 'Activate',
|
||||
component: () => import('@/views/activate/index.vue'),
|
||||
props: { id: 'activate' }
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
export default constantRouterMap
|
||||
12
frontend/src/types/env.d.ts
vendored
Normal file
12
frontend/src/types/env.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
// declare global {
|
||||
// interface Window {
|
||||
// electron?: any;
|
||||
// }
|
||||
// }
|
||||
0
frontend/src/types/pinia.d.ts
vendored
Normal file
0
frontend/src/types/pinia.d.ts
vendored
Normal file
0
frontend/src/types/shim.d.ts
vendored
Normal file
0
frontend/src/types/shim.d.ts
vendored
Normal file
7
frontend/src/types/source.d.ts
vendored
Normal file
7
frontend/src/types/source.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// 声明一个模块,防止引入文件时报错
|
||||
declare module '*.json';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
declare module '*.scss';
|
||||
declare module '*.ts';
|
||||
declare module '*.js';
|
||||
27
frontend/src/utils/iconList.ts
Normal file
27
frontend/src/utils/iconList.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export default [
|
||||
{ name: '对话框', type: 'icon-duihuakuang' },
|
||||
{ name: '闹钟', type: 'icon-naozhong' },
|
||||
{ name: '笑脸', type: 'icon-xiaolian' },
|
||||
{ name: 'ok', type: 'icon-ok' },
|
||||
{ name: '风车', type: 'icon-fengche' },
|
||||
{ name: '汗颜', type: 'icon-hanyan' },
|
||||
{ name: '相机', type: 'icon-xiangji' },
|
||||
{ name: '礼物', type: 'icon-liwu' },
|
||||
{ name: '礼花', type: 'icon-lihua' },
|
||||
{ name: '扭蛋', type: 'icon-niudan' },
|
||||
{ name: '流星', type: 'icon-liuxing' },
|
||||
{ name: '风筝', type: 'icon-fengzheng' },
|
||||
{ name: '蛋糕', type: 'icon-dangao' },
|
||||
{ name: '泡泡', type: 'icon-paopao' },
|
||||
{ name: '购物', type: 'icon-gouwu' },
|
||||
{ name: '饮料', type: 'icon-yinliao' },
|
||||
{ name: '云彩', type: 'icon-yuncai' },
|
||||
{ name: '彩铅', type: 'icon-caiqian' },
|
||||
{ name: '纸飞机', type: 'icon-zhifeiji' },
|
||||
{ name: '点赞', type: 'icon-dianzan' },
|
||||
{ name: '煎蛋', type: 'icon-jiandan' },
|
||||
{ name: '小熊', type: 'icon-xiaoxiong' },
|
||||
{ name: '花', type: 'icon-hua' },
|
||||
{ name: '眼睛', type: 'icon-yanjing' },
|
||||
]
|
||||
|
||||
39
frontend/src/utils/ipcRenderer.ts
Normal file
39
frontend/src/utils/ipcRenderer.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
electron?: any;
|
||||
}
|
||||
}
|
||||
|
||||
const Renderer = (window.require && window.require('electron')) || window.electron || {};
|
||||
|
||||
/**
|
||||
* ipc
|
||||
* 官方api说明:https://www.electronjs.org/zh/docs/latest/api/ipc-renderer
|
||||
*
|
||||
* 属性/方法
|
||||
* ipc.invoke(channel, param) - 发送异步消息(invoke/handle 模型)
|
||||
* ipc.sendSync(channel, param) - 发送同步消息(send/on 模型)
|
||||
* ipc.on(channel, listener) - 监听 channel, 当新消息到达,调用 listener
|
||||
* ipc.once(channel, listener) - 添加一次性 listener 函数
|
||||
* ipc.removeListener(channel, listener) - 为特定的 channel 从监听队列中删除特定的 listener 监听者
|
||||
* ipc.removeAllListeners(channel) - 移除所有的监听器,当指定 channel 时只移除与其相关的所有监听器
|
||||
* ipc.send(channel, ...args) - 通过channel向主进程发送异步消息
|
||||
* ipc.postMessage(channel, message, [transfer]) - 发送消息到主进程
|
||||
* ipc.sendTo(webContentsId, channel, ...args) - 通过 channel 发送消息到带有 webContentsId 的窗口
|
||||
* ipc.sendToHost(channel, ...args) - 消息会被发送到 host 页面上的 <webview> 元素
|
||||
*/
|
||||
|
||||
/**
|
||||
* ipc
|
||||
*/
|
||||
const ipc = Renderer.ipcRenderer || undefined;
|
||||
|
||||
/**
|
||||
* 是否为EE环境
|
||||
*/
|
||||
const isEE = ipc ? true : false;
|
||||
|
||||
export {
|
||||
Renderer, ipc, isEE
|
||||
};
|
||||
|
||||
47
frontend/src/utils/rsa.ts
Normal file
47
frontend/src/utils/rsa.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
|
||||
// 获取 RSA 公钥
|
||||
const publicKey = import.meta.env.VITE_RSA_PUBLIC_KEY
|
||||
|
||||
// 获取 RSA 私钥
|
||||
const privateKey = import.meta.env.VITE_RSA_PRIVATE_KEY
|
||||
|
||||
// RSA加密
|
||||
const encrypt = (data: string): string => {
|
||||
try {
|
||||
const encrypt = new JSEncrypt()
|
||||
encrypt.setPublicKey(publicKey)
|
||||
const encrypted = encrypt.encrypt(data)
|
||||
if (encrypted) {
|
||||
return encrypted
|
||||
} else {
|
||||
throw new Error('加密失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加密失败:', error)
|
||||
throw new Error('RSA加密失败')
|
||||
}
|
||||
}
|
||||
|
||||
// RSA解密
|
||||
const decrypt = (encryptedData: string): string => {
|
||||
try {
|
||||
const decrypt = new JSEncrypt()
|
||||
decrypt.setPrivateKey(privateKey)
|
||||
const decrypted = decrypt.decrypt(encryptedData)
|
||||
if (decrypted) {
|
||||
return decrypted as string
|
||||
} else {
|
||||
throw new Error('解密失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解密失败:', error)
|
||||
throw new Error('RSA解密失败')
|
||||
}
|
||||
}
|
||||
export default {
|
||||
encrypt,
|
||||
decrypt,
|
||||
publicKey,
|
||||
privateKey
|
||||
}
|
||||
56
frontend/src/views/activate/index.ts
Normal file
56
frontend/src/views/activate/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
export namespace Activate {
|
||||
|
||||
export interface ApplicationModule {
|
||||
/**
|
||||
* 是否申请 1是 0否
|
||||
*/
|
||||
apply: number;
|
||||
|
||||
}
|
||||
|
||||
export interface ActivateModule extends ApplicationModule {
|
||||
/**
|
||||
* 是否永久 1是 0否
|
||||
*/
|
||||
permanently: number;
|
||||
}
|
||||
|
||||
export interface ApplicationCodePlaintext {
|
||||
|
||||
/**
|
||||
* 模拟式模块
|
||||
*/
|
||||
simulate: ApplicationModule;
|
||||
|
||||
/**
|
||||
* 数字式模块
|
||||
*/
|
||||
digital: ApplicationModule;
|
||||
|
||||
/**
|
||||
* 比对式模块
|
||||
*/
|
||||
contrast: ApplicationModule;
|
||||
}
|
||||
export interface ActivationCodePlaintext {
|
||||
|
||||
/**
|
||||
* 模拟式模块
|
||||
*/
|
||||
simulate: ActivateModule;
|
||||
|
||||
/**
|
||||
* 数字式模块
|
||||
*/
|
||||
digital: ActivateModule;
|
||||
|
||||
/**
|
||||
* 比对式模块
|
||||
*/
|
||||
contrast: ActivateModule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
205
frontend/src/views/activate/index.vue
Normal file
205
frontend/src/views/activate/index.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<div class="activation-page">
|
||||
<a-card title="RSA密钥配置" style="margin-bottom: 20px;">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-alert
|
||||
message="注意:请妥善保管私钥,不要泄露给他人"
|
||||
type="warning"
|
||||
show-icon
|
||||
style="margin-bottom: 16px;"
|
||||
/>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-form-item label="RSA公钥">
|
||||
<a-textarea
|
||||
v-model:value="rsaKeys.publicKey"
|
||||
:rows="5"
|
||||
readonly
|
||||
placeholder="RSA公钥内容"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click="copyToClipboard(rsaKeys.publicKey)"
|
||||
:disabled="!rsaKeys.publicKey"
|
||||
style="margin-top: 8px;"
|
||||
>
|
||||
复制公钥
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12">
|
||||
<a-form-item label="RSA私钥">
|
||||
<a-textarea
|
||||
v-model:value="rsaKeys.privateKey"
|
||||
:rows="5"
|
||||
readonly
|
||||
placeholder="RSA私钥内容"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click="copyToClipboard(rsaKeys.privateKey)"
|
||||
:disabled="!rsaKeys.privateKey"
|
||||
style="margin-top: 8px;"
|
||||
>
|
||||
复制私钥
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card title="设备激活">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-divider orientation="left" orientation-margin="0px">设备申请码</a-divider>
|
||||
<a-form-item>
|
||||
<a-textarea
|
||||
v-model:value="activationForm.requestCode"
|
||||
:rows="3"
|
||||
placeholder="请输入设备申请码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-divider orientation="left" orientation-margin="0px">设备申请码</a-divider>
|
||||
<a-form-item>
|
||||
<a-textarea
|
||||
v-model:value="activationForm.activationCode"
|
||||
:rows="3"
|
||||
placeholder="生成的激活码将显示在这里"
|
||||
readonly
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="generateActivationCode"
|
||||
:loading="generating"
|
||||
:disabled="!activationForm.requestCode.trim()"
|
||||
>
|
||||
生成激活码
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
@click="copyToClipboard(activationForm.activationCode)"
|
||||
:disabled="!activationForm.activationCode"
|
||||
>
|
||||
复制激活码
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {message} from 'ant-design-vue'
|
||||
import rsa from '@/utils/rsa'
|
||||
import {Activate} from "@/views/activate/index";
|
||||
|
||||
const rsaKeys = ref({
|
||||
publicKey: rsa.publicKey,
|
||||
privateKey: rsa.privateKey
|
||||
})
|
||||
|
||||
const activationForm = ref({
|
||||
requestCode: '',
|
||||
activationCode: ''
|
||||
})
|
||||
|
||||
const generating = ref(false)
|
||||
|
||||
|
||||
// 生成激活码
|
||||
const generateActivationCode = () => {
|
||||
if (!activationForm.value.requestCode.trim()) {
|
||||
message.error('请输入设备申请码')
|
||||
return
|
||||
}
|
||||
generating.value = true
|
||||
let activationCodePlaintext
|
||||
try {
|
||||
const plaintext = rsa.decrypt(activationForm.value.requestCode)
|
||||
activationCodePlaintext = JSON.parse(plaintext) as Activate.ActivationCodePlaintext
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
if (!activationCodePlaintext) {
|
||||
generating.value = false
|
||||
message.error('无效的设备申请码')
|
||||
return
|
||||
}
|
||||
const contrast = activationCodePlaintext.contrast
|
||||
const digital = activationCodePlaintext.digital
|
||||
const simulate = activationCodePlaintext.simulate
|
||||
if (!contrast && !digital && !simulate) {
|
||||
generating.value = false
|
||||
message.error('无效的设备申请码')
|
||||
return
|
||||
}
|
||||
if (contrast.apply === 1) {
|
||||
activationCodePlaintext.contrast.permanently = 1
|
||||
}
|
||||
if (digital.apply === 1) {
|
||||
activationCodePlaintext.digital.permanently = 1
|
||||
}
|
||||
if (simulate.apply === 1) {
|
||||
activationCodePlaintext.simulate.permanently = 1
|
||||
}
|
||||
const data = JSON.stringify(activationCodePlaintext)
|
||||
try {
|
||||
setTimeout(() => {
|
||||
activationForm.value.activationCode = rsa.encrypt(data)
|
||||
generating.value = false
|
||||
}, 1000)
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
generating.value = false
|
||||
message.error('生成激活码失败')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
const copyToClipboard = (text: string) => {
|
||||
if (!text) {
|
||||
message.warning('没有内容可复制')
|
||||
return
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('复制成功')
|
||||
}).catch(() => {
|
||||
message.error('复制失败')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.activation-page {
|
||||
:deep(textarea) {
|
||||
font-family: consolas, monospace;
|
||||
resize: none;
|
||||
}
|
||||
:deep(.ant-form-item-label) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
35
frontend/tsconfig.json
Normal file
35
frontend/tsconfig.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
|
||||
"jsx": "preserve",
|
||||
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importHelpers": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
// 定义一个变量就必须给它一个初始值
|
||||
"strictPropertyInitialization": false,
|
||||
// 允许使用obj[key]访问对象属性
|
||||
//"suppressImplicitAnyIndexErrors": true,
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
}
|
||||
},
|
||||
"include":["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "*.d.ts"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
54
frontend/vite.config.ts
Normal file
54
frontend/vite.config.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { defineConfig } from 'vite';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig((mode) => {
|
||||
return {
|
||||
// Project plugins
|
||||
plugins: [
|
||||
vue(),
|
||||
viteCompression({
|
||||
verbose: true,
|
||||
disable: false,
|
||||
threshold: 1025,
|
||||
algorithm: 'gzip',
|
||||
ext: '.gz',
|
||||
}),
|
||||
],
|
||||
// Base configuration
|
||||
base: './',
|
||||
publicDir: 'public',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
modifyVars: {
|
||||
'@border-color-base': '#dce3e8',
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
assetsInlineLimit: 4096,
|
||||
cssCodeSplit: true,
|
||||
brotliSize: false,
|
||||
sourcemap: false,
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
// Remove console and debugger in production
|
||||
drop_console: false,
|
||||
drop_debugger: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user