commit
93e810a17e
45 changed files with 1867 additions and 0 deletions
-
6.env.dev
-
6.env.pre
-
6.env.prod
-
6.env.test
-
27.gitignore
-
0.npmrc
-
53.postcssrc.js
-
10.prettierrc
-
23README.md
-
BINcommitlint.config.js
-
19eslint.config.js
-
41increment-version.js
-
27index.html
-
86package.json
-
BINpublic/favicon.ico
-
21src/app.vue
-
1src/assets/images/home.svg
-
91src/assets/styles/common.scss
-
19src/assets/styles/element.scss
-
12src/assets/styles/main.scss
-
126src/components/common/FTButton/index.vue
-
79src/components/common/FTDialog/index.vue
-
169src/components/common/FTStream/index.vue
-
7src/components/common/FTTable/expand.ts
-
145src/components/common/FTTable/index.vue
-
28src/components/home/Stop/index.vue
-
20src/env.d.ts
-
76src/layouts/default.vue
-
5src/libs/constant.ts
-
92src/libs/http.ts
-
37src/libs/message.ts
-
242src/libs/socket.ts
-
13src/libs/token.ts
-
74src/libs/utils.ts
-
27src/main.ts
-
26src/router/index.ts
-
46src/router/routes.ts
-
7src/stores/index.ts
-
22src/stores/useSystemStore.ts
-
11src/views/craft/index.vue
-
3src/views/debug/index.vue
-
12src/views/home/index.vue
-
12src/views/login/index.vue
-
38tsconfig.json
-
96vite.config.ts
@ -0,0 +1,6 @@ |
|||||
|
# 开发环境 |
||||
|
|
||||
|
FT_NODE_ENV=dev |
||||
|
|
||||
|
FT_WS_URL=ws://192.168.1.199:8080/ws |
||||
|
FT_PROXY=http://192.168.1.199:8080 |
@ -0,0 +1,6 @@ |
|||||
|
# 预发环境 |
||||
|
|
||||
|
FT_NODE_ENV=pre |
||||
|
|
||||
|
FT_WS_URL=ws://192.168.1.140:8080/ws |
||||
|
FT_PROXY=http://192.168.1.140 |
@ -0,0 +1,6 @@ |
|||||
|
# 生产环境 |
||||
|
|
||||
|
FT_NODE_ENV=prod |
||||
|
|
||||
|
FT_WS_URL=ws://192.168.100.168:8080/ws |
||||
|
FT_PROXY=http://192.168.1.168 |
@ -0,0 +1,6 @@ |
|||||
|
# 测试环境 |
||||
|
|
||||
|
FT_NODE_ENV=test |
||||
|
|
||||
|
FT_WS_URL=ws://192.168.1.200:8080/ws |
||||
|
FT_PROXY=http://192.168.1.200:8080 |
@ -0,0 +1,27 @@ |
|||||
|
# Logs |
||||
|
logs |
||||
|
*.log |
||||
|
npm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
pnpm-debug.log* |
||||
|
lerna-debug.log* |
||||
|
auto-imports.d.ts |
||||
|
.eslintrc-auto-import.json |
||||
|
pnpm-lock.yaml |
||||
|
|
||||
|
node_modules |
||||
|
dist |
||||
|
dist-* |
||||
|
*.local |
||||
|
|
||||
|
# Editor directories and files |
||||
|
.vscode/* |
||||
|
!.vscode/extensions.json |
||||
|
.idea |
||||
|
.DS_Store |
||||
|
*.suo |
||||
|
*.ntvs* |
||||
|
*.njsproj |
||||
|
*.sln |
||||
|
*.sw? |
@ -0,0 +1,53 @@ |
|||||
|
export default { |
||||
|
|
||||
|
plugins: { |
||||
|
|
||||
|
'postcss-import': {}, |
||||
|
|
||||
|
'postcss-url': {}, |
||||
|
|
||||
|
'postcss-aspect-ratio-mini': {}, |
||||
|
|
||||
|
'postcss-write-svg': { |
||||
|
|
||||
|
utf8: false, |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
'postcss-px-to-viewport': { |
||||
|
|
||||
|
viewportWidth: 1120, |
||||
|
|
||||
|
viewportHeight: 736, |
||||
|
|
||||
|
unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
|
||||
|
|
||||
|
viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
|
||||
|
|
||||
|
selectorBlackList: ['.ignore', '.hairlines', ':after'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
|
||||
|
|
||||
|
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
|
||||
|
|
||||
|
mediaQuery: false, // 允许在媒体查询中转换`px`
|
||||
|
|
||||
|
}, |
||||
|
|
||||
|
'postcss-viewport-units': { |
||||
|
filterRule: rule => |
||||
|
!rule.selector.includes('::after') |
||||
|
&& !rule.selector.includes('::before') |
||||
|
&& !rule.selector.includes(':after') |
||||
|
&& !rule.selector.includes(':before'), |
||||
|
}, |
||||
|
|
||||
|
'cssnano': { |
||||
|
|
||||
|
'autoprefixer': false, |
||||
|
|
||||
|
'postcss-zindex': false, |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"printWidth": 120, |
||||
|
"tabWidth": 2, |
||||
|
"endOfLine": "lf", |
||||
|
"singleQuote": true, |
||||
|
"semi": true, |
||||
|
"trailingComma": "none", |
||||
|
"bracketSpacing": true, |
||||
|
"arrowParens": "avoid" |
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
### 前端项目模板 |
||||
|
##### 文件目录 |
||||
|
- src |
||||
|
- components 组件目录 |
||||
|
- apis 接口文件目录 |
||||
|
- assets 静态资源目录 |
||||
|
- hooks hooks目录 |
||||
|
- libs 公共工具类 |
||||
|
- router 页面路由 |
||||
|
- stores 项目公共状态管理 |
||||
|
- types 类型申明 |
||||
|
- views 页面目录 |
||||
|
##### 项目运行 |
||||
|
项目采用pnpm 进行依赖构建,请先安装pnpm |
||||
|
[pnpm - 速度快、节省磁盘空间的软件包管理器](https://pnpm.io/) |
||||
|
```bash |
||||
|
npm install pnpm -g |
||||
|
``` |
||||
|
|
||||
|
```bash |
||||
|
pnpm install // 安装依赖 |
||||
|
pnpm dev // 运行项目 |
||||
|
``` |
@ -0,0 +1,19 @@ |
|||||
|
// import eslint_js from '@eslint/js'
|
||||
|
// import eslint_ts from 'typescript-eslint';
|
||||
|
// import eslint_vue from 'eslint-plugin-vue';
|
||||
|
// import vue_parser from 'vue-eslint-parser';
|
||||
|
|
||||
|
import lintConfig from '@antfu/eslint-config' |
||||
|
|
||||
|
export default lintConfig({ |
||||
|
vue: true, |
||||
|
markdown: true, |
||||
|
ignores: [], |
||||
|
rules: { |
||||
|
'no-console': 0, |
||||
|
'antfu/top-level-function': 0, |
||||
|
'ts/no-use-before-define': 0, |
||||
|
'no-alert': 0, |
||||
|
}, |
||||
|
globals: { process: 'readonly' }, |
||||
|
}) |
@ -0,0 +1,41 @@ |
|||||
|
import fs from 'node:fs'; |
||||
|
import path, { dirname } from 'node:path'; |
||||
|
import { fileURLToPath } from 'node:url'; |
||||
|
import semver from 'semver'; |
||||
|
import { execSync } from 'child_process'; // 引入 child_process 模块用于执行 Git 命令
|
||||
|
|
||||
|
const __filename = fileURLToPath(import.meta.url); |
||||
|
const __dirname = dirname(__filename); |
||||
|
|
||||
|
const packagePath = path.resolve(__dirname, 'package.json'); |
||||
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8')); |
||||
|
|
||||
|
// 读取命令行参数(默认使用 'patch')
|
||||
|
const versionType = process.argv[2] || 'patch'; |
||||
|
|
||||
|
// 递增版本
|
||||
|
const newVersion = semver.inc(packageJson.version, versionType); |
||||
|
if (!newVersion) { |
||||
|
throw new Error(`Invalid version type: ${versionType}`); |
||||
|
} |
||||
|
|
||||
|
packageJson.version = newVersion; |
||||
|
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)); |
||||
|
console.log(`Version updated to: ${newVersion}`); |
||||
|
|
||||
|
// 新增:自动提交 package.json 到远程仓库
|
||||
|
try { |
||||
|
// 将 package.json 添加到暂存区
|
||||
|
execSync('git add package.json'); |
||||
|
console.log('Added package.json to staging area.'); |
||||
|
|
||||
|
// 提交更改
|
||||
|
execSync(`git commit -m "fix: Update version to V${newVersion}"`); |
||||
|
console.log(`Committed changes with message: Update version to ${newVersion}`); |
||||
|
|
||||
|
// 推送到远程仓库
|
||||
|
execSync('git push'); |
||||
|
console.log('Pushed changes to remote repository.'); |
||||
|
} catch (error) { |
||||
|
console.error('Failed to commit and push changes:', error.message); |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8" /> |
||||
|
<link rel="icon" href="/favicon.ico" /> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
|
<title>石墨消解仪</title> |
||||
|
<style> |
||||
|
html,body{ |
||||
|
height:100%; |
||||
|
width:100%; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
#app { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="app"></div> |
||||
|
<script type="module" src="/src/main.ts"></script> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,86 @@ |
|||||
|
{ |
||||
|
"name": "matrix-spray-web", |
||||
|
"type": "module", |
||||
|
"version": "1.0.2", |
||||
|
"description": "", |
||||
|
"author": "", |
||||
|
"license": "ISC", |
||||
|
"keywords": [], |
||||
|
"main": "index.js", |
||||
|
"scripts": { |
||||
|
"dev": "vite --mode dev", |
||||
|
"dev:test": "vite --mode test", |
||||
|
"dev:prod": "vite --mode prod", |
||||
|
"build": "vite build --mode dev", |
||||
|
"build:test": "vite build --mode test", |
||||
|
"build:prod:patch": "node increment-version.js patch && vite build --mode prod", |
||||
|
"build:prod:minor": "node increment-version.js minor && vite build --mode prod", |
||||
|
"build:prod:major": "node increment-version.js major && vite build --mode prod", |
||||
|
"build:pre": "vite build --mode pre", |
||||
|
"build:dev": "vite build --mode dev", |
||||
|
"prepare": "husky", |
||||
|
"lint:lint-staged": "lint-staged", |
||||
|
"lint": "vue-tsc --noEmit --skipLibCheck && eslint", |
||||
|
"eslint": "eslint --fix --ext .ts,.vue src", |
||||
|
"prettier": "prettier --write ." |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@element-plus/icons-vue": "^2.3.1", |
||||
|
"autoprefixer": "^10.4.20", |
||||
|
"axios": "^1.8.1", |
||||
|
"cssnano": "^7.0.6", |
||||
|
"element-plus": "^2.9.5", |
||||
|
"konva": "^9.3.18", |
||||
|
"lodash": "^4.17.21", |
||||
|
"pinia": "^3.0.1", |
||||
|
"pinia-plugin-persistedstate": "^4.2.0", |
||||
|
"postcss": "^8.5.3", |
||||
|
"postcss-aspect-ratio-mini": "^1.1.0", |
||||
|
"postcss-import": "^16.1.0", |
||||
|
"postcss-px-to-viewport": "^1.1.1", |
||||
|
"postcss-url": "^10.1.3", |
||||
|
"postcss-viewport-units": "^0.1.6", |
||||
|
"postcss-write-svg": "^3.0.1", |
||||
|
"tailwindcss": "^4.0.12", |
||||
|
"vue": "^3.5.13", |
||||
|
"vue-router": "^4.5.0" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@antfu/eslint-config": "^4.3.0", |
||||
|
"@commitlint/cli": "^19.7.1", |
||||
|
"@commitlint/config-conventional": "^19.7.1", |
||||
|
"@types/node": "^22.13.5", |
||||
|
"@typescript-eslint/eslint-plugin": "^8.25.0", |
||||
|
"@typescript-eslint/parser": "^8.25.0", |
||||
|
"@vitejs/plugin-vue": "^5.2.1", |
||||
|
"@vue/eslint-config-prettier": "^10.2.0", |
||||
|
"eslint": "^9.21.0", |
||||
|
"eslint-config-prettier": "^10.0.2", |
||||
|
"eslint-plugin-prettier": "^5.2.3", |
||||
|
"eslint-plugin-vue": "^9.32.0", |
||||
|
"husky": "^9.1.7", |
||||
|
"lint-staged": "^15.4.3", |
||||
|
"prettier": "^3.5.2", |
||||
|
"sass": "^1.85.1", |
||||
|
"semver": "^7.7.1", |
||||
|
"typescript": "^5.7.3", |
||||
|
"unplugin-auto-import": "^19.1.1", |
||||
|
"unplugin-vue-components": "^28.4.1", |
||||
|
"vite": "^6.2.0", |
||||
|
"vite-plugin-compression": "^0.5.1", |
||||
|
"vite-plugin-eslint": "^1.8.1", |
||||
|
"vue-tsc": "^2.2.4" |
||||
|
}, |
||||
|
"husky": { |
||||
|
"hooks": { |
||||
|
"pre-commit": "lint-staged" |
||||
|
} |
||||
|
}, |
||||
|
"lint-staged": { |
||||
|
"*.{js,jsx,vue,ts,tsx}": [ |
||||
|
"eslint --fix", |
||||
|
"prettier --write", |
||||
|
"lint-staged" |
||||
|
] |
||||
|
} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
// socket.init(() => {}) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<router-view v-slot="{ Component }" class="main-content"> |
||||
|
<transition> |
||||
|
<component :is="Component" /> |
||||
|
</transition> |
||||
|
</router-view> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.main-content { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
//background: url("assets/images/background.jpg") no-repeat center; |
||||
|
background-size: cover; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1 @@ |
|||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M20.0370799,43.6256C20.866486,47.6246,22.91091,51.222300000000004,25.94731,54.0277C26.3526,54.4028,26.9106,54.5752,27.46181,54.4956C28.0134,54.4165,28.49784,54.0943,28.77694,53.6208L31.1482,49.591300000000004C31.5442,48.9178,31.451999999999998,48.070499999999996,30.92,47.4942C29.68039,46.1482,28.77662,44.537800000000004,28.28009,42.7903C28.06083,42.015,27.34183,41.4784,26.52231,41.4784L21.82395,41.4784C21.27636,41.4781,20.757754,41.7198,20.411853,42.1363C20.0647341,42.552,19.9270634,43.0991,20.0370799,43.6256ZM44.1657,51.909C43.7696,51.233000000000004,42.9726,50.8869,42.1963,51.053799999999995C40.231899999999996,51.4761,38.1899,51.3965,36.2658,50.8227C35.453900000000004,50.580600000000004,34.579,50.9191,34.1541,51.6398L31.8262,55.5982C31.5578,56.055,31.5084,56.6046,31.691000000000003,57.1004C31.872999999999998,57.5967,32.2697,57.9884,32.7742,58.1703C34.9795,58.9695,37.3089,59.375,39.695,59.375C41.6678,59.375,43.6156,59.0954,45.482600000000005,58.543C46.0156,58.3847,46.446,57.9968,46.652,57.4891C46.8572,56.9806,46.814099999999996,56.408,46.535,55.9346L44.1657,51.909ZM52.5476,24.57127C52.2201,24.3027,51.806799999999996,24.15552,51.379999999999995,24.15542C51.2742,24.15542,51.1663,24.166330000000002,51.060500000000005,24.18424C50.531,24.2771,50.0703,24.59457,49.8016,25.05176L47.4685,29.01324C47.045100000000005,29.73472,47.183499999999995,30.6473,47.802800000000005,31.2165C49.39,32.6707,50.5347,34.5286,51.1096,36.5833C51.3275,37.3604,52.0476,37.8989,52.8691,37.899100000000004L57.568,37.899100000000004C58.1145,37.8988,58.6321,37.658,58.9784,37.2432C59.3258,36.8281,59.464,36.2815,59.3546,35.7553C58.4579,31.3953,56.0407,27.42316,52.5476,24.57127ZM46.652,21.88784C46.446600000000004,21.37975,46.0159,20.99153,45.482600000000005,20.833661C43.6053,20.279568,41.6553,19.99869145,39.695,20.00000417001C37.3089,20.00000417001,34.9795,20.405219,32.7742,21.20446C31.714199999999998,21.58978,31.262,22.8179,31.8262,23.77903L34.1541,27.73687C34.579,28.457549999999998,35.453900000000004,28.79609,36.2658,28.55402C37.3775,28.22171,38.5332,28.05317,39.6953,28.05394C40.530100000000004,28.05394,41.372299999999996,28.14349,42.1963,28.32119C42.9721,28.48767,43.7685,28.14262,44.1657,27.46794L46.5353,23.44182C46.8143,22.96858,46.857299999999995,22.39608,46.652,21.88784ZM57.568,41.4781L52.8691,41.4781C52.0475,41.477599999999995,51.327,42.0163,51.1096,42.7936C50.5345,44.848299999999995,49.3898,46.7063,47.802800000000005,48.160799999999995C47.1834,48.729,47.0449,49.6411,47.4685,50.3618L49.8016,54.3249C50.07,54.7824,50.5309,55.1001,51.060500000000005,55.1927C51.1663,55.2106,51.2742,55.2196,51.379999999999995,55.2196C51.8066,55.2202,52.2199,55.0737,52.5476,54.8057C56.0407,51.9518,58.4579,47.9814,59.3546,43.622C59.4641,43.0957,59.3259,42.549099999999996,58.9784,42.134C58.6326,41.718599999999995,58.1147,41.4778,57.568,41.4781ZM20.411568,37.241C20.757316,37.6573,21.27588,37.8989,21.82367,37.8989L26.52259,37.8989C27.34233,37.8986,28.06128,37.361999999999995,28.280369999999998,36.587C28.776699999999998,34.8389,29.68048,33.2279,30.920299999999997,31.8814C31.4524,31.3057,31.544600000000003,30.4588,31.1485,29.785890000000002L28.77722,25.75641C28.49772,25.28303,28.01361,24.96039,27.46209,24.87994C27.37433,24.86765,27.28578,24.86166,27.19713,24.86203C26.73679,24.86203,26.28729,25.03413,25.9476,25.349510000000002C22.91091,28.15468,20.866486,31.7521,20.0370799,35.7513C19.9271883,36.2779,20.0647273,36.825,20.411568,37.241Z" fill="#FFFFFF" fill-opacity="1"/></g></g></svg> |
@ -0,0 +1,91 @@ |
|||||
|
/* CSS Document */ |
||||
|
html { |
||||
|
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; |
||||
|
height: 100vh; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
html, body { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
html, |
||||
|
body, |
||||
|
ol, |
||||
|
dl, |
||||
|
dd, |
||||
|
dt, |
||||
|
p, |
||||
|
h1, |
||||
|
h2, |
||||
|
h3, |
||||
|
h4, |
||||
|
h5, |
||||
|
h6, |
||||
|
form, |
||||
|
fieldset, |
||||
|
legend, |
||||
|
img { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
fieldset { |
||||
|
border: none; |
||||
|
} |
||||
|
img { |
||||
|
display: block; |
||||
|
} |
||||
|
address, |
||||
|
caption, |
||||
|
cite, |
||||
|
code, |
||||
|
dfn, |
||||
|
th, |
||||
|
var { |
||||
|
font-style: normal; |
||||
|
font-weight: normal; |
||||
|
} |
||||
|
ul, |
||||
|
ol, |
||||
|
li { |
||||
|
list-style: none; |
||||
|
} |
||||
|
a { |
||||
|
color: #666; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
* { |
||||
|
box-sizing: border-box !important; |
||||
|
} |
||||
|
|
||||
|
a { |
||||
|
&:visited { |
||||
|
color: inherit; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
input, |
||||
|
button, |
||||
|
select, |
||||
|
textarea { |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
textarea { |
||||
|
resize: none; |
||||
|
} |
||||
|
|
||||
|
input[type='number'] { |
||||
|
&::-webkit-outer-spin-button, |
||||
|
&::-webkit-inner-spin-button { |
||||
|
-webkit-appearance: none; |
||||
|
} |
||||
|
-moz-appearance: textfield; |
||||
|
} |
||||
|
.clear { |
||||
|
clear: both; |
||||
|
} |
||||
|
|
||||
|
.konvajs-content{ |
||||
|
canvas{ |
||||
|
border: 1px solid #ccc !important; |
||||
|
} |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
:root { |
||||
|
--el-font-size-base: 50px; |
||||
|
--el-button-size: 80px; |
||||
|
|
||||
|
|
||||
|
|
||||
|
--el-color-primary: #26509C; |
||||
|
//--el-button-active-bg-color: linear-gradient(90deg, #0657C0 24%, #096AE0 101%); |
||||
|
//--text-color-primary: #17213c; |
||||
|
//--el-color-success: rgba(88, 162, 95, 1); |
||||
|
//--text-color-info: #838b99; |
||||
|
//--el-input-border: #dae0f2; |
||||
|
//--el-font-weight-primary: 400; |
||||
|
//--color-red: #f56c6c; |
||||
|
//--color-green: #67c23a; |
||||
|
//--color-yellow: #e6a23c; |
||||
|
//--color-blue: --el-color-primary; |
||||
|
--el-font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
$primary-color: #0a57ea; |
||||
|
$success-color: #67c23a; |
||||
|
$danger-color: #f56c6c; |
||||
|
$warn-color: #e6a23c; |
||||
|
$info-color: #909399; |
||||
|
|
||||
|
@use './common.scss'; |
||||
|
@use './element.scss'; |
||||
|
|
||||
|
.mt-20 { |
||||
|
margin-top: 20px; |
||||
|
} |
@ -0,0 +1,126 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { ref } from 'vue' |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
type: { |
||||
|
type: String, |
||||
|
default: 'default', |
||||
|
}, |
||||
|
disabled: { |
||||
|
type: Boolean, |
||||
|
default: false, |
||||
|
}, |
||||
|
loading: { // 添加 loading 属性 |
||||
|
type: Boolean, |
||||
|
default: false, |
||||
|
}, |
||||
|
clickHandle: { |
||||
|
type: Function, |
||||
|
required: false, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const isLoading = ref(false) |
||||
|
|
||||
|
async function handleClick() { |
||||
|
if (!props.clickHandle || isLoading.value) |
||||
|
return |
||||
|
isLoading.value = true // 进入 loading |
||||
|
try { |
||||
|
await props.clickHandle() // 执行异步操作 |
||||
|
} |
||||
|
finally { |
||||
|
isLoading.value = false // 结束 loading |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const setLoading = (loading: boolean) => { |
||||
|
isLoading.value = loading |
||||
|
} |
||||
|
|
||||
|
defineExpose({ |
||||
|
setLoading, |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="ft-button" :class="{ 'ft-button-disabled': disabled || isLoading }" @click="handleClick"> |
||||
|
<!-- 添加 loading 判断 --> |
||||
|
<div v-show="disabled || isLoading" class="my-button-shadow" /> <!-- 添加 loading 判断 --> |
||||
|
<div |
||||
|
class="my-button" :class="{ |
||||
|
[`my-button-${type}`]: true, |
||||
|
'button-disabled': disabled || isLoading, // 添加 loading 判断 |
||||
|
}" |
||||
|
> |
||||
|
<el-icon v-if="isLoading" :color="type === 'default' ? '#26509C' : '#fff'"> |
||||
|
<!-- 添加 loading 判断 --> |
||||
|
<Loading class="rotate-loading" /> <!-- 添加旋转类 --> |
||||
|
</el-icon> |
||||
|
<slot /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.ft-button { |
||||
|
position: relative; |
||||
|
display: inline-block; |
||||
|
margin-right: 20px; |
||||
|
} |
||||
|
.ft-button-disabled { |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
.my-button-shadow { |
||||
|
position: absolute; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 100; |
||||
|
} |
||||
|
.my-button { |
||||
|
height: var(--el-button-size); |
||||
|
padding: 0 50px; |
||||
|
border-radius: 10px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
cursor: pointer; |
||||
|
font-size: 40px; |
||||
|
width: fit-content; |
||||
|
position: relative; |
||||
|
.el-icon { |
||||
|
position: absolute; |
||||
|
left: 5px; |
||||
|
svg { |
||||
|
width: 35px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.button-disabled { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
.my-button-default { |
||||
|
background: linear-gradient(180deg, #D8E3F8 0%, #FBFCFE 100%); |
||||
|
color: #26509C; |
||||
|
border: 1px solid #D8E3F8; |
||||
|
} |
||||
|
.my-button-primary { |
||||
|
background: linear-gradient(90deg, #0657C0 24%, #096AE0 101%); |
||||
|
color: #fff; |
||||
|
border: 1px solid #D8E3F8; |
||||
|
} |
||||
|
|
||||
|
.my-button-info { |
||||
|
background: #335AA5; |
||||
|
color: #fff; |
||||
|
border: 1px solid #335AA5; |
||||
|
} |
||||
|
|
||||
|
.rotate-loading { |
||||
|
animation: spin 1s linear infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,79 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { ref, watch } from 'vue' |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
title: { |
||||
|
type: String, |
||||
|
default: '', |
||||
|
}, |
||||
|
visible: { |
||||
|
type: Boolean, |
||||
|
default: false, |
||||
|
}, |
||||
|
width: { |
||||
|
type: String, |
||||
|
default: '50%', |
||||
|
}, |
||||
|
okLoading: { |
||||
|
type: Boolean, |
||||
|
default: false, |
||||
|
}, |
||||
|
}) |
||||
|
const emits = defineEmits(['update:visible', 'ok', 'cancel']) |
||||
|
|
||||
|
const cancel = () => { |
||||
|
show.value = false |
||||
|
emits('cancel') |
||||
|
} |
||||
|
const ok = () => { |
||||
|
emits('ok') |
||||
|
} |
||||
|
|
||||
|
const show = ref(false) |
||||
|
|
||||
|
watch( |
||||
|
() => props.visible, |
||||
|
(newVal) => { |
||||
|
show.value = newVal |
||||
|
}, |
||||
|
{ |
||||
|
// 对于对象需要深度监听 |
||||
|
deep: true, |
||||
|
immediate: true, |
||||
|
}, |
||||
|
) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-dialog |
||||
|
v-model="show" |
||||
|
center |
||||
|
:close-on-click-modal="false" |
||||
|
:close-on-press-escape="false" |
||||
|
:show-close="false" |
||||
|
destroy-on-close |
||||
|
append-to-body |
||||
|
:title="title" |
||||
|
:width="width" |
||||
|
:before-close="cancel" |
||||
|
> |
||||
|
<slot /> |
||||
|
<template #footer> |
||||
|
<div v-if="$slots.footer" class="dialog-footer"> |
||||
|
<slot name="footer" /> |
||||
|
</div> |
||||
|
<div v-else class="dialog-footer"> |
||||
|
<ft-button @click="cancel"> |
||||
|
取消 |
||||
|
</ft-button> |
||||
|
<ft-button type="primary" :loading="okLoading" @click="ok"> |
||||
|
确认 |
||||
|
</ft-button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-dialog> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
|
||||
|
</style> |
@ -0,0 +1,169 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { cmdNameMap } from 'libs/utils' |
||||
|
import { useSystemStore } from 'stores/useSystemStore' |
||||
|
import { computed, nextTick, ref, watch } from 'vue' |
||||
|
|
||||
|
defineProps({ |
||||
|
visible: { |
||||
|
type: Boolean, |
||||
|
default: false, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const systemStore = useSystemStore() |
||||
|
const title = computed(() => { |
||||
|
return cmdNameMap[systemStore.systemList[0]?.cmdCode as keyof typeof cmdNameMap] || systemStore.systemList[0]?.cmdCode |
||||
|
}) |
||||
|
|
||||
|
const maskBodyRef = ref<HTMLElement | null>(null) |
||||
|
const maskRef = ref<HTMLElement | null>(null) |
||||
|
const maskHeaderRef = ref<HTMLElement | null>(null) |
||||
|
|
||||
|
const statusMap = { |
||||
|
fail: 'danger', |
||||
|
error: 'danger', |
||||
|
success: 'success', |
||||
|
finish: 'primary', |
||||
|
SEND: 'primary', |
||||
|
receive: 'primary', |
||||
|
start: 'primary', |
||||
|
result: 'primary', |
||||
|
} |
||||
|
|
||||
|
watch( |
||||
|
() => systemStore.systemList, |
||||
|
async () => { |
||||
|
await nextTick() |
||||
|
if (maskBodyRef.value) { |
||||
|
maskBodyRef.value.scrollTop = maskBodyRef.value.scrollHeight |
||||
|
} |
||||
|
}, |
||||
|
{ deep: true }, |
||||
|
) |
||||
|
|
||||
|
// 拖拽相关逻辑 |
||||
|
let isDragging = false |
||||
|
let offsetX = 0 |
||||
|
let offsetY = 0 |
||||
|
|
||||
|
const handleMouseDown = (event: MouseEvent | TouchEvent) => { |
||||
|
if (maskRef.value && maskHeaderRef.value) { |
||||
|
isDragging = true |
||||
|
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX |
||||
|
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY |
||||
|
offsetX = clientX - maskRef.value.offsetLeft |
||||
|
offsetY = clientY - maskRef.value.offsetTop |
||||
|
document.addEventListener('mousemove', handleMouseMove) |
||||
|
document.addEventListener('mouseup', handleMouseUp) |
||||
|
document.addEventListener('touchmove', handleMouseMove) |
||||
|
document.addEventListener('touchend', handleMouseUp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const handleMouseMove = (event: MouseEvent | TouchEvent) => { |
||||
|
if (maskRef.value && isDragging) { |
||||
|
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX |
||||
|
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY |
||||
|
|
||||
|
// 获取 body 的宽度和高度 |
||||
|
const bodyWidth = document.body.clientWidth |
||||
|
const bodyHeight = document.body.clientHeight |
||||
|
|
||||
|
// 计算新的位置,并确保不会超出 body 的范围 |
||||
|
const newLeft = Math.max(0, Math.min(clientX - offsetX, bodyWidth - maskRef.value.offsetWidth)) |
||||
|
const newTop = Math.max(0, Math.min(clientY - offsetY, bodyHeight - maskRef.value.offsetHeight)) |
||||
|
|
||||
|
maskRef.value.style.left = `${newLeft}px` |
||||
|
maskRef.value.style.top = `${newTop}px` |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const handleMouseUp = () => { |
||||
|
isDragging = false |
||||
|
document.removeEventListener('mousemove', handleMouseMove) |
||||
|
document.removeEventListener('mouseup', handleMouseUp) |
||||
|
document.removeEventListener('touchmove', handleMouseMove) |
||||
|
document.removeEventListener('touchend', handleMouseUp) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<teleport to="body"> |
||||
|
<!-- 使用 transition 组件包裹 mask 元素 --> |
||||
|
<transition name="mask-fade"> |
||||
|
<div |
||||
|
v-if="visible && systemStore.isDebug" |
||||
|
ref="maskRef" |
||||
|
class="mask" |
||||
|
> |
||||
|
<div |
||||
|
ref="maskHeaderRef" class="mask-header" @mousedown="handleMouseDown" |
||||
|
@touchstart="handleMouseDown" |
||||
|
> |
||||
|
<p>{{ title }}</p> |
||||
|
<el-icon @click="systemStore.updateStreamVisible(false)"> |
||||
|
<Close /> |
||||
|
</el-icon> |
||||
|
</div> |
||||
|
<div ref="maskBodyRef" class="mask-body"> |
||||
|
<el-timeline> |
||||
|
<el-timeline-item |
||||
|
v-for="item in systemStore.systemList" :key="item" |
||||
|
:timestamp="JSON.stringify(item.content)" |
||||
|
> |
||||
|
<el-tag :type="statusMap[item.status]" class="mask-tag"> |
||||
|
{{ item.title }} |
||||
|
</el-tag> |
||||
|
</el-timeline-item> |
||||
|
</el-timeline> |
||||
|
</div> |
||||
|
</div> |
||||
|
</transition> |
||||
|
</teleport> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.mask { |
||||
|
width: 800px; |
||||
|
height: 500px; |
||||
|
padding: 20px; |
||||
|
background: #fff; |
||||
|
box-shadow: var(--el-box-shadow-light); |
||||
|
position: absolute; |
||||
|
bottom: 130px; |
||||
|
right: 100px; |
||||
|
border-radius: 10px; |
||||
|
font-size: 30px; |
||||
|
.mask-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
height: 50px; |
||||
|
font-size: 30px; |
||||
|
border-bottom: 1px solid #ddd; |
||||
|
cursor: move; // 添加鼠标指针样式 |
||||
|
.el-icon svg{ |
||||
|
width: 30px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
} |
||||
|
.mask-body { |
||||
|
padding: 10px; |
||||
|
height: calc(100% - 41px); |
||||
|
overflow: auto; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 定义过渡效果 */ |
||||
|
.mask-fade-enter-active, .mask-fade-leave-active { |
||||
|
transition: transform 0.5s ease; |
||||
|
} |
||||
|
|
||||
|
.mask-fade-enter-from { |
||||
|
transform: translateX(100%); |
||||
|
} |
||||
|
|
||||
|
.mask-fade-leave-to { |
||||
|
transform: translateX(100%); |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,7 @@ |
|||||
|
export default { |
||||
|
props: ['row', 'render', 'index', 'column'], |
||||
|
inheritAttrs: false, |
||||
|
setup(props: any) { |
||||
|
return () => props.render(props.row) |
||||
|
}, |
||||
|
} |
@ -0,0 +1,145 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VNode } from 'vue' |
||||
|
import { onMounted, reactive } from 'vue' |
||||
|
import Expand from './expand' |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'FtTable', |
||||
|
}) |
||||
|
const props = withDefaults(defineProps<TableProp>(), { |
||||
|
columns: () => [], |
||||
|
mustInit: true, |
||||
|
hasHeader: true, |
||||
|
}) |
||||
|
const emits = defineEmits([]) |
||||
|
enum ColumnType { |
||||
|
index = 'index', |
||||
|
selection = 'selection', |
||||
|
expand = 'expand', |
||||
|
} |
||||
|
interface TableColumn { |
||||
|
title: string |
||||
|
key: string |
||||
|
type?: ColumnType |
||||
|
width?: number // 列宽 |
||||
|
fixed?: 'left' | 'right' | undefined // 是否固定列 |
||||
|
render?: (row: any) => VNode // 内容自定义 |
||||
|
} |
||||
|
|
||||
|
interface Btn { |
||||
|
name: string |
||||
|
icon?: string |
||||
|
type?: string |
||||
|
serverUrl: string |
||||
|
} |
||||
|
|
||||
|
interface TableProp { |
||||
|
columns: TableColumn[] |
||||
|
getDataFn: (params: any) => Promise<any> // 表格数据的接口 |
||||
|
mustInit?: boolean // 是否在mounted里执行getDataFn |
||||
|
hasHeader?: boolean |
||||
|
btnList?: Btn[] |
||||
|
} |
||||
|
|
||||
|
// const attrs = useAttrs() |
||||
|
|
||||
|
async function methodParent(fn: any) { |
||||
|
const newFn = fn[0] === '/' ? fn.slice(1) : fn |
||||
|
emits(newFn as never) |
||||
|
} |
||||
|
|
||||
|
onMounted(() => { |
||||
|
if (props.mustInit) { |
||||
|
initData() |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
const state = reactive({ |
||||
|
loading: false, |
||||
|
dataTotal: 0, |
||||
|
tableData: [], |
||||
|
}) |
||||
|
|
||||
|
function initData() { |
||||
|
state.loading = true |
||||
|
props |
||||
|
.getDataFn({}) |
||||
|
.then((data) => { |
||||
|
console.log(data) |
||||
|
state.tableData = data |
||||
|
state.loading = false |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
state.loading = false |
||||
|
}) |
||||
|
} |
||||
|
defineExpose({ |
||||
|
initData, |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-main> |
||||
|
<div v-if="hasHeader" class="header"> |
||||
|
<div v-for="btn in btnList" :key="btn.serverUrl"> |
||||
|
<el-button :icon="btn.icon" :type="btn.type" @click="methodParent(btn.serverUrl)"> |
||||
|
{{ btn.name }} |
||||
|
</el-button> |
||||
|
</div> |
||||
|
<div class="search"> |
||||
|
<el-input> |
||||
|
<template #suffix> |
||||
|
<el-icon class="el-input__icon"> |
||||
|
<search /> |
||||
|
</el-icon> |
||||
|
</template> |
||||
|
</el-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<el-table |
||||
|
v-loading="state.loading" |
||||
|
:="$attrs" |
||||
|
:data="state.tableData" |
||||
|
style="width: 100%" |
||||
|
height="calc(100% - 100px)" |
||||
|
:highlight-current-row="true" |
||||
|
class="container-table" |
||||
|
header-row-class-name="header-row-class" |
||||
|
> |
||||
|
<template v-for="(column, index) in columns" :key="column.key"> |
||||
|
<el-table-column |
||||
|
show-overflow-tooltip |
||||
|
:prop="column.key" |
||||
|
:label="column.title" |
||||
|
:width="column.width" |
||||
|
:type="column.type" |
||||
|
:fixed="column.fixed" |
||||
|
> |
||||
|
<template v-if="column.render" #default="scope"> |
||||
|
<Expand :column="column" :row="scope.row" :render="column.render" :index="index" /> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</template> |
||||
|
</el-table> |
||||
|
</el-main> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
height: 100px; |
||||
|
.search { |
||||
|
width: 200px; |
||||
|
} |
||||
|
} |
||||
|
:deep(.header-row-class) { |
||||
|
th { |
||||
|
background: #f7f8fa; |
||||
|
font-weight: 900; |
||||
|
//border-bottom: none; |
||||
|
color: #000; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,28 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Teleport to="body"> |
||||
|
<div class="mask-box"> |
||||
|
设备急停中 |
||||
|
</div> |
||||
|
</Teleport> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.mask-box { |
||||
|
position: absolute; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: rgba(0, 0, 0, 0.7); |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
color: var(--el-color-danger); |
||||
|
font-size: 100px; |
||||
|
z-index: 10000; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,20 @@ |
|||||
|
/// <reference types="vite/client" />
|
||||
|
|
||||
|
interface ImportMetaEnv { |
||||
|
readonly FT_NODE_ENV: string |
||||
|
readonly FT_WS_URL: string |
||||
|
readonly FT_PROXY: string |
||||
|
} |
||||
|
|
||||
|
interface ImportMeta { |
||||
|
readonly env: ImportMetaEnv |
||||
|
} |
||||
|
|
||||
|
declare const __APP_VERSION__: string; |
||||
|
|
||||
|
declare module '*.vue' { |
||||
|
import type { DefineComponent } from 'vue' |
||||
|
|
||||
|
const component: DefineComponent<object, object, any> |
||||
|
export default component |
||||
|
} |
@ -0,0 +1,76 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { authRoutes } from 'router/routes' |
||||
|
import { useRouter } from 'vue-router' |
||||
|
|
||||
|
const router = useRouter() |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-container class="main"> |
||||
|
<el-header class="header" /> |
||||
|
<el-container> |
||||
|
<el-aside class="aside"> |
||||
|
<div v-for="item in authRoutes" :key="item.path" class="aside-item" :class="{ 'aside-item-active': router.currentRoute.value.path === item.path }" @click="router.push(item.path)"> |
||||
|
<img src="../assets/images/home.svg" alt=""> |
||||
|
<span>{{ item.meta!.title }}</span> |
||||
|
</div> |
||||
|
</el-aside> |
||||
|
<el-container> |
||||
|
<el-main> |
||||
|
<div class="content"> |
||||
|
<router-view /> |
||||
|
</div> |
||||
|
</el-main> |
||||
|
</el-container> |
||||
|
</el-container> |
||||
|
<el-footer class="footer" /> |
||||
|
</el-container> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.main { |
||||
|
box-sizing: border-box; |
||||
|
height: 100%; |
||||
|
background: #F6F6F6; |
||||
|
.header { |
||||
|
height: 60px; |
||||
|
} |
||||
|
} |
||||
|
.aside { |
||||
|
width: 150px; |
||||
|
overflow: auto; |
||||
|
padding-left: 10px; |
||||
|
.aside-item { |
||||
|
width: 100%; |
||||
|
height: 50px; |
||||
|
background: #fff; |
||||
|
border-radius: 10px; |
||||
|
color: #1989FA; |
||||
|
margin: 10px 0; |
||||
|
padding: 0 10px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
img { |
||||
|
height: 80%; |
||||
|
margin-right: 20px; |
||||
|
} |
||||
|
} |
||||
|
.aside-item-active { |
||||
|
background: #1989FA; |
||||
|
color: #fff; |
||||
|
} |
||||
|
} |
||||
|
.el-main { |
||||
|
padding: 0 15px; |
||||
|
} |
||||
|
.content { |
||||
|
height: 100%; |
||||
|
background: #fff; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 1px rgba(0, 0, 0, 0.1); |
||||
|
padding: 10px; |
||||
|
} |
||||
|
.footer { |
||||
|
height: 60px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,5 @@ |
|||||
|
// 请求头里token的名称
|
||||
|
export const HEADER_TOKEN_KEY = 'Authorization' |
||||
|
|
||||
|
// sessionStorage里token的名称
|
||||
|
export const SESSIONSTORAGE_TOKEN_KEY = 'web_token' |
@ -0,0 +1,92 @@ |
|||||
|
import { HEADER_TOKEN_KEY } from '@/libs/constant' |
||||
|
import axios from 'axios' |
||||
|
import { FtMessage } from 'libs/message' |
||||
|
import { getToken } from 'libs/token' |
||||
|
|
||||
|
const http = axios.create({ |
||||
|
baseURL: `/api`, |
||||
|
timeout: 1000 * 60, |
||||
|
}) |
||||
|
|
||||
|
// 请求拦截器
|
||||
|
http.interceptors.request.use( |
||||
|
(config) => { |
||||
|
if (getToken()) { |
||||
|
config.headers![HEADER_TOKEN_KEY] = getToken() |
||||
|
} |
||||
|
return config |
||||
|
}, |
||||
|
(error: any) => { |
||||
|
return Promise.reject(error) |
||||
|
}, |
||||
|
) |
||||
|
|
||||
|
// 响应拦截器
|
||||
|
http.interceptors.response.use( |
||||
|
(response) => { |
||||
|
if ( |
||||
|
response.status === 200 |
||||
|
&& response.data.code !== '00000' |
||||
|
) { |
||||
|
// 返回错误拦截
|
||||
|
FtMessage.error(response.data.msg) |
||||
|
return Promise.reject(response) |
||||
|
} |
||||
|
else if ( |
||||
|
response.config.url?.includes('/files/download') |
||||
|
|| response.config.url?.includes('downloadStream') |
||||
|
) { |
||||
|
return response.data |
||||
|
} |
||||
|
else if (response.data instanceof Blob) { |
||||
|
return response.data |
||||
|
} |
||||
|
return response.data.data // 返回数据体
|
||||
|
}, |
||||
|
(error: any) => { |
||||
|
console.log(error) |
||||
|
if (error.response && error.response.status === 401) { |
||||
|
FtMessage.error('账号权限过期') |
||||
|
// TODO 登出
|
||||
|
} |
||||
|
else { |
||||
|
if (error.message.includes('timeout')) { |
||||
|
FtMessage.error('请求超时') |
||||
|
} |
||||
|
else if (error.message.includes('Network')) { |
||||
|
FtMessage.error('网络连接错误') |
||||
|
} |
||||
|
else { |
||||
|
FtMessage.error('接口请求失败') |
||||
|
} |
||||
|
error.response = { |
||||
|
data: { |
||||
|
res: false, |
||||
|
}, |
||||
|
} |
||||
|
return Promise.reject(error.response) |
||||
|
} |
||||
|
}, |
||||
|
) |
||||
|
|
||||
|
// 封装 GET 请求
|
||||
|
export function get<T>(url: string, params?: any): Promise<T> { |
||||
|
return http.get(url, { params }) |
||||
|
} |
||||
|
|
||||
|
// 封装 POST 请求
|
||||
|
export function post<T>(url: string, data?: any): Promise<T> { |
||||
|
return http.post(url, data) |
||||
|
} |
||||
|
|
||||
|
// 封装 PUT 请求
|
||||
|
export function put<T>(url: string, data?: any): Promise<T> { |
||||
|
return http.put(url, data) |
||||
|
} |
||||
|
|
||||
|
// 封装 DELETE 请求
|
||||
|
export function del<T>(url: string, params?: any): Promise<T> { |
||||
|
return http.delete(url, { params }) |
||||
|
} |
||||
|
|
||||
|
export default http |
@ -0,0 +1,37 @@ |
|||||
|
import { ElMessage } from 'element-plus' |
||||
|
|
||||
|
export const FtMessage = { |
||||
|
info: (message: string) => { |
||||
|
ElMessage({ |
||||
|
message, |
||||
|
type: 'info', |
||||
|
grouping: true, |
||||
|
plain: true, |
||||
|
}) |
||||
|
}, |
||||
|
success: (message: string) => { |
||||
|
ElMessage({ |
||||
|
message, |
||||
|
type: 'success', |
||||
|
grouping: true, |
||||
|
plain: true, |
||||
|
}) |
||||
|
}, |
||||
|
warning: (message: string) => { |
||||
|
ElMessage({ |
||||
|
message, |
||||
|
type: 'warning', |
||||
|
grouping: true, |
||||
|
plain: true, |
||||
|
}) |
||||
|
}, |
||||
|
error: (message: string) => { |
||||
|
ElMessage({ |
||||
|
message, |
||||
|
type: 'error', |
||||
|
grouping: true, |
||||
|
plain: true, |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
} |
@ -0,0 +1,242 @@ |
|||||
|
/* |
||||
|
* @description: 封装socket方法 |
||||
|
* @date: 2023-01-20 |
||||
|
* @author: 郭安鹏 |
||||
|
*/ |
||||
|
import { ElMessage } from 'element-plus' |
||||
|
import { ref } from 'vue' |
||||
|
|
||||
|
export const isClose = ref(true) |
||||
|
|
||||
|
interface socket { |
||||
|
appKey: any |
||||
|
websocket: any |
||||
|
connectURL: string |
||||
|
socket_open: boolean |
||||
|
hearBeat_timer: any |
||||
|
hearBeat_interval: number |
||||
|
is_reconnect: boolean |
||||
|
reconnect_count: number |
||||
|
reconnect_current: number |
||||
|
reconnect_number: number |
||||
|
reconnect_timer: any |
||||
|
reconnect_interval: number |
||||
|
receiveMessageCallBackObj: { [key: string]: any[] } |
||||
|
initCallBacks: any |
||||
|
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
|
receiveMessage: Function |
||||
|
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
|
registerCallback: Function |
||||
|
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
|
unregisterCallback: Function |
||||
|
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
|
registerInitCallback: Function |
||||
|
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
|
init: (receiveMessage?: Function | null, type?: string, connectURL?: string) => any |
||||
|
heartbeat: () => void |
||||
|
heartSend: () => void |
||||
|
send: (data: any, callback?: any) => void |
||||
|
close: () => void |
||||
|
reconnect: () => void |
||||
|
sendAppJoin: () => void |
||||
|
} |
||||
|
|
||||
|
// eslint-disable-next-line ts/no-redeclare
|
||||
|
export const socket: socket = { |
||||
|
appKey: null, |
||||
|
websocket: null, |
||||
|
connectURL: import.meta.env.FT_WS_URL, |
||||
|
// 开启标识
|
||||
|
socket_open: false, |
||||
|
// 心跳timer
|
||||
|
hearBeat_timer: null, |
||||
|
// 心跳发送频率
|
||||
|
hearBeat_interval: 5000, |
||||
|
// 是否需要重连
|
||||
|
is_reconnect: true, |
||||
|
// 重连次数
|
||||
|
reconnect_count: 10, |
||||
|
// 已发起重连次数
|
||||
|
reconnect_current: 1, |
||||
|
// 网络错误提示此时
|
||||
|
reconnect_number: 0, |
||||
|
// 重连timer
|
||||
|
reconnect_timer: null, |
||||
|
// 重连频率
|
||||
|
reconnect_interval: 1000, |
||||
|
|
||||
|
receiveMessageCallBackObj: {}, |
||||
|
initCallBacks: [], |
||||
|
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
|
registerInitCallback: (fn: Function, ...args: any) => { |
||||
|
// socket 连接成功后的回调
|
||||
|
socket.initCallBacks.push({ |
||||
|
fn, |
||||
|
args, |
||||
|
}) |
||||
|
}, |
||||
|
// 接收消息的方法
|
||||
|
receiveMessage: (e: any) => { |
||||
|
const message = JSON.parse(e.data) |
||||
|
const callbacks = socket.receiveMessageCallBackObj[message.type] |
||||
|
if (callbacks) { |
||||
|
callbacks.forEach((fn) => { |
||||
|
fn(message.data) |
||||
|
}) |
||||
|
} |
||||
|
else { |
||||
|
// console.error('请注册当前类型的回调函数', message)
|
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 修改 registerCallback 方法
|
||||
|
registerCallback: (fn: any, type: any) => { |
||||
|
if (!socket.receiveMessageCallBackObj[type]) { |
||||
|
socket.receiveMessageCallBackObj[type] = [] |
||||
|
} |
||||
|
socket.receiveMessageCallBackObj[type].push(fn) |
||||
|
}, |
||||
|
|
||||
|
// 添加 unregisterCallback 方法
|
||||
|
unregisterCallback: (fn: any, type: any) => { |
||||
|
if (socket.receiveMessageCallBackObj[type]) { |
||||
|
const index = socket.receiveMessageCallBackObj[type].indexOf(fn) |
||||
|
if (index !== -1) { |
||||
|
socket.receiveMessageCallBackObj[type].splice(index, 1) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
init: async ( |
||||
|
receiveMessageCallBack: any, |
||||
|
type?: string, |
||||
|
connectURL?: string, |
||||
|
reconnection?: boolean, |
||||
|
) => { |
||||
|
if (!('WebSocket' in window)) { |
||||
|
ElMessage.warning('浏览器不支持WebSocket') |
||||
|
return null |
||||
|
} |
||||
|
|
||||
|
// 注册回调函数
|
||||
|
if (receiveMessageCallBack && type) { |
||||
|
socket.registerCallback(receiveMessageCallBack, type) |
||||
|
} |
||||
|
// 已经创建过连接无需重复创建
|
||||
|
if (socket.websocket && !reconnection) { |
||||
|
return socket.websocket |
||||
|
} |
||||
|
|
||||
|
await new Promise((rs) => { |
||||
|
socket.websocket = new WebSocket(connectURL || socket.connectURL) |
||||
|
// 消息接收
|
||||
|
socket.websocket.onmessage = (e: any) => { |
||||
|
socket.receiveMessage(e) |
||||
|
} |
||||
|
// socket关闭
|
||||
|
socket.websocket.onclose = () => { |
||||
|
console.error('onclose') |
||||
|
|
||||
|
clearInterval(socket.hearBeat_interval) |
||||
|
socket.socket_open = false |
||||
|
isClose.value = true |
||||
|
// 需要重新连接
|
||||
|
if (socket.is_reconnect) { |
||||
|
socket.reconnect_timer = setTimeout(() => { |
||||
|
// 超过重连次数
|
||||
|
// if (socket.reconnect_current > socket.reconnect_count) {
|
||||
|
// clearTimeout(socket.reconnect_timer);
|
||||
|
// socket.is_reconnect = false;
|
||||
|
// console.error('超出重连次数,不再重连', new Date());
|
||||
|
// return;
|
||||
|
// }
|
||||
|
// 记录重连次数
|
||||
|
socket.reconnect_current++ |
||||
|
socket.reconnect() |
||||
|
}, socket.reconnect_interval) |
||||
|
} |
||||
|
} |
||||
|
// 连接发生错误
|
||||
|
socket.websocket.onerror = function () { |
||||
|
console.error('onerror') |
||||
|
isClose.value = true |
||||
|
socket.socket_open = false |
||||
|
} |
||||
|
// 连接成功
|
||||
|
socket.websocket.onopen = function () { |
||||
|
socket.socket_open = true |
||||
|
socket.is_reconnect = true |
||||
|
// 开启心跳
|
||||
|
socket.heartbeat() |
||||
|
// 连接成功后发起app加入消息
|
||||
|
socket.sendAppJoin() |
||||
|
|
||||
|
for (const fnItem of socket.initCallBacks) { |
||||
|
fnItem.fn(...fnItem.args) |
||||
|
} |
||||
|
|
||||
|
isClose.value = false |
||||
|
|
||||
|
rs(true) // socket 已连接
|
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
send: (data, callback = null) => { |
||||
|
// 开启状态直接发送
|
||||
|
if (socket.websocket.readyState === socket.websocket.OPEN) { |
||||
|
socket.websocket.send(JSON.stringify(data)) |
||||
|
if (callback) { |
||||
|
callback() |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
clearInterval(socket.hearBeat_timer) |
||||
|
socket.reconnect_number++ |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
heartbeat: () => { |
||||
|
if (socket.hearBeat_timer) { |
||||
|
clearInterval(socket.hearBeat_timer) |
||||
|
} |
||||
|
socket.hearBeat_timer = setInterval(() => { |
||||
|
socket.heartSend() |
||||
|
}, socket.hearBeat_interval) |
||||
|
}, |
||||
|
heartSend: () => { |
||||
|
socket.send({ |
||||
|
type: 'ping', // ping
|
||||
|
}) |
||||
|
}, |
||||
|
close: () => { |
||||
|
clearInterval(socket.hearBeat_timer) |
||||
|
socket.is_reconnect = false |
||||
|
socket.websocket && socket.websocket.close() |
||||
|
socket.websocket = null |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 重新连接 |
||||
|
*/ |
||||
|
reconnect: () => { |
||||
|
if (socket.websocket) { |
||||
|
// 需要重连
|
||||
|
if (socket.is_reconnect) { |
||||
|
socket.websocket.close() |
||||
|
socket.websocket = null |
||||
|
socket.init() |
||||
|
} |
||||
|
else { |
||||
|
socket.close() |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
sendAppJoin: () => { |
||||
|
socket.send({ |
||||
|
appKey: socket.appKey, |
||||
|
type: 1, // appJoin
|
||||
|
}) |
||||
|
}, |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
import { SESSIONSTORAGE_TOKEN_KEY } from './constant' |
||||
|
|
||||
|
export function getToken() { |
||||
|
return sessionStorage[SESSIONSTORAGE_TOKEN_KEY] |
||||
|
} |
||||
|
|
||||
|
export function setToken(token: string) { |
||||
|
sessionStorage[SESSIONSTORAGE_TOKEN_KEY] = token |
||||
|
} |
||||
|
|
||||
|
export function delToken() { |
||||
|
sessionStorage.removeItem(SESSIONSTORAGE_TOKEN_KEY) |
||||
|
} |
@ -0,0 +1,74 @@ |
|||||
|
import { FtMessage } from 'libs/message' |
||||
|
import { socket } from 'libs/socket' |
||||
|
import { useSystemStore } from 'stores/useSystemStore' |
||||
|
|
||||
|
export const sendControl = async (params: any) => { |
||||
|
if (!params.cmdId) { |
||||
|
params.cmdId = Date.now().toString() |
||||
|
} |
||||
|
const systemStore = useSystemStore() |
||||
|
|
||||
|
systemStore.systemList = [] |
||||
|
const cmdName = cmdNameMap[params.cmdCode as keyof typeof cmdNameMap] || params.cmdCode |
||||
|
|
||||
|
socket.init((data: any) => { |
||||
|
if (data.cmdId === params.cmdId) { |
||||
|
systemStore.pushSystemList(data) |
||||
|
} |
||||
|
}, 'cmd_debug') |
||||
|
socket.init((data: any) => { |
||||
|
if (data.cmdId === params.cmdId) { |
||||
|
systemStore.pushSystemList(data) |
||||
|
} |
||||
|
}, 'cmd_response') |
||||
|
// TODO 接口调用
|
||||
|
// await (type === 'debug' ? debugControl(params) : control(params))
|
||||
|
systemStore.updateStreamVisible(true) |
||||
|
FtMessage.success(`[${cmdName}]已发送`) |
||||
|
} |
||||
|
|
||||
|
export const cmdNameMap = { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export const generateColors = (count: number): string[] => { |
||||
|
const colors: string[] = [] |
||||
|
for (let i = 0; i < count; i++) { |
||||
|
// Increase hue step to make colors more distinct
|
||||
|
const hue = (i * 360) / count |
||||
|
// Introduce variation in saturation and lightness with larger steps
|
||||
|
const saturation = 30 + (i % 5) * 20 // Alternate between 30, 50, 70, 90, 110
|
||||
|
const lightness = 30 + (i % 4) * 20 // Alternate between 30, 50, 70, 90
|
||||
|
// Convert HSL to RGB
|
||||
|
const rgb = hslToRgb(hue, saturation, lightness) |
||||
|
// Convert RGB to hex
|
||||
|
const hex = rgbToHex(rgb.r, rgb.g, rgb.b) |
||||
|
colors.push(hex) |
||||
|
} |
||||
|
return colors |
||||
|
} |
||||
|
|
||||
|
const hslToRgb = (h: number, s: number, l: number): { r: number, g: number, b: number } => { |
||||
|
s /= 100 |
||||
|
l /= 100 |
||||
|
const k = (n: number) => (n + h / 30) % 12 |
||||
|
const a = s * Math.min(l, 1 - l) |
||||
|
const f = (n: number) => |
||||
|
l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))) |
||||
|
return { |
||||
|
r: Math.round(f(0) * 255), |
||||
|
g: Math.round(f(8) * 255), |
||||
|
b: Math.round(f(4) * 255), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const rgbToHex = (r: number, g: number, b: number): string => { |
||||
|
const toHex = (c: number) => `0${c.toString(16)}`.slice(-2) |
||||
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}` |
||||
|
} |
||||
|
|
||||
|
export const colors = generateColors(100) |
||||
|
|
||||
|
export function isNumber(value: any) { |
||||
|
return typeof value === 'number' && !Number.isNaN(value) |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue' // 引入 ElementPlusIconsVue
|
||||
|
import FtButton from 'components/common/FTButton/index.vue' |
||||
|
import FtDialog from 'components/common/FTDialog/index.vue' |
||||
|
import FtStream from 'components/common/FTStream/index.vue' |
||||
|
import FtTable from 'components/common/FTTable/index.vue' |
||||
|
import ElementPlus from 'element-plus' |
||||
|
import locale from 'element-plus/es/locale/lang/zh-cn' |
||||
|
import pinia from 'stores/index' |
||||
|
import { createApp } from 'vue' |
||||
|
import App from './app.vue' |
||||
|
import router from './router' |
||||
|
import 'element-plus/dist/index.css' |
||||
|
import 'assets/styles/main.scss' |
||||
|
|
||||
|
const app = createApp(App) |
||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { |
||||
|
app.component(key, component) |
||||
|
} |
||||
|
app.use(pinia) |
||||
|
app.component('FtTable', FtTable) |
||||
|
app.component('FtButton', FtButton) |
||||
|
app.component('FtDialog', FtDialog) |
||||
|
app.component('FtStream', FtStream) |
||||
|
app |
||||
|
.use(router) |
||||
|
.use(ElementPlus, { locale, zIndex: 3000 }) |
||||
|
.mount('#app') |
@ -0,0 +1,26 @@ |
|||||
|
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' |
||||
|
import { getToken } from '@/libs/token' |
||||
|
import { createRouter, createWebHashHistory } from 'vue-router' |
||||
|
import routes from './routes' |
||||
|
|
||||
|
const router = createRouter({ |
||||
|
history: createWebHashHistory(), |
||||
|
routes, |
||||
|
}) |
||||
|
|
||||
|
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { |
||||
|
if (getToken()) { |
||||
|
next() |
||||
|
} |
||||
|
else { |
||||
|
// 未登录
|
||||
|
if (to.name === 'login') { |
||||
|
next() |
||||
|
} |
||||
|
else { |
||||
|
next({ name: 'login' }) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
export default router |
@ -0,0 +1,46 @@ |
|||||
|
import type { RouteRecordRaw } from 'vue-router' |
||||
|
|
||||
|
const authRoutes: RouteRecordRaw[] = [ |
||||
|
{ |
||||
|
path: '/home', |
||||
|
name: 'home', |
||||
|
component: () => import('views/home/index.vue'), |
||||
|
meta: { |
||||
|
isDefault: true, |
||||
|
title: '首页', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
path: '/craft', |
||||
|
name: 'craft', |
||||
|
component: () => import('views/craft/index.vue'), |
||||
|
meta: { |
||||
|
isDefault: true, |
||||
|
title: '工艺', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
path: '/debug', |
||||
|
name: 'debug', |
||||
|
component: () => import('views/debug/index.vue'), |
||||
|
meta: { |
||||
|
title: '调试', |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
|
const routes: RouteRecordRaw[] = [ |
||||
|
{ |
||||
|
path: '/login', |
||||
|
name: 'login', |
||||
|
component: () => import('../views/login/index.vue'), |
||||
|
}, |
||||
|
{ |
||||
|
path: '/', |
||||
|
component: () => import('../layouts/default.vue'), |
||||
|
redirect: '/home', |
||||
|
children: authRoutes, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
export { authRoutes } |
||||
|
export default routes |
@ -0,0 +1,7 @@ |
|||||
|
import { createPinia } from 'pinia' |
||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' |
||||
|
|
||||
|
const pinia = createPinia() |
||||
|
pinia.use(piniaPluginPersistedstate) |
||||
|
|
||||
|
export default pinia |
@ -0,0 +1,22 @@ |
|||||
|
import { defineStore } from 'pinia' |
||||
|
|
||||
|
export const useSystemStore = defineStore('system', { |
||||
|
state: () => ({ |
||||
|
systemStatus: { |
||||
|
}, |
||||
|
systemSensor: { |
||||
|
humidity: 0, |
||||
|
}, |
||||
|
isDebug: import.meta.env.FT_NODE_ENV === 'dev', |
||||
|
streamVisible: false, |
||||
|
systemList: [{ cmdCode: '' }], |
||||
|
}), |
||||
|
actions: { |
||||
|
updateStreamVisible(bool: boolean) { |
||||
|
this.streamVisible = bool |
||||
|
}, |
||||
|
pushSystemList(text: any) { |
||||
|
this.systemList.push(text) |
||||
|
}, |
||||
|
}, |
||||
|
}) |
@ -0,0 +1,11 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div>工艺</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
|
||||
|
</style> |
@ -0,0 +1,3 @@ |
|||||
|
<template> |
||||
|
调试 |
||||
|
</template> |
@ -0,0 +1,12 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div> |
||||
|
首页 |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
</style> |
@ -0,0 +1,12 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { setToken } from 'libs/token' |
||||
|
|
||||
|
setToken('111') |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div>登录</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
</style> |
@ -0,0 +1,38 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"types": ["node"], |
||||
|
"composite": true, |
||||
|
"target": "esnext", |
||||
|
"jsx": "preserve", |
||||
|
"jsxFactory": "h", |
||||
|
"jsxFragmentFactory": "Fragment", |
||||
|
"lib": ["esnext", "dom"], |
||||
|
"useDefineForClassFields": true, |
||||
|
"baseUrl": ".", |
||||
|
"module": "esnext", |
||||
|
"moduleResolution": "node", |
||||
|
"paths": { |
||||
|
"@/*": ["src/*"], |
||||
|
"apis/*": ["src/apis/*"], |
||||
|
"assets/*": ["src/assets/*"], |
||||
|
"components/*": ["src/components/*"], |
||||
|
"hooks/*": ["src/hooks/*"], |
||||
|
"languages/*": ["src/languages/*"], |
||||
|
"libs/*": ["src/libs/*"], |
||||
|
"stores/*": ["src/stores/*"], |
||||
|
"views/*": ["src/views/*"], |
||||
|
"router/*": ["src/router/*"] |
||||
|
}, |
||||
|
"resolveJsonModule": true, |
||||
|
"strict": true, |
||||
|
"strictPropertyInitialization": false, |
||||
|
"noImplicitThis": false, |
||||
|
"sourceMap": true, |
||||
|
"esModuleInterop": true, |
||||
|
"skipLibCheck": true |
||||
|
}, |
||||
|
"types": ["vite/client", "jest", "node", "element-plus/global.d.ts", "lodash"], |
||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/**/*.vue"], |
||||
|
"exclude": [ |
||||
|
] |
||||
|
} |
@ -0,0 +1,96 @@ |
|||||
|
import { resolve } from 'node:path' |
||||
|
import vue from '@vitejs/plugin-vue' |
||||
|
import AutoImport from 'unplugin-auto-import/vite' |
||||
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' |
||||
|
import { defineConfig } from 'vite' |
||||
|
import viteCompression from 'vite-plugin-compression' |
||||
|
import eslintPlugin from 'vite-plugin-eslint' |
||||
|
import packageJson from './package.json' |
||||
|
|
||||
|
const Timestamp = new Date().getTime() |
||||
|
export default defineConfig({ |
||||
|
define: { |
||||
|
__APP_VERSION__: JSON.stringify(packageJson.version), |
||||
|
}, |
||||
|
base: './', |
||||
|
envPrefix: 'FT_', |
||||
|
esbuild: { |
||||
|
drop: process.env.NODE_ENV === 'production' ? ['console'] : [], |
||||
|
}, |
||||
|
build: { |
||||
|
sourcemap: false, |
||||
|
outDir: `dist-v${packageJson.version}`, |
||||
|
rollupOptions: { |
||||
|
output: { |
||||
|
manualChunks(id) { |
||||
|
if (id.includes('node_modules')) { |
||||
|
return id.toString().split('node_modules/')[1].split('/')[0].toString() |
||||
|
} |
||||
|
}, |
||||
|
chunkFileNames: (chunkInfo) => { |
||||
|
const facadeModuleId = chunkInfo.facadeModuleId |
||||
|
? chunkInfo.facadeModuleId.split('/') |
||||
|
: [] |
||||
|
const fileName = facadeModuleId.slice(-2)[0] || '[name]' |
||||
|
return `js/${fileName}/[name].[hash].${Timestamp}.js` |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
plugins: [ |
||||
|
vue(), |
||||
|
AutoImport({ |
||||
|
imports: ['vue', 'vue-router', { |
||||
|
vue: ['withModifiers'], |
||||
|
}, { |
||||
|
from: 'element-plus/es', |
||||
|
imports: ['TabPaneName'], |
||||
|
type: true, |
||||
|
}], |
||||
|
dts: true, |
||||
|
eslintrc: { |
||||
|
enabled: true, |
||||
|
}, |
||||
|
resolvers: [ElementPlusResolver({ importStyle: 'sass' })], |
||||
|
}), |
||||
|
eslintPlugin({ |
||||
|
cache: false, // 禁用缓存,以确保每次修改后都能及时生效
|
||||
|
}), |
||||
|
viteCompression({ |
||||
|
verbose: true, |
||||
|
disable: false, |
||||
|
threshold: 10240, |
||||
|
algorithm: 'gzip', |
||||
|
ext: '.gz', |
||||
|
}), |
||||
|
], |
||||
|
resolve: { |
||||
|
alias: { |
||||
|
'@': resolve(__dirname, './src'), |
||||
|
'apis': resolve(__dirname, 'src/apis'), |
||||
|
'assets': resolve(__dirname, 'src/assets'), |
||||
|
'components': resolve(__dirname, 'src/components'), |
||||
|
'hooks': resolve(__dirname, 'src/hooks'), |
||||
|
'libs': resolve(__dirname, 'src/libs'), |
||||
|
'stores': resolve(__dirname, 'src/stores'), |
||||
|
'views': resolve(__dirname, 'src/views'), |
||||
|
'router': resolve(__dirname, 'src/router'), |
||||
|
}, |
||||
|
}, |
||||
|
// 本地化配置
|
||||
|
server: { |
||||
|
// open: true,
|
||||
|
hmr: true, |
||||
|
port: 3010, |
||||
|
host: '0.0.0.0', |
||||
|
proxy: { |
||||
|
'/api': { |
||||
|
target: 'http://192.168.1.199:8080', |
||||
|
// target: 'http://192.168.1.200:8080',
|
||||
|
// secure: false,
|
||||
|
changeOrigin: true, // 是否跨域
|
||||
|
rewrite: path => path.replace(/^\/api/, 'api'), |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue