第一次代码提交

This commit is contained in:
WORK\64751 2025-08-22 09:43:16 +08:00
parent 00660837d4
commit b525eaccbe
63 changed files with 13224 additions and 10 deletions

10
.env Normal file
View File

@ -0,0 +1,10 @@
VITE_APP_VERSION=1.0.0
VITE_APP_NAME=自动起降机场
VITE_APP_API_BASE_URL=https://airport-test.t-aaron.com/
VITE_APP_SERVER = "/airport/admin/api"
VITE_APP_PLATFORM = "tuoheng-airport-admin"
VITE_APP_CLIENT_ID = 'tuoheng-airport-admin'
VITE_APP_CLIENT_SECRET = 'NjHifmmB41rH6bJTd4A7RA=='

21
.env.development Normal file
View File

@ -0,0 +1,21 @@
# .env.local
VUE_APP_VERSION=1.0.0
NODE_ENV=development
VUE_APP_NAME=自动起降机场
#程璐
#VITE_APP_API_BASE_URL=http://192.168.12.117:9060/airport/
VITE_APP_API_BASE_URL=https://airport-test.t-aaron.com/airport/
# VUE_APP_API_BASE_URL=https://airport.t-aaron.com/airport/
# VUE_APP_API_BASE_URL=https://airportdev.t-aaron.com/airport/
# VUE_APP_AUTHORITY = 'http://192.168.11.11:8090'
#测试环境
VITE_APP_AUTHORITY =https://login-test.t-aaron.com
#线上环境 (配合开发环境使用)
#VUE_APP_AUTHORITY = 'https://oidc.t-aaron.com'
# 机场大屏跳转地址
VITE_APP_ASURL = 'https://airport-screen-test.t-aaron.com'
# 低空
VITE_APP_BUSINESS ='http://192.168.14.12:8082'

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

35
.gitignore vendored
View File

@ -1,11 +1,30 @@
# ---> Vue
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# TODO: where does this rule come from?
docs/_book
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# TODO: where does this rule come from?
test/
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

6
.prettierrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}

6
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"recommendations": [
"Vue.volar",
"esbenp.prettier-vscode"
]
}

View File

@ -1,3 +1,21 @@
# tuoheng_virtualAirPlan_web
#
虚拟座舱-前端项目
虚拟座舱的前端项目,里面只有虚拟座舱这一个功能。方便嵌入其他项目使用。
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

10
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}

54
components.d.ts vendored Normal file
View File

@ -0,0 +1,54 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AButton: typeof import('ant-design-vue/es')['Button']
ACol: typeof import('ant-design-vue/es')['Col']
AInput: typeof import('ant-design-vue/es')['Input']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
AProgress: typeof import('ant-design-vue/es')['Progress']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARow: typeof import('ant-design-vue/es')['Row']
ASpin: typeof import('ant-design-vue/es')['Spin']
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
Form: typeof import('./src/components/Forms/form.vue')['default']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Spin1: typeof import('./src/components/loading/spin1.vue')['default']
Table: typeof import('./src/components/vxetable/table.vue')['default']
TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
Video: typeof import('./src/components/video/index.vue')['default']
VideoPlayPopup: typeof import('./src/components/custom_popup/videoPlayPopup.vue')['default']
Vuxpopup: typeof import('./src/components/custom_popup/vuxpopup.vue')['default']
WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
}
}

18
index.html Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/liveplayer-lib.min.js"></script>
<!-- 如果正在使用 vue-cli:
<script src="<%= BASE_URL %>js/liveplayer-lib.min.js"></script>
-->
<title>应用</title>
</head>
<body>
<div id="airapp"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

7368
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "singlework",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite -- mode development",
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write src/"
},
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@liveqing/liveplayer-v3": "^3.7.10",
"ant-design-vue": "^4.2.6",
"axios": "^1.10.0",
"mitt": "^3.0.1",
"pinia": "^3.0.1",
"rollup-plugin-copy": "^3.5.0",
"video.js": "^7.14.3",
"vite-plugin-qiankun": "^1.0.15",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vxe-pc-ui": "^4.6.26",
"vxe-table": "^4.13.43"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"autoprefixer": "^10.4.21",
"less": "^4.3.0",
"less-loader": "^12.3.0",
"postcss": "^8.5.6",
"prettier": "3.5.3",
"sass": "^1.89.2",
"tailwindcss": "^3.4.13",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.7.0",
"vite": "^6.2.4",
"vite-plugin-lazy-import": "^1.0.7",
"vite-plugin-vue-devtools": "^7.7.2"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

1
public/js/liveplayer-lib.min.js vendored Normal file

File diff suppressed because one or more lines are too long

9
src/App.vue Normal file
View File

@ -0,0 +1,9 @@
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style scoped></style>

85
src/apis/common.js Normal file
View File

@ -0,0 +1,85 @@
import request from '@/utils/request'
import axios from 'axios'
//机场查询
export function queryAirportApi(params) {
return request({
url: `/api/airportStatus/index`,
method: 'get',
params: params,
})
}
//无人机查询
export function queryDroneApi(params) {
return request({
url: `/api/drone/getDroneList`,
method: 'get',
params: params,
})
}
//电池查询
export function queryBatteryApi(params) {
return request({
url: `/api/batteryRecord/queryBatteryBaseInfoList`,
method: 'get',
params: params,
})
}
//航线查询
// api/airlineFile/getAirlineFileListByAirportId?airportId=67
export function queryAirLineApi(params) {
return request({
url: `api/airlineFile/getAirlineFileListByAirportId`,
method: 'get',
params: params,
})
}
//无人机飞前自检
export function beforeCheckApi(params) {
return request({
url: `/api/airportLog/virtualCockpit/progressBar`,
method: 'get',
params: params,
})
}
//获取航线文件
export async function getAirWayPointsToJson(url) {
// console.log(url)
let res = await axios.get(url)
if (res.status === 200) {
const text = res.data
let mapPointList = []
let arr = text.split('\r\n')
if (arr.length <= 1) {
arr = text.split('\n')
}
arr.shift()
arr.pop()
// console.log(arr)
arr = arr.map((text) => {
return text.split(/\s+/)
})
// 删除前两个起飞点
arr.splice(0, 2)
// 删除最后一个降落点
arr.pop()
// 航线节点
for (let i = 0; i < arr.length; i++) {
const stop = arr[i]
if (parseInt(stop[3]) === 16) {
// 地图点,用于绘制
mapPointList.push({
// id
id: mapPointList.length + 1,
// 经纬度
lngLat: [parseFloat(stop[9]), parseFloat(stop[8])],
alt: parseInt(stop[10]), // 高度
})
}
}
// console.log(mapPointList)
return mapPointList
}
}

10
src/apis/resources.js Normal file
View File

@ -0,0 +1,10 @@
import request from '@/utils/request'
//电池列表查询
export function querybatteryRecordApi(params) {
return request({
url: `/api/batteryRecord/index`,
method: 'get',
params: params,
})
}

View File

@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -0,0 +1,832 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=4970295" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xeb21;</span>
<div class="name">返航</div>
<div class="code-name">&amp;#xeb21;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe600;</span>
<div class="name">暂停</div>
<div class="code-name">&amp;#xe600;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe60b;</span>
<div class="name">蓄电池</div>
<div class="code-name">&amp;#xe60b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61f;</span>
<div class="name">贴地距离</div>
<div class="code-name">&amp;#xe61f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe604;</span>
<div class="name">flight-takeoff-line</div>
<div class="code-name">&amp;#xe604;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6f7;</span>
<div class="name">偏航角-1</div>
<div class="code-name">&amp;#xe6f7;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe753;</span>
<div class="name">飞行器偏航角</div>
<div class="code-name">&amp;#xe753;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe975;</span>
<div class="name">飞行器偏航角</div>
<div class="code-name">&amp;#xe975;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6ca;</span>
<div class="name">摄像头</div>
<div class="code-name">&amp;#xe6ca;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe609;</span>
<div class="name">H</div>
<div class="code-name">&amp;#xe609;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe635;</span>
<div class="name">24上升&下降</div>
<div class="code-name">&amp;#xe635;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6a0;</span>
<div class="name">无人机-fill</div>
<div class="code-name">&amp;#xe6a0;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe857;</span>
<div class="name">箭头左右 </div>
<div class="code-name">&amp;#xe857;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6dc;</span>
<div class="name">舱内_线性</div>
<div class="code-name">&amp;#xe6dc;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe603;</span>
<div class="name">减号</div>
<div class="code-name">&amp;#xe603;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe711;</span>
<div class="name"></div>
<div class="code-name">&amp;#xe711;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe622;</span>
<div class="name">切换账号</div>
<div class="code-name">&amp;#xe622;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6b6;</span>
<div class="name">回中</div>
<div class="code-name">&amp;#xe6b6;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe661;</span>
<div class="name">搜索</div>
<div class="code-name">&amp;#xe661;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6ab;</span>
<div class="name">路线</div>
<div class="code-name">&amp;#xe6ab;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe617;</span>
<div class="name">下双箭头</div>
<div class="code-name">&amp;#xe617;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe602;</span>
<div class="name">标签</div>
<div class="code-name">&amp;#xe602;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe686;</span>
<div class="name">时间</div>
<div class="code-name">&amp;#xe686;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61a;</span>
<div class="name">完成</div>
<div class="code-name">&amp;#xe61a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe699;</span>
<div class="name">加载</div>
<div class="code-name">&amp;#xe699;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe647;</span>
<div class="name">切换</div>
<div class="code-name">&amp;#xe647;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe643;</span>
<div class="name">起飞点</div>
<div class="code-name">&amp;#xe643;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe959;</span>
<div class="name">信号塔,卫星信号</div>
<div class="code-name">&amp;#xe959;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1752124461773') format('woff2'),
url('iconfont.woff?t=1752124461773') format('woff'),
url('iconfont.ttf?t=1752124461773') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-fanhang"></span>
<div class="name">
返航
</div>
<div class="code-name">.icon-fanhang
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zanting"></span>
<div class="name">
暂停
</div>
<div class="code-name">.icon-zanting
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xudianchi"></span>
<div class="name">
蓄电池
</div>
<div class="code-name">.icon-xudianchi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tiedijuli"></span>
<div class="name">
贴地距离
</div>
<div class="code-name">.icon-tiedijuli
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-flight-takeoff-line"></span>
<div class="name">
flight-takeoff-line
</div>
<div class="code-name">.icon-flight-takeoff-line
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-pianhangjiao-1"></span>
<div class="name">
偏航角-1
</div>
<div class="code-name">.icon-pianhangjiao-1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-feihangqipianhangjiao"></span>
<div class="name">
飞行器偏航角
</div>
<div class="code-name">.icon-feihangqipianhangjiao
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-feihangqipianhangjiao1"></span>
<div class="name">
飞行器偏航角
</div>
<div class="code-name">.icon-feihangqipianhangjiao1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shexiangtou"></span>
<div class="name">
摄像头
</div>
<div class="code-name">.icon-shexiangtou
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-H"></span>
<div class="name">
H
</div>
<div class="code-name">.icon-H
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shangshengxiajiang"></span>
<div class="name">
24上升&下降
</div>
<div class="code-name">.icon-shangshengxiajiang
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-uav-fill"></span>
<div class="name">
无人机-fill
</div>
<div class="code-name">.icon-uav-fill
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-jiantouzuoyou"></span>
<div class="name">
箭头左右
</div>
<div class="code-name">.icon-jiantouzuoyou
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-cangnei_xianxing"></span>
<div class="name">
舱内_线性
</div>
<div class="code-name">.icon-cangnei_xianxing
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-jianhao"></span>
<div class="name">
减号
</div>
<div class="code-name">.icon-jianhao
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-jia"></span>
<div class="name">
</div>
<div class="code-name">.icon-jia
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-qiehuanzhanghao"></span>
<div class="name">
切换账号
</div>
<div class="code-name">.icon-qiehuanzhanghao
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-huizhong"></span>
<div class="name">
回中
</div>
<div class="code-name">.icon-huizhong
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-sousuo"></span>
<div class="name">
搜索
</div>
<div class="code-name">.icon-sousuo
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-route1-fill"></span>
<div class="name">
路线
</div>
<div class="code-name">.icon-route1-fill
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiashuangjiantou"></span>
<div class="name">
下双箭头
</div>
<div class="code-name">.icon-xiashuangjiantou
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-biaoqian"></span>
<div class="name">
标签
</div>
<div class="code-name">.icon-biaoqian
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-time"></span>
<div class="name">
时间
</div>
<div class="code-name">.icon-time
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-wancheng"></span>
<div class="name">
完成
</div>
<div class="code-name">.icon-wancheng
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-jiazai"></span>
<div class="name">
加载
</div>
<div class="code-name">.icon-jiazai
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-qiehuan"></span>
<div class="name">
切换
</div>
<div class="code-name">.icon-qiehuan
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-qifeidian"></span>
<div class="name">
起飞点
</div>
<div class="code-name">.icon-qifeidian
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-satellite-signal-full"></span>
<div class="name">
信号塔,卫星信号
</div>
<div class="code-name">.icon-satellite-signal-full
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-fanhang"></use>
</svg>
<div class="name">返航</div>
<div class="code-name">#icon-fanhang</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zanting"></use>
</svg>
<div class="name">暂停</div>
<div class="code-name">#icon-zanting</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xudianchi"></use>
</svg>
<div class="name">蓄电池</div>
<div class="code-name">#icon-xudianchi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tiedijuli"></use>
</svg>
<div class="name">贴地距离</div>
<div class="code-name">#icon-tiedijuli</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-flight-takeoff-line"></use>
</svg>
<div class="name">flight-takeoff-line</div>
<div class="code-name">#icon-flight-takeoff-line</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-pianhangjiao-1"></use>
</svg>
<div class="name">偏航角-1</div>
<div class="code-name">#icon-pianhangjiao-1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-feihangqipianhangjiao"></use>
</svg>
<div class="name">飞行器偏航角</div>
<div class="code-name">#icon-feihangqipianhangjiao</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-feihangqipianhangjiao1"></use>
</svg>
<div class="name">飞行器偏航角</div>
<div class="code-name">#icon-feihangqipianhangjiao1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shexiangtou"></use>
</svg>
<div class="name">摄像头</div>
<div class="code-name">#icon-shexiangtou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-H"></use>
</svg>
<div class="name">H</div>
<div class="code-name">#icon-H</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shangshengxiajiang"></use>
</svg>
<div class="name">24上升&下降</div>
<div class="code-name">#icon-shangshengxiajiang</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-uav-fill"></use>
</svg>
<div class="name">无人机-fill</div>
<div class="code-name">#icon-uav-fill</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-jiantouzuoyou"></use>
</svg>
<div class="name">箭头左右 </div>
<div class="code-name">#icon-jiantouzuoyou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-cangnei_xianxing"></use>
</svg>
<div class="name">舱内_线性</div>
<div class="code-name">#icon-cangnei_xianxing</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-jianhao"></use>
</svg>
<div class="name">减号</div>
<div class="code-name">#icon-jianhao</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-jia"></use>
</svg>
<div class="name"></div>
<div class="code-name">#icon-jia</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-qiehuanzhanghao"></use>
</svg>
<div class="name">切换账号</div>
<div class="code-name">#icon-qiehuanzhanghao</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-huizhong"></use>
</svg>
<div class="name">回中</div>
<div class="code-name">#icon-huizhong</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-sousuo"></use>
</svg>
<div class="name">搜索</div>
<div class="code-name">#icon-sousuo</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-route1-fill"></use>
</svg>
<div class="name">路线</div>
<div class="code-name">#icon-route1-fill</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiashuangjiantou"></use>
</svg>
<div class="name">下双箭头</div>
<div class="code-name">#icon-xiashuangjiantou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-biaoqian"></use>
</svg>
<div class="name">标签</div>
<div class="code-name">#icon-biaoqian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-time"></use>
</svg>
<div class="name">时间</div>
<div class="code-name">#icon-time</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-wancheng"></use>
</svg>
<div class="name">完成</div>
<div class="code-name">#icon-wancheng</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-jiazai"></use>
</svg>
<div class="name">加载</div>
<div class="code-name">#icon-jiazai</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-qiehuan"></use>
</svg>
<div class="name">切换</div>
<div class="code-name">#icon-qiehuan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-qifeidian"></use>
</svg>
<div class="name">起飞点</div>
<div class="code-name">#icon-qifeidian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-satellite-signal-full"></use>
</svg>
<div class="name">信号塔,卫星信号</div>
<div class="code-name">#icon-satellite-signal-full</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@ -0,0 +1,127 @@
@font-face {
font-family: "iconfont"; /* Project id 4970295 */
src: url('iconfont.woff2?t=1752124461773') format('woff2'),
url('iconfont.woff?t=1752124461773') format('woff'),
url('iconfont.ttf?t=1752124461773') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-fanhang:before {
content: "\eb21";
}
.icon-zanting:before {
content: "\e600";
}
.icon-xudianchi:before {
content: "\e60b";
}
.icon-tiedijuli:before {
content: "\e61f";
}
.icon-flight-takeoff-line:before {
content: "\e604";
}
.icon-pianhangjiao-1:before {
content: "\e6f7";
}
.icon-feihangqipianhangjiao:before {
content: "\e753";
}
.icon-feihangqipianhangjiao1:before {
content: "\e975";
}
.icon-shexiangtou:before {
content: "\e6ca";
}
.icon-H:before {
content: "\e609";
}
.icon-shangshengxiajiang:before {
content: "\e635";
}
.icon-uav-fill:before {
content: "\e6a0";
}
.icon-jiantouzuoyou:before {
content: "\e857";
}
.icon-cangnei_xianxing:before {
content: "\e6dc";
}
.icon-jianhao:before {
content: "\e603";
}
.icon-jia:before {
content: "\e711";
}
.icon-qiehuanzhanghao:before {
content: "\e622";
}
.icon-huizhong:before {
content: "\e6b6";
}
.icon-sousuo:before {
content: "\e661";
}
.icon-route1-fill:before {
content: "\e6ab";
}
.icon-xiashuangjiantou:before {
content: "\e617";
}
.icon-biaoqian:before {
content: "\e602";
}
.icon-time:before {
content: "\e686";
}
.icon-wancheng:before {
content: "\e61a";
}
.icon-jiazai:before {
content: "\e699";
}
.icon-qiehuan:before {
content: "\e647";
}
.icon-qifeidian:before {
content: "\e643";
}
.icon-satellite-signal-full:before {
content: "\e959";
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,205 @@
{
"id": "4970295",
"name": "恒享飞",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "38050835",
"name": "返航",
"font_class": "fanhang",
"unicode": "eb21",
"unicode_decimal": 60193
},
{
"icon_id": "2815740",
"name": "暂停",
"font_class": "zanting",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "9030926",
"name": "蓄电池",
"font_class": "xudianchi",
"unicode": "e60b",
"unicode_decimal": 58891
},
{
"icon_id": "31538068",
"name": "贴地距离",
"font_class": "tiedijuli",
"unicode": "e61f",
"unicode_decimal": 58911
},
{
"icon_id": "34156290",
"name": "flight-takeoff-line",
"font_class": "flight-takeoff-line",
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "43335678",
"name": "偏航角-1",
"font_class": "pianhangjiao-1",
"unicode": "e6f7",
"unicode_decimal": 59127
},
{
"icon_id": "43460159",
"name": "飞行器偏航角",
"font_class": "feihangqipianhangjiao",
"unicode": "e753",
"unicode_decimal": 59219
},
{
"icon_id": "44179152",
"name": "飞行器偏航角",
"font_class": "feihangqipianhangjiao1",
"unicode": "e975",
"unicode_decimal": 59765
},
{
"icon_id": "8361828",
"name": "摄像头",
"font_class": "shexiangtou",
"unicode": "e6ca",
"unicode_decimal": 59082
},
{
"icon_id": "9437945",
"name": "H",
"font_class": "H",
"unicode": "e609",
"unicode_decimal": 58889
},
{
"icon_id": "13301993",
"name": "24上升&下降",
"font_class": "shangshengxiajiang",
"unicode": "e635",
"unicode_decimal": 58933
},
{
"icon_id": "25015238",
"name": "无人机-fill",
"font_class": "uav-fill",
"unicode": "e6a0",
"unicode_decimal": 59040
},
{
"icon_id": "34201212",
"name": "箭头左右 ",
"font_class": "jiantouzuoyou",
"unicode": "e857",
"unicode_decimal": 59479
},
{
"icon_id": "43177341",
"name": "舱内_线性",
"font_class": "cangnei_xianxing",
"unicode": "e6dc",
"unicode_decimal": 59100
},
{
"icon_id": "8721205",
"name": "减号",
"font_class": "jianhao",
"unicode": "e603",
"unicode_decimal": 58883
},
{
"icon_id": "9974761",
"name": "加",
"font_class": "jia",
"unicode": "e711",
"unicode_decimal": 59153
},
{
"icon_id": "10936676",
"name": "切换账号",
"font_class": "qiehuanzhanghao",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "43679289",
"name": "回中",
"font_class": "huizhong",
"unicode": "e6b6",
"unicode_decimal": 59062
},
{
"icon_id": "7310585",
"name": "搜索",
"font_class": "sousuo",
"unicode": "e661",
"unicode_decimal": 58977
},
{
"icon_id": "25551841",
"name": "路线",
"font_class": "route1-fill",
"unicode": "e6ab",
"unicode_decimal": 59051
},
{
"icon_id": "3930487",
"name": "下双箭头",
"font_class": "xiashuangjiantou",
"unicode": "e617",
"unicode_decimal": 58903
},
{
"icon_id": "924665",
"name": "标签",
"font_class": "biaoqian",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "6135585",
"name": "时间",
"font_class": "time",
"unicode": "e686",
"unicode_decimal": 59014
},
{
"icon_id": "14405294",
"name": "完成",
"font_class": "wancheng",
"unicode": "e61a",
"unicode_decimal": 58906
},
{
"icon_id": "1239930",
"name": "加载",
"font_class": "jiazai",
"unicode": "e699",
"unicode_decimal": 59033
},
{
"icon_id": "9568050",
"name": "切换",
"font_class": "qiehuan",
"unicode": "e647",
"unicode_decimal": 58951
},
{
"icon_id": "41875291",
"name": "起飞点",
"font_class": "qifeidian",
"unicode": "e643",
"unicode_decimal": 58947
},
{
"icon_id": "18169975",
"name": "信号塔,卫星信号",
"font_class": "satellite-signal-full",
"unicode": "e959",
"unicode_decimal": 59737
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
src/assets/icons/size1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

BIN
src/assets/icons/size2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

BIN
src/assets/icons/state1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

BIN
src/assets/icons/state2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

1
src/assets/main.css Normal file
View File

@ -0,0 +1 @@

View File

@ -0,0 +1,80 @@
//导入tailwind的基础指令组件
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
margin: 0 auto;
padding: 0;
width: 100%;
height: 100%;
}
body {
margin: 0 auto;
padding: 0;
width: 100%;
height: 100%;
}
#airapp {
margin: 0 auto;
padding: 0;
width: 100%;
height: 100%;
}
//自定义样式
//自定义的输入框(圆角 深蓝色底)
.blueInput {
background-color: rgba(36, 90, 151, 0.5);
border-radius: 50px;
height: 32px;
border-color: rgba(0, 111, 255, 0.5);
input {
background-color: rgba(0, 0, 0, 0);
color: rgba(255, 255, 255, 0.6);
}
.ant-input {
&::placeholder {
color: rgba(255, 255, 255, 0.6);
}
}
}
//自定义按钮渐变(上浅蓝 下深蓝)
.blueBtn {
background: linear-gradient(0deg, #0143f4, #0195fa);
border: none;
}
//自定义菜单颜色(深色底,选中菜单是渐变蓝)
.blueMenus {
background: rgba(23, 29, 47, 0.88);
color: #fff !important;
.ant-menu-item {
color: #fff !important;
}
.ant-menu-item:hover {
background: linear-gradient(0deg, #0143f4, #0195fa);
color: #fff !important;
}
}
.blueToolTip {
.ant-tooltip-inner {
padding-left: 0 !important;
padding-right: 0 !important;
}
}
//滚动条
div::-webkit-scrollbar {
width: 8px; /*高宽分别对应横竖滚动条的尺寸*/
height: 100%;
}
div::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: rgba(255, 255, 255, 0.2);
cursor: pointer;
}
div::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
border-radius: 0;
background: rgba(0, 0, 0, 0.1);
}

View File

View File

@ -0,0 +1,293 @@
<script setup>
// import {
// ElRow,
// ElCol,
// ElForm,
// ElFormItem,
// ElInput,
// ElSelect,
// ElOption
// } from 'element-plus'
import { reactive, ref, defineEmits, defineProps, defineExpose, watch } from 'vue'
defineProps(['formObj'])
/*
//
const formObj = ref({
// labelWidth: 100,//,
col:6,
// options
selectData:{
},
rules:{ //form
// projectName: [
// { required: true, message: '', trigger: 'change' }
// ]
},
formArr:[
{
type:'nullComp',
title:'',
key:'test1',
maxlength: 50,
col:4,
clearable:true,
autocomplete:'on'
},
{
type:'inputComp',
title:'模型名称',
key:'suName3',
maxlength: 50,
clearable:true,
autocomplete:'on'
},
{
type:'inputComp',
title:'模型名称',
key:'suName4',
maxlength: 50,
clearable:true,
autocomplete:'on'
}
]
})
*/
const formData = ref({})
const formRef = reactive({})
const getRef = (el) => {
if ((el && el.$attrs[`refName`] >= 0) || (el && el.$attrs[`refName`])) {
formRef[el.$attrs[`refName`]] = el
}
}
//
const dateRangeShortcuts = [
{
text: 'Last week',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
},
},
{
text: 'Last month',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
},
},
{
text: 'Last 3 months',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
},
},
]
//emit
const emit = defineEmits(['formChange', 'formBlur', 'formBtnEvent'])
const formChange = (e, item) => {
// console.log(JSON.parse(JSON.stringify(item)))
emit('formChange', e, item)
}
const formBlur = (e, item) => {
emit('formBlur', e, item)
}
const formBtnEvent = (e, item) => {
emit('formBtnEvent', e, item)
}
//
const getFormData = () => {
let params = {}
let len = Object.keys(formData.value).length
if (len > 0) {
for (let item in formData.value) {
if (formData.value[item] !== undefined) {
params[item] = formData.value[item]
} else {
}
}
if (Object.keys(params).length > 0) {
return params
} else {
return null
}
} else {
return null
}
}
//form
const clearFormData = () => {
let len = Object.keys(formData.value).length
if (len > 0) {
for (let item in formData.value) {
formData.value[item] = undefined
}
}
}
//
defineExpose({
formData,
formRef,
getFormData,
clearFormData,
})
</script>
<template>
<div>
<ElForm
:ref="getRef"
:refName="'form_' + formObj.id"
:model="formData"
:label-width="formObj.labelWidth ? formObj.labelWidth + 'px' : null"
:rules="formObj.rules ? formObj.rules : null"
:validate-on-rule-change="false"
>
<ElRow>
<ElCol
v-for="item in formObj.formArr"
:key="item.key"
:span="
24 / (item.col && item.col != 0 && item.col != 1 ? formObj.col / item.col : formObj.col)
"
>
<ElFormItem :label="item.title" :prop="item.key">
<!-- null只是占位置的 -->
<div class="nullComp" v-if="item.type == 'nullComp'" style="width: 100%"></div>
<!-- 输入框 -->
<div style="display: flex; width: 100%">
<ElInput
style="flex: 1"
v-if="item.type == 'inputComp'"
v-model="formData[item.key]"
:minlength="item.minlength"
:maxlength="item.maxlength"
:clearable="item.clearable ? item.clearable : false"
:disabled="item.disabled ? item.disabled : false"
:autocomplete="item.autocomplete ? item.autocomplete : 'off'"
:type="item.inputType ? item.inputType : 'text'"
@input="formChange($event, item)"
@change="formBlur($event, item)"
:placeholder="item.placeholder ? item.placeholder : '请输入内容'"
/>
<!-- 下拉select -->
<ElSelect
v-if="item.type == 'selectComp'"
v-model="formData[item.key]"
:placeholder="item.placeholder ? item.placeholder : '请选择'"
:clearable="item.clearable != null ? item.clearable : true"
:popper-append-to-body="
item.appendBody || item.appendBody == undefined ? true : false
"
style="flex: 1"
@change="formChange($event, item)"
@blur="formBlur($event, item)"
>
<ElOption
v-for="it in formObj.selectData[item.key]"
:key="it[item.custValue] ? it[item.custValue] : it.value"
:label="it[item.custText] ? it[item.custText] : it.text"
:value="it[item.custValue] ? it[item.custValue] : it.value"
/>
</ElSelect>
<!-- 日期范围选择器 -->
<el-date-picker
v-if="item.type == 'dateRangeComp'"
v-model="formData[item.key]"
type="daterange"
unlink-panels
range-separator="To"
start-placeholder="Start date"
end-placeholder="End date"
:shortcuts="dateRangeShortcuts"
/>
<!-- 后面的单位 -->
<span v-if="item.unit != null" :style="{ color: item.unitColor || '#cfd3dc' }">{{
item.unit
}}</span>
</div>
<!-- <ElInput
v-if="item.type=='inputComp'"
v-model="formData[item.key]"
:minlength="item.minlength"
:maxlength="item.maxlength"
:clearable="item.clearable?item.clearable:false"
:disabled="item.disabled?item.disabled:false"
:autocomplete="item.autocomplete?item.autocomplete:'off'"
:type="item.inputType?item.inputType:'text'"
@input="formChange($event,item)"
@change="formBlur($event,item)"
:placeholder="item.placeholder?item.placeholder:'请输入内容'"
/> -->
<!-- 下拉select -->
<!-- <ElSelect
v-if="item.type=='selectComp'"
v-model="formData[item.key]"
:placeholder="item.placeholder?item.placeholder:'请选择'"
:clearable="item.clearable!=null?item.clearable:true"
:popper-append-to-body="item.appendBody||item.appendBody==undefined?true:false"
style="width: 100%"
@change="formChange($event,item)"
@blur="formBlur($event,item)"
>
<ElOption
v-for="(it) in formObj.selectData[item.key]"
:key="it[item.custValue]?it[item.custValue]:it.value"
:label="it[item.custText]?it[item.custText]:it.text"
:value="it[item.custValue]?it[item.custValue]:it.value"
/>
</ElSelect> -->
</ElFormItem>
</ElCol>
<div
v-if="formObj.queryBtnArr && formObj.queryBtnArr.length > 0"
:class="$style.btnFroup"
style="display: flex"
>
<el-button
:class="$style.queryBtn"
v-for="(item, index) in formObj.queryBtnArr"
:type="item.type"
:key="item.key"
@click="formBtnEvent($event, item)"
>{{ item.title }}</el-button
>
</div>
</ElRow>
</ElForm>
</div>
</template>
<style lang="less" module>
.btnFroup {
margin-left: 10px;
// margin-top: -1px;
}
.queryBtn {
}
</style>

View File

@ -0,0 +1,92 @@
<script setup>
import { ref, reactive,defineProps, onMounted,defineEmits,computed } from 'vue'
import VModal from '@/components/custom_popup/vuxpopup.vue'
import LivePlayer from '@/components/VideoPlayer/index.vue'
const props = defineProps({
videoData:{
type:Object,
default:{}
}
})
const emits = defineEmits(['popupBtn'])
//
const videoPopObj=reactive({
title: "", //
padding:'0 0 0 0',
wPercent:'60',
hPercent:"75",
minWidth:500,//
minHeight:200,//
hideFooter: true, //
resize:true
// commitBtnTxt: "",
// cancelBtnTxt: "",
})
//
const videoPopupBtn=(type)=>{
emits('popupBtn')
}
const videoPopupResize=()=>{
}
const videoPopupZoom=()=>{
}
//
const getVideoOptions = computed(() => {
return () => {
const videoData = props.videoData
console.log(videoData);
// const key = data.checkedUrl[index]
let url = videoData.fileUrl
//
// if (['external_monitor_url', 'internal_monitor_url'].includes(key)) {
if (!url.includes('flv') && !url.includes('webrtc') && !url.includes('rtmp') && !url.includes('mp4')) {
url = '' // videoUrl
}
return {
videoUrl: url,
videoTitle: null,
live: false,//
aspect: 'fullscreen'
}
}
})
const handleError = (status, index) => {
data.videoStatus[index] = status
}
onMounted(()=>{
videoPopObj.title = props.videoData.fileName;
})
</script>
<template>
<VModal class="VideoPlayModal" :popObj="videoPopObj" @popupResize="videoPopupResize" @popupZoom="videoPopupZoom" @popupBtn="videoPopupBtn">
<template v-slot:slot>
<LivePlayer :options="getVideoOptions(index)" @status="handleError($event,index)" />
</template>
</VModal>
</template>
<style lang="less" scoped>
.videoContainer{
height: calc(100% - 10px);
}
.VideoPlayModal{
:deep(.vxe-modal--box){
border:1px solid #000;
}
:deep(.vxe-modal--content){
position: relative;
height: calc(100% - 0.1px);
overflow: hidden!important;
background: #000;
}
}
</style>

View File

@ -0,0 +1,266 @@
<script setup>
/********************************
let popObj = ref({
title: "", //
width: "440",
height: "180",
wPercent:'50',//
hPercent:'70',//
minWidth:'100',//
minHeight:'200',//
padding:'2px 3px',//padding
resize:true/false,//
hideFooter: true/false, //
hideClose:true/false,//
mask:true/false,//mask
commitBtnTxt: "确定",//
cancelBtnTxt: "取消",//
//
btnArr:[
{
key: "next",
title: "下载",
type: "primary",
disabled: false,
},
],
btnPos:'center'//center
});
//
const popupBtn = (type) => {
console.log(type)
}
*/
import { ref, defineAsyncComponent, defineProps, onMounted, watch } from 'vue'
import { debounce, throttle } from 'lodash'
const props = defineProps({
popObj: Object,
})
let dialogVisible = ref(true)
let commitBtnTxt = ref('确定')
let cancelBtnTxt = ref('取消')
let contentpadding = ref('10px 16px')
let whitespace = ref('pre-line')
const emit = defineEmits(['popupBtn', 'popupResize', 'popupZoom'])
// throttle(popupClick,1000)
// const popupClick=(event,type)=>{
// debounce(()=>{
// },500)
// console.log('')
// emit('popupBtn',type)
// }
onMounted(() => {
if (props.popObj.commitBtnTxt) {
commitBtnTxt.value = props.popObj.commitBtnTxt
}
if (props.popObj.cancelBtnTxt) {
cancelBtnTxt.value = props.popObj.cancelBtnTxt
}
if (props.popObj.padding) {
contentpadding.value = props.popObj.padding
// console.log(contentpadding.value);
}
})
//1
const popupClick = throttle((event, type) => {
// console.log("");
emit('popupBtn', type)
}, 1000)
const popupResize = (event, type) => {
emit('popupResize', type)
}
const popupZoom = (event, type) => {
emit('popupZoom', type)
}
</script>
<template>
<div class="norem-popupBox" v-if="dialogVisible">
<vxe-modal
:width="popObj.wPercent ? popObj.wPercent + '%' : popObj.width + 'px'"
:height="popObj.hPercent ? popObj.hPercent + '%' : popObj.height + 'px'"
:min-width="popObj.minWidth"
:min-height="popObj.minHeight"
v-model="dialogVisible"
:resize="popObj.resize != null ? popObj.resize : true"
:title="popObj.title ? popObj.title : ''"
:destroy-on-close="true"
:show-close="!popObj.hideClose || false"
:show-zoom="popObj.resize != null ? popObj.resize : true"
:dblclickZoom="popObj.resize != null ? popObj.resize : true"
:mask="popObj.mask != null ? popObj.mask : true"
@zoom="popupZoom($event, 'zoom')"
@close="popupClick($event, 'close')"
@resize="popupResize($event, 'resize')"
>
<slot name="slot"></slot>
<div v-if="!popObj.hideFooter" slot="footer" class="norem-dialog-footer">
<!-- 自定义的按钮组 -->
<div
v-if="popObj.btnArr && popObj.btnArr.length > 0"
:style="{
textAlign: popObj.btnPos && popObj.btnPos == 'center' ? 'center' : 'none',
float: popObj.btnPos && popObj.btnPos == 'center' ? 'none' : 'right',
marginRight: popObj.btnPos && popObj.btnPos == 'center' ? '0' : '0',
}"
>
<!-- <el-button
round
v-for="item in popObj.btnArr"
:key="item.key"
:type="item.type ? item.type : ''"
:disabled="item.disabled != null ? item.disabled : false"
@click="popupClick($event, item.key)"
>{{ item.title }}</el-button
> -->
<a-button
v-for="item in popObj.btnArr"
:key="item.key"
:type="item.type ? item.type : ''"
:disabled="item.disabled != null ? item.disabled : false"
@click="popupClick($event, item.key)"
>{{ item.title }}</a-button
>
</div>
<!-- 默认的按钮组 -->
<!-- style="float: right; margin-right: 18px" -->
<div
v-else
:style="{
textAlign: popObj.btnPos && popObj.btnPos == 'center' ? 'center' : 'none',
float: popObj.btnPos && popObj.btnPos == 'center' ? 'none' : 'right',
marginRight: popObj.btnPos && popObj.btnPos == 'center' ? '0' : '0',
}"
>
<a-button @click="popupClick($event, 'cancle')" round>{{ cancelBtnTxt }}</a-button>
<a-button type="primary" @click="popupClick($event, 'submit')" round>{{
commitBtnTxt
}}</a-button>
</div>
</div>
</vxe-modal>
</div>
</template>
<style>
.vxe-modal--box {
border-radius: 2px;
}
.vxe-modal--content {
white-space: normal !important;
}
</style>
<style lang="less" scoped>
.norem-popupBox {
//
--contentpadding: v-bind(contentpadding);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
z-index: 1000;
:deep(.vxe-modal--header-title) {
color: var(--heading-color) !important;
font-weight: 500 !important;
font-size: 16px !important;
line-height: 22px !important;
word-wrap: break-word !important;
padding: 0;
}
:deep(.vxe-modal--header) {
padding: 14px 24px;
background: #fff;
}
:deep(.vxe-modal--header-right) {
padding-right: 0;
}
// :deep(.vxe-icon-square) {
// font-size: 1.3em;
// }
:deep(.vxe-modal--body-default) {
padding: var(--contentpadding);
}
.el-dialog__wrapper {
position: absolute;
}
.dialogMask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000000;
opacity: 0.5;
z-index: 1000;
}
.el-dialog {
margin-top: 0 !important;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto !important;
width: 100%;
height: 100%;
overflow: hidden;
}
//
.vxe-modal--header {
background: #fff;
}
//
.vxe-modal--content {
height: calc(100% - 52px);
overflow: auto;
background: #fff;
position: relative;
}
.norem-dialog-footer {
width: 100%;
position: absolute;
bottom: 0px;
right: 0px;
padding: 10px 16px;
border-top: 1px solid var(--modal-footer-border-color-split);
background: #fff;
}
:deep(.el-input) {
height: 32px;
}
:deep(.el-input__inner) {
height: 32px;
}
:deep(.el-button) {
// width: 65px;
height: 30px;
font-weight: 400;
font-size: 14px;
// color: #ffffff;
// line-height: 20px;
// text-align: left;
font-style: normal;
}
:deep(button + button) {
margin-left: 8px;
}
}
</style>

View File

@ -0,0 +1,29 @@
<template>
<div>
<div class="loader"></div>
</div>
</template>
<style lang="less" scoped>
.loader {
--d: 22px;
width: 4px;
height: 4px;
border-radius: 50%;
color: #ffffff;
box-shadow:
calc(1 * var(--d)) calc(0 * var(--d)) 0 0,
calc(0.707 * var(--d)) calc(0.707 * var(--d)) 0 1px,
calc(0 * var(--d)) calc(1 * var(--d)) 0 2px,
calc(-0.707 * var(--d)) calc(0.707 * var(--d)) 0 3px,
calc(-1 * var(--d)) calc(0 * var(--d)) 0 4px,
calc(-0.707 * var(--d)) calc(-0.707 * var(--d)) 0 5px,
calc(0 * var(--d)) calc(-1 * var(--d)) 0 6px;
animation: l27 1s infinite steps(8);
}
@keyframes l27 {
100% {
transform: rotate(1turn);
}
}
</style>

View File

@ -0,0 +1,351 @@
<template>
<div ref="liveplayer" class="liveplayer">
<LivePlayer
v-if="isAlive"
ref="videoRef"
v-bind="getProperty"
@play="handlePlay"
@fullscreen="handleScreen"
@error="handleError"
@customButtons="handleCustom"
/>
</div>
</template>
<script>
// import videojs from 'video.js'
import LivePlayer from '@liveqing/liveplayer-v3'
import {
defineComponent,
ref,
reactive,
toRefs,
computed,
watch,
nextTick,
onMounted,
defineExpose,
} from 'vue'
const DEFAULT_OPTIONS = {
/* 视频地址 */
videoUrl: null,
/* 视频标题 */
videoTitle: '视频',
/* 封面 */
poster: null,
/* 没有地址或异常时显示 */
alt: `<div class="loading">
<span style="--s:1">L</span>
<span style="--s:2">O</span>
<span style="--s:3">A</span>
<span style="--s:4">D</span>
<span style="--s:5">I</span>
<span style="--s:6">N</span>
<span style="--s:7">G</span>
<span style="--s:8">...</span>
</div>`,
/* 自动播放 */
autoplay: true,
/* 显示控制栏 */
controls: true,
/* 是否直播 */
live: false,
/* 是否静音 */
muted: true,
/* 视频显示区域宽高比,全屏: fullscreen全屏时需设置宽高 */
aspect: '16:9',
/* 流畅模式 */
fluent: false,
/* 是否拉伸 */
stretch: false,
/* m3u8加载超时 */
timeout: 20,
/* 加载 */
loading: true,
/* 是否显示工具栏按钮 */
showCustomButton: true,
/* 是否隐藏屏幕中间的播放按钮 */
hideBigPlayButton: false,
/* 是否隐藏快照按钮 */
hideSnapshotButton: true,
/* 是否隐藏全屏按钮 */
hideFullscreenButton: false,
/* hls流播放清晰度yh: 原始分辨率fhd: 超清hd: 高清sd: 标清 */
resolution: 'yh',
/* hls播放默认分辨率 */
resolutiondefault: 'yh',
cors: 'fasle',
/* hls播放倍速列表 [0.5, 1, 2, 3] */
playbackRates: null,
/* hls播放默认倍速 */
playbackRate: 1,
/* 自定义按钮(按钮名称:class) */
customButtons: 'refresh:vjs-icon-replay',
/* 双击全屏 */
dblclickFullscreen: false,
}
export default defineComponent({
name: 'LivePlayerDemo',
components: { LivePlayer },
props: {
options: {
type: Object,
default: () => {},
},
play: {
type: Function,
default: null,
},
},
emits: ['play', 'pause', 'ended', 'time-update', 'screen', 'error', 'status', 'doubleClick'],
setup(props, { emit }) {
const videoRef = ref(null)
const liveplayer = ref(null)
const data = reactive({
isAlive: true,
status: 'init',
})
const getProperty = computed(() => {
return {
...DEFAULT_OPTIONS,
...props.options,
}
})
/**
* @description: 开始播放
* @param {Number} time
* @return {Number}
*/
const handlePlay = (time) => {
data.status = 'play'
emit('play', time)
}
/**
* @description: 暂停播放
* @param {Number} time
* @return {Number}
*/
const handlePause = (time) => {
data.status = 'pause'
emit('pause', time)
}
/**
* @description: 播放结束
* @param {Number} time
* @return {Number}
*/
const handleEnded = (time) => {
data.status = 'ended'
emit('ended', time)
}
/**
* @description: 获取视频播放时间
* @param {Number} time
* @return {Number}
*/
const handleUpdate = (time) => {
emit('time-update', time)
}
/**
* @description: 全屏状态变更
* @return {*}
*/
const handleScreen = (val) => {
emit('screen', val)
console.log('parentInstance', liveplayer)
//
const showBigScreen = window.screen.width > 4000
if (val) {
// data.fullscreen = !data.fullscreen
if (showBigScreen) {
document
.querySelector('.vjs-control-bar')
.setAttribute('class', 'vjs-control-bar bigScreen')
}
liveplayer.value.querySelector('.vjs-fullscreen-control').setAttribute('title', '退出全屏')
} else {
liveplayer.value.querySelector('.vjs-fullscreen-control').setAttribute('title', '全屏')
if (showBigScreen) {
document.querySelector('.vjs-control-bar').setAttribute('class', 'vjs-control-bar')
}
}
}
/**
* @description: 全屏
* @return {*}
*/
const handleFullscreen = () => {
videoRef.value.requestFullscreen()
}
const handleError = (err) => {
data.status = 'error'
emit('error', err)
}
watch(
() => data.status,
(val) => {
emit('status', val)
},
)
/**
* @description: 获取视频时长
* @return {Number}
*/
const getDuration = () => {
return videoRef.value.getDuration()
}
/**
* @description: 设置视频时长
* @param {Number} time
*/
const setCurrentTime = (time) => {
nextTick(() => {
videoRef.value?.setCurrentTime(time)
})
}
/**
* @description: 自定义指令
* @param {*} type
* @return {*}
*/
const handleCustom = (type) => {
switch (type) {
case 'refresh':
handleRefresh()
break
default:
break
}
}
/**
* @description: 刷新播放器
* @return {*}
*/
const handleRefresh = () => {
const { live = false } = props.options
const currentTime = videoRef.value?.getCurrentTime() || 0
data.isAlive = false
data.status = 'init'
nextTick(() => {
data.isAlive = true
if (!live) {
setCurrentTime(currentTime)
}
})
}
// /**
// * @description:
// * @param {MouseEvent} event
// */
// const handleDoubleClick = (event) => {
// const videoElement = liveplayer.value
// if (!videoElement) {
// console.log('')
// return
// }
// const rect = videoElement.getBoundingClientRect()
// const xRatio = (event.clientX - rect.left) / rect.width
// const yRatio = (event.clientY - rect.top) / rect.height
// const position = {
// x: Number(xRatio.toFixed(1)), // x1
// y: Number(yRatio.toFixed(1)), // y1
// absX: event.clientX - rect.left, //
// absY: event.clientY - rect.top, //
// width: rect.width,
// height: rect.height
// }
// emit('doubleClick', position)
// }
onMounted(() => {
// console.log(videoRef)
})
defineExpose({
handleCustom,
})
return {
...toRefs(data),
videoRef,
liveplayer,
getProperty,
handlePlay,
handlePause,
handleEnded,
handleUpdate,
handleScreen,
handleFullscreen,
handleError,
getDuration,
setCurrentTime,
handleCustom,
// handleDoubleClick
}
},
})
</script>
<style scoped lang="scss">
.liveplayer {
width: 100%;
height: auto;
}
::v-deep(.player-wrapper) {
.alt {
.loading {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 16px;
white-space: nowrap;
}
.loading span {
font-size: 1.5em;
font-family: Arial, Helvetica, sans-serif;
font-weight: bold;
animation: light 1.6s linear infinite calc(0.2s * var(--s));
}
}
}
@keyframes light {
50% {
filter: hue-rotate(360deg) blur(1px);
color: white;
text-shadow:
0 0 10px #3498db,
0 0 20px #3498db,
0 0 40px #3498db,
0 0 80px #3498db,
0 0 160px #3498db,
0 0 320px #3498db;
}
}
::v-deep(.vjs-icon-replay) {
width: 22px;
height: 22px;
font-size: 24px;
&::before {
font-size: 20px;
}
}
::v-deep(.vjs-control-bar.bigScreen) {
zoom: 4;
}
</style>

View File

@ -0,0 +1,484 @@
<template>
<div
:style="{
height: `calc(100% - ${tableObj.offsetHeight ? tableObj.offsetHeight : 0}px)`,
overflow: 'hidden',
}"
>
<vxe-table
v-if="tableObj && tableObj.col.length > 0"
border
show-overflow
keep-source
:ref="getRef"
:refName="'table_' + tableObj.id"
resizable
auto-resize
highlight-current-row
highlight-hover-row
:loading="tableObj.loading ? tableObj.loading : false"
:height="tableObj.height ? tableObj.height : '100%'"
class="mytable-style"
:row-class-name="tableObj.rowClassName"
:data="tableObj.tableData"
:edit-config="tableObj.editConfig ? tableObj.editConfig : { trigger: 'click', mode: 'cell' }"
:edit-rules="tableObj.validRules"
:checkbox-config="tableObj.checkboxConfig"
@checkbox-all="selectAll"
@checkbox-change="selectChange"
@cell-click="cellClickEvent"
@cell-dblclick="cellDBLClickEvent"
:filter-config="{ remote: true }"
@filter-change="filterMethod"
@radio-change="radioChangeEvent"
>
<template v-for="items in tableObj.col">
<vxe-table-colgroup
:align="items.align ? items.align : 'center'"
v-if="items.type == 'colgroup' && (!items.display ? items.display : true)"
:title="items.title"
:field="items.key"
:key="items.key"
:min-width="items.minWidth"
:gtype="items.gtype ? items.gtype : 'column'"
>
<!-- type=group :type="items.type?items.type:'column'"-->
<template v-if="items.gtype == 'colgroup'">
<vxe-table-colgroup
v-for="itemg in items.column"
:title="itemg.title"
:field="itemg.key"
:key="itemg.key"
:align="items.align ? items.align : 'center'"
>
<vxe-table-column
v-for="item1 in itemg.column"
:key="item1.key"
:align="item1.align ? item1.align : 'center'"
:type="item1.type ? item1.type : ''"
:width="item1.width ? item1.width : 'auto'"
:min-width="item1.minWidth"
:field="item1.key"
:title="item1.title"
:sortable="item1.sortable ? item1.sortable : false"
:formatter="item1.formatter"
:cell-render="item1.cellRender ? item1.cellRender : null"
:edit-render="item1.editRender ? item1.editRender : null"
:visible="!item1.display ? item1.display : true"
:tree-node="item.tree ? true : false"
:sort-by="item.sortBy"
>
</vxe-table-column>
</vxe-table-colgroup>
</template>
<template v-if="items.gtype == 'column'">
<vxe-table-column
v-for="item in items.column"
:key="item.key"
:align="item.align ? item.align : 'center'"
:type="item.type ? item.type : ''"
:width="item.width ? item.width : 'auto'"
:min-width="item.minWidth"
:field="item.key"
:title="item.title"
:sortable="item.sortable ? item.sortable : false"
:formatter="item.formatter"
:cell-render="item.cellRender ? item.cellRender : null"
:edit-render="item.editRender ? item.editRender : null"
:visible="!item.display ? item.display : true"
:tree-node="item.tree ? true : false"
:sort-by="item.sortBy"
>
<!-- 表头过滤 -->
<!-- <template v-slot:filter="{ $panel, column }">
<div v-if="item.category == 'select'">
<select class="my-select" v-model="option.data" v-for="(option, index01) in column.filters" :key="index01" @change="$panel.changeOption($event, !!option.data, option)">
<option v-for="(label, cIndex) in item.complaintList" :key="cIndex" :value="label.value">
{{
label.label
}}
</option>
</select>
</div>
<div v-if="item.category == 'input'">
<input type="type" v-for="(option, index02) in column.filters" :key="index02" v-model="option.data" @input="$panel.changeOption($event, !!option.data, option)" />
</div>
</template> -->
<!-- 预留插槽 -->
<template v-if="item.slot" v-slot="{ row }">
<slot name="custColumn" :data="{ row }" :dataitem="item"></slot>
</template>
</vxe-table-column>
</template>
</vxe-table-colgroup>
<template v-if="items.type == 'column'">
<vxe-table-column
v-for="item in items.column"
:key="item.key"
:align="item.align ? item.align : 'center'"
:type="item.type ? item.type : ''"
:width="item.width ? item.width : 'auto'"
:min-width="item.minWidth"
:field="item.key"
:title="item.title"
:sortable="item.sortable ? item.sortable : false"
:formatter="item.formatter"
:cell-render="item.cellRender ? item.cellRender : null"
:edit-render="item.editRender ? item.editRender : null"
:visible="item.display"
:fixed="item.fixed ? item.fixed : null"
:filters="item.category ? [{ data: '' }] : null"
:tree-node="item.tree ? true : false"
:sort-by="item.sortBy"
>
<template v-slot:filter="{ $panel, column }">
<div v-if="item.category == 'select'">
<select
class="my-select"
v-model="option.data"
v-for="(option, index1) in column.filters"
:key="index1"
@change="$panel.changeOption($event, !!option.data, option)"
>
<option
v-for="(label, cIndex) in item.complaintList"
:key="cIndex"
:value="label.value"
>
{{ label.label }}
</option>
</select>
</div>
<div v-if="item.category == 'input'">
<input
type="type"
v-for="(option, index2) in column.filters"
:key="index2"
v-model="option.data"
@input="$panel.changeOption($event, !!option.data, option)"
/>
</div>
</template>
<!-- 过滤器 -->
<template v-if="item.filterMethod" v-slot="{ row }">
<span v-text="item.filterMethod(row[item.key])"></span>
</template>
<!-- 预留插槽 -->
<template v-else-if="item.slot" v-slot="{ row }">
<slot
:name="item.slotName ? item.slotName : 'custColumn'"
:data="{ row }"
:dataitem="item"
></slot>
</template>
<template v-else-if="item.slotclick" v-slot="{ row }">
<span
:class="{ cellclick: row[item.key] !== 0 }"
@click="CellClick(row[item.key], item.title, row)"
>{{ row[item.key] }}</span
>
</template>
<!-- 操作 待添加-->
<template v-else-if="item.operation" v-slot="{ row, rowIndex }">
<template v-for="(it, index) in item.child">
<ElButton
v-if="it.type == 'text'"
:key="it.key"
link
:disabled="it.disabled ? it.disabled : false"
:style="{
color: it.color ? it.color : 'default',
display: it.hide && it.hide == true ? 'none' : 'inline-block',
}"
@click="operationFn($event, row, it.key, index, rowIndex)"
>{{ it.title }}</ElButton
>
<!-- 删除专用 -->
<ElPopconfirm
v-if="it.type == 'popconfirm'"
:key="it.key"
width="220"
:placement="it.placement ? it.placement : 'top'"
confirm-button-text="OK"
cancel-button-text="No, Thanks"
:icon="InfoFilled"
icon-color="#626AEF"
:title="it.popTitle ? it.popTitle : '确定删除吗?'"
@confirm="operationFn($event, row, it.key, index, rowIndex)"
>
<template #reference>
<ElButton
link
text
:disabled="it.disabled ? it.disabled : false"
:style="{
color: it.color ? it.color : 'default',
display: it.hide && it.hide == true ? 'none' : 'inline-block',
}"
>{{ it.title }}</ElButton
>
</template>
</ElPopconfirm>
</template>
</template>
<!-- 表头添加图标 待添加-->
</vxe-table-column>
</template>
</template>
</vxe-table>
</div>
</template>
<script lang="jsx" setup>
import { ref, reactive, defineProps, onMounted, nextTick } from 'vue'
import { ElButton, ElPopconfirm, ElPopover } from 'element-plus'
import { InfoFilled } from '@element-plus/icons-vue'
import { VXETable } from 'vxe-table'
defineProps(['tableObj'])
const tableRef = reactive({})
const getRef = (el) => {
if ((el && el.$attrs[`refName`] >= 0) || (el && el.$attrs[`refName`])) {
tableRef[el.$attrs[`refName`]] = el
}
}
// console.log(tableRef)
defineExpose({
//
tableRef,
})
/*
const tableObj = ref({
loading:false,
showFooter:true,
notArrowDrag:true,
// offsetHeight:60,
footerMethod:null,//null,tablemap ,top
col:[
{
type:'column',
column:[
// {
// type:'checkbox',
// width:60,
// align:'center'
// },
{
type:'seq',
title:'序号',
width:60,
align:'center'
},
{
title:'回收建材类型(单位)',
key:'recoveryType',
minWidth:100,
align:'center',
editRender:{
name: '$input', //
props: {
type: 'number',//
// readonly: true,
placeholder: '请选择',
},
events:{
focus:(row)=>{
},
blur:(row)=>{
}
}
}
},
{
title:'回收建材类型(单位)',
key:'recoveryType2',
minWidth:100,
align:'center',
editRender:{
name: '$input', //
props: {
type: 'text',//
// readonly: true,
placeholder: '请选择',
},
events:{
focus:(row)=>{
},
blur:(row)=>{
}
}
}
},
{
title:'操作',
key:'operation',
width:150,
align:'center',
fixed:'right',
operation:true,
child:[
// {
// title:'',
// key:'association',
// type:'text'
// },
{
title:'删除',
color:'#ff0000',
key:'delete',
type:'popconfirm',
popTitle:'确定删除该条数据吗?',
placement:'top'
}
]
}
]
}
],
tableData:null
})
*/
// const validEvent = async () => {
// const $table = tableRef.value
// if ($table) {
// const errMap = await $table.validate()
// if (errMap) {
// VXETable.modal.message({ status: 'error', message: '' })
// } else {
// VXETable.modal.message({ status: 'success', message: '' })
// }
// }
// }
// const changeCellEvent = (params) => {
// const $table = tableRef.value
// if ($table) {
// $table.updateStatus(params)
// }
// }
// const removeSelectEvent = () => {
// const $table = tableRef.value
// if ($table) {
// $table.removeCheckboxRow()
// }
// }
// const insertEvent = async () => {
// const $table = tableRef.value
// if ($table) {
// const { row: newRow } = await $table.insert({})
// //
// const errMap = await $table.validate(newRow)
// if (errMap) {
// //
// }
// }
// }
// const getSelectEvent = () => {
// const $table = tableRef.value
// if ($table) {
// const selectRecords = $table.getCheckboxRecords()
// VXETable.modal.alert(selectRecords.length)
// }
// }
// const getInsertEvent = () => {
// const $table = tableRef.value
// if ($table) {
// const insertRecords = $table.getInsertRecords()
// VXETable.modal.alert(insertRecords.length)
// }
// }
// const getRemoveEvent = () => {
// const $table = tableRef.value
// if ($table) {
// const removeRecords = $table.getRemoveRecords()
// VXETable.modal.alert(removeRecords.length)
// }
// }
// const getUpdateEvent = () => {
// const $table = tableRef.value
// if ($table) {
// const updateRecords = $table.getUpdateRecords()
// VXETable.modal.alert(updateRecords.length)
// }
// }
/////////////////////////////////////////////////////
const emit = defineEmits([
'tableReady',
'operationFn',
'selectAll',
'selectChange',
'cellClickEvent',
'cellDBLClickEvent',
'filterMethod',
'radioChangeEvent',
])
onMounted(() => {
nextTick(() => {
emit('tableReady')
})
})
//table
const operationFn = (event, row, key, index, rowIndex) => {
emit('operationFn', event, row, key, index, rowIndex)
}
//
const selectAll = (val) => {
emit('selectAll', val)
}
//
const selectChange = (val) => {
emit('selectChange', val)
}
//
const cellClickEvent = (val) => {
emit('cellClickEvent', val)
}
//
const cellDBLClickEvent = (val) => {
emit('cellDBLClickEvent', val)
}
//
const filterMethod = (option, row) => {
emit('filterMethod', option, row)
}
//
const radioChangeEvent = (val) => {
emit('radioChangeEvent', val)
}
</script>
<style lang="less" scoped>
.vxe-table {
height: 100%;
:deep(.vxe-table--header) {
width: 100% !important;
}
:deep(.vxe-table--body) {
width: 100% !important;
}
:deep(.vxe-cell) {
width: 100% !important;
}
}
</style>

184
src/main.js Normal file
View File

@ -0,0 +1,184 @@
//iconfont
import '@/assets/iconfont/iconfont.css'
import './assets/style/main.less'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// import 'video.js/dist/video-js.css' // css 一定要引入
//引入vxe-table
import VxeUIAll from 'vxe-pc-ui'
import 'vxe-pc-ui/lib/style.css'
import VxeUITable from 'vxe-table'
import 'vxe-table/lib/style.css'
import { useSettingStore } from '@/stores/setting.js'
import { qiankunWindow, renderWithQiankun } from 'vite-plugin-qiankun/dist/helper'
// 创建实例
let app
const setupAll = async (props) => {
const { container } = props
app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(VxeUIAll)
app.use(VxeUITable)
app.mount(container instanceof Element ? container.querySelector('#airapp') : container)
app.config.warnHandler = () => null
}
//qiankun微前端
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
//测试时候用的代码
const { VITE_APP_AUTHORITY, VITE_APP_CLIENT_ID } = import.meta.env
console.log(process.env)
let oidcSession = JSON.stringify({
id_token:
'eyJraWQiOiI3M2I5NTI0Ni02NjI2LTQ3N2YtYWFmYS1kMDJiODFhNjFkZmYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJjc2FkbWluIiwiYXVkIjoidHVvaGVuZy1haXJwb3J0LWFkbWluIiwicm9sZSI6WyJ0dW9oZW5nLWFpcnBvcnRTY3JlZW4tbXAiLCJ0dW9oZW5nLXBpbG90LW1wIiwidHVvaGVuZy13YXRlcndheS1hZG1pbiIsInR1b2hlbmctdGVsZWNvbXVtYWxlLW1wIiwidHVvaGVuZy1haXJwb3J0U2NyZWVuLWFkbWluIiwidHVvaGVuZy1kbXAtbXAiLCJ0dW9oZW5nLWZseXBvcnRhbC1hZG1pbiIsInR1b2hlbmctdGVsZWNvbXVtYWxlLWFkbWluIiwidHVvaGVuZy1oaHotYWRtaW4iLCJ0dW9oZW5nLWhoei1tcCIsInR1b2hlbmctZnJlZXdheS1hZG1pbiIsInR1b2hlbmctd2VwdHNwLW1wIiwidHVvaGVuZy1waWxvdC1hZG1pbiIsInR1b2hlbmctc3BhY2V0aW1lLWFkbWluIiwidHVvaGVuZy1hbGVydC1tcCIsInR1b2hlbmctYnVzaW5lc3MtYWRtaW4iLCJ0dW9oZW5nLWJ1c2luZXNzLW1wIiwidHVvaGVuZy13ZXB0c3AtYWRtaW4iLCJ0dW9oZW5nLWFpcm1vbml0b3ItbXAiLCJ0dW9oZW5nLWFpcnBvcnQtbXAiLCJ0dW9oZW5nLWFpcm1vbml0b3ItYWRtaW4iLCJ0dW9oZW5nLWFsZXJ0LWFkbWluIiwidHVvaGVuZy1haXJwb3J0LWFkbWluIiwidHVvaGVuZy13YXRlcndheS1tcCIsInR1b2hlbmctZnJlZXdheS1tcCIsInR1b2hlbmctZG1wLWFkbWluIiwidHVvaGVuZy1zcGFjZXRpbWUtbXAiXSwiYXpwIjoidHVvaGVuZy1haXJwb3J0LWFkbWluIiwiaXNzIjoiaHR0cHM6XC9cL2xvZ2luLXRlc3QudC1hYXJvbi5jb20iLCJleHAiOjE3NTUwNzA3MjMsImlhdCI6MTc1NTA2ODkyM30.deCJZsyu2dgLrbl-rnnoYTBhZD59zUJoLzFkadL7m_RJ_jWlvQvLXjQn2h0tuba0HRN7ZT2COVmNxTgLLATbdJU2SQ-_wVt30XwyX1hauDV1DALzdk5UiRelP2lIJlPWZKhpqMX52gYsGZXyliErbORBLMc_920vTZDHKeuiriuODXlT7__5MnKFTCGXDegZRPALY7kLwpQfL4DSn3ILeSGMIqU7dPx3kM4CV1iIJs2f2jEZT4HOzXy51o_GjCW7enY2qWERRfvsLIX8a8DPg0YvN-j3AHoUoKWYX_1ZCmD3eGHhqQbEvZsLJV2J6MlwF6eZOkz08M-mdkyU2_Trpg',
access_token:
'eyJraWQiOiI3M2I5NTI0Ni02NjI2LTQ3N2YtYWFmYS1kMDJiODFhNjFkZmYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJjc2FkbWluIiwiaXNBYmxlIjoxLCJpc3MiOiJodHRwczpcL1wvbG9naW4tdGVzdC50LWFhcm9uLmNvbSIsImF1ZCI6InR1b2hlbmctYWlycG9ydC1hZG1pbiIsIm5iZiI6MTc1NTc1OTQyNSwib1VzZXJJZCI6NTQzLCJzY29wZSI6WyJ0dW9oZW5nLWFpcnBvcnRTY3JlZW4tbXAiLCJ0dW9oZW5nLXBpbG90LW1wIiwidHVvaGVuZy13YXRlcndheS1hZG1pbiIsInR1b2hlbmctdGVsZWNvbXVtYWxlLW1wIiwidHVvaGVuZy1haXJwb3J0U2NyZWVuLWFkbWluIiwidHVvaGVuZy1kbXAtbXAiLCJ0dW9oZW5nLWZseXBvcnRhbC1hZG1pbiIsInR1b2hlbmctdGVsZWNvbXVtYWxlLWFkbWluIiwidHVvaGVuZy1oaHotYWRtaW4iLCJ0dW9oZW5nLWhoei1tcCIsInR1b2hlbmctZnJlZXdheS1hZG1pbiIsInR1b2hlbmctd2VwdHNwLW1wIiwidHVvaGVuZy1waWxvdC1hZG1pbiIsInR1b2hlbmctc3BhY2V0aW1lLWFkbWluIiwidHVvaGVuZy1hbGVydC1tcCIsInR1b2hlbmctYnVzaW5lc3MtYWRtaW4iLCJ0dW9oZW5nLWJ1c2luZXNzLW1wIiwidHVvaGVuZy13ZXB0c3AtYWRtaW4iLCJ0dW9oZW5nLWFpcm1vbml0b3ItbXAiLCJ0dW9oZW5nLWFpcnBvcnQtbXAiLCJ0dW9oZW5nLWFpcm1vbml0b3ItYWRtaW4iLCJ0dW9oZW5nLWFsZXJ0LWFkbWluIiwidHVvaGVuZy1haXJwb3J0LWFkbWluIiwidHVvaGVuZy13YXRlcndheS1tcCIsInR1b2hlbmctZnJlZXdheS1tcCIsInR1b2hlbmctZG1wLWFkbWluIiwidHVvaGVuZy1zcGFjZXRpbWUtbXAiXSwiY2xpZW50Um9sZUxpc3QiOiJbe1wiY2xpZW50SWRcIjpcInR1b2hlbmctZmx5cG9ydGFsLWFkbWluXCIsXCJyb2xlSWRcIjoxfSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy1idXNpbmVzcy1tcFwiLFwicm9sZUlkXCI6MTE0NX0se1wiY2xpZW50SWRcIjpcInR1b2hlbmctYnVzaW5lc3MtYWRtaW5cIixcInJvbGVJZFwiOjExNDV9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLWFpcnBvcnRTY3JlZW4tYWRtaW5cIixcInJvbGVJZFwiOjF9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLXNwYWNldGltZS1hZG1pblwiLFwicm9sZUlkXCI6MX0se1wiY2xpZW50SWRcIjpcInR1b2hlbmctc3BhY2V0aW1lLW1wXCIsXCJyb2xlSWRcIjoxfSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy1oaHotYWRtaW5cIixcInJvbGVJZFwiOjEwMDN9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLWhoei1tcFwiLFwicm9sZUlkXCI6MTAwM30se1wiY2xpZW50SWRcIjpcInR1b2hlbmctd2F0ZXJ3YXktYWRtaW5cIixcInJvbGVJZFwiOjF9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLXdhdGVyd2F5LW1wXCIsXCJyb2xlSWRcIjoxfSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy1kbXAtYWRtaW5cIixcInJvbGVJZFwiOjF9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLWRtcC1tcFwiLFwicm9sZUlkXCI6MX0se1wiY2xpZW50SWRcIjpcInR1b2hlbmctYWlycG9ydC1hZG1pblwiLFwicm9sZUlkXCI6NjYzfSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy1haXJwb3J0LW1wXCIsXCJyb2xlSWRcIjo2NjN9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLWFsZXJ0LWFkbWluXCIsXCJyb2xlSWRcIjoyN30se1wiY2xpZW50SWRcIjpcInR1b2hlbmctYWxlcnQtbXBcIixcInJvbGVJZFwiOjI3fSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy1haXJtb25pdG9yLWFkbWluXCIsXCJyb2xlSWRcIjoxfSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy1haXJtb25pdG9yLW1wXCIsXCJyb2xlSWRcIjoxfSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy13ZXB0c3AtYWRtaW5cIixcInJvbGVJZFwiOjI1fSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy13ZXB0c3AtbXBcIixcInJvbGVJZFwiOjI1fSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy10ZWxlY29tdW1hbGUtYWRtaW5cIixcInJvbGVJZFwiOjF9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLXRlbGVjb211bWFsZS1tcFwiLFwicm9sZUlkXCI6MX0se1wiY2xpZW50SWRcIjpcInR1b2hlbmctcGlsb3QtbXBcIixcInJvbGVJZFwiOjF9LHtcImNsaWVudElkXCI6XCJ0dW9oZW5nLXBpbG90LWFkbWluXCIsXCJyb2xlSWRcIjoxfSx7XCJjbGllbnRJZFwiOlwidHVvaGVuZy1mcmVld2F5LW1wXCIsXCJyb2xlSWRcIjo1OH0se1wiY2xpZW50SWRcIjpcInR1b2hlbmctZnJlZXdheS1hZG1pblwiLFwicm9sZUlkXCI6NTh9XSIsImV4cCI6MTc1NTg0NTgyNSwiaXNFeHBpcmUiOjEsImlhdCI6MTc1NTc1OTQyNSwidXNlcm5hbWUiOiJjc2FkbWluIn0.AWMLWQODIm82pet6D6e_jtb--CbKMXFLZPUmvIIP18Ezk5r-rA4ZT37mC5ZZgP3p9I792ulk-Xg-fpbC4Gm6d4TmDUHwZztLXc_sTH0XOHqblNy_G6rjV1UZKex9hOKpGYH3eXxZVjUTfjcp9tbs5w5DkxzYu-hdpeVr4JUYasqVVFOZIEhOML0kBgA85P-RjK0kH1hMbyI1mRqV1t5Mnyqhd2pmybqmWDnh-ohq5JiYaquxZ2jROappWvg11gOGZ2PWptDH9pfqLMt07Td1sZOstLKNrq6fpfeJNuJ43YHcQHy6fkoM8Wf9N7Nxpv_qydfrW_9cjqwDgAgvkgf0kA',
refresh_token:
'HNyAXnoDhR2kgjXUSVwqMldvgzazvaY3tVlCodSiDon7fA7P8Ci3BMsnk31cjeSKwcbsPzUaMdCdBilBhSqQ1dZLk-Md-ERqh5mbbeu7rJDWX4MexO0eKtRBwz-mbnbR',
token_type: 'Bearer',
scope: 'openid profile',
profile: {
sub: 'csadmin',
role: [
'tuoheng-airportScreen-mp',
'tuoheng-pilot-mp',
'tuoheng-waterway-admin',
'tuoheng-telecomumale-mp',
'tuoheng-airportScreen-admin',
'tuoheng-dmp-mp',
'tuoheng-flyportal-admin',
'tuoheng-telecomumale-admin',
'tuoheng-hhz-admin',
'tuoheng-hhz-mp',
'tuoheng-freeway-admin',
'tuoheng-weptsp-mp',
'tuoheng-pilot-admin',
'tuoheng-spacetime-admin',
'tuoheng-alert-mp',
'tuoheng-business-admin',
'tuoheng-business-mp',
'tuoheng-weptsp-admin',
'tuoheng-airmonitor-mp',
'tuoheng-airport-mp',
'tuoheng-airmonitor-admin',
'tuoheng-alert-admin',
'tuoheng-airport-admin',
'tuoheng-waterway-mp',
'tuoheng-freeway-mp',
'tuoheng-dmp-admin',
'tuoheng-spacetime-mp',
],
azp: 'tuoheng-airport-admin',
userId: 543,
userName: 'csadmin',
isExpire: 1,
isAble: 1,
authority: [
'tuoheng-flyportal-admin',
'tuoheng-business-mp',
'tuoheng-business-admin',
'tuoheng-airportScreen-admin',
'tuoheng-airportScreen-mp',
'tuoheng-spacetime-admin',
'tuoheng-spacetime-mp',
'tuoheng-hhz-admin',
'tuoheng-hhz-mp',
'tuoheng-waterway-admin',
'tuoheng-waterway-mp',
'tuoheng-dmp-admin',
'tuoheng-dmp-mp',
'tuoheng-airport-admin',
'tuoheng-airport-mp',
'tuoheng-alert-admin',
'tuoheng-alert-mp',
'tuoheng-airmonitor-admin',
'tuoheng-airmonitor-mp',
'tuoheng-weptsp-admin',
'tuoheng-weptsp-mp',
'tuoheng-telecomumale-admin',
'tuoheng-telecomumale-mp',
'tuoheng-pilot-mp',
'tuoheng-pilot-admin',
'tuoheng-freeway-mp',
'tuoheng-freeway-admin',
],
clientRoleList: [
{ clientId: 'tuoheng-flyportal-admin', roleId: 1 },
{ clientId: 'tuoheng-business-mp', roleId: 1145 },
{ clientId: 'tuoheng-business-admin', roleId: 1145 },
{ clientId: 'tuoheng-airportScreen-admin', roleId: 1 },
{ clientId: 'tuoheng-spacetime-admin', roleId: 1 },
{ clientId: 'tuoheng-spacetime-mp', roleId: 1 },
{ clientId: 'tuoheng-hhz-admin', roleId: 1003 },
{ clientId: 'tuoheng-hhz-mp', roleId: 1003 },
{ clientId: 'tuoheng-waterway-admin', roleId: 1 },
{ clientId: 'tuoheng-waterway-mp', roleId: 1 },
{ clientId: 'tuoheng-dmp-admin', roleId: 1 },
{ clientId: 'tuoheng-dmp-mp', roleId: 1 },
{ clientId: 'tuoheng-airport-admin', roleId: 663 },
{ clientId: 'tuoheng-airport-mp', roleId: 663 },
{ clientId: 'tuoheng-alert-admin', roleId: 27 },
{ clientId: 'tuoheng-alert-mp', roleId: 27 },
{ clientId: 'tuoheng-airmonitor-admin', roleId: 1 },
{ clientId: 'tuoheng-airmonitor-mp', roleId: 1 },
{ clientId: 'tuoheng-weptsp-admin', roleId: 25 },
{ clientId: 'tuoheng-weptsp-mp', roleId: 25 },
{ clientId: 'tuoheng-telecomumale-admin', roleId: 1 },
{ clientId: 'tuoheng-telecomumale-mp', roleId: 1 },
{ clientId: 'tuoheng-pilot-mp', roleId: 1 },
{ clientId: 'tuoheng-pilot-admin', roleId: 1 },
{ clientId: 'tuoheng-freeway-mp', roleId: 58 },
{ clientId: 'tuoheng-freeway-admin', roleId: 58 },
],
},
expires_at: 1755155322,
})
sessionStorage.setItem(`oidc.user:${VITE_APP_AUTHORITY}:${VITE_APP_CLIENT_ID}`, oidcSession)
let userInfo = JSON.parse(oidcSession)
const access_token = `Bearer ${userInfo.access_token}`
//设置token
localStorage.setItem('access_token', access_token)
sessionStorage.setItem('access_token', access_token)
//测试时候用的代码
setupAll({ container: '#airapp' })
} else {
renderWithQiankun({
mount(props) {
console.log('--进入子应用')
setupAll(props)
useSettingStore().systemSetting = props.setting
//获取主应用的token
// userStore.setToken(props.token)
//强制刷新一次页面
// let mtoken = window.localStorage.getItem('mtoken')
// if (!mtoken) {
// window.localStorage.setItem('mtoken', props.token)
// //刷新页面
// // window.location.reload()
// }
},
bootstrap() {
console.log('--bootstrap')
},
update() {
console.log('--update')
},
unmount() {
window.localStorage.removeItem('mtoken')
app.unmount()
console.log('--离开子应用')
},
})
}

24
src/router/index.js Normal file
View File

@ -0,0 +1,24 @@
import { createRouter, createWebHistory } from 'vue-router'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({
// history: createWebHistory(import.meta.env.BASE_URL),
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/qiankunother/' : '/'),
routes: [
{
path: '/',
name: 'carbin',
component: () => import('../views/carbin/index.vue'),
},
// {
// path: '/about',
// name: 'about',
// // route level code-splitting
// // this generates a separate chunk (About.[hash].js) for this route
// // which is lazy-loaded when the route is visited.
// component: () => import('../views/AboutView.vue'),
// },
],
})
export default router

View File

@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useAirPortSocketStore = defineStore('airportSocket', () => {
const currentAirPort = ref(null)
// function increment() {
// count.value++
// }
return { currentAirPort }
})

12
src/stores/counter.js Normal file
View File

@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

8
src/stores/setting.js Normal file
View File

@ -0,0 +1,8 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useSettingStore = defineStore('setting', {
state: () => ({
systemSetting: ref(null),
}),
})

124
src/utils/request/index.js Normal file
View File

@ -0,0 +1,124 @@
import axios from 'axios'
// import { ElLoading, ElMessage } from 'element-plus'
import router from '@/router'
import { useSettingStore } from '@/stores/setting.js'
// request是一个axios实例,每一个实例你都可以单独定制它的baseURL,超时时间,请求头和一些其他配置项。
// const baseUrl = import.meta.env.VITE_APP_API_BASE_URL + 'admin' //接口统一域名
const baseUrl = '/airport/admin'
// 设置统一的url
// axios.defaults.baseURL = '/airport/admin'
const instance = axios.create({
baseURL: baseUrl,
timeout: 60 * 1000, //设置超时
headers: {
'Content-Type': 'application/json;charset=UTF-8;',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'.AspNetCore.Culture': 'c=zh-Hans|uic=zh-Hans',
},
})
let loading
//正在请求的数量
let requestCount = 0
//显示loading
// const showLoading = () => {
// if (requestCount === 0) {
// // loading = ElLoading.service({
// // fullscreen: true,
// // text: 'Loading ',
// // background: 'rgba(0, 0, 0, 0.7)',
// // })
// }
// requestCount++
// }
//隐藏loading
// const hideLoading = () => {
// requestCount--
// if (requestCount == 0) {
// loading.close()
// }
// }
//请求拦截器
instance.interceptors.request.use(
(config) => {
// 每次发送请求之前判断是否存在token如果存在则统一在http请求的header都加上token不用每次请求都手动添加了
// const { token_type, access_token } = userInfo
config.headers.Authorization = `${localStorage.getItem('access_token')}`
config.headers['Client-Id'] = import.meta.env.VITE_APP_CLIENT_ID
//若请求方式为post则将data参数转为JSON字符串
if (config.method === 'POST') {
config.data = JSON.stringify(config.data)
}
return config
},
(error) =>
// 对请求错误做些什么
Promise.reject(error),
)
//响应拦截器
instance.interceptors.response.use(
(response) => {
// hideLoading()
if (response.data.code == 402) {
// ElMessage.error(response.data.msg)
router.push('/')
}
//响应成功
// console.log('拦截器报错');
else return response.data
},
(error) => {
// hideLoading()
//响应错误
let message = ''
if (error.response && error.response.status) {
const status = error.response.status
switch (status) {
case 400:
message = '请求错误'
break
case 401:
message = '请求错误'
break
case 404:
message = '请求地址出错'
break
case 408:
message = '请求超时'
break
case 500:
message = '服务器内部错误!'
break
case 501:
message = '服务未实现!'
break
case 502:
message = '网关错误!'
break
case 503:
message = '服务不可用!'
break
case 504:
message = '网关超时!'
break
case 505:
message = 'HTTP版本不受支持'
break
default:
message = '请求失败'
}
// ElMessage.error(message)
return Promise.reject(error)
}
return Promise.reject(error)
},
)
export default instance

View File

@ -0,0 +1,31 @@
import instance from './index'
/**
* @param {String} method 请求的方法getpostdeleteput
* @param {String} url 请求的url:
* @param {Object} data 请求的参数
* @param {Object} config 请求的配置
* @returns {Promise} 返回一个promise对象其实就相当于axios请求数据的返回值
*/
export const request = ({ method, url, data, config }) => {
//把大写转换成小写
method = method.toLowerCase()
if (method == 'post') {
return instance.post(url, data, { ...config })
} else if (method == 'get') {
return instance.get(url, {
params: data,
...config,
})
} else if (method == 'delete') {
return instance.delete(url, {
params: data,
...config,
})
} else if (method == 'put') {
return instance.put(url, data, { ...config })
} else {
console.error('未知的method' + method)
return false
}
}

View File

@ -0,0 +1,113 @@
class WebSocketService {
constructor() {
this.socketCount = 30
}
initConnection(conn) {
// 先清理旧连接
conn.clearConnection()
if (conn.closed) return
conn.socket = new WebSocket(conn.url)
conn.socket.onopen = () => {
console.log(`Connection opened: ${conn.url}`)
function sendFn() {
conn.intervalId = setTimeout(() => {
// if (conn.closed) return
// 超过 30 秒没收到心跳就重连 (最多重连 30 次)
if (Date.now() - conn.lastHeartbeat > 30000 && conn.againTimes < this.socketCount) {
let againTimes = conn.againTimes++
console.log('ws心跳超时尝试重连222', againTimes, conn.againTimes)
this.initConnection(conn)
//超过30次了
clearTimeout(conn.intervalId)
return
} else {
conn.socket.send('ping')
sendFn()
}
}, 10000)
}
sendFn()
}
conn.socket.onmessage = (event) => {
conn.lastHeartbeat = Date.now()
let msg
try {
msg = JSON.parse(event.data)
} catch {
// 非 JSON 消息直接转给业务
return conn.onmessage(event)
}
if (msg.type === 'pong') {
console.log('收到心跳 pong')
} else {
conn.onmessage(event)
}
}
conn.socket.onerror = (err) => {
console.error(`Error in connection ${conn.url}:`, err)
conn.onerror(err)
}
conn.socket.onclose = () => {
console.log(`Connection closed: ${conn.url}`)
conn.onclose()
if (!conn.closed) {
let againTimes = conn.againTimes++
console.log('ws心跳超时尝试重连1111', againTimes, conn.againTimes)
// 超过 30 秒没收到心跳就重连 (最多重连 30 次)
if (againTimes > this.socketCount) {
return
}
conn.intervalId = setTimeout(() => {
// 自动重连
this.initConnection(conn)
}, 10000)
} else {
clearTimeout(conn.intervalId)
}
}
}
createConnection(url) {
const conn = {
url,
socket: null,
lastHeartbeat: Date.now(),
intervalId: null,
closed: false,
againTimes: 0, // 重连次数
onopen: () => {},
onmessage: () => {},
onerror: () => {},
onclose: () => {},
sendMessage(msg) {
if (this.closed) {
console.error('WebSocket 已关闭')
return
}
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(msg)
} else {
console.error('WebSocket 未打开')
}
},
clearConnection() {
if (this.socket) this.socket.close()
if (this.intervalId) clearTimeout(this.intervalId)
},
close() {
console.log(`Connection 人工关闭`)
this.closed = true
this.clearConnection()
},
}
this.initConnection(conn)
return conn
}
}
export default new WebSocketService()

View File

@ -0,0 +1,174 @@
<script setup>
//线
import { ref, onMounted } from 'vue'
import { RightOutlined, CloseOutlined, UserOutlined } from '@ant-design/icons-vue'
//
import { queryAirportApi } from '@/apis/common'
const emit = defineEmits(['modifPlaneWidth'])
let airportName = ref('')
let selectedAirPort = ref(1)
let airportList = ref([
{
id: 1,
name: '南庄收费站日常巡检',
select: 1,
},
{
id: 2,
name: '南庄收费站日常巡检这是新的巡楼点啊哈哈哈哈',
select: 2,
},
{
id: 3,
name: '南庄收3费站日常巡检',
select: 3,
},
{
id: 4,
name: '南庄收费2站日常巡检',
select: 4,
},
{
id: 5,
name: '南庄收费站1日常巡检',
select: 5,
},
])
let airlistItem_width = ref('calc(50% - 5px)')
let leftBox_width = ref('100%')
let showAirPortList = ref(false)
//
const showAirPortListFn = () => {
showAirPortList.value = true
leftBox_width.value = 'calc(50% - 16px)'
emit('modifPlaneWidth', 2)
}
const hideAirPortListFn = () => {
showAirPortList.value = false
leftBox_width.value = '100%'
emit('modifPlaneWidth', 1)
}
const queryAirPortList = async () => {
let res = await queryAirportApi()
if (res.code == 0) {
let arr = []
res.data.map((item) => {
let t = {
id: item.id,
name: item.name,
select: item.id,
}
arr.push(t)
})
airportList.value = arr
console.log(airportList.value)
}
// setTimeout(() => {
// queryAirPortList()
// }, 6000)
}
const selectAirPortFn = (item) => {
console.log(item)
selectedAirPort.value = item.id
showAirPortList.value = false
leftBox_width.value = '100%'
emit('modifPlaneWidth', 1)
}
onMounted(() => {
queryAirPortList()
})
</script>
<template>
<div class="airlinePlane h-full py-6 px-4 flex flex-row justify-between">
<div class="leftBox relative h-full">
<p class="text-[#ffffff] text-base font-medium">航线飞行立即计划</p>
<div
@click="showAirPortListFn"
class="mt-4 h-[32px] px-4 flex flex-row items-center justify-between border border-[#394C73] rounded bg-[#1D314A] cursor-pointer text-[#ffffff] text-sm/7"
>
<span class="opacity-60">南庄收费站日常巡检路线</span>
<RightOutlined class="opacity-60" />
</div>
<!-- 按钮 -->
<a-button type="primary" shape="round" class="blueBtn w-full h-[36px] absolute bottom-2">
立即下发
</a-button>
</div>
<div v-show="showAirPortList" class="overflow-hidden w-1/2 h-full flex flex-col">
<p class="text-[#ffffff] text-base font-medium relative">
&nbsp
<CloseOutlined
@click="hideAirPortListFn"
class="absolute right-0 top-0 text-lg cursor-pointer"
/>
</p>
<div
class="airlinebox h-full border border-[#394C73] rounded flex-1 mt-4 py-2.5 px-4 flex flex-col"
>
<p class="text-[#ffffff] text-base font-medium">全部航线</p>
<a-input
class="blueInput mt-4"
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
v-model:value="airportName"
placeholder="输入航线名称搜索"
>
<template #prefix>
<user-outlined />
</template>
</a-input>
<div
style="height: 62%; scrollbar-gutter: stable"
class="w-full flex-wrap flex flex-row justify-between mt-4 overflow-y-auto"
>
<a-radio-group v-model:value="selectedAirPort">
<a-row :gutter="[16, 10]">
<a-col
class="gutter-row cursor-pointer"
:span="12"
v-for="(item, index) in airportList"
:key="item"
>
<div
class="w-full h-full p-[12px] flex flex-row items-center justify-between bg-[#1D314A] border border-[#394C73] rounded text-center"
:class="selectedAirPort == item.select ? 'blueGradientColor' : ''"
@click="selectAirPortFn(item)"
>
<span class="iconfont icon-route1-fill text-2xl text-[#93BBEC]"></span>
<span class="text-[#B4D5FF] text-sm mx-1 text-left">{{ item.name }}</span>
<a-radio class="text-center mr-0" :value="item.select"></a-radio>
</div>
</a-col>
</a-row>
</a-radio-group>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.airlinePlane {
--airlistItem_width: v-bind(airlistItem_width);
--leftBox_width: v-bind(leftBox_width);
.leftBox {
width: var(--leftBox_width);
}
.blueGradientColor {
background: linear-gradient(0deg, #0143f4, #0195fa);
}
}
</style>

View File

@ -0,0 +1,47 @@
<script setup>
//
import { ref, h, defineAsyncComponent, onMounted, watch, toRaw, defineProps, reactive } from 'vue'
import { LoadingOutlined } from '@ant-design/icons-vue'
const indicator = h(LoadingOutlined, {
style: {
fontSize: '20px',
color: '#ffffff',
},
spin: true,
})
</script>
<template>
<div class="p-2.5">
<div class="flex flex-row items-center justify-between mt-4">
<span class="text-[18px] text-[#ffffff] font-medium">TAKEOFF PREPARING</span>
<a-spin :indicator="indicator" />
</div>
<a-progress :percent="50" :show-info="false" trailColor="#ffffff" />
<div class="flex flex-row items-center justify-between text-sm">
<span>
<span class="text-[#ffffff]">耗时:</span>
<span class="text-[#3BA3FB] ml-2">10s</span>
</span>
<span>
<span class="text-[#ffffff]">起飞前准备中 预计还剩:</span>
<span class="text-[#FFA800] ml-2">50s</span>
</span>
</div>
<div
class="w-full lg:h-[211px] xk:h-[211px] x1k:h-[296px] x2k:h-[394px] mt-6 p-3 border border-[#394C73] rounded-bl-md rounded-br-md"
>
<div class="w-full flex flex-row items-center">
<span class="iconfont icon-biaoqian text-[#ffffff] opacity-50 mr-5"></span>
<span class="text-[#FFA800] text-sm mr-5">15:30:20</span>
<span class="text-[#ffffff] text-sm mr-5">机场完成自检</span>
<span class="text-[#ffffff] text-sm">总共耗时19s</span>
</div>
</div>
</div>
</template>
<style lang="less" scoped></style>

View File

@ -0,0 +1,268 @@
<script setup>
//
import {
ref,
h,
defineEmits,
defineAsyncComponent,
onMounted,
watch,
toRaw,
defineProps,
reactive,
} from 'vue'
import { useAirPortSocketStore } from '@/stores/airportSocket.js'
const emits = defineEmits(['switchView'])
const airPortSocketStore = useAirPortSocketStore()
let size1Img = ref(new URL(`@/assets/icons/size1active.png`, import.meta.url).href)
let size2Img = ref(new URL(`@/assets/icons/size2.png`, import.meta.url).href)
let viewMode = ref(1) //1-1/4 2-
let littleViewMode = ref(2) //1- 2- 3-
let littleViewModeName = ref('舱内画面')
let planeStateList = ref([
{
id: 1,
key: 'vspeed',
name: '爬升速度',
icon: 'icon-shangshengxiajiang',
value: '-',
touch: false,
content: '爬升<br>速度',
unit: '/s',
},
{
id: 2,
name: '平飞速度',
icon: 'icon-jiantouzuoyou',
value: '-',
touch: false,
content: '平飞<br>速度',
unit: '/s',
},
{
id: 3,
name: '距离机场',
icon: 'icon-H',
value: '-',
touch: false,
content: '距离<br>机场',
unit: 'm',
},
{
id: 4,
name: '海拔高度',
icon: 'icon-tiedijuli',
value: '-',
touch: false,
content: '海拔<br>高度',
unit: 'm',
},
{
id: 5,
name: '无人机俯仰角',
icon: 'icon-flight-takeoff-line',
value: '-',
touch: false,
content: '无人机<br>俯仰角',
unit: '°',
},
{
id: 6,
name: '无人机偏航角',
icon: 'icon-feihangqipianhangjiao',
value: '-',
content: '无人机<br>偏航角',
touch: false,
},
{
id: 7,
name: '云台俯仰角',
icon: 'icon-pianhangjiao-1',
value: '-',
content: '云台<br>俯仰角',
touch: false,
},
{
id: 8,
name: '云台偏航角',
icon: 'icon-pianhangjiao-1',
value: '-',
content: '云台<br>偏航角',
touch: false,
},
])
let selectedAirPort = ref(1)
//
const switchView = (viewtype) => {
if (viewtype == 1) {
size1Img.value = new URL(`@/assets/icons/size1active.png`, import.meta.url).href
size2Img.value = new URL(`@/assets/icons/size2.png`, import.meta.url).href
emits('switchView', 1)
} else if (viewtype == 2) {
size1Img.value = new URL(`@/assets/icons/size1.png`, import.meta.url).href
size2Img.value = new URL(`@/assets/icons/size2active.png`, import.meta.url).href
emits('switchView', 2)
}
viewMode.value = viewtype
}
//
const switchPlaneVideo = (viewtype) => {
littleViewMode.value = viewtype
switch (viewtype) {
case 1:
littleViewModeName.value = '舱外画面'
break
case 2:
littleViewModeName.value = '舱内画面'
break
case 3:
littleViewModeName.value = '无人机画面'
break
default:
break
}
}
//
const siwtchVideoPosFn = () => {}
//pinia
airPortSocketStore.$subscribe((mutate, state) => {
console.log(state.currentAirPort)
})
</script>
<template>
<div class="w-full h-full flex flex-row relative">
<div class="leftPlane flex flex-row w-1/4 h-full py-3 px-2.5">
<div class="h-full flex flex-col">
<div
@click="switchView(1)"
class="size-12 rounded mb-2 border p-1 cursor-pointer"
:class="viewMode == 1 ? 'border-[#006FFE]' : 'border-[#BFBFBF]'"
>
<img :src="size1Img" alt="" />
<p
class="text-center text-[#ffffff] opacity-30 text-[11px]"
:class="viewMode == 1 ? 'opacity-100' : 'opacity-30'"
>
常规
</p>
</div>
<div
@click="switchView(2)"
class="size-12 rounded border border-[#BFBFBF] p-1 cursor-pointer"
:class="viewMode != 1 ? 'border-[#006FFE]' : 'border-[#BFBFBF]'"
>
<img :src="size2Img" alt="" />
<p
class="text-center text-[#ffffff] text-[11px]"
:class="viewMode != 1 ? 'opacity-100' : 'opacity-30'"
>
等比
</p>
</div>
</div>
<div class="w-[250px] h-full bg-[#bdbdbd] ml-2.5 px-2 py-1 rounded relative">
<div id="#plane_small_video_area" class="w-full h-full absolute left-0 top-0"></div>
<div class="w-full flex flex-row justify-between items-center">
<div class="w-full flex flex-row">
<div
class="size-7 p-0.5 rounded-sm cursor-pointer"
:class="littleViewMode == 1 ? 'bg-[#006FFE]' : 'bg-[#000000]'"
@click="switchPlaneVideo(1)"
>
<div class="w-full h-1"></div>
<div class="bg-[rgba(255,255,255,0.3)] w-full h-5 text-center text-[#fff]">
<span
class="iconfont icon-cangnei_xianxing text-[24px]/6"
:class="littleViewMode == 1 ? 'opacity-100' : 'opacity-60'"
></span>
</div>
</div>
<div
class="size-7 p-0.5 rounded-sm ml-2 cursor-pointer"
:class="littleViewMode == 2 ? 'bg-[#006FFE]' : 'bg-[#000000]'"
@click="switchPlaneVideo(2)"
>
<div class="w-full h-1"></div>
<div class="bg-[rgba(255,255,255,0.3)] w-full h-5 text-center text-[#fff]">
<span
class="iconfont icon-shexiangtou text-[18px]/6"
:class="littleViewMode == 2 ? 'opacity-100' : 'opacity-60'"
></span>
</div>
</div>
<div
class="size-7 p-0.5 rounded-sm ml-2 cursor-pointer"
:class="littleViewMode == 3 ? 'bg-[#006FFE]' : 'bg-[#000000]'"
@click="switchPlaneVideo(3)"
>
<div class="w-full h-1"></div>
<div class="bg-[rgba(255,255,255,0.3)] w-full h-5 text-center text-[#fff]">
<span
class="iconfont icon-uav-fill text-[18px]/6"
:class="littleViewMode == 3 ? 'opacity-100' : 'opacity-60'"
></span>
</div>
</div>
</div>
<div
class="w-[26px] h-[24px] bg-[rgba(25,27,36,0.59)] border border-[#191B24] rounded-sm text-center"
>
<span
class="iconfont icon-qiehuan text-[#ffffff] cursor-pointer"
@click="siwtchVideoPosFn"
></span>
</div>
</div>
<div
class="px-1 h-6 rounded-sm absolute left-2 bottom-2.5 bottom-0 bg-[rgba(7,100,217,0.5)] border-[#0764D9] text-[#ffffff] text-[11px]/6 text-center"
>
{{ littleViewModeName }}
</div>
</div>
</div>
<!-- 右边 -->
<div class="rightPlane flex flex-row w-3/4 h-full py-3 pr-2.5">
<div class="w-[260px] m-auto">
<a-row :gutter="[4, 8]" style="height: fit-content">
<a-col
class="gutter-row cursor-pointer"
:span="6"
v-for="item in planeStateList"
:key="item"
>
<div
class="w-[54px] h-[60px] bg-[#1D314A] border border-[#394C73] rounded text-center relative"
:class="selectedAirPort == item.select ? 'blueGradientColor' : ''"
@mouseover="item.touch = true"
@mouseout="item.touch = false"
>
<img src="@/assets/icons/state1.png" class="w-full h-full" alt="" />
<span
v-if="!item.touch"
:class="
'iconfont absolute top-1.5 left-0 right-0 m-auto text-lg text-[#ffffff] ' +
item.icon
"
></span>
<span
class="iconfont absolute top-0.5 left-0 right-0 m-auto text-xs text-[#ffffff]"
v-if="item.touch"
v-html="item.content"
></span>
<span class="absolute bottom-0.5 left-0 right-0 m-auto text-[#ffffff]">{{
item.value
}}</span>
</div>
</a-col>
</a-row>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,130 @@
<script setup>
import { ref, defineAsyncComponent, onMounted, watch, toRaw, defineProps, reactive } from 'vue'
// const LivePlayer = defineAsyncComponent(() => import('@/components/video/index.vue'))
const emit = defineEmits(['switchScreen'])
const props = defineProps({
row: Object, //
})
let out_title = ref('舱外直播')
let in_title = ref('舱内直播')
let lastType = ref(null)
const LiveOptions_out = reactive({
videoUrl: null,
// videoTitle: null,
// live: true,
// hideFullscreenButton: true,
// stretch: true
})
const LiveOptions_in = reactive({
videoUrl: null,
// videoTitle: null,
// live: true,
// hideFullscreenButton: true,
// stretch: true
})
const switchScreen = (type) => {
if (lastType != type) {
out_title.value = '舱外直播'
in_title.value = '舱内直播'
}
if (type == 'out') {
if (out_title.value == '地图') {
out_title.value = '舱外直播'
} else {
out_title.value = '地图'
}
}
if (type == 'in') {
if (in_title.value == '地图') {
in_title.value = '舱内直播'
} else {
in_title.value = '地图'
}
}
lastType = type
emit('switchScreen', type)
}
watch(
() => props.row,
(val) => {
console.log('来值了呢')
// console.log(val)
// LiveOptions_out.videoUrl = val.external_monitor_url
// LiveOptions_in.videoUrl = val.internal_monitor_url
},
{ deep: true },
)
onMounted(() => {})
</script>
<template>
<div class="w-full h-full p-2.5">
<div class="w-full h-full relative">
<!-- 直播窗口1 -->
<div>
<div class="header-area flex flex-row items-center justify-between">
<div class="flex flex-row items-center">
<img src="@/assets/icons/icon1@2x.png" alt="" class="w-9" />
<span class="text-sm text-[#ffffff]">{{ out_title }}</span>
</div>
<div
class="flex flex-row items-center px-3 bg-[#016CF8] text-[#ffffff] rounded-sm cursor-pointer"
@click="switchScreen('out')"
>
<span class="iconfont icon-qiehuan"></span>
<span class="text-xs ml-1">切换</span>
</div>
</div>
<div
id="out_small_video_area"
class="relative w-full aspect-video bg-[#0B2038] mt-2 rounded-md border border-[#3C89C6] overflow-hidden"
>
<!-- <LivePlayer
v-show="LiveOptions_out.videoUrl?.includes('.flv')"
ref="OutlivePlayerRef"
id="realOutTimeCamera"
:options="LiveOptions_out"
/> -->
</div>
</div>
<!-- 直播窗口2 -->
<div>
<div class="header-area flex flex-row items-center justify-between">
<div class="flex flex-row items-center">
<img src="@/assets/icons/icon1@2x.png" alt="" class="w-9" />
<span class="text-sm text-[#ffffff]">{{ in_title }}</span>
</div>
<div
class="flex flex-row items-center px-3 bg-[#016CF8] text-[#ffffff] rounded-sm cursor-pointer"
@click="switchScreen('in')"
>
<span class="iconfont icon-qiehuan"></span>
<span class="text-xs ml-1">切换</span>
</div>
</div>
<div
id="in_small_video_area"
class="relative w-full aspect-video bg-[#0B2038] mt-2 rounded-md border border-[#3C89C6] overflow-hidden"
></div>
</div>
<!-- 按钮 暂时不做-->
<!-- <a-button type="primary" shape="round" class="blueBtn w-full h-[36px] absolute bottom-2">
<template #icon>
<span class="iconfont icon-qifeidian text-xl/3 mr-2"></span>
</template>
航线飞行
</a-button> -->
</div>
</div>
</template>

397
src/views/carbin/index.vue Normal file
View File

@ -0,0 +1,397 @@
<script setup>
import { ref, defineAsyncComponent, onMounted, watch, toRaw, defineProps, reactive } from 'vue'
import { useAirPortSocketStore } from '@/stores/airportSocket.js'
//-
const LivePlayer = defineAsyncComponent(() => import('@/components/video/index.vue'))
//1-
const leftPlaneLiveVideo = defineAsyncComponent(
() => import('@/views/carbin/components/liveVideo.vue'),
)
//2-线
const leftPlaneAirLine = defineAsyncComponent(() => import('@/views/carbin/components/airLine.vue'))
//-
const leftPlaneCheck = defineAsyncComponent(
() => import('@/views/carbin/components/beforeCheck.vue'),
)
//-
const bottomPlaneFlying = defineAsyncComponent(() => import('@/views/carbin/components/flying.vue'))
//-
const rightSwitchVideo = defineAsyncComponent(
() => import('@/views/carbin/toolComp/switchVideoComp.vue'),
)
//
const planeControl = defineAsyncComponent(() => import('@/views/carbin/toolComp/planeControl.vue'))
//
import { queryAirportApi, queryAirLineApi, getAirWayPointsToJson } from '@/apis/common'
let leftPlaneWidth = ref('25%')
let rightPlaneWidth = ref('100%')
let rightPlaneHeight = ref('100%')
let rightPlaneBottom = ref('0')
let showLiveVideo = ref(false)
let outVideo_teleport = ref('#out_small_video_area')
let inVideo_teleport = ref('#in_small_video_area')
let planeVideo_teleport = ref('#plane_small_video_area')
let map_teleport = ref('#big_area')
let switchType = null
//socket
let currentAirPortSocket = ref(null)
const airPortSocketStore = useAirPortSocketStore()
//socket
let isUsedSocket = ref(false)
//
let statelliteCount = ref(0)
//
let currentAirPortInfo = ref(null)
let currentAirPortStatus = ref(1)
const statusList = reactive({
1: '空闲',
2: '飞行中',
3: '准备中',
})
let virturalDrive_iframe_full = ref(null)
let showIframeMap = ref(false)
let iframeData = null
//1
const LiveOptions_out = reactive({
videoUrl: null,
// videoTitle: null,
// live: true,
// hideFullscreenButton: true,
// stretch: true
})
//2
const LiveOptions_in = reactive({
videoUrl: null,
// videoTitle: null,
// live: true,
// hideFullscreenButton: true,
// stretch: true
})
//-
const LiveOptions_plane = reactive({
videoUrl: null,
// videoTitle: null,
// live: true,
// hideFullscreenButton: true,
// stretch: true
})
//
const modifPlaneWidthFn = (num) => {
if (num == 1) {
leftPlaneWidth.value = '25%'
} else if (num == 2) {
leftPlaneWidth.value = '50%'
}
}
//
const switchViewFn = (viewPrecent) => {
if (viewPrecent == 1) {
leftPlaneWidth.value = '25%'
rightPlaneWidth.value = '100%'
} else if (viewPrecent == 2) {
leftPlaneWidth.value = '50%'
rightPlaneWidth.value = '50%'
}
}
//
const queryAirLine_AirPort = async () => {
let params = {
airportId: 67,
}
let res = await Promise.allSettled([queryAirportApi(params), queryAirLineApi(params)])
// console.log(res)
if (res[0].value.code == 0) {
//
currentAirPortInfo.value = res[0].value.data[0]
//
currentAirPortStatus.value = Number(currentAirPortInfo.value.status)
console.log('现在的状态是:', currentAirPortStatus.value)
if (Number(currentAirPortStatus.value) == 2) {
//
rightPlaneHeight.value = 'calc(100% - 150px)'
rightPlaneBottom.value = '150px'
}
//
LiveOptions_out.videoUrl = currentAirPortInfo.value.external_monitor_url
LiveOptions_in.videoUrl = currentAirPortInfo.value.internal_monitor_url
LiveOptions_plane.videoUrl = currentAirPortInfo.value.camera_url
//
showLiveVideo.value = true
//socket
// console.log(currentAirPortInfo.value)
if (!isUsedSocket.value) {
let data = {
id: currentAirPortInfo.value.id,
code: currentAirPortInfo.value.code,
}
socketFn(data)
//socket
isUsedSocket.value = true
}
}
if (res[1].value.code == 0) {
//线
let airLineInfo = res[1].value.data[0]
//线
let resultWayPoint = await getAirWayPointsToJson(airLineInfo.fileUrl)
//iframe
showIframeMap.value = true
iframeData = {
airportName: currentAirPortInfo.value.airportName,
airportLocation: {
lon: currentAirPortInfo.value.longitude,
lat: currentAirPortInfo.value.latitude,
},
flyPath: resultWayPoint,
coverage: currentAirPortInfo.value.coverage,
}
}
//6
setTimeout(() => {
queryAirLine_AirPort()
}, 6000)
}
//socket线
const socketFn = (workParams) => {
//线,使type:moduleworkerimportjs
const worker = new Worker(new URL('@/workers/worker.js', import.meta.url), { type: 'module' })
worker.postMessage(JSON.stringify(workParams))
worker.onmessage = function (event) {
//线Worker
let data = JSON.parse(event.data)
currentAirPortSocket.value = data
//pinia
airPortSocketStore.currentAirPort = data
}
}
const iframeLoaded = () => {
// let iframeItem = document.getElementById('iframeItem')
let iframeItem = virturalDrive_iframe_full.value
let iframeContent = iframeItem.contentWindow
let data = JSON.stringify(iframeData)
// console.log(data)
iframeContent.postMessage(data, '*')
//
setTimeout(() => {
sendPosToPlane()
}, 2000)
}
//
const sendPosToPlane = () => {
// console.log('..................................')
let pathArr = iframeData.flyPath
let len = pathArr.length
let startCount = 0
// console.log(pathArr)
//lngLat
//alt
function sendPosFn() {
if (startCount >= len) {
startCount = 0
}
// console.log(':', startCount)
let t = {
currentPos: 1, //
currentPos_lon: pathArr[startCount].lngLat[0],
currentPos_lat: pathArr[startCount].lngLat[1],
currentPos_height: pathArr[startCount].alt,
}
let iframeItem = virturalDrive_iframe_full.value
let iframeContent = iframeItem.contentWindow
let data = JSON.stringify(t)
// console.log(data)
iframeContent.postMessage(data, '*')
startCount++
setTimeout(() => {
sendPosFn()
}, 2000)
}
sendPosFn()
}
//
const switchScreenFn = (type) => {
console.log(type)
if (switchType != type) {
//
outVideo_teleport.value = '#out_small_video_area'
inVideo_teleport.value = '#in_small_video_area'
map_teleport.value = '#big_area'
}
if (type == 'out') {
//
;[outVideo_teleport.value, map_teleport.value] = [map_teleport.value, outVideo_teleport.value]
} else if (type == 'in') {
//
;[inVideo_teleport.value, map_teleport.value] = [map_teleport.value, inVideo_teleport.value]
}
switchType = type
}
onMounted(() => {
//线 &
queryAirLine_AirPort()
})
</script>
<template>
<div class="flex flex-col w-full h-full min-w-[1366px] bg-[#0B2038]">
<!-- 顶部 -->
<div
class="rem-header w-full px-3.5 flex flex-row items-center justify-between lg:h-[36px] xk:h-[36px] x1k:h-[50px] x2k:h-[66px]"
>
<div>
<span class="text-sm text-[#B4D5FF]">江苏软件园银杏湖机场</span>
<span class="ml-2.5 text-xs px-4 py-1 bg-[#016CF8] text-white rounded-sm">{{
statusList[currentAirPortStatus]
}}</span>
</div>
<div
class="px-2 py-1 bg-[#245A97]/50 border border-[#006FFF]/50 rounded-sm text-sm text-[#B4D5FF]"
>
<span class="iconfont icon-satellite-signal-full"></span>
<span class="ml-1">{{ statelliteCount }}</span>
</div>
</div>
<!-- 底部 -->
<div class="rem-bottom-area w-full flex flex-1 flex-row relative">
<!-- 左侧面板 -->
<div class="leftPlane h-full bg-[#0B2038] rounded-r-lg absolute left-0 z-[2]">
<!-- 航线选择面板-先不做 -->
<!-- <leftPlaneAirLine @modifPlaneWidth="modifPlaneWidthFn" /> -->
<!-- 飞前空闲面板 空闲-->
<leftPlaneLiveVideo
v-if="currentAirPortStatus == 1"
:row="currentAirPortInfo"
@switchScreen="switchScreenFn"
/>
<!-- 自检面板 准备中-->
<leftPlaneCheck v-if="currentAirPortStatus == 3" />
</div>
<!-- 右侧面板 -->
<div class="rightPlane absolute right-0 h-full bg-[#ffffff] z-1">
<rightSwitchVideo class="absolute right-3 top-3" />
<div class="fullScreen w-3/4 absolute right-0">
<!-- <div class="w-full h-10 absolute right-0 bottom-[20px]">
<planeControl class="absolute left-0 right-0 top-0 bottom-0 m-auto" />
</div> -->
<div id="big_area" class="relative w-full h-full overflow-hidden"></div>
<!-- <div id="big_area" class="w-full h-full overflow-hidden"></div> -->
</div>
</div>
<div
v-if="currentAirPortStatus == 2"
class="bottomPlane w-full lg:h-[150px] xk:h-[150px] x1k:h-[212px] x2k:h-[282px] bg-[#0B2038] absolute bottom-0 left-0 z-[3]"
>
<bottomPlaneFlying v-if="currentAirPortStatus == 2" @switchView="switchViewFn" />
</div>
</div>
</div>
<!-- 直播播放器 -->
<teleport :to="outVideo_teleport" v-if="showLiveVideo && currentAirPortStatus == 1">
<LivePlayer
class="h-full absolute left-0 right-0 top-0 right-0 m-auto"
v-show="showLiveVideo && LiveOptions_out.videoUrl?.includes('.flv')"
ref="OutlivePlayerRef"
:options="LiveOptions_out"
/>
</teleport>
<teleport :to="inVideo_teleport" v-if="showLiveVideo && currentAirPortStatus == 1">
<LivePlayer
class="h-full absolute left-0 right-0 top-0 right-0 m-auto"
v-show="showLiveVideo && LiveOptions_in.videoUrl?.includes('.flv')"
ref="inlivePlayerRef"
:options="LiveOptions_in"
/>
</teleport>
<!-- 无人机视角 -->
<teleport :to="planeVideo_teleport" v-if="showLiveVideo && currentAirPortStatus == 2">
<LivePlayer
class="h-full absolute left-0 right-0 top-0 right-0 m-auto"
v-show="showLiveVideo && LiveOptions_plane.videoUrl?.includes('.flv')"
ref="planelivePlayerRef"
:options="LiveOptions_in"
/>
</teleport>
<!-- map -->
<teleport :to="map_teleport">
<iframe
v-if="showIframeMap"
id="virturalDrive_iframe_full"
ref="virturalDrive_iframe_full"
src="https://gisdata.t-aaron.com/virturalDrive/VirturalDrive.html"
@load="iframeLoaded"
></iframe>
</teleport>
</template>
<style lang="scss" scoped>
:deep(.liveplayer) {
height: 100% !important;
}
:deep(.player-wrapper) {
height: 100%;
}
:deep(.video-wrapper) {
height: 100%;
}
:deep(.vjs-control-bar) {
display: none !important;
}
.rem-header {
background: linear-gradient(0deg, #051a31, #09315d);
}
.rem-bottom-area {
}
.leftPlane {
--leftPlaneWidth: v-bind(leftPlaneWidth);
width: var(--leftPlaneWidth);
}
.rightPlane {
--rightPlaneWidth: v-bind(rightPlaneWidth);
width: var(--rightPlaneWidth);
}
.fullScreen {
--rightPlaneHeight: v-bind(rightPlaneHeight);
--rightPlaneBottom: v-bind(rightPlaneBottom);
height: var(--rightPlaneHeight);
bottom: var(--rightPlaneBottom);
background: #ffffff;
}
#virturalDrive_iframe_full {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,12 @@
<script setup>
//
</script>
<template>
<div class="w-[506px] flex flex-row items-center">
<div class="w-[136px] h-[46px] bg-[#ff0000]"></div>
<div class="w-[210px] h-[46px] bg-[#8a1515] mx-3"></div>
<div class="w-[136px] h-[46px] bg-[#8a1515]"></div>
</div>
</template>

View File

@ -0,0 +1,61 @@
<script setup>
import {
ref,
defineAsyncComponent,
onMounted,
computed,
watch,
toRaw,
defineProps,
reactive,
} from 'vue'
let current = ref()
let items = reactive([
{
key: '1',
label: '舱外画面',
title: 'Option1',
},
{
key: '2',
label: '舱内画面',
title: 'Option2',
},
{
key: '3',
label: '无人机画面',
title: 'Option3',
},
])
const arrow = ref('show')
const mergedArrow = computed(() => {
switch (arrow.value) {
case 'show':
return true
case 'hide':
return false
case 'center':
default:
return { pointAtCenter: true }
}
})
</script>
<template>
<a-tooltip
overlayClassName="blueToolTip"
placement="leftTop"
:arrow="mergedArrow"
:getPopupContainer="(triggerNode) => triggerNode.parentElement"
>
<template #title>
<a-menu class="blueMenus" v-model:selectedKeys="current" :items="items"> </a-menu>
</template>
<a-button type="primary" size="default">
<template #icon>
<span class="iconfont icon-qiehuanzhanghao text-base/5 text-[#ffffff] text-center"></span>
</template>
</a-button>
</a-tooltip>
</template>

186
src/views/cell/detail.vue Normal file
View File

@ -0,0 +1,186 @@
<script setup>
import {
ref,
defineAsyncComponent,
defineEmits,
onMounted,
watch,
toRaw,
defineProps,
reactive,
} from 'vue'
//
const VModal = defineAsyncComponent(() => import('@/components/custom_popup/vuxpopup.vue'))
const props = defineProps({
rowData: {
type: Object,
default: {},
},
})
const emits = defineEmits(['popupBtn'])
//form
const Form = defineAsyncComponent(() => import('@/components/Forms/form.vue'))
//
const TableComp = defineAsyncComponent(() => import('@/components/vxetable/table.vue'))
//
const PopupObj = reactive({
title: '电池查看', //
padding: '0 0 0 0',
wPercent: '90',
hPercent: '90',
minWidth: 500, //
minHeight: 200, //
hideFooter: true, //
resize: true,
// commitBtnTxt: "",
// cancelBtnTxt: "",
})
//form
const formObj = ref({
id: 'detailForm',
labelWidth: 100, //,
col: 3,
// options
selectData: {},
rules: {
//form
equipModel: [{ required: true, message: '该项不能为空', trigger: 'blur' }],
},
formArr: [
{
type: 'dateRangeComp',
title: '通知时间',
key: 'noticeTime',
},
],
queryBtnArr: [
{
key: 'query',
type: 'primary',
title: '查询',
},
{
key: 'reset',
type: 'default',
title: '重置',
},
{
key: 'export',
type: 'primary',
title: '导出',
},
],
})
//
let tableData = ref({
id: 'deviceTable',
loading: false,
showFooter: true,
notArrowDrag: true,
// offsetHeight: 1000, //
footerMethod: null, //null,tablemap ,top
validRules: {},
col: [
{
type: 'column',
column: [
// {
// type:'checkbox',
// width:60,
// align:'center'
// },
{
type: 'seq',
title: '序号',
width: 60,
align: 'center',
},
{
title: '时间',
key: 'airportName',
minWidth: 100,
align: 'center',
},
{
title: '电压(V)',
key: 'droneName',
minWidth: 100,
align: 'center',
},
],
},
],
tableData: null,
})
//
let paginationData = reactive({
currentPage: 1,
pageSize: 10,
size: [10, 20, 30, 50],
total: 0,
})
const formChange = (e, item) => {}
const formBtnEvent = (e, item) => {
if (item.key == 'reset') {
//form
formRef.value.clearFormData()
}
//
querybatteryRecordApiFn()
}
const tableOperationFn = () => {}
//
const popupBtn = (type) => {
emits('popupBtn')
}
watch(
() => props.rowData,
(val) => {
console.log(val)
},
{
deep: true,
immediate: true,
},
)
</script>
<template>
<VModal class="popupModal" :popObj="PopupObj" @popupBtn="popupBtn">
<template v-slot:slot>
<el-container style="height: 100%">
<el-header height="90px">
<p style="margin-top: 10px" :class="$style.title">
{{ props.rowData?.airportName }}{{ props.rowData?.droneName }}{{
props.rowData?.batteryCode
}}
</p>
<Form
ref="formRef"
:formObj="formObj"
@formChange="formChange"
@formBtnEvent="formBtnEvent"
></Form>
</el-header>
<el-main>
<TableComp
ref="tableRef"
:tableObj="tableData"
@operationFn="tableOperationFn"
></TableComp>
</el-main>
</el-container>
</template>
</VModal>
</template>
<style lang="less" scoped module>
.title {
font-size: 16px;
font-weight: 600;
}
</style>

View File

@ -0,0 +1,113 @@
class WebSocketService {
constructor() {
this.socketCount = 30
}
initConnection(conn) {
// 先清理旧连接
conn.clearConnection()
if (conn.closed) return
conn.socket = new WebSocket(conn.url)
conn.socket.onopen = () => {
console.log(`Connection opened: ${conn.url}`)
function sendFn() {
conn.intervalId = setTimeout(() => {
// if (conn.closed) return
// 超过 30 秒没收到心跳就重连 (最多重连 30 次)
if (Date.now() - conn.lastHeartbeat > 30000 && conn.againTimes < this.socketCount) {
let againTimes = conn.againTimes++
console.log('ws心跳超时尝试重连222', againTimes, conn.againTimes)
this.initConnection(conn)
//超过30次了
clearTimeout(conn.intervalId)
return
} else {
conn.socket.send('ping')
sendFn()
}
}, 10000)
}
sendFn()
}
conn.socket.onmessage = (event) => {
conn.lastHeartbeat = Date.now()
let msg
try {
msg = JSON.parse(event.data)
} catch {
// 非 JSON 消息直接转给业务
return conn.onmessage(event)
}
if (msg.type === 'pong') {
console.log('收到心跳 pong')
} else {
conn.onmessage(event)
}
}
conn.socket.onerror = (err) => {
console.error(`Error in connection ${conn.url}:`, err)
conn.onerror(err)
}
conn.socket.onclose = () => {
console.log(`Connection closed: ${conn.url}`)
conn.onclose()
if (!conn.closed) {
let againTimes = conn.againTimes++
console.log('ws心跳超时尝试重连1111', againTimes, conn.againTimes)
// 超过 30 秒没收到心跳就重连 (最多重连 30 次)
if (againTimes > this.socketCount) {
return
}
conn.intervalId = setTimeout(() => {
// 自动重连
this.initConnection(conn)
}, 10000)
} else {
clearTimeout(conn.intervalId)
}
}
}
createConnection(url) {
const conn = {
url,
socket: null,
lastHeartbeat: Date.now(),
intervalId: null,
closed: false,
againTimes: 0, // 重连次数
onopen: () => {},
onmessage: () => {},
onerror: () => {},
onclose: () => {},
sendMessage(msg) {
if (this.closed) {
console.error('WebSocket 已关闭')
return
}
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(msg)
} else {
console.error('WebSocket 未打开')
}
},
clearConnection() {
if (this.socket) this.socket.close()
if (this.intervalId) clearTimeout(this.intervalId)
},
close() {
console.log(`Connection 人工关闭`)
this.closed = true
this.clearConnection()
},
}
this.initConnection(conn)
return conn
}
}
export default new WebSocketService()

176
src/workers/worker.js Normal file
View File

@ -0,0 +1,176 @@
addEventListener('message', (e) => {
const { data } = e
let datas = JSON.parse(data)
// console.log(datas)
if (!WebSocket) message.error('你的浏览器不支持WebSocket')
const baseUrl = import.meta.env.VITE_APP_API_BASE_URL.match(/^http(s)?:\/\/([^\/]*).*/)[2]
// if (process.env.NODE_ENV !== 'development') {
// data.wsAirport = WebSocketService.createConnection(`wss://${baseUrl}/airport/socket/webSocket/${url}`)
// } else {
// data.wsAirport = WebSocketService.createConnection(`wss://${baseUrl}/airport/socket/webSocket/${url}`)
// }
if (datas.id && datas.code) {
// return
//开始发送sockets请求
//当前机场socket
let wsAirport = new WebSocketService()
wsAirport.createConnection(`wss://${baseUrl}/airport/socket/webSocket/0:0:${datas.code}`)
//日志socket
// let wsLog = new WebSocketService().createConnection(
// `wss://${baseUrl}/airport/socket/logSocket/${datas.id}`,
// )
wsAirport.onmessage = (event) => {
if (!WebSocketService.isJsonString(event.data)) return
// const wsData = JSON.parse(event.data)
// console.log(wsData)
//发送给主线程
return postMessage(event.data)
// const currentDroneInfo data.currentDroneInfo || null
let droneYaw = null
// if(data.currentDroneInfo){
// // 定义起点和终点坐标
// const startPoint = turf.point([ data.currentDroneInfo.longitude, data.currentDroneInfo.latitude]) // 扬子大队
// const endPoint = turf.point([wsData.lon, wsData.lat]) // 六合大队
// // 计算两点之间的方位角(偏航角)
// droneYaw = turf.bearing(startPoint, endPoint)
// }
// 无人机
// data.wsAirportInfo = wsData ? { ...wsData, droneYaw } : {}
// 天气数据
// data.weather = wsData.weather || {}
}
// 日志消息
// wsLog.onmessage = (event) => {
// if (!WebSocketService.isJsonString(event.data)) return
// const wsData = JSON.parse(event.data)
// console.log(wsData)
// }
}
// return postMessage('111111111111111')
// if (airLineBuffer) {
//向主线程发送数据
// return postMessage(JSON.stringify(airLineBuffer))
// }
})
const socketCount = 30
class WebSocketService {
constructor() {}
initConnection(conn, that) {
// 先清理旧连接
conn.clearConnection()
if (conn.closed) return
conn.socket = new WebSocket(conn.url)
conn.socket.onopen = () => {
// console.log(`Connection opened: ${conn.url}`)
function sendFn() {
// console.log(that)
conn.intervalId = setTimeout(() => {
// if (conn.closed) return
// 超过 30 秒没收到心跳就重连 (最多重连 30 次)
if (Date.now() - conn.lastHeartbeat > 30000 && conn.againTimes < socketCount) {
let againTimes = conn.againTimes++
console.log('ws心跳超时尝试重连222', againTimes, conn.againTimes)
that.initConnection(conn)
//超过30次了
clearTimeout(conn.intervalId)
return
} else {
conn.socket.send('ping')
sendFn()
}
}, 10000)
}
sendFn()
}
conn.socket.onmessage = (event) => {
conn.lastHeartbeat = Date.now()
let msg
try {
msg = JSON.parse(event.data)
} catch {
// 非 JSON 消息直接转给业务
return conn.onmessage(event)
}
if (msg.type === 'pong') {
console.log('收到心跳 pong')
} else {
conn.onmessage(event)
}
}
conn.socket.onerror = (err) => {
console.error(`Error in connection ${conn.url}:`, err)
conn.onerror(err)
}
conn.socket.onclose = () => {
console.log(`Connection closed: ${conn.url}`)
conn.onclose()
if (!conn.closed) {
let againTimes = conn.againTimes++
console.log('ws心跳超时尝试重连1111', againTimes, conn.againTimes)
// 超过 30 秒没收到心跳就重连 (最多重连 30 次)
if (againTimes > socketCount) {
return
}
conn.intervalId = setTimeout(() => {
// 自动重连
this.initConnection(conn)
}, 10000)
} else {
clearTimeout(conn.intervalId)
}
}
}
createConnection(url) {
const conn = {
url,
socket: null,
lastHeartbeat: Date.now(),
intervalId: null,
closed: false,
againTimes: 0, // 重连次数
onopen: () => {},
onmessage: () => {},
onerror: () => {},
onclose: () => {},
sendMessage(msg) {
if (this.closed) {
console.error('WebSocket 已关闭')
return
}
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(msg)
} else {
console.error('WebSocket 未打开')
}
},
clearConnection() {
if (this.socket) this.socket.close()
if (this.intervalId) clearTimeout(this.intervalId)
},
close() {
console.log(`Connection 人工关闭`)
this.closed = true
this.clearConnection()
},
}
this.initConnection(conn, this)
return conn
}
static isJsonString(str) {
try {
JSON.parse(str)
} catch (e) {
return false
}
return true
}
}

24
tailwind.config.js Normal file
View File

@ -0,0 +1,24 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {
// 添加自定义的断点
screens: {
md: '0px',
lg: '500px',
xk: '1366px',
x1k: '1920px',
x2k: '2560px',
x4k: '3840px',
},
//自定义宽度
width: {},
//自定义高度
height: {
200: '200px',
},
},
},
plugins: [],
}

125
vite.config.js Normal file
View File

@ -0,0 +1,125 @@
import { fileURLToPath, URL } from 'node:url'
import copy from 'rollup-plugin-copy'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import qiankun from 'vite-plugin-qiankun'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'
const root = process.cwd()
function pathResolve(dir) {
return resolve(root, '.', dir)
}
// https://vite.dev/config/
export default ({ command, mode }) => {
let env = {}
const isBuild = command === 'build'
if (!isBuild) {
env = loadEnv(process.argv[3] === '--mode' ? process.argv[4] : process.argv[3], root)
} else {
env = loadEnv(mode, root)
}
return {
//base: '0.0.0.0', //开发环境和自己单独程序时候打包环境使用
//base: 'http://192.168.50.197:5173/',//微前端打包环境使用,最终打包的时候,微前端需要修改地址,否则访问不了
//base: 'http://127.0.0.1:8080/',
plugins: [
vue(),
copy({
targets: [
{
src: 'node_modules/@liveqing/liveplayer-v3/dist/component/liveplayer-lib.min.js',
dest: 'public/js',
},
],
}),
vueJsx(),
vueDevTools(),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
//vxe table按需加载
lazyImport({
resolvers: [
VxeResolver({
libraryName: 'vxe-table',
}),
VxeResolver({
libraryName: 'vxe-pc-ui',
}),
],
}),
qiankun('qiankunother', {
useDevMode: true,
}),
],
define: {
'process.env': {
env: env,
},
},
resolve: {
extensions: [
'.mjs',
'.js',
'.ts',
'.jsx',
'.tsx',
'.json',
'.less',
'.css',
'.jpeg',
'.jpg',
'.png',
'.svg',
'mkv',
'mp4',
],
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "@/assets/style/variables.scss" as *;',
},
// 配置全局共享变量less
less: {
additionalData: '@import "@/assets/style/main.less";',
},
},
},
server: {
host: '0.0.0.0',
port: 8080,
cors: true,
//http: true,
open: false, //不自动开启
proxy: {
'/airport': {
target: env.VITE_APP_API_BASE_URL,
changeOrigin: true,
logLeve: 'debug', //输出真实的地址
pathRewrite: {
'^/airport': '',
},
},
},
},
}
}