init
@ -0,0 +1,13 @@
|
|||||||
|
# 页面标题
|
||||||
|
VITE_APP_TITLE = IPS后台管理系统
|
||||||
|
|
||||||
|
# 开发环境配置
|
||||||
|
VITE_APP_ENV = 'development'
|
||||||
|
|
||||||
|
# 若依管理系统/开发环境
|
||||||
|
VITE_APP_BASE_API = '/flow-api'
|
||||||
|
VITE_APP_SECURITY_API = '/security-api'
|
||||||
|
VITE_APP_ADMIN_API = '/admin-api'
|
||||||
|
VITE_APP_PLANT_API = '/plant-api'
|
||||||
|
VITE_APP_WSS_API = '/wss-api'
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
# 页面标题
|
||||||
|
VITE_APP_TITLE = IPS后台管理系统
|
||||||
|
|
||||||
|
# 生产环境配置
|
||||||
|
VITE_APP_ENV = 'production'
|
||||||
|
|
||||||
|
# 若依管理系统/生产环境
|
||||||
|
VITE_APP_BASE_API = '/flow-api'
|
||||||
|
VITE_APP_SECURITY_API = '/security-api'
|
||||||
|
VITE_APP_ADMIN_API = '/admin-api'
|
||||||
|
VITE_APP_PLANT_API = '/plant-api'
|
||||||
|
VITE_APP_WSS_API = '/wss-api'
|
||||||
|
|
||||||
|
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||||
|
VITE_BUILD_COMPRESS = gzip
|
@ -0,0 +1,15 @@
|
|||||||
|
# 页面标题
|
||||||
|
VITE_APP_TITLE = IPS后台管理系统
|
||||||
|
|
||||||
|
# 生产环境配置
|
||||||
|
VITE_APP_ENV = 'production'
|
||||||
|
|
||||||
|
# 若依管理系统/生产环境
|
||||||
|
VITE_APP_BASE_API = '/flow-api'
|
||||||
|
VITE_APP_SECURITY_API = '/security-api'
|
||||||
|
VITE_APP_ADMIN_API = '/admin-api'
|
||||||
|
VITE_APP_PLANT_API = '/plant-api'
|
||||||
|
VITE_APP_WSS_API = '/wss-api'
|
||||||
|
|
||||||
|
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||||
|
VITE_BUILD_COMPRESS = gzip
|
@ -1,11 +1,5 @@
|
|||||||
# ---> Vue
|
node_modules
|
||||||
# gitignore template for Vue.js projects
|
.DS_Store
|
||||||
#
|
dist
|
||||||
# Recommended template: Node.gitignore
|
dist-ssr
|
||||||
|
*.local
|
||||||
# TODO: where does this rule come from?
|
|
||||||
docs/_book
|
|
||||||
|
|
||||||
# TODO: where does this rule come from?
|
|
||||||
test/
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
@ -1,2 +1,37 @@
|
|||||||
# ips-plantai
|
# ips-aiplant
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
3d协同云平台
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
软件架构说明
|
||||||
|
|
||||||
|
|
||||||
|
#### 安装教程
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 新建 Feat_xxx 分支
|
||||||
|
3. 提交代码
|
||||||
|
4. 新建 Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### 特技
|
||||||
|
|
||||||
|
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||||
|
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||||
|
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFBDCCAuwCAQAwDQYJKoZIhvcNAQENBQAwSDELMAkGA1UEBhMCTkwxFjAUBgNV
|
||||||
|
BAoMDVByaXZhY3lGaWx0ZXIxITAfBgNVBAMMGGh0dHBzOi8vMTkyLjE2OC4zLjY6
|
||||||
|
ODA4MTAeFw0yMzA0MjAwMzM2NTBaFw0yNDA0MTkwMzM2NTBaMEgxCzAJBgNVBAYT
|
||||||
|
Ak5MMRYwFAYDVQQKDA1Qcml2YWN5RmlsdGVyMSEwHwYDVQQDDBhodHRwczovLzE5
|
||||||
|
Mi4xNjguMy42OjgwODEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCd
|
||||||
|
prWXO+8gouYfg54+i84oRsMumMp3yN0TGF7nfsjZeGbTvSSZhc2s+g+wfBsxCs9y
|
||||||
|
FjdmXEY99gJ2RTL+l++4VXptzWnxV2v+Unh9tpv62M/cm1qshxpawBy5wU2Vgya6
|
||||||
|
3HBL3kkaktQtqmdJaQ3Ec6TRe88gSOvJLObbM7w1q34wUbTeNx8gO/+7JWWlayAj
|
||||||
|
gTh6TTBMEa7B2UHxno+FS1YgChNKevD7vpNXedB+2T3bEq1tdilogMSFrOOWwnNk
|
||||||
|
gkjDBcyLB6y2mukMWWVW1SNKf7A5y1DUW98qDAcgqp1X9KI/fmaBWvZdzuvj6SFS
|
||||||
|
s5tKUkBbdX/uibv34ACL6HMro0nLwTA4JV7uf9RaJTCMW5/3vSbdnUf8mVqmxPw2
|
||||||
|
TgJS3Ni44sPQV6jQRBW0BtasU5iSNM8xaqQtfP2iVnbjI0CoixV8VK5ogYv4Xajy
|
||||||
|
4orILe570TJj5wCRNh0w0B6dN6GKOFRG0rJtdx4TwcYAMqh4OSqbIGTr950OWuPc
|
||||||
|
8D24mjnVG3ZTkks7Z72OXbEomYYHrvbid7e7pACThIR7MZzNZv8nSBD7iNC+gj5L
|
||||||
|
CYobMO5rwtc+rqDCq06r6zyCMU9LR2KO8TTSgItdeSCNhOeIZMVwviCR+GrF2xLh
|
||||||
|
bpNbIHA99YVs9mruypiVGIycrW4s8lNHrMvuw/k75wIDAQABMA0GCSqGSIb3DQEB
|
||||||
|
DQUAA4ICAQBhgjBeb9N0+4dutdoHAabYqJHenIHuhWiwqHYpY+3tUBaciNEmfThN
|
||||||
|
1OAL2fg397B8F2RRZHf1IF8VtBXKguSa16OZ4Y+de7hpRLdzuzQDrtKZu6Fx1mDP
|
||||||
|
Nle2cPhi02BHZr5JyN/vZOzwd2Q/s2Qn+bffSg1IzHP2DuBAPl5vghJy6pimcirA
|
||||||
|
yhT/9v8Mfnqb9eHhzz9CfXhYJueIrag9HnIT/DyK6Q+b6yxS6YrTzO4dx4vN8g6W
|
||||||
|
OHjKvWNM+1GUjbAkch9f8/ZfYLXoD1Vwd3yNN7L4Ocab1k9dinrBlsdGm/zVaUJ9
|
||||||
|
CyDKYvuf4dcoM+/57FElxM9sfpjBmE/8yWeV7+e6zrYmBBdc4EfcagXsCdyGPTbC
|
||||||
|
iNrlbqj8qSH+HQ2XCKdNZE5gQHJbvIbaY/iobaUe4o8N/wbf678QIcyPg8u2+PrE
|
||||||
|
2XA6B/0LCMbv9kEFDF9MrETWLjk7bmShz9xBtONYurLeGp/v4SH585diunASpJMG
|
||||||
|
KumhOSUNL7eNsn/hr3k6o4XkOgBAKGBJF1/iC2l//ScIj/9HYxRjQflvKR6K3QdT
|
||||||
|
TDj8q2/AWadRfM5u6DS7kp2d6snT5TcWKQI8Hnj/PcjS4g37MblNQc8v7u1OcYsQ
|
||||||
|
5DjN95zbqUbGHqO0s9WlM8eonnq9uVgUJtzkRCc1p8v6Bq1a7Z9zyA==
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
NjEXrlY8zHRda4jMttjJb25qxw49
|
||||||
|
-----END PRIVATE KEY-----
|
@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="./src/assets/logo.png" />
|
||||||
|
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
|
||||||
|
<meta name="viewport" content="width=device-width, maximum-scale=1.0, minimum-scale=1.0, initial-scale=1.0, user-scalable=no">
|
||||||
|
<script type="text/javascript" src="/webRtcPlayer.js"></script>
|
||||||
|
<script type="text/javascript" src="/app.js"></script>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
<script src="/jquery-3.6.0.min.js"></script>
|
||||||
|
<link type="text/css" rel="stylesheet" href="/player.css">
|
||||||
|
<title>PLANTAI</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="playerUI">
|
||||||
|
<div id="player"></div>
|
||||||
|
<div id="play2d"></div>
|
||||||
|
</div>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script>
|
||||||
|
// window.onload = function(){
|
||||||
|
// // window.addEventListener("load", () => {
|
||||||
|
// load() //webrtc
|
||||||
|
// // clickData()
|
||||||
|
// // })
|
||||||
|
|
||||||
|
// console.log(window)
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
background-size: cover;
|
||||||
|
user-select:none;
|
||||||
|
min-height: 94.074075vh;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#playerUI {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
#play2d{
|
||||||
|
background-image: url('./src/assets/2dBG.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "IPS-UE",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/preset-env": "^7.22.9",
|
||||||
|
"@lyno/lib-jitsi-meet": "^2.6726.0",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@vitejs/plugin-legacy": "^1.0.0",
|
||||||
|
"amfe-flexible": "^2.2.1",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"bpmn-js": "^11.4.1",
|
||||||
|
"diagram-js": "^11.9.1",
|
||||||
|
"echarts": "^5.4.0",
|
||||||
|
"echarts-gl": "^2.0.9",
|
||||||
|
"echarts-liquidfill": "^3.1.0",
|
||||||
|
"element-plus": "^2.2.26",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"highlight.js": "11.7.0",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"jsencrypt": "^3.2.1",
|
||||||
|
"lib-flexible-computer": "^1.0.2",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"pdfjs-dist": "^2.5.207",
|
||||||
|
"peerjs": "^1.5.2",
|
||||||
|
"pinia": "2.0.22",
|
||||||
|
"sass": "^1.50.0",
|
||||||
|
"socket.io-client": "^4.7.4",
|
||||||
|
"vue": "^3.0.4",
|
||||||
|
"vue-router": "^4.1.6",
|
||||||
|
"vuex": "^4.1.0",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^2.3.0",
|
||||||
|
"@vue/compiler-sfc": "^3.0.4",
|
||||||
|
"fast-glob": "^3.3.1",
|
||||||
|
"postcss": "^8.4.21",
|
||||||
|
"postcss-pxtorem": "^6.0.0",
|
||||||
|
"vite": "^2.0.0",
|
||||||
|
"vite-plugin-svg-icons": "^2.0.1"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 456 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 259 KiB |
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,338 @@
|
|||||||
|
/*Copyright Epic Games, Inc. All Rights Reserved.*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/*Using colour scheme https://color.adobe.com/TD-Colors---Option-3-color-theme-10394433/*/
|
||||||
|
--colour1:#2B3A42;
|
||||||
|
--colour2:#3F5765;
|
||||||
|
--colour3:#BDD4DE;
|
||||||
|
--colour4:#EFEFEF;
|
||||||
|
--colour5:#FF5035;
|
||||||
|
|
||||||
|
--buttonFont:Helvetica;
|
||||||
|
--inputFont:Helvetica;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
margin: 0px;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#playerUI {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
/*top: 0;
|
||||||
|
left: 0;*/
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsContainer {
|
||||||
|
background-color: rgba(0,0,0,0.8);
|
||||||
|
text-align: left;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 6px;
|
||||||
|
color: lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas{
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
video{
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background-color: #F8F8F8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay{
|
||||||
|
-moz-border-radius-bottomright: 5px;
|
||||||
|
-moz-border-radius-bottomleft: 5px;
|
||||||
|
-webkit-border-bottom-right-radius: 5px;
|
||||||
|
-webkit-border-bottom-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px; /* future proofing */
|
||||||
|
border-bottom-left-radius: 5px; /* future proofing */
|
||||||
|
-khtml-border-bottom-right-radius: 5px; /* for old Konqueror browsers */
|
||||||
|
-khtml-border-bottom-left-radius: 5px; /* for old Konqueror browsers */
|
||||||
|
|
||||||
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-khtml-user-select: none; /* Konqueror HTML */
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
|
user-select: none; /* Non-prefixed version, currently
|
||||||
|
supported by Chrome and Opera */
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
padding: 4px;
|
||||||
|
top: 0;
|
||||||
|
right: 2%;
|
||||||
|
z-index: 100;
|
||||||
|
border: 2px solid var(--colour4);
|
||||||
|
border-top-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
background-color: var(--colour2);
|
||||||
|
font-family: var(--buttonFont);
|
||||||
|
font-weight: lighter;
|
||||||
|
color: var(--colour4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-shown > #overlaySettings {
|
||||||
|
padding: 50px 4px 4px 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-shown > div > #overlayButton {
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
-webkit-transform: rotate(-135deg); /* Safari */
|
||||||
|
-moz-transform: rotate(-135deg); /* Firefox */
|
||||||
|
-ms-transform: rotate(-135deg); /* IE */
|
||||||
|
-o-transform: rotate(-135deg); /* Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlayButton:hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlayButton{
|
||||||
|
transition-duration: 250ms;
|
||||||
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qualityStatus{
|
||||||
|
float: left;
|
||||||
|
font-size: 37px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlaySettings{
|
||||||
|
width: 400px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greyStatus {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limeStatus {
|
||||||
|
color: lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orangeStatus {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redStatus {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#videoMessageOverlay{
|
||||||
|
z-index: 20;
|
||||||
|
color: var(--colour4);;
|
||||||
|
font-size: 1.8em;
|
||||||
|
position: absolute;
|
||||||
|
margin: auto;
|
||||||
|
font-family: var(--inputFont);;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#videoPlayOverlay{
|
||||||
|
z-index: 30;
|
||||||
|
position: absolute;
|
||||||
|
color: var(--colour4);
|
||||||
|
font-size: 1.8em;
|
||||||
|
font-family: var(--inputFont);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(100, 100, 100, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* State for element to be clickable */
|
||||||
|
.clickableState{
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* State for element to show text, this is for informational use*/
|
||||||
|
.textDisplayState{
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* State to hide overlay, WebRTC communication is in progress and or is playing */
|
||||||
|
.hiddenState{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#playButton{
|
||||||
|
display: inline-block;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
img#playButton{
|
||||||
|
max-width: 241px;
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#UIInteraction{
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#UIInteractionButtonBoundary{
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#UIInteractionButton{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hiddenInput{
|
||||||
|
position: absolute;
|
||||||
|
left: -10%; /* Although invisible, push off-screen to prevent user interaction. */
|
||||||
|
width: 0px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editTextButton{
|
||||||
|
position: absolute;
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-text{
|
||||||
|
color: var(--colour4);
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: normal;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-button{
|
||||||
|
line-height: 1.1;
|
||||||
|
padding: 1px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-overlay{
|
||||||
|
float: right;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-flat{
|
||||||
|
background: var(--colour4);
|
||||||
|
border: 2px solid var(--colour5);
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--buttonFont);
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--colour5);
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-flat:disabled{
|
||||||
|
background: var(--colour4);
|
||||||
|
border-color: var(--colour3);
|
||||||
|
color: var(--colour3);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-flat:active{
|
||||||
|
border-color: var(--colour2);
|
||||||
|
color: var(--colour2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-flat:focus{
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
/*** Toggle Switch styles ***/
|
||||||
|
.tgl-switch {
|
||||||
|
float: right;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl-switch .tgl {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl, .tgl:after, .tgl:before, .tgl *, .tgl *:after, .tgl *:before, .tgl + .tgl-slider {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.tgl::-moz-selection, .tgl:after::-moz-selection, .tgl:before::-moz-selection, .tgl *::-moz-selection, .tgl *:after::-moz-selection, .tgl *:before::-moz-selection, .tgl + .tgl-slider::-moz-selection {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.tgl::selection, .tgl:after::selection, .tgl:before::selection, .tgl *::selection, .tgl *:after::selection, .tgl *:before::selection, .tgl + .tgl-slider::selection {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl + .tgl-slider {
|
||||||
|
outline: 0;
|
||||||
|
display: block;
|
||||||
|
width: 40px;
|
||||||
|
height: 18px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl + .tgl-slider:after, .tgl + .tgl-slider:before {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.tgl + .tgl-slider:after {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.tgl + .tgl-slider:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl-flat + .tgl-slider {
|
||||||
|
padding: 2px;
|
||||||
|
-webkit-transition: all .2s ease;
|
||||||
|
transition: all .2s ease;
|
||||||
|
background: #fff;
|
||||||
|
border: 3px solid var(--colour4);
|
||||||
|
border-radius: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl-flat + .tgl-slider:after {
|
||||||
|
-webkit-transition: all .2s ease;
|
||||||
|
transition: all .2s ease;
|
||||||
|
background: var(--colour4);
|
||||||
|
content: "";
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl-flat:checked + .tgl-slider {
|
||||||
|
border: 3px solid var(--colour5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tgl-flat:checked + .tgl-slider:after {
|
||||||
|
left: 50%;
|
||||||
|
background: var(--colour5);
|
||||||
|
}
|
||||||
|
/*** Toggle Switch styles ***/
|
@ -0,0 +1,533 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
// universal module definition - read https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/
|
||||||
|
|
||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module.
|
||||||
|
define(["./adapter"], factory);
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory(require("./adapter"));
|
||||||
|
} else {
|
||||||
|
// Browser globals (root is window)
|
||||||
|
root.webRtcPlayer = factory(root.adapter);
|
||||||
|
}
|
||||||
|
}(this, function (adapter) {
|
||||||
|
|
||||||
|
function webRtcPlayer(parOptions) {
|
||||||
|
parOptions = typeof parOptions !== 'undefined' ? parOptions : {};
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Config setup
|
||||||
|
//**********************
|
||||||
|
this.cfg = typeof parOptions.peerConnectionOptions !== 'undefined' ? parOptions.peerConnectionOptions : {};
|
||||||
|
this.cfg.sdpSemantics = 'unified-plan';
|
||||||
|
// this.cfg.rtcAudioJitterBufferMaxPackets = 10;
|
||||||
|
// this.cfg.rtcAudioJitterBufferFastAccelerate = true;
|
||||||
|
// this.cfg.rtcAudioJitterBufferMinDelayMs = 0;
|
||||||
|
|
||||||
|
// If this is true in Chrome 89+ SDP is sent that is incompatible with UE Pixel Streaming 4.26 and below.
|
||||||
|
// However 4.27 Pixel Streaming does not need this set to false as it supports `offerExtmapAllowMixed`.
|
||||||
|
// tdlr; uncomment this line for older versions of Pixel Streaming that need Chrome 89+.
|
||||||
|
this.cfg.offerExtmapAllowMixed = false;
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Variables
|
||||||
|
//**********************
|
||||||
|
this.pcClient = null;
|
||||||
|
this.dcClient = null;
|
||||||
|
this.tnClient = null;
|
||||||
|
|
||||||
|
this.sdpConstraints = {
|
||||||
|
offerToReceiveAudio: 1, //Note: if you don't need audio you can get improved latency by turning this off.
|
||||||
|
offerToReceiveVideo: 1,
|
||||||
|
voiceActivityDetection: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
|
||||||
|
this.dataChannelOptions = {ordered: true};
|
||||||
|
|
||||||
|
// This is useful if the video/audio needs to autoplay (without user input) as browsers do not allow autoplay non-muted of sound sources without user interaction.
|
||||||
|
this.startVideoMuted = typeof parOptions.startVideoMuted !== 'undefined' ? parOptions.startVideoMuted : false;
|
||||||
|
this.autoPlayAudio = typeof parOptions.autoPlayAudio !== 'undefined' ? parOptions.autoPlayAudio : true;
|
||||||
|
|
||||||
|
// To enable mic in browser use SSL/localhost and have ?useMic in the query string.
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
this.useMic = urlParams.has('useMic');
|
||||||
|
if(!this.useMic)
|
||||||
|
{
|
||||||
|
console.log("Microphone access is not enabled. Pass ?useMic in the url to enable it.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// When ?useMic check for SSL or localhost
|
||||||
|
let isLocalhostConnection = location.hostname === "localhost" || location.hostname === "127.0.0.1";
|
||||||
|
let isHttpsConnection = location.protocol === 'https:';
|
||||||
|
if(this.useMic && !isLocalhostConnection && !isHttpsConnection)
|
||||||
|
{
|
||||||
|
this.useMic = false;
|
||||||
|
console.error("Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.");
|
||||||
|
console.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Latency tester
|
||||||
|
this.latencyTestTimings =
|
||||||
|
{
|
||||||
|
TestStartTimeMs: null,
|
||||||
|
UEReceiptTimeMs: null,
|
||||||
|
UEPreCaptureTimeMs: null,
|
||||||
|
UEPostCaptureTimeMs: null,
|
||||||
|
UEPreEncodeTimeMs: null,
|
||||||
|
UEPostEncodeTimeMs: null,
|
||||||
|
UETransmissionTimeMs: null,
|
||||||
|
BrowserReceiptTimeMs: null,
|
||||||
|
FrameDisplayDeltaTimeMs: null,
|
||||||
|
Reset: function()
|
||||||
|
{
|
||||||
|
this.TestStartTimeMs = null;
|
||||||
|
this.UEReceiptTimeMs = null;
|
||||||
|
this.UEPreCaptureTimeMs = null;
|
||||||
|
this.UEPostCaptureTimeMs = null;
|
||||||
|
this.UEPreEncodeTimeMs = null;
|
||||||
|
this.UEPostEncodeTimeMs = null;
|
||||||
|
this.UETransmissionTimeMs = null;
|
||||||
|
this.BrowserReceiptTimeMs = null;
|
||||||
|
this.FrameDisplayDeltaTimeMs = null;
|
||||||
|
},
|
||||||
|
SetUETimings: function(UETimings)
|
||||||
|
{
|
||||||
|
this.UEReceiptTimeMs = UETimings.ReceiptTimeMs;
|
||||||
|
this.UEPreCaptureTimeMs = UETimings.PreCaptureTimeMs;
|
||||||
|
this.UEPostCaptureTimeMs = UETimings.PostCaptureTimeMs;
|
||||||
|
this.UEPreEncodeTimeMs = UETimings.PreEncodeTimeMs;
|
||||||
|
this.UEPostEncodeTimeMs = UETimings.PostEncodeTimeMs;
|
||||||
|
this.UETransmissionTimeMs = UETimings.TransmissionTimeMs;
|
||||||
|
this.BrowserReceiptTimeMs = Date.now();
|
||||||
|
this.OnAllLatencyTimingsReady(this);
|
||||||
|
},
|
||||||
|
SetFrameDisplayDeltaTime: function(DeltaTimeMs)
|
||||||
|
{
|
||||||
|
if(this.FrameDisplayDeltaTimeMs == null)
|
||||||
|
{
|
||||||
|
this.FrameDisplayDeltaTimeMs = Math.round(DeltaTimeMs);
|
||||||
|
this.OnAllLatencyTimingsReady(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnAllLatencyTimingsReady: function(Timings){}
|
||||||
|
}
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Functions
|
||||||
|
//**********************
|
||||||
|
|
||||||
|
//Create Video element and expose that as a parameter
|
||||||
|
this.createWebRtcVideo = function() {
|
||||||
|
var video = document.createElement('video');
|
||||||
|
|
||||||
|
video.id = "streamingVideo";
|
||||||
|
video.playsInline = true;
|
||||||
|
video.disablepictureinpicture = true;
|
||||||
|
video.muted = self.startVideoMuted;;
|
||||||
|
|
||||||
|
video.addEventListener('loadedmetadata', function(e){
|
||||||
|
if(self.onVideoInitialised){
|
||||||
|
self.onVideoInitialised();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// Check if request video frame callback is supported
|
||||||
|
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
|
||||||
|
// The API is supported!
|
||||||
|
|
||||||
|
const onVideoFrameReady = (now, metadata) => {
|
||||||
|
|
||||||
|
if(metadata.receiveTime && metadata.expectedDisplayTime)
|
||||||
|
{
|
||||||
|
const receiveToCompositeMs = metadata.presentationTime - metadata.receiveTime;
|
||||||
|
self.aggregatedStats.receiveToCompositeMs = receiveToCompositeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Re-register the callback to be notified about the next frame.
|
||||||
|
video.requestVideoFrameCallback(onVideoFrameReady);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initially register the callback to be notified about the first frame.
|
||||||
|
video.requestVideoFrameCallback(onVideoFrameReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.video = this.createWebRtcVideo();
|
||||||
|
|
||||||
|
onsignalingstatechange = function(state) {
|
||||||
|
console.info('signaling state change:', state)
|
||||||
|
};
|
||||||
|
|
||||||
|
oniceconnectionstatechange = function(state) {
|
||||||
|
console.info('ice connection state change:', state)
|
||||||
|
};
|
||||||
|
|
||||||
|
onicegatheringstatechange = function(state) {
|
||||||
|
console.info('ice gathering state change:', state)
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOnTrack = function(e) {
|
||||||
|
console.log('handleOnTrack', e.streams);
|
||||||
|
|
||||||
|
if (e.track)
|
||||||
|
{
|
||||||
|
console.log('Got track - ' + e.track.kind + ' id=' + e.track.id + ' readyState=' + e.track.readyState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.track.kind == "audio")
|
||||||
|
{
|
||||||
|
handleOnAudioTrack(e.streams[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else(e.track.kind == "video" && self.video.srcObject !== e.streams[0])
|
||||||
|
{
|
||||||
|
self.video.srcObject = e.streams[0];
|
||||||
|
console.log('Set video source from video track ontrack.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOnAudioTrack = function(audioMediaStream)
|
||||||
|
{
|
||||||
|
// do nothing the video has the same media stream as the audio track we have here (they are linked)
|
||||||
|
if(self.video.srcObject == audioMediaStream)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// video element has some other media stream that is not associated with this audio track
|
||||||
|
else if(self.video.srcObject && self.video.srcObject !== audioMediaStream)
|
||||||
|
{
|
||||||
|
// create a new audio element
|
||||||
|
let audioElem = document.createElement("Audio");
|
||||||
|
audioElem.srcObject = audioMediaStream;
|
||||||
|
|
||||||
|
// there is no way to autoplay audio (even muted), so we defer audio until first click
|
||||||
|
if(!self.autoPlayAudio) {
|
||||||
|
|
||||||
|
let clickToPlayAudio = function() {
|
||||||
|
audioElem.play();
|
||||||
|
self.video.removeEventListener("click", clickToPlayAudio);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.video.addEventListener("click", clickToPlayAudio);
|
||||||
|
}
|
||||||
|
// we assume the user has clicked somewhere on the page and autoplaying audio will work
|
||||||
|
else {
|
||||||
|
audioElem.play();
|
||||||
|
}
|
||||||
|
console.log('Created new audio element to play seperate audio stream.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDataChannel = function(pc, label, options) {
|
||||||
|
try {
|
||||||
|
let datachannel = pc.createDataChannel(label, options);
|
||||||
|
console.log(`Created datachannel (${label})`)
|
||||||
|
|
||||||
|
// Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
|
||||||
|
datachannel.binaryType = "arraybuffer";
|
||||||
|
|
||||||
|
datachannel.onopen = function (e) {
|
||||||
|
console.log(`data channel (${label}) connect`)
|
||||||
|
if(self.onDataChannelConnected){
|
||||||
|
self.onDataChannelConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datachannel.onclose = function (e) {
|
||||||
|
console.log(`data channel (${label}) closed`)
|
||||||
|
}
|
||||||
|
|
||||||
|
datachannel.onmessage = function (e) {
|
||||||
|
//console.log(`Got message (${label})`, e.data)
|
||||||
|
if (self.onDataChannelMessage)
|
||||||
|
self.onDataChannelMessage(e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return datachannel;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('No data channel', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onicecandidate = function (e) {
|
||||||
|
console.log('ICE candidate', e)
|
||||||
|
if (e.candidate && e.candidate.candidate) {
|
||||||
|
self.onWebRtcCandidate(e.candidate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCreateOffer = function (pc) {
|
||||||
|
pc.createOffer(self.sdpConstraints).then(function (offer) {
|
||||||
|
|
||||||
|
// Munging is where we modifying the sdp string to set parameters that are not exposed to the browser's WebRTC API
|
||||||
|
mungeSDPOffer(offer);
|
||||||
|
|
||||||
|
// Set our munged SDP on the local peer connection so it is "set" and will be send across
|
||||||
|
pc.setLocalDescription(offer);
|
||||||
|
if (self.onWebRtcOffer) {
|
||||||
|
self.onWebRtcOffer(offer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function () { console.warn("Couldn't create offer") });
|
||||||
|
}
|
||||||
|
|
||||||
|
mungeSDPOffer = function (offer) {
|
||||||
|
|
||||||
|
// turn off video-timing sdp sent from browser
|
||||||
|
//offer.sdp = offer.sdp.replace("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "");
|
||||||
|
|
||||||
|
// this indicate we support stereo (Chrome needs this)
|
||||||
|
offer.sdp = offer.sdp.replace('useinbandfec=1', 'useinbandfec=1;stereo=1;sprop-maxcapturerate=48000');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPeerConnection = function (pc) {
|
||||||
|
if (pc.SetBitrate)
|
||||||
|
console.log("Hurray! there's RTCPeerConnection.SetBitrate function");
|
||||||
|
|
||||||
|
//Setup peerConnection events
|
||||||
|
pc.onsignalingstatechange = onsignalingstatechange;
|
||||||
|
pc.oniceconnectionstatechange = oniceconnectionstatechange;
|
||||||
|
pc.onicegatheringstatechange = onicegatheringstatechange;
|
||||||
|
|
||||||
|
pc.ontrack = handleOnTrack;
|
||||||
|
pc.onicecandidate = onicecandidate;
|
||||||
|
};
|
||||||
|
|
||||||
|
generateAggregatedStatsFunction = function(){
|
||||||
|
if(!self.aggregatedStats)
|
||||||
|
self.aggregatedStats = {};
|
||||||
|
|
||||||
|
return function(stats){
|
||||||
|
//console.log('Printing Stats');
|
||||||
|
|
||||||
|
let newStat = {};
|
||||||
|
|
||||||
|
stats.forEach(stat => {
|
||||||
|
// console.log(JSON.stringify(stat, undefined, 4));
|
||||||
|
if (stat.type == 'inbound-rtp'
|
||||||
|
&& !stat.isRemote
|
||||||
|
&& (stat.mediaType == 'video' || stat.id.toLowerCase().includes('video'))) {
|
||||||
|
|
||||||
|
newStat.timestamp = stat.timestamp;
|
||||||
|
newStat.bytesReceived = stat.bytesReceived;
|
||||||
|
newStat.framesDecoded = stat.framesDecoded;
|
||||||
|
newStat.packetsLost = stat.packetsLost;
|
||||||
|
newStat.bytesReceivedStart = self.aggregatedStats && self.aggregatedStats.bytesReceivedStart ? self.aggregatedStats.bytesReceivedStart : stat.bytesReceived;
|
||||||
|
newStat.framesDecodedStart = self.aggregatedStats && self.aggregatedStats.framesDecodedStart ? self.aggregatedStats.framesDecodedStart : stat.framesDecoded;
|
||||||
|
newStat.timestampStart = self.aggregatedStats && self.aggregatedStats.timestampStart ? self.aggregatedStats.timestampStart : stat.timestamp;
|
||||||
|
|
||||||
|
if(self.aggregatedStats && self.aggregatedStats.timestamp){
|
||||||
|
if(self.aggregatedStats.bytesReceived){
|
||||||
|
// bitrate = bits received since last time / number of ms since last time
|
||||||
|
//This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
|
||||||
|
newStat.bitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceived) / (newStat.timestamp - self.aggregatedStats.timestamp);
|
||||||
|
newStat.bitrate = Math.floor(newStat.bitrate);
|
||||||
|
newStat.lowBitrate = self.aggregatedStats.lowBitrate && self.aggregatedStats.lowBitrate < newStat.bitrate ? self.aggregatedStats.lowBitrate : newStat.bitrate
|
||||||
|
newStat.highBitrate = self.aggregatedStats.highBitrate && self.aggregatedStats.highBitrate > newStat.bitrate ? self.aggregatedStats.highBitrate : newStat.bitrate
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.aggregatedStats.bytesReceivedStart){
|
||||||
|
newStat.avgBitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceivedStart) / (newStat.timestamp - self.aggregatedStats.timestampStart);
|
||||||
|
newStat.avgBitrate = Math.floor(newStat.avgBitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.aggregatedStats.framesDecoded){
|
||||||
|
// framerate = frames decoded since last time / number of seconds since last time
|
||||||
|
newStat.framerate = (newStat.framesDecoded - self.aggregatedStats.framesDecoded) / ((newStat.timestamp - self.aggregatedStats.timestamp) / 1000);
|
||||||
|
newStat.framerate = Math.floor(newStat.framerate);
|
||||||
|
newStat.lowFramerate = self.aggregatedStats.lowFramerate && self.aggregatedStats.lowFramerate < newStat.framerate ? self.aggregatedStats.lowFramerate : newStat.framerate
|
||||||
|
newStat.highFramerate = self.aggregatedStats.highFramerate && self.aggregatedStats.highFramerate > newStat.framerate ? self.aggregatedStats.highFramerate : newStat.framerate
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.aggregatedStats.framesDecodedStart){
|
||||||
|
newStat.avgframerate = (newStat.framesDecoded - self.aggregatedStats.framesDecodedStart) / ((newStat.timestamp - self.aggregatedStats.timestampStart) / 1000);
|
||||||
|
newStat.avgframerate = Math.floor(newStat.avgframerate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read video track stats
|
||||||
|
if(stat.type == 'track' && (stat.trackIdentifier == 'video_label' || stat.kind == 'video')) {
|
||||||
|
newStat.framesDropped = stat.framesDropped;
|
||||||
|
newStat.framesReceived = stat.framesReceived;
|
||||||
|
newStat.framesDroppedPercentage = stat.framesDropped / stat.framesReceived * 100;
|
||||||
|
newStat.frameHeight = stat.frameHeight;
|
||||||
|
newStat.frameWidth = stat.frameWidth;
|
||||||
|
newStat.frameHeightStart = self.aggregatedStats && self.aggregatedStats.frameHeightStart ? self.aggregatedStats.frameHeightStart : stat.frameHeight;
|
||||||
|
newStat.frameWidthStart = self.aggregatedStats && self.aggregatedStats.frameWidthStart ? self.aggregatedStats.frameWidthStart : stat.frameWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stat.type =='candidate-pair' && stat.hasOwnProperty('currentRoundTripTime') && stat.currentRoundTripTime != 0){
|
||||||
|
newStat.currentRoundTripTime = stat.currentRoundTripTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if(self.aggregatedStats.receiveToCompositeMs)
|
||||||
|
{
|
||||||
|
newStat.receiveToCompositeMs = self.aggregatedStats.receiveToCompositeMs;
|
||||||
|
self.latencyTestTimings.SetFrameDisplayDeltaTime(self.aggregatedStats.receiveToCompositeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.aggregatedStats = newStat;
|
||||||
|
|
||||||
|
if(self.onAggregatedStats)
|
||||||
|
self.onAggregatedStats(newStat)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupTracksToSendAsync = async function(pc){
|
||||||
|
|
||||||
|
// Setup a transceiver for getting UE video
|
||||||
|
pc.addTransceiver("video", { direction: "recvonly" });
|
||||||
|
|
||||||
|
// Setup a transceiver for sending mic audio to UE and receiving audio from UE
|
||||||
|
if(!self.useMic)
|
||||||
|
{
|
||||||
|
pc.addTransceiver("audio", { direction: "recvonly" });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let audioSendOptions = self.useMic ?
|
||||||
|
{
|
||||||
|
autoGainControl: false,
|
||||||
|
channelCount: 1,
|
||||||
|
echoCancellation: false,
|
||||||
|
latency: 0,
|
||||||
|
noiseSuppression: false,
|
||||||
|
sampleRate: 16000,
|
||||||
|
volume: 1.0
|
||||||
|
} : false;
|
||||||
|
|
||||||
|
// Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({video: false, audio: audioSendOptions});
|
||||||
|
if(stream)
|
||||||
|
{
|
||||||
|
for (const track of stream.getTracks()) {
|
||||||
|
if(track.kind && track.kind == "audio")
|
||||||
|
{
|
||||||
|
pc.addTransceiver(track, { direction: "sendrecv" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pc.addTransceiver("audio", { direction: "recvonly" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Public functions
|
||||||
|
//**********************
|
||||||
|
|
||||||
|
this.setVideoEnabled = function(enabled) {
|
||||||
|
self.video.srcObject.getTracks().forEach(track => track.enabled = enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startLatencyTest = function(onTestStarted) {
|
||||||
|
// Can't start latency test without a video element
|
||||||
|
if(!self.video)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.latencyTestTimings.Reset();
|
||||||
|
self.latencyTestTimings.TestStartTimeMs = Date.now();
|
||||||
|
onTestStarted(self.latencyTestTimings.TestStartTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This is called when revceiving new ice candidates individually instead of part of the offer
|
||||||
|
//This is currently not used but would be called externally from this class
|
||||||
|
this.handleCandidateFromServer = function(iceCandidate) {
|
||||||
|
console.log("ICE candidate: ", iceCandidate);
|
||||||
|
let candidate = new RTCIceCandidate(iceCandidate);
|
||||||
|
self.pcClient.addIceCandidate(candidate).then(_=>{
|
||||||
|
console.log('ICE candidate successfully added');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//Called externaly to create an offer for the server
|
||||||
|
this.createOffer = function() {
|
||||||
|
if(self.pcClient){
|
||||||
|
console.log("Closing existing PeerConnection")
|
||||||
|
self.pcClient.close();
|
||||||
|
self.pcClient = null;
|
||||||
|
}
|
||||||
|
self.pcClient = new RTCPeerConnection(self.cfg);
|
||||||
|
|
||||||
|
setupTracksToSendAsync(self.pcClient).finally(function()
|
||||||
|
{
|
||||||
|
setupPeerConnection(self.pcClient);
|
||||||
|
self.dcClient = setupDataChannel(self.pcClient, 'cirrus', self.dataChannelOptions);
|
||||||
|
handleCreateOffer(self.pcClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//Called externaly when an answer is received from the server
|
||||||
|
this.receiveAnswer = function(answer) {
|
||||||
|
console.log('Received answer:');
|
||||||
|
console.log(answer);
|
||||||
|
var answerDesc = new RTCSessionDescription(answer);
|
||||||
|
self.pcClient.setRemoteDescription(answerDesc);
|
||||||
|
|
||||||
|
let receivers = self.pcClient.getReceivers();
|
||||||
|
for(let receiver of receivers)
|
||||||
|
{
|
||||||
|
receiver.playoutDelayHint = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.close = function(){
|
||||||
|
if(self.pcClient){
|
||||||
|
console.log("Closing existing peerClient")
|
||||||
|
self.pcClient.close();
|
||||||
|
self.pcClient = null;
|
||||||
|
}
|
||||||
|
if(self.aggregateStatsIntervalId)
|
||||||
|
clearInterval(self.aggregateStatsIntervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sends data across the datachannel
|
||||||
|
this.send = function(data){
|
||||||
|
if(self.dcClient && self.dcClient.readyState == 'open'){
|
||||||
|
//console.log('Sending data on dataconnection', self.dcClient)
|
||||||
|
self.dcClient.send(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getStats = function(onStats){
|
||||||
|
if(self.pcClient && onStats){
|
||||||
|
self.pcClient.getStats(null).then((stats) => {
|
||||||
|
onStats(stats);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aggregateStats = function(checkInterval){
|
||||||
|
let calcAggregatedStats = generateAggregatedStatsFunction();
|
||||||
|
let printAggregatedStats = () => { self.getStats(calcAggregatedStats); }
|
||||||
|
self.aggregateStatsIntervalId = setInterval(printAggregatedStats, checkInterval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return webRtcPlayer;
|
||||||
|
|
||||||
|
}));
|
@ -0,0 +1,533 @@
|
|||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
// universal module definition - read https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/
|
||||||
|
|
||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module.
|
||||||
|
define(["./adapter"], factory);
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory(require("./adapter"));
|
||||||
|
} else {
|
||||||
|
// Browser globals (root is window)
|
||||||
|
root.webRtcPlayer = factory(root.adapter);
|
||||||
|
}
|
||||||
|
}(this, function (adapter) {
|
||||||
|
|
||||||
|
function webRtcPlayer(parOptions) {
|
||||||
|
parOptions = typeof parOptions !== 'undefined' ? parOptions : {};
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Config setup
|
||||||
|
//**********************
|
||||||
|
this.cfg = typeof parOptions.peerConnectionOptions !== 'undefined' ? parOptions.peerConnectionOptions : {};
|
||||||
|
this.cfg.sdpSemantics = 'unified-plan';
|
||||||
|
// this.cfg.rtcAudioJitterBufferMaxPackets = 10;
|
||||||
|
// this.cfg.rtcAudioJitterBufferFastAccelerate = true;
|
||||||
|
// this.cfg.rtcAudioJitterBufferMinDelayMs = 0;
|
||||||
|
|
||||||
|
// If this is true in Chrome 89+ SDP is sent that is incompatible with UE Pixel Streaming 4.26 and below.
|
||||||
|
// However 4.27 Pixel Streaming does not need this set to false as it supports `offerExtmapAllowMixed`.
|
||||||
|
// tdlr; uncomment this line for older versions of Pixel Streaming that need Chrome 89+.
|
||||||
|
this.cfg.offerExtmapAllowMixed = false;
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Variables
|
||||||
|
//**********************
|
||||||
|
this.pcClient = null;
|
||||||
|
this.dcClient = null;
|
||||||
|
this.tnClient = null;
|
||||||
|
|
||||||
|
this.sdpConstraints = {
|
||||||
|
offerToReceiveAudio: 1, //Note: if you don't need audio you can get improved latency by turning this off.
|
||||||
|
offerToReceiveVideo: 1,
|
||||||
|
voiceActivityDetection: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
|
||||||
|
this.dataChannelOptions = {ordered: true};
|
||||||
|
|
||||||
|
// This is useful if the video/audio needs to autoplay (without user input) as browsers do not allow autoplay non-muted of sound sources without user interaction.
|
||||||
|
this.startVideoMuted = typeof parOptions.startVideoMuted !== 'undefined' ? parOptions.startVideoMuted : false;
|
||||||
|
this.autoPlayAudio = typeof parOptions.autoPlayAudio !== 'undefined' ? parOptions.autoPlayAudio : true;
|
||||||
|
|
||||||
|
// To enable mic in browser use SSL/localhost and have ?useMic in the query string.
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
this.useMic = urlParams.has('useMic');
|
||||||
|
if(!this.useMic)
|
||||||
|
{
|
||||||
|
console.log("Microphone access is not enabled. Pass ?useMic in the url to enable it.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// When ?useMic check for SSL or localhost
|
||||||
|
let isLocalhostConnection = location.hostname === "localhost" || location.hostname === "127.0.0.1";
|
||||||
|
let isHttpsConnection = location.protocol === 'https:';
|
||||||
|
if(this.useMic && !isLocalhostConnection && !isHttpsConnection)
|
||||||
|
{
|
||||||
|
this.useMic = false;
|
||||||
|
console.error("Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.");
|
||||||
|
console.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Latency tester
|
||||||
|
this.latencyTestTimings =
|
||||||
|
{
|
||||||
|
TestStartTimeMs: null,
|
||||||
|
UEReceiptTimeMs: null,
|
||||||
|
UEPreCaptureTimeMs: null,
|
||||||
|
UEPostCaptureTimeMs: null,
|
||||||
|
UEPreEncodeTimeMs: null,
|
||||||
|
UEPostEncodeTimeMs: null,
|
||||||
|
UETransmissionTimeMs: null,
|
||||||
|
BrowserReceiptTimeMs: null,
|
||||||
|
FrameDisplayDeltaTimeMs: null,
|
||||||
|
Reset: function()
|
||||||
|
{
|
||||||
|
this.TestStartTimeMs = null;
|
||||||
|
this.UEReceiptTimeMs = null;
|
||||||
|
this.UEPreCaptureTimeMs = null;
|
||||||
|
this.UEPostCaptureTimeMs = null;
|
||||||
|
this.UEPreEncodeTimeMs = null;
|
||||||
|
this.UEPostEncodeTimeMs = null;
|
||||||
|
this.UETransmissionTimeMs = null;
|
||||||
|
this.BrowserReceiptTimeMs = null;
|
||||||
|
this.FrameDisplayDeltaTimeMs = null;
|
||||||
|
},
|
||||||
|
SetUETimings: function(UETimings)
|
||||||
|
{
|
||||||
|
this.UEReceiptTimeMs = UETimings.ReceiptTimeMs;
|
||||||
|
this.UEPreCaptureTimeMs = UETimings.PreCaptureTimeMs;
|
||||||
|
this.UEPostCaptureTimeMs = UETimings.PostCaptureTimeMs;
|
||||||
|
this.UEPreEncodeTimeMs = UETimings.PreEncodeTimeMs;
|
||||||
|
this.UEPostEncodeTimeMs = UETimings.PostEncodeTimeMs;
|
||||||
|
this.UETransmissionTimeMs = UETimings.TransmissionTimeMs;
|
||||||
|
this.BrowserReceiptTimeMs = Date.now();
|
||||||
|
this.OnAllLatencyTimingsReady(this);
|
||||||
|
},
|
||||||
|
SetFrameDisplayDeltaTime: function(DeltaTimeMs)
|
||||||
|
{
|
||||||
|
if(this.FrameDisplayDeltaTimeMs == null)
|
||||||
|
{
|
||||||
|
this.FrameDisplayDeltaTimeMs = Math.round(DeltaTimeMs);
|
||||||
|
this.OnAllLatencyTimingsReady(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnAllLatencyTimingsReady: function(Timings){}
|
||||||
|
}
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Functions
|
||||||
|
//**********************
|
||||||
|
|
||||||
|
//Create Video element and expose that as a parameter
|
||||||
|
this.createWebRtcVideo = function() {
|
||||||
|
var video = document.createElement('video');
|
||||||
|
|
||||||
|
video.id = "streamingVideo";
|
||||||
|
video.playsInline = true;
|
||||||
|
video.disablepictureinpicture = true;
|
||||||
|
video.muted = self.startVideoMuted;;
|
||||||
|
|
||||||
|
video.addEventListener('loadedmetadata', function(e){
|
||||||
|
if(self.onVideoInitialised){
|
||||||
|
self.onVideoInitialised();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// Check if request video frame callback is supported
|
||||||
|
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
|
||||||
|
// The API is supported!
|
||||||
|
|
||||||
|
const onVideoFrameReady = (now, metadata) => {
|
||||||
|
|
||||||
|
// if(metadata.receiveTime && metadata.expectedDisplayTime)
|
||||||
|
// {
|
||||||
|
// const receiveToCompositeMs = metadata.presentationTime - metadata.receiveTime;
|
||||||
|
// self.aggregatedStats.receiveToCompositeMs = receiveToCompositeMs;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// Re-register the callback to be notified about the next frame.
|
||||||
|
video.requestVideoFrameCallback(onVideoFrameReady);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initially register the callback to be notified about the first frame.
|
||||||
|
video.requestVideoFrameCallback(onVideoFrameReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.video = this.createWebRtcVideo();
|
||||||
|
|
||||||
|
onsignalingstatechange = function(state) {
|
||||||
|
console.info('signaling state change:', state)
|
||||||
|
};
|
||||||
|
|
||||||
|
oniceconnectionstatechange = function(state) {
|
||||||
|
console.info('ice connection state change:', state)
|
||||||
|
};
|
||||||
|
|
||||||
|
onicegatheringstatechange = function(state) {
|
||||||
|
console.info('ice gathering state change:', state)
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOnTrack = function(e) {
|
||||||
|
console.log('handleOnTrack', e.streams);
|
||||||
|
|
||||||
|
if (e.track)
|
||||||
|
{
|
||||||
|
console.log('Got track - ' + e.track.kind + ' id=' + e.track.id + ' readyState=' + e.track.readyState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.track.kind == "audio")
|
||||||
|
{
|
||||||
|
handleOnAudioTrack(e.streams[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else(e.track.kind == "video" && self.video.srcObject !== e.streams[0])
|
||||||
|
{
|
||||||
|
self.video.srcObject = e.streams[0];
|
||||||
|
console.log('Set video source from video track ontrack.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOnAudioTrack = function(audioMediaStream)
|
||||||
|
{
|
||||||
|
// do nothing the video has the same media stream as the audio track we have here (they are linked)
|
||||||
|
if(self.video.srcObject == audioMediaStream)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// video element has some other media stream that is not associated with this audio track
|
||||||
|
else if(self.video.srcObject && self.video.srcObject !== audioMediaStream)
|
||||||
|
{
|
||||||
|
// create a new audio element
|
||||||
|
let audioElem = document.createElement("Audio");
|
||||||
|
audioElem.srcObject = audioMediaStream;
|
||||||
|
|
||||||
|
// there is no way to autoplay audio (even muted), so we defer audio until first click
|
||||||
|
if(!self.autoPlayAudio) {
|
||||||
|
|
||||||
|
let clickToPlayAudio = function() {
|
||||||
|
audioElem.play();
|
||||||
|
self.video.removeEventListener("click", clickToPlayAudio);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.video.addEventListener("click", clickToPlayAudio);
|
||||||
|
}
|
||||||
|
// we assume the user has clicked somewhere on the page and autoplaying audio will work
|
||||||
|
else {
|
||||||
|
audioElem.play();
|
||||||
|
}
|
||||||
|
console.log('Created new audio element to play seperate audio stream.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDataChannel = function(pc, label, options) {
|
||||||
|
try {
|
||||||
|
let datachannel = pc.createDataChannel(label, options);
|
||||||
|
console.log(`Created datachannel (${label})`)
|
||||||
|
|
||||||
|
// Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
|
||||||
|
datachannel.binaryType = "arraybuffer";
|
||||||
|
|
||||||
|
datachannel.onopen = function (e) {
|
||||||
|
console.log(`data channel (${label}) connect`)
|
||||||
|
if(self.onDataChannelConnected){
|
||||||
|
self.onDataChannelConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datachannel.onclose = function (e) {
|
||||||
|
console.log(`data channel (${label}) closed`)
|
||||||
|
}
|
||||||
|
|
||||||
|
datachannel.onmessage = function (e) {
|
||||||
|
//console.log(`Got message (${label})`, e.data)
|
||||||
|
if (self.onDataChannelMessage)
|
||||||
|
self.onDataChannelMessage(e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return datachannel;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('No data channel', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onicecandidate = function (e) {
|
||||||
|
console.log('ICE candidate', e)
|
||||||
|
if (e.candidate && e.candidate.candidate) {
|
||||||
|
self.onWebRtcCandidate(e.candidate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCreateOffer = function (pc) {
|
||||||
|
pc.createOffer(self.sdpConstraints).then(function (offer) {
|
||||||
|
|
||||||
|
// Munging is where we modifying the sdp string to set parameters that are not exposed to the browser's WebRTC API
|
||||||
|
mungeSDPOffer(offer);
|
||||||
|
|
||||||
|
// Set our munged SDP on the local peer connection so it is "set" and will be send across
|
||||||
|
pc.setLocalDescription(offer);
|
||||||
|
if (self.onWebRtcOffer) {
|
||||||
|
self.onWebRtcOffer(offer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function () { console.warn("Couldn't create offer") });
|
||||||
|
}
|
||||||
|
|
||||||
|
mungeSDPOffer = function (offer) {
|
||||||
|
|
||||||
|
// turn off video-timing sdp sent from browser
|
||||||
|
//offer.sdp = offer.sdp.replace("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "");
|
||||||
|
|
||||||
|
// this indicate we support stereo (Chrome needs this)
|
||||||
|
offer.sdp = offer.sdp.replace('useinbandfec=1', 'useinbandfec=1;stereo=1;sprop-maxcapturerate=48000');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPeerConnection = function (pc) {
|
||||||
|
if (pc.SetBitrate)
|
||||||
|
console.log("Hurray! there's RTCPeerConnection.SetBitrate function");
|
||||||
|
|
||||||
|
//Setup peerConnection events
|
||||||
|
pc.onsignalingstatechange = onsignalingstatechange;
|
||||||
|
pc.oniceconnectionstatechange = oniceconnectionstatechange;
|
||||||
|
pc.onicegatheringstatechange = onicegatheringstatechange;
|
||||||
|
|
||||||
|
pc.ontrack = handleOnTrack;
|
||||||
|
pc.onicecandidate = onicecandidate;
|
||||||
|
};
|
||||||
|
|
||||||
|
generateAggregatedStatsFunction = function(){
|
||||||
|
if(!self.aggregatedStats)
|
||||||
|
self.aggregatedStats = {};
|
||||||
|
|
||||||
|
return function(stats){
|
||||||
|
//console.log('Printing Stats');
|
||||||
|
|
||||||
|
let newStat = {};
|
||||||
|
|
||||||
|
stats.forEach(stat => {
|
||||||
|
// console.log(JSON.stringify(stat, undefined, 4));
|
||||||
|
if (stat.type == 'inbound-rtp'
|
||||||
|
&& !stat.isRemote
|
||||||
|
&& (stat.mediaType == 'video' || stat.id.toLowerCase().includes('video'))) {
|
||||||
|
|
||||||
|
newStat.timestamp = stat.timestamp;
|
||||||
|
newStat.bytesReceived = stat.bytesReceived;
|
||||||
|
newStat.framesDecoded = stat.framesDecoded;
|
||||||
|
newStat.packetsLost = stat.packetsLost;
|
||||||
|
newStat.bytesReceivedStart = self.aggregatedStats && self.aggregatedStats.bytesReceivedStart ? self.aggregatedStats.bytesReceivedStart : stat.bytesReceived;
|
||||||
|
newStat.framesDecodedStart = self.aggregatedStats && self.aggregatedStats.framesDecodedStart ? self.aggregatedStats.framesDecodedStart : stat.framesDecoded;
|
||||||
|
newStat.timestampStart = self.aggregatedStats && self.aggregatedStats.timestampStart ? self.aggregatedStats.timestampStart : stat.timestamp;
|
||||||
|
|
||||||
|
if(self.aggregatedStats && self.aggregatedStats.timestamp){
|
||||||
|
if(self.aggregatedStats.bytesReceived){
|
||||||
|
// bitrate = bits received since last time / number of ms since last time
|
||||||
|
//This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
|
||||||
|
newStat.bitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceived) / (newStat.timestamp - self.aggregatedStats.timestamp);
|
||||||
|
newStat.bitrate = Math.floor(newStat.bitrate);
|
||||||
|
newStat.lowBitrate = self.aggregatedStats.lowBitrate && self.aggregatedStats.lowBitrate < newStat.bitrate ? self.aggregatedStats.lowBitrate : newStat.bitrate
|
||||||
|
newStat.highBitrate = self.aggregatedStats.highBitrate && self.aggregatedStats.highBitrate > newStat.bitrate ? self.aggregatedStats.highBitrate : newStat.bitrate
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.aggregatedStats.bytesReceivedStart){
|
||||||
|
newStat.avgBitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceivedStart) / (newStat.timestamp - self.aggregatedStats.timestampStart);
|
||||||
|
newStat.avgBitrate = Math.floor(newStat.avgBitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.aggregatedStats.framesDecoded){
|
||||||
|
// framerate = frames decoded since last time / number of seconds since last time
|
||||||
|
newStat.framerate = (newStat.framesDecoded - self.aggregatedStats.framesDecoded) / ((newStat.timestamp - self.aggregatedStats.timestamp) / 1000);
|
||||||
|
newStat.framerate = Math.floor(newStat.framerate);
|
||||||
|
newStat.lowFramerate = self.aggregatedStats.lowFramerate && self.aggregatedStats.lowFramerate < newStat.framerate ? self.aggregatedStats.lowFramerate : newStat.framerate
|
||||||
|
newStat.highFramerate = self.aggregatedStats.highFramerate && self.aggregatedStats.highFramerate > newStat.framerate ? self.aggregatedStats.highFramerate : newStat.framerate
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self.aggregatedStats.framesDecodedStart){
|
||||||
|
newStat.avgframerate = (newStat.framesDecoded - self.aggregatedStats.framesDecodedStart) / ((newStat.timestamp - self.aggregatedStats.timestampStart) / 1000);
|
||||||
|
newStat.avgframerate = Math.floor(newStat.avgframerate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read video track stats
|
||||||
|
if(stat.type == 'track' && (stat.trackIdentifier == 'video_label' || stat.kind == 'video')) {
|
||||||
|
newStat.framesDropped = stat.framesDropped;
|
||||||
|
newStat.framesReceived = stat.framesReceived;
|
||||||
|
newStat.framesDroppedPercentage = stat.framesDropped / stat.framesReceived * 100;
|
||||||
|
newStat.frameHeight = stat.frameHeight;
|
||||||
|
newStat.frameWidth = stat.frameWidth;
|
||||||
|
newStat.frameHeightStart = self.aggregatedStats && self.aggregatedStats.frameHeightStart ? self.aggregatedStats.frameHeightStart : stat.frameHeight;
|
||||||
|
newStat.frameWidthStart = self.aggregatedStats && self.aggregatedStats.frameWidthStart ? self.aggregatedStats.frameWidthStart : stat.frameWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stat.type =='candidate-pair' && stat.hasOwnProperty('currentRoundTripTime') && stat.currentRoundTripTime != 0){
|
||||||
|
newStat.currentRoundTripTime = stat.currentRoundTripTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if(self.aggregatedStats.receiveToCompositeMs)
|
||||||
|
{
|
||||||
|
newStat.receiveToCompositeMs = self.aggregatedStats.receiveToCompositeMs;
|
||||||
|
self.latencyTestTimings.SetFrameDisplayDeltaTime(self.aggregatedStats.receiveToCompositeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.aggregatedStats = newStat;
|
||||||
|
|
||||||
|
if(self.onAggregatedStats)
|
||||||
|
self.onAggregatedStats(newStat)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupTracksToSendAsync = async function(pc){
|
||||||
|
|
||||||
|
// Setup a transceiver for getting UE video
|
||||||
|
pc.addTransceiver("video", { direction: "recvonly" });
|
||||||
|
|
||||||
|
// Setup a transceiver for sending mic audio to UE and receiving audio from UE
|
||||||
|
if(!self.useMic)
|
||||||
|
{
|
||||||
|
pc.addTransceiver("audio", { direction: "recvonly" });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let audioSendOptions = self.useMic ?
|
||||||
|
{
|
||||||
|
autoGainControl: false,
|
||||||
|
channelCount: 1,
|
||||||
|
echoCancellation: false,
|
||||||
|
latency: 0,
|
||||||
|
noiseSuppression: false,
|
||||||
|
sampleRate: 16000,
|
||||||
|
volume: 1.0
|
||||||
|
} : false;
|
||||||
|
|
||||||
|
// Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({video: false, audio: audioSendOptions});
|
||||||
|
if(stream)
|
||||||
|
{
|
||||||
|
for (const track of stream.getTracks()) {
|
||||||
|
if(track.kind && track.kind == "audio")
|
||||||
|
{
|
||||||
|
pc.addTransceiver(track, { direction: "sendrecv" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pc.addTransceiver("audio", { direction: "recvonly" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//**********************
|
||||||
|
//Public functions
|
||||||
|
//**********************
|
||||||
|
|
||||||
|
this.setVideoEnabled = function(enabled) {
|
||||||
|
self.video.srcObject.getTracks().forEach(track => track.enabled = enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startLatencyTest = function(onTestStarted) {
|
||||||
|
// Can't start latency test without a video element
|
||||||
|
if(!self.video)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.latencyTestTimings.Reset();
|
||||||
|
self.latencyTestTimings.TestStartTimeMs = Date.now();
|
||||||
|
onTestStarted(self.latencyTestTimings.TestStartTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This is called when revceiving new ice candidates individually instead of part of the offer
|
||||||
|
//This is currently not used but would be called externally from this class
|
||||||
|
this.handleCandidateFromServer = function(iceCandidate) {
|
||||||
|
console.log("ICE candidate: ", iceCandidate);
|
||||||
|
let candidate = new RTCIceCandidate(iceCandidate);
|
||||||
|
self.pcClient.addIceCandidate(candidate).then(_=>{
|
||||||
|
console.log('ICE candidate successfully added');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//Called externaly to create an offer for the server
|
||||||
|
this.createOffer = function() {
|
||||||
|
if(self.pcClient){
|
||||||
|
console.log("Closing existing PeerConnection")
|
||||||
|
self.pcClient.close();
|
||||||
|
self.pcClient = null;
|
||||||
|
}
|
||||||
|
self.pcClient = new RTCPeerConnection(self.cfg);
|
||||||
|
|
||||||
|
setupTracksToSendAsync(self.pcClient).finally(function()
|
||||||
|
{
|
||||||
|
setupPeerConnection(self.pcClient);
|
||||||
|
self.dcClient = setupDataChannel(self.pcClient, 'cirrus', self.dataChannelOptions);
|
||||||
|
handleCreateOffer(self.pcClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//Called externaly when an answer is received from the server
|
||||||
|
this.receiveAnswer = function(answer) {
|
||||||
|
console.log('Received answer:');
|
||||||
|
console.log(answer);
|
||||||
|
var answerDesc = new RTCSessionDescription(answer);
|
||||||
|
self.pcClient.setRemoteDescription(answerDesc);
|
||||||
|
|
||||||
|
let receivers = self.pcClient.getReceivers();
|
||||||
|
for(let receiver of receivers)
|
||||||
|
{
|
||||||
|
receiver.playoutDelayHint = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.close = function(){
|
||||||
|
if(self.pcClient){
|
||||||
|
console.log("Closing existing peerClient")
|
||||||
|
self.pcClient.close();
|
||||||
|
self.pcClient = null;
|
||||||
|
}
|
||||||
|
if(self.aggregateStatsIntervalId)
|
||||||
|
clearInterval(self.aggregateStatsIntervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sends data across the datachannel
|
||||||
|
this.send = function(data){
|
||||||
|
if(self.dcClient && self.dcClient.readyState == 'open'){
|
||||||
|
//console.log('Sending data on dataconnection', self.dcClient)
|
||||||
|
self.dcClient.send(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getStats = function(onStats){
|
||||||
|
if(self.pcClient && onStats){
|
||||||
|
self.pcClient.getStats(null).then((stats) => {
|
||||||
|
onStats(stats);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aggregateStats = function(checkInterval){
|
||||||
|
let calcAggregatedStats = generateAggregatedStatsFunction();
|
||||||
|
let printAggregatedStats = () => { self.getStats(calcAggregatedStats); }
|
||||||
|
self.aggregateStatsIntervalId = setInterval(printAggregatedStats, checkInterval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return webRtcPlayer;
|
||||||
|
|
||||||
|
}));
|
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,130 @@
|
|||||||
|
// 导入axios实例
|
||||||
|
import httpRequest from '../util/request';
|
||||||
|
const baseUrl = import.meta.env.VITE_APP_PLANT_API;
|
||||||
|
|
||||||
|
|
||||||
|
// 获取工厂列表
|
||||||
|
export function getFactoryList(id) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/index/factory-intro/getInfo/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取工厂能耗
|
||||||
|
export function getFactoryEnergy(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/index/factory-intro/energy/${query.id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取快捷菜单
|
||||||
|
export function getButton() {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/index/menu/getMenu`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 新增快捷菜单
|
||||||
|
export function addButton(param) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/index/menu`,
|
||||||
|
method: 'post',
|
||||||
|
data: param,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取生产订单
|
||||||
|
export function queryOrderNumberList(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/work-orders/get-list`,
|
||||||
|
method: 'post',
|
||||||
|
data: query,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取工序
|
||||||
|
export function queryWorkSeqs(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/work-seqs/get-list`,
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建会议房间
|
||||||
|
export function meetingsCreate(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/meetings/create`,
|
||||||
|
method: 'post',
|
||||||
|
data: query,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 加入会议房间
|
||||||
|
export function meetingsJoin(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/meetings/join-meeting/${query}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 结束会议
|
||||||
|
export function meetingsEnd(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/meetings/end-meeting/${query}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询会议列表
|
||||||
|
export function getMeetings(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/meetings/get-meetings`,
|
||||||
|
method: 'post',
|
||||||
|
data: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 查询评审列表
|
||||||
|
export function getReviewList(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/review/get-review-list`,
|
||||||
|
method: 'post',
|
||||||
|
data: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 查询维保人员信息
|
||||||
|
export function getMaintenance() {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/user/get-maintenance`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 查询维保人员信息
|
||||||
|
export function sendMeetingInfo(params) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/meetings/send-meeting-info`,
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 发布评审
|
||||||
|
export function submitReviewInfo(params) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/review/submit`,
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 创建评审
|
||||||
|
export function createReview(params) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/review/create`,
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除未发布评审
|
||||||
|
export function deleteReview(id) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl + `/ips-ai-plant/review/delete-review/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
|||||||
|
import request from '@/util/request';
|
||||||
|
const devUrl = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
|
||||||
|
|
||||||
|
// 查询流程定义列表
|
||||||
|
export function listDefinition(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部署流程实例
|
||||||
|
export function definitionStart(procDefId, data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/start/' + procDefId,
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取流程变量
|
||||||
|
export function getProcessVariables(taskId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/processVariables/' + taskId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活/挂起流程
|
||||||
|
export function updateState(params) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/updateState',
|
||||||
|
method: 'put',
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定流程办理人员列表
|
||||||
|
export function userList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/userList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定流程办理组列表
|
||||||
|
export function roleList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/roleList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定流程表达式
|
||||||
|
export function expList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/expList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取xml文件
|
||||||
|
export function readXml(deployId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/readXml/' + deployId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取image文件
|
||||||
|
export function readImage(deployId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/readImage/' + deployId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取流程执行节点
|
||||||
|
export function getFlowViewer(procInsId, executionId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/flowViewer/' + procInsId + '/' + executionId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流程节点数据
|
||||||
|
export function flowXmlAndNode(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/flowXmlAndNode',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取xml文件
|
||||||
|
export function saveXml(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/save',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流程定义
|
||||||
|
export function addDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流程定义
|
||||||
|
export function updateDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程定义
|
||||||
|
export function delDeployment(deployId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/definition/' + deployId,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出流程定义
|
||||||
|
export function exportDeployment(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/export',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import request from '@/util/request';
|
||||||
|
const devUrl = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
// 查询流程达式列表
|
||||||
|
export function listExpression(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/expression/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流程达式详细
|
||||||
|
export function getExpression(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/expression/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流程达式
|
||||||
|
export function addExpression(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/expression',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流程达式
|
||||||
|
export function updateExpression(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/expression',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程达式
|
||||||
|
export function delExpression(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/expression/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import request from '@/util/request'
|
||||||
|
const devUrl = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
|
||||||
|
// 查询已办任务列表
|
||||||
|
export function finishedList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/finishedList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务流转记录
|
||||||
|
export function flowRecord(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/flowRecord',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 撤回任务
|
||||||
|
export function revokeProcess(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/revokeProcess',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部署流程实例
|
||||||
|
export function deployStart(deployId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/process/startFlow/' + deployId,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流程定义详细
|
||||||
|
export function getDeployment(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流程定义
|
||||||
|
export function addDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流程定义
|
||||||
|
export function updateDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程定义
|
||||||
|
export function delDeployment(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/instance/delete/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出流程定义
|
||||||
|
export function exportDeployment(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/export',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
import request from '@/util/request'
|
||||||
|
const devUrl = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
|
||||||
|
// 查询流程表单列表
|
||||||
|
export function listForm(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function listAllForm(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form/formList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流程表单详细
|
||||||
|
export function getForm(formId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form/' + formId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流程表单
|
||||||
|
export function addForm(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流程表单
|
||||||
|
export function updateForm(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 挂载表单
|
||||||
|
export function addDeployForm(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form/addDeployForm',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程表单
|
||||||
|
export function delForm(formId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form/' + formId,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出流程表单
|
||||||
|
export function exportForm(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/form/export',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
import request from '@/util/request'
|
||||||
|
const devUrl = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
|
||||||
|
// 查询流程监听列表
|
||||||
|
export function listListener(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/listener/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流程监听详细
|
||||||
|
export function getListener(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/listener/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流程监听
|
||||||
|
export function addListener(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/listener',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流程监听
|
||||||
|
export function updateListener(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/listener',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程监听
|
||||||
|
export function delListener(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/listener/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
import request from '@/util/request'
|
||||||
|
const devUrl = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
|
||||||
|
// 我的发起的流程
|
||||||
|
export function myProcessList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/myProcess',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flowFormData(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/flowFormData',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flowTaskInfo(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/flowTaskInfo',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成任务
|
||||||
|
export function complete(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/complete',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消申请
|
||||||
|
export function stopProcess(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/stopProcess',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 驳回任务
|
||||||
|
export function rejectTask(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/reject',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可退回任务列表
|
||||||
|
export function returnList(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/returnList',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部署流程实例
|
||||||
|
export function deployStart(deployId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/process/startFlow/' + deployId,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流程定义详细
|
||||||
|
export function getDeployment(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流程定义
|
||||||
|
export function addDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流程定义
|
||||||
|
export function updateDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程定义
|
||||||
|
export function delDeployment(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出流程定义
|
||||||
|
export function exportDeployment(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/export',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
import request from '@/util/request'
|
||||||
|
const devUrl = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
|
||||||
|
// 查询待办任务列表
|
||||||
|
export function todoList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/todoList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成任务
|
||||||
|
export function complete(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/complete',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 委派任务
|
||||||
|
export function delegate(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/delegate',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退回任务
|
||||||
|
export function returnTask(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/return',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 驳回任务
|
||||||
|
export function rejectTask(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/reject',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可退回任务列表
|
||||||
|
export function returnList(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/returnList',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一节点
|
||||||
|
export function getNextFlowNode(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/nextFlowNode',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一节点
|
||||||
|
export function getNextFlowNodeByStart(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/nextFlowNodeByStart',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部署流程实例
|
||||||
|
export function deployStart(deployId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/process/startFlow/' + deployId,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流程定义详细
|
||||||
|
export function getDeployment(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流程定义
|
||||||
|
export function addDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流程定义
|
||||||
|
export function updateDeployment(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流程定义
|
||||||
|
export function delDeployment(id) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出流程定义
|
||||||
|
export function exportDeployment(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/system/deployment/export',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 流程节点表单
|
||||||
|
export function flowTaskForm(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + '/flowable/task/flowTaskForm',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
import request from '@/util/request';
|
||||||
|
const baseUrl = '/ips-sys-admin';
|
||||||
|
const devUrl = import.meta.env.VITE_APP_ADMIN_API;
|
||||||
|
|
||||||
|
// 查询字典数据列表
|
||||||
|
export function listData(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/data/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询字典数据详细
|
||||||
|
export function getData(dictCode) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/data/' + dictCode,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据字典类型查询字典数据信息
|
||||||
|
export function getDicts(dictType) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/data/type/' + dictType,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增字典数据
|
||||||
|
export function addData(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/data',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改字典数据
|
||||||
|
export function updateData(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/data',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除字典数据
|
||||||
|
export function delData(dictCode) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/data/' + dictCode,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
import request from '@/util/request';
|
||||||
|
const baseUrl = '/ips-sys-admin';
|
||||||
|
const devUrl = import.meta.env.VITE_APP_ADMIN_API;
|
||||||
|
|
||||||
|
// 查询字典类型列表
|
||||||
|
export function listType(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/type/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询字典类型详细
|
||||||
|
export function getType(dictId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/type/' + dictId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增字典类型
|
||||||
|
export function addType(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/type',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改字典类型
|
||||||
|
export function updateType(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/type',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除字典类型
|
||||||
|
export function delType(dictId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/type/' + dictId,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新字典缓存
|
||||||
|
export function refreshCache() {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/type/refreshCache',
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字典选择框列表
|
||||||
|
export function optionselect() {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dict/type/optionselect',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
import request from '@/util/request';
|
||||||
|
const baseUrl = '/ips-sys-admin';
|
||||||
|
const devUrl = import.meta.env.VITE_APP_ADMIN_API;
|
||||||
|
|
||||||
|
// 查询角色列表
|
||||||
|
export function listRole(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询角色详细
|
||||||
|
export function getRole(roleId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/' + roleId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增角色
|
||||||
|
export function addRole(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改角色
|
||||||
|
export function updateRole(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 角色数据权限
|
||||||
|
export function dataScope(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/dataScope',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 角色状态修改
|
||||||
|
export function changeRoleStatus(roleId, status) {
|
||||||
|
const data = {
|
||||||
|
roleId,
|
||||||
|
status
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + `/system/role/${roleId}/status`,
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除角色
|
||||||
|
export function delRole(roleId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/' + roleId,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询角色已授权用户列表
|
||||||
|
export function allocatedUserList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/authUser/allocatedList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询角色未授权用户列表
|
||||||
|
export function unallocatedUserList(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/authUser/unallocatedList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消用户授权角色
|
||||||
|
export function authUserCancel(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/authUser/cancel',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量取消用户授权角色
|
||||||
|
export function authUserCancelAll(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/authUser/cancelAll',
|
||||||
|
method: 'put',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 授权用户选择
|
||||||
|
export function authUserSelectAll(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/role/authUser/selectAll',
|
||||||
|
method: 'put',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据角色ID查询部门树结构
|
||||||
|
export function deptTreeSelect(roleId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/dept/dropdownList/role/' + roleId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
import request from '@/util/request'
|
||||||
|
import { parseStrEmpty } from "@/util/ruoyi";
|
||||||
|
const baseUrl = '/ips-sys-admin';
|
||||||
|
const devUrl = import.meta.env.VITE_APP_ADMIN_API;
|
||||||
|
|
||||||
|
// 查询用户列表
|
||||||
|
export function listUser(query) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户详细
|
||||||
|
export function getUser(userId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/' + parseStrEmpty(userId),
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增用户
|
||||||
|
export function addUser(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改用户
|
||||||
|
export function updateUser(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
export function delUser(userId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/' + userId,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户密码重置
|
||||||
|
export function resetUserPwd(userId, password) {
|
||||||
|
const data = {
|
||||||
|
userId,
|
||||||
|
password
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/resetPwd',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户状态修改
|
||||||
|
export function changeUserStatus(userId, status) {
|
||||||
|
const data = {
|
||||||
|
userId,
|
||||||
|
status
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/changeStatus',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户个人信息
|
||||||
|
export function getUserProfile() {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/profile',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改用户个人信息
|
||||||
|
export function updateUserProfile(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/profile',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户密码重置
|
||||||
|
export function updateUserPwd(oldPassword, newPassword) {
|
||||||
|
const data = {
|
||||||
|
oldPassword,
|
||||||
|
newPassword
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/profile/updatePwd',
|
||||||
|
method: 'put',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户头像上传
|
||||||
|
export function uploadAvatar(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/profile/avatar',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询授权角色
|
||||||
|
export function getAuthRole(userId) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/authRole/' + userId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存授权角色
|
||||||
|
export function updateAuthRole(data) {
|
||||||
|
return request({
|
||||||
|
url: devUrl + baseUrl + '/system/user/authRole',
|
||||||
|
method: 'put',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询部门下拉树结构
|
||||||
|
export function deptTreeSelect() {
|
||||||
|
return request({
|
||||||
|
// url: devUrl + baseUrl + '/system/user/deptTree',
|
||||||
|
url: devUrl + baseUrl + '/system/dept/dropdownList',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// 导入axios实例
|
||||||
|
import httpRequest from '../util/request';
|
||||||
|
const baseUrl = import.meta.env.VITE_APP_SECURITY_API;
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
export function login(param) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl+ '/ips-sys-security/ai-plant/login',
|
||||||
|
method: 'post',
|
||||||
|
data: param,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 登出
|
||||||
|
export function apiLogout() {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl+ '/ips-sys-security/logout',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取登录用户信息
|
||||||
|
export function getUserInfo() {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl+ '/ips-sys-security/getLoginUserInfo',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取登录路由
|
||||||
|
export function getRouters(query) {
|
||||||
|
return httpRequest({
|
||||||
|
url: baseUrl+ '/ips-sys-security/getRouters',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 776 B |
After Width: | Height: | Size: 520 B |
After Width: | Height: | Size: 456 KiB |
After Width: | Height: | Size: 812 B |
After Width: | Height: | Size: 551 B |
After Width: | Height: | Size: 362 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 615 B |
After Width: | Height: | Size: 345 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 642 B |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 259 KiB |
After Width: | Height: | Size: 6.6 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="44" height="43" viewBox="0 0 44 43" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.071 29.5287C18.9566 29.5287 17.1625 27.7987 17.1625 25.6202C17.1625 23.5058 18.8925 21.7117 21.071 21.7117C23.2495 21.7117 24.9795 23.4417 24.9795 25.6202C24.9795 27.7987 23.2495 29.5287 21.071 29.5287ZM21.0069 26.7095C21.5836 26.7095 22.0962 26.261 22.0962 25.6843C22.1602 25.1076 21.7117 24.6591 21.071 24.595C20.4943 24.531 19.9817 25.0436 19.9817 25.6202C19.9817 26.1969 20.4303 26.7095 21.0069 26.7095ZM29.6568 21.5195C29.5287 21.5195 29.4646 21.4554 29.4646 21.3273V19.7895H36.0001V21.3914C36.0001 21.5195 35.9361 21.5836 35.8079 21.5836H33.6294V23.5699L33.4372 24.2106H33.309V24.4028H33.1168V24.531H32.5402V24.4028H32.3479V24.2106H32.2198L32.0276 23.3776V21.5195H29.6568ZM35.8079 14.7277C35.9361 14.7277 36.0001 14.7918 36.0001 14.9199V18.8284H29.4646V14.9199C29.4646 14.7918 29.5287 14.7277 29.6568 14.7277H30.7461V14.0229H30.6179V13.6385H29.7209V9.2174H30.7461V8.32037C30.7461 8.12815 30.8742 8 31.0665 8H34.7187C34.9109 8 35.039 8.12815 35.039 8.32037V8.38444H35.5516C35.7438 8.38444 35.872 8.51259 35.872 8.70481V14.7277H35.8079ZM32.2198 17.9955C32.2198 18.2518 32.412 18.5081 32.7324 18.5081C33.0527 18.5081 33.309 18.2518 33.245 17.9955C33.245 17.7392 33.0527 17.4829 32.7324 17.4829C32.4761 17.4829 32.2198 17.6751 32.2198 17.9955ZM31.8353 14.7277H34.3983V14.2792H31.8353V14.7277ZM34.9109 14.7277H35.6798V8.89703H35.039V14.0229H34.9109V14.7277ZM26.261 13.3181V9.53776C26.261 9.34554 26.3891 9.2174 26.5813 9.2174H29.3365V13.6385H26.5813C26.3891 13.6385 26.261 13.5103 26.261 13.3181ZM26.9017 31.5791C26.9658 32.0276 27.158 32.2198 27.6065 32.1557C27.9269 32.1557 28.2472 32.1557 28.6317 32.0916V34.5905H13.3181V32.0916H15.1122C15.3721 30.7919 15.6742 29.4922 15.9843 28.1583C16.0564 27.8482 16.1289 27.5363 16.2014 27.2221C18.1236 31.8353 24.2106 31.7072 25.9406 27.2221C26.261 28.6957 26.5813 30.1694 26.9017 31.5791ZM19.2874 17.9928C19.9574 18.8449 20.6312 19.7017 21.3273 20.5584C18.9566 20.6225 17.2907 21.6477 16.3296 23.8262C15.6247 22.7049 14.9199 21.5996 14.2151 20.4943C13.5103 19.3891 12.8055 18.2838 12.1007 17.1625C14.0229 17.0344 15.4325 16.1373 16.4577 14.4714C17.4275 15.6277 18.3539 16.8057 19.2874 17.9928ZM25.4921 13.7025H16.714C17.0984 12.2289 17.0344 10.3707 16.2014 9.28147H25.4921C25.5561 9.28147 25.6202 9.34554 25.6202 9.47369V13.5103C25.6202 13.5744 25.5561 13.7025 25.4921 13.7025ZM11.9726 8.12815C14.1511 8.12815 15.881 9.85813 15.881 12.0366C15.881 14.1511 14.1511 15.9451 11.9726 16.0092C9.79406 16.0092 8 14.2151 8 12.0366C8 9.85813 9.79406 8.12815 11.9726 8.12815ZM10.9474 11.9726C10.8833 12.5492 11.3318 13.0618 11.9085 13.0618C12.4851 13.0618 12.9977 12.6133 12.9977 12.0366C12.9977 11.524 12.5492 11.0115 11.9726 11.0115C11.46 11.0115 10.9474 11.3959 10.9474 11.9726Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 436 B |
After Width: | Height: | Size: 569 B |
@ -0,0 +1,183 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "iconfont"; /* Project id 4002868 */
|
||||||
|
src: url('iconfont.woff2?t=1681283227538') format('woff2'),
|
||||||
|
url('iconfont.woff?t=1681283227538') format('woff'),
|
||||||
|
url('iconfont.ttf?t=1681283227538') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-family: "iconfont" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-neirongqiehuan:before {
|
||||||
|
content: "\e781";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yewushouce:before {
|
||||||
|
content: "\e647";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-quanbudingdan:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shebei:before {
|
||||||
|
content: "\e6c9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shipinjiankong:before {
|
||||||
|
content: "\e609";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-chanzhinenghao:before {
|
||||||
|
content: "\e60c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shebei1:before {
|
||||||
|
content: "\e62a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-teamwork:before {
|
||||||
|
content: "\e870";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-01zhushuju_wuliaoguanli:before {
|
||||||
|
content: "\e611";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-3d:before {
|
||||||
|
content: "\e601";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-ditu:before {
|
||||||
|
content: "\e620";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-Dditu:before {
|
||||||
|
content: "\e602";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon--Down-Right-Arrow:before {
|
||||||
|
content: "\e613";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-_delete-big:before {
|
||||||
|
content: "\e619";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-hy_arrow_down:before {
|
||||||
|
content: "\e61a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-icon_2d:before {
|
||||||
|
content: "\e679";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-navigation2daohang:before {
|
||||||
|
content: "\e6de";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shengchanguanli:before {
|
||||||
|
content: "\e67e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tiaoduguanli:before {
|
||||||
|
content: "\e65f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-cangchuwuliu:before {
|
||||||
|
content: "\e6b0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-ziyuan14:before {
|
||||||
|
content: "\e608";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sousuo:before {
|
||||||
|
content: "\e651";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shouye:before {
|
||||||
|
content: "\e663";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-jisuxiangying:before {
|
||||||
|
content: "\e60b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wuliu:before {
|
||||||
|
content: "\e606";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zhiliang-xianxing:before {
|
||||||
|
content: "\e88b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shezhi:before {
|
||||||
|
content: "\e70f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yunyingguanli:before {
|
||||||
|
content: "\ebd1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shebeiguanli:before {
|
||||||
|
content: "\e684";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-fanshe:before {
|
||||||
|
content: "\ec50";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wodezhanghao:before {
|
||||||
|
content: "\e62d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zhuanyegaoxiao:before {
|
||||||
|
content: "\e752";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-cangkuguanli:before {
|
||||||
|
content: "\e667";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-xitongjiankong:before {
|
||||||
|
content: "\e66c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-quyuguanli:before {
|
||||||
|
content: "\e60d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-bangzhu:before {
|
||||||
|
content: "\e8ac";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-chuanganshebeiguanli:before {
|
||||||
|
content: "\e607";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-lishihuisu:before {
|
||||||
|
content: "\e654";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-renwuguihua:before {
|
||||||
|
content: "\e7a0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zhanshipingtai:before {
|
||||||
|
content: "\e9e9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-chejianguanli:before {
|
||||||
|
content: "\e662";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shebeibaoxiu:before {
|
||||||
|
content: "\e68c";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,303 @@
|
|||||||
|
{
|
||||||
|
"id": "4002868",
|
||||||
|
"name": "数字孪生产品dome",
|
||||||
|
"font_family": "iconfont",
|
||||||
|
"css_prefix_text": "icon-",
|
||||||
|
"description": "",
|
||||||
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "3092112",
|
||||||
|
"name": "内容切换",
|
||||||
|
"font_class": "neirongqiehuan",
|
||||||
|
"unicode": "e781",
|
||||||
|
"unicode_decimal": 59265
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "956167",
|
||||||
|
"name": "业务手册",
|
||||||
|
"font_class": "yewushouce",
|
||||||
|
"unicode": "e647",
|
||||||
|
"unicode_decimal": 58951
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "88780",
|
||||||
|
"name": "全部订单",
|
||||||
|
"font_class": "quanbudingdan",
|
||||||
|
"unicode": "e600",
|
||||||
|
"unicode_decimal": 58880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6893981",
|
||||||
|
"name": "设备",
|
||||||
|
"font_class": "shebei",
|
||||||
|
"unicode": "e6c9",
|
||||||
|
"unicode_decimal": 59081
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9656296",
|
||||||
|
"name": "视频监控",
|
||||||
|
"font_class": "shipinjiankong",
|
||||||
|
"unicode": "e609",
|
||||||
|
"unicode_decimal": 58889
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9923726",
|
||||||
|
"name": "产值能耗",
|
||||||
|
"font_class": "chanzhinenghao",
|
||||||
|
"unicode": "e60c",
|
||||||
|
"unicode_decimal": 58892
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10017847",
|
||||||
|
"name": "设备",
|
||||||
|
"font_class": "shebei1",
|
||||||
|
"unicode": "e62a",
|
||||||
|
"unicode_decimal": 58922
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "18164896",
|
||||||
|
"name": "组织,合作,协作,人员,分支,关联人员",
|
||||||
|
"font_class": "teamwork",
|
||||||
|
"unicode": "e870",
|
||||||
|
"unicode_decimal": 59504
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "19134722",
|
||||||
|
"name": "01 主数据_物料管理",
|
||||||
|
"font_class": "01zhushuju_wuliaoguanli",
|
||||||
|
"unicode": "e611",
|
||||||
|
"unicode_decimal": 58897
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "705650",
|
||||||
|
"name": "3d",
|
||||||
|
"font_class": "3d",
|
||||||
|
"unicode": "e601",
|
||||||
|
"unicode_decimal": 58881
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "954148",
|
||||||
|
"name": "地图",
|
||||||
|
"font_class": "ditu",
|
||||||
|
"unicode": "e620",
|
||||||
|
"unicode_decimal": 58912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "2486573",
|
||||||
|
"name": "3D地图",
|
||||||
|
"font_class": "Dditu",
|
||||||
|
"unicode": "e602",
|
||||||
|
"unicode_decimal": 58882
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "5354383",
|
||||||
|
"name": "142-Down-Right-Arrow",
|
||||||
|
"font_class": "-Down-Right-Arrow",
|
||||||
|
"unicode": "e613",
|
||||||
|
"unicode_decimal": 58899
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10909329",
|
||||||
|
"name": "close",
|
||||||
|
"font_class": "_delete-big",
|
||||||
|
"unicode": "e619",
|
||||||
|
"unicode_decimal": 58905
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "11243030",
|
||||||
|
"name": "hy_arrow2_down",
|
||||||
|
"font_class": "hy_arrow_down",
|
||||||
|
"unicode": "e61a",
|
||||||
|
"unicode_decimal": 58906
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "15790781",
|
||||||
|
"name": "icon_2d",
|
||||||
|
"font_class": "icon_2d",
|
||||||
|
"unicode": "e679",
|
||||||
|
"unicode_decimal": 59001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "20009273",
|
||||||
|
"name": "navigation2导航",
|
||||||
|
"font_class": "navigation2daohang",
|
||||||
|
"unicode": "e6de",
|
||||||
|
"unicode_decimal": 59102
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "24271987",
|
||||||
|
"name": "生产管理",
|
||||||
|
"font_class": "shengchanguanli",
|
||||||
|
"unicode": "e67e",
|
||||||
|
"unicode_decimal": 59006
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "8183583",
|
||||||
|
"name": "调度管理",
|
||||||
|
"font_class": "tiaoduguanli",
|
||||||
|
"unicode": "e65f",
|
||||||
|
"unicode_decimal": 58975
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "14627498",
|
||||||
|
"name": "仓储物流",
|
||||||
|
"font_class": "cangchuwuliu",
|
||||||
|
"unicode": "e6b0",
|
||||||
|
"unicode_decimal": 59056
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "20741730",
|
||||||
|
"name": "机械",
|
||||||
|
"font_class": "ziyuan14",
|
||||||
|
"unicode": "e608",
|
||||||
|
"unicode_decimal": 58888
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "629357",
|
||||||
|
"name": "搜索",
|
||||||
|
"font_class": "sousuo",
|
||||||
|
"unicode": "e651",
|
||||||
|
"unicode_decimal": 58961
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "949572",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "shouye",
|
||||||
|
"unicode": "e663",
|
||||||
|
"unicode_decimal": 58979
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1249051",
|
||||||
|
"name": "极速响应",
|
||||||
|
"font_class": "jisuxiangying",
|
||||||
|
"unicode": "e60b",
|
||||||
|
"unicode_decimal": 58891
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1287697",
|
||||||
|
"name": "物流",
|
||||||
|
"font_class": "wuliu",
|
||||||
|
"unicode": "e606",
|
||||||
|
"unicode_decimal": 58886
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1727275",
|
||||||
|
"name": "08质量-线性",
|
||||||
|
"font_class": "zhiliang-xianxing",
|
||||||
|
"unicode": "e88b",
|
||||||
|
"unicode_decimal": 59531
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "3456457",
|
||||||
|
"name": "设置",
|
||||||
|
"font_class": "shezhi",
|
||||||
|
"unicode": "e70f",
|
||||||
|
"unicode_decimal": 59151
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "4518102",
|
||||||
|
"name": "运营管理",
|
||||||
|
"font_class": "yunyingguanli",
|
||||||
|
"unicode": "ebd1",
|
||||||
|
"unicode_decimal": 60369
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "4960610",
|
||||||
|
"name": "设备管理",
|
||||||
|
"font_class": "shebeiguanli",
|
||||||
|
"unicode": "e684",
|
||||||
|
"unicode_decimal": 59012
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "5961302",
|
||||||
|
"name": "反射",
|
||||||
|
"font_class": "fanshe",
|
||||||
|
"unicode": "ec50",
|
||||||
|
"unicode_decimal": 60496
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6234367",
|
||||||
|
"name": "我的账号",
|
||||||
|
"font_class": "wodezhanghao",
|
||||||
|
"unicode": "e62d",
|
||||||
|
"unicode_decimal": 58925
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7734748",
|
||||||
|
"name": "专业高效",
|
||||||
|
"font_class": "zhuanyegaoxiao",
|
||||||
|
"unicode": "e752",
|
||||||
|
"unicode_decimal": 59218
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "8199287",
|
||||||
|
"name": "仓库管理",
|
||||||
|
"font_class": "cangkuguanli",
|
||||||
|
"unicode": "e667",
|
||||||
|
"unicode_decimal": 58983
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9454030",
|
||||||
|
"name": "系统监控",
|
||||||
|
"font_class": "xitongjiankong",
|
||||||
|
"unicode": "e66c",
|
||||||
|
"unicode_decimal": 58988
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9923738",
|
||||||
|
"name": "区域管理",
|
||||||
|
"font_class": "quyuguanli",
|
||||||
|
"unicode": "e60d",
|
||||||
|
"unicode_decimal": 58893
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "11372644",
|
||||||
|
"name": "帮助",
|
||||||
|
"font_class": "bangzhu",
|
||||||
|
"unicode": "e8ac",
|
||||||
|
"unicode_decimal": 59564
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "12875278",
|
||||||
|
"name": "传感设备管理",
|
||||||
|
"font_class": "chuanganshebeiguanli",
|
||||||
|
"unicode": "e607",
|
||||||
|
"unicode_decimal": 58887
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "13409756",
|
||||||
|
"name": "历史回溯",
|
||||||
|
"font_class": "lishihuisu",
|
||||||
|
"unicode": "e654",
|
||||||
|
"unicode_decimal": 58964
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "20172654",
|
||||||
|
"name": "任务规划",
|
||||||
|
"font_class": "renwuguihua",
|
||||||
|
"unicode": "e7a0",
|
||||||
|
"unicode_decimal": 59296
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "22612650",
|
||||||
|
"name": "展示平台",
|
||||||
|
"font_class": "zhanshipingtai",
|
||||||
|
"unicode": "e9e9",
|
||||||
|
"unicode_decimal": 59881
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "26913668",
|
||||||
|
"name": "车间管理",
|
||||||
|
"font_class": "chejianguanli",
|
||||||
|
"unicode": "e662",
|
||||||
|
"unicode_decimal": 58978
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "29657616",
|
||||||
|
"name": "设备报修",
|
||||||
|
"font_class": "shebeibaoxiu",
|
||||||
|
"unicode": "e68c",
|
||||||
|
"unicode_decimal": 59020
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 235 KiB |
After Width: | Height: | Size: 407 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 252 KiB |
After Width: | Height: | Size: 609 B |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 3.6 MiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 518 B |
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 991 B |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 991 B |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 573 B |
After Width: | Height: | Size: 407 B |
After Width: | Height: | Size: 779 B |
After Width: | Height: | Size: 540 B |
After Width: | Height: | Size: 567 B |
After Width: | Height: | Size: 412 B |
After Width: | Height: | Size: 885 B |
After Width: | Height: | Size: 560 B |
After Width: | Height: | Size: 561 B |
After Width: | Height: | Size: 933 B |
After Width: | Height: | Size: 569 B |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 7.3 KiB |