[
  {
    "path": ".eslintignore",
    "content": "/src/vendors/**\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    browser: true,\n    commonjs: true,\n    es2021: true,\n    node: true\n  },\n  extends: ['standard', 'plugin:vue/vue3-essential'],\n  parserOptions: {\n    ecmaVersion: 12,\n    parser: '@typescript-eslint/parser'\n  },\n  plugins: ['vue', '@typescript-eslint'],\n  rules: {\n    'comma-dangle': 'off',\n    'import/no-absolute-path': 'off',\n    'no-unused-vars': 'off',\n    camelcase: 'off',\n    'no-redeclare': 'off',\n    'vue/no-unused-components': 'off'\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\nnode_modules/\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n# .vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n.history\n/coverage\n/backup\nnode_modules\n"
  },
  {
    "path": ".gitignore copy",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n"
  },
  {
    "path": ".npmrc",
    "content": "registry=https://registry.npmmirror.com/"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 DJI-SDK\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 关于DJI Cloud API Demo 终止维护公告\n\n发布日期：2025年4月10日\n\n1.项目终止维护说明\n即日起，大疆创新(DJI)将停止对DJI Cloud API Demo (地址：https://github.com/dji-sdk/Cloud-API-Demo-Web、https://github.com/dji-sdk/DJI-Cloud-API-Demo）示例项目的更新与技术支持。\n该项目作为官方提供的云端集成参考实现，旨在辅助开发者理解API调用逻辑。并非生产级解决方案，可能存在未修复的安全隐患（如数据泄露、未授权访问等）。请避免在生产环境中直接使用Demo中的代码，若直接使用我们强烈建议您启动安全自查，或避免将基于该Demo的服务暴露于公网环境。\n\n2.免责声明\n因直接使用Demo代码导致的业务损失、数据风险或第三方纠纷，DJI将不承担任何责任。\n\n3.后续支持\n如有疑问，请联系DJI开发者支持团队（邮箱：developer@dji.com)或访问大疆开发者社区获取最新技术资源。\n感谢您一直以来的理解与支持！\n\n# DJI Cloud API\n\n## What is the DJI Cloud API?\n\nThe launch of the Cloud API mainly solves the problem of developers reinventing the wheel. For developers who do not need in-depth customization of APP, they can directly use DJI Pilot2 to communicate with the third cloud platform, and developers can focus on the development and implementation of cloud service interfaces.\n\n## Docker\n\nIf you don't want to install the development environment, you can try deploying with docker. [Click the link to download.](https://terra-sz-hc1pro-cloudapi.oss-cn-shenzhen.aliyuncs.com/c0af9fe0d7eb4f35a8fe5b695e4d0b96/docker/cloud_api_sample_docker.zip)\n\n## Usage\n\nFor more documentation, please visit the [DJI Developer Documentation](https://developer.dji.com/doc/cloud-api-tutorial/cn/).\n\n## Latest Release\n\nCloud API 1.10.0 was released on 7 April 2024. For more information, please visit the [Release Note](https://developer.dji.com/doc/cloud-api-tutorial/cn/).\n\n## License\n\nCloud API is MIT-licensed. Please refer to the LICENSE file for more information.\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>demo-web</title>\n  </head>\n  <body>\n    <div id=\"demo-app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"demo-web\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"serve\": \"vite\",\n    \"build:test\": \"vite build --mode stag\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"lint\": \"eslint --fix\"\n  },\n  \"dependencies\": {\n    \"@amap/amap-jsapi-loader\": \"^1.0.1\",\n    \"@ant-design/icons-vue\": \"^6.0.1\",\n    \"@vitejs/plugin-legacy\": \"^1.6.2\",\n    \"agora-rtc-sdk-ng\": \"^4.12.1\",\n    \"ant-design-vue\": \"^2.2.8\",\n    \"axios\": \"^0.21.1\",\n    \"eventemitter3\": \"^5.0.0\",\n    \"mitt\": \"^3.0.0\",\n    \"mqtt\": \"^4.3.7\",\n    \"query-string\": \"^7.0.1\",\n    \"reconnecting-websocket\": \"^4.4.0\",\n    \"vconsole\": \"^3.8.1\",\n    \"vite-plugin-components\": \"^0.13.3\",\n    \"vite-plugin-importer\": \"^0.2.5\",\n    \"vite-plugin-optimize-persist\": \"^0.1.2\",\n    \"vite-plugin-package-config\": \"^0.1.1\",\n    \"vue\": \"^3.2.26\",\n    \"vue-cookies\": \"^1.7.4\",\n    \"vue-i18n\": \"^9.1.6\",\n    \"vue-router\": \"4\",\n    \"vuex\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^16.3.2\",\n    \"@types/urlencode\": \"^1.1.2\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.8.1\",\n    \"@typescript-eslint/parser\": \"^5.8.1\",\n    \"@vitejs/plugin-vue\": \"^1.2.4\",\n    \"@vue/compiler-sfc\": \"^3.0.5\",\n    \"eslint\": \"^7.30.0\",\n    \"eslint-config-standard\": \"^16.0.3\",\n    \"eslint-plugin-import\": \"^2.23.4\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-promise\": \"^5.1.0\",\n    \"eslint-plugin-vue\": \"^7.13.0\",\n    \"rollup-plugin-external-globals\": \"^0.6.1\",\n    \"sass\": \"^1.35.1\",\n    \"typescript\": \"^4.5.4\",\n    \"vite\": \"^2.4.0\",\n    \"vite-plugin-eslint\": \"^1.3.0\",\n    \"vite-plugin-style-import\": \"^1.0.1\",\n    \"vite-plugin-svg-icons\": \"^1.0.5\",\n    \"vite-plugin-vconsole\": \"^1.1.0\",\n    \"vue-tsc\": \"^0.0.24\"\n  },\n  \"license\": \"ISC\",\n  \"vite\": {\n    \"optimizeDeps\": {\n      \"include\": [\n        \"@amap/amap-jsapi-loader\",\n        \"@ant-design/icons-vue\",\n        \"@vue/reactivity\",\n        \"agora-rtc-sdk-ng\",\n        \"ant-design-vue\",\n        \"ant-design-vue/es\",\n        \"ant-design-vue/es/avatar/style/css\",\n        \"ant-design-vue/es/breadcrumb/style/css\",\n        \"ant-design-vue/es/button/style/css\",\n        \"ant-design-vue/es/checkbox/style/css\",\n        \"ant-design-vue/es/col/style/css\",\n        \"ant-design-vue/es/collapse/style/css\",\n        \"ant-design-vue/es/date-picker/style/css\",\n        \"ant-design-vue/es/divider/style/css\",\n        \"ant-design-vue/es/drawer/style/css\",\n        \"ant-design-vue/es/dropdown/style/css\",\n        \"ant-design-vue/es/empty/style/css\",\n        \"ant-design-vue/es/form/style/css\",\n        \"ant-design-vue/es/image/style/css\",\n        \"ant-design-vue/es/input-number/style/css\",\n        \"ant-design-vue/es/input/style/css\",\n        \"ant-design-vue/es/layout/style/css\",\n        \"ant-design-vue/es/menu/style/css\",\n        \"ant-design-vue/es/message/style/css\",\n        \"ant-design-vue/es/modal/style/css\",\n        \"ant-design-vue/es/pagination/style/css\",\n        \"ant-design-vue/es/popconfirm/style/css\",\n        \"ant-design-vue/es/popover/style/css\",\n        \"ant-design-vue/es/progress/style/css\",\n        \"ant-design-vue/es/radio/style/css\",\n        \"ant-design-vue/es/row/style/css\",\n        \"ant-design-vue/es/select/style/css\",\n        \"ant-design-vue/es/space/style/css\",\n        \"ant-design-vue/es/spin/style/css\",\n        \"ant-design-vue/es/switch/style/css\",\n        \"ant-design-vue/es/table/style/css\",\n        \"ant-design-vue/es/tag/style/css\",\n        \"ant-design-vue/es/time-picker/style/css\",\n        \"ant-design-vue/es/tooltip/style/css\",\n        \"ant-design-vue/es/tree/style/css\",\n        \"ant-design-vue/es/upload/style/css\",\n        \"axios\",\n        \"eventemitter3\",\n        \"lodash\",\n        \"mitt\",\n        \"moment\",\n        \"mqtt\",\n        \"mqtt/dist/mqtt.min\",\n        \"reconnecting-websocket\",\n        \"vconsole\",\n        \"vue\",\n        \"vue-router\",\n        \"vuex\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "src/App.vue",
    "content": "<template>\n  <div class=\"demo-app\">\n    <router-view />\n    <!-- <div class=\"map-wrapper\">\n      <GMap/>\n    </div> -->\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { computed, defineComponent, ref } from 'vue'\nimport { useMyStore } from './store'\nimport GMap from '/@/components/GMap.vue'\n\nexport default defineComponent({\n  name: 'App',\n  components: { GMap },\n\n  setup () {\n    const store = useMyStore()\n    return {}\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.demo-app {\n  width: 100%;\n  height: 100%;\n\n  .map-wrapper {\n    height: 100%;\n    width: 100%;\n  }\n}\n</style>\n\n<style lang=\"scss\">\n#demo-app {\n  width: 100%;\n  height: 100%\n}\n</style>\n"
  },
  {
    "path": "src/antd.ts",
    "content": "// import Icon from '@ant-design/icons-vue'\nimport * as antDesign from 'ant-design-vue'\nimport 'ant-design-vue/dist/antd.css'\nimport { App } from 'vue'\nimport svgIcon from '/@/components/svgIcon.vue'\n\nexport const antComponents = {\n  install (app: App): void {\n    app.use(antDesign)\n    // app.component('Icon', Icon)\n    app.component('svg-icon', svgIcon)\n  }\n}\n"
  },
  {
    "path": "src/api/device-cmd/index.ts",
    "content": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'\n\nconst CMD_API_PREFIX = '/control/api/v1'\n\nexport interface SendCmdParams {\n  dock_sn: string, // 机场cn\n  device_cmd: DeviceCmd // 指令\n}\n\nexport interface PostSendCmdBody {\n  action: DeviceCmdItemAction\n}\n/**\n * 发送机场控制指令\n * @param params\n * @returns\n */\n// /control/api/v1/devices/{dock_sn}/jobs/{service_identifier}\nexport async function postSendCmd (params: SendCmdParams, body?: PostSendCmdBody): Promise<IWorkspaceResponse<{}>> {\n  const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, body)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/device-log/index.ts",
    "content": "import request, { IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'\nimport { DeviceValue, DOMAIN } from '/@/types/device'\nimport { DeviceLogUploadStatusEnum } from '/@/types/device-log'\nimport { ELocalStorageKey } from '/@/types'\nimport { CURRENT_CONFIG } from '/@/api/http/config'\n\nconst MNG_API_PREFIX = '/manage/api/v1'\n\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''\n\nexport interface GetDeviceUploadLogListParams {\n  device_sn: string,\n  page: number,\n  page_size: number,\n  begin_time?: number, // 开始时间\n  end_time?: number, // 结束时间\n  status?: DeviceLogUploadStatusEnum, // 日志上传状态\n  logs_information?: string // 搜索内容\n}\n\nexport interface BriefDeviceInfo {\n  sn: string,\n  device_model: DeviceValue,\n  device_callsign: string\n}\n\nexport interface DeviceLogProgressInfo{\n  device_sn: string,\n  device_model_domain: DOMAIN,\n  progress: number, // 进度\n  result: number, // 上传结果\n  upload_rate: number, // 上传速率\n  status: DeviceLogUploadStatusEnum // 上传状态\n}\n\nexport interface DeviceLogItem {\n  boot_index: number, // 日志id\n  start_time: number, // 日志开始时间\n  end_time: number, // 日志结束时间\n  size: number // 日志大小\n}\n\nexport interface DeviceLogFileInfo {\n  device_sn: string,\n  module: DOMAIN,\n  result: number,\n  object_key: string,\n  file_id: string,\n  list: DeviceLogItem[]\n}\n\nexport interface DeviceLogFileListInfo {\n  files: DeviceLogFileInfo[]\n}\n\nexport interface GetDeviceUploadLogListRsp {\n  logs_id: string, // 记录id\n  happen_time: string, // 发生时间\n  user_name: string, // 用户\n  logs_information: string, // 异常描述\n  create_time: string, // 上传时间\n  status:DeviceLogUploadStatusEnum, // 日志上传状态\n  device_topo:{ // 设备topo\n    hosts: BriefDeviceInfo[],\n    parents: BriefDeviceInfo[]\n  },\n  logs_progress: DeviceLogProgressInfo[], // 日志上传进度\n  device_logs: DeviceLogFileListInfo // 设备日志\n}\n\n/**\n * 获取设备上传日志列表信息\n * @param params\n * @returns\n */\nexport async function getDeviceUploadLogList (params: GetDeviceUploadLogListParams): Promise<IListWorkspaceResponse<GetDeviceUploadLogListRsp>> {\n  const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs-uploaded`, {\n    params: params\n  })\n  return resp.data\n}\n\nexport interface GetDeviceLogListParams{\n  device_sn: string,\n  domain: DOMAIN[]\n}\n\n/**\n * 获取设备日志列表信息\n * @param params\n * @returns\n */\nexport async function getDeviceLogList (params: GetDeviceLogListParams): Promise<IWorkspaceResponse<DeviceLogFileListInfo>> {\n  const domain = params.domain ? params.domain : []\n  const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${params.device_sn}/logs`, {\n    params: {\n      domain_list: domain.join(',')\n    }\n  })\n  return resp.data\n}\n\nexport interface UploadDeviceLogBody {\n  device_sn: string\n  happen_time: string // 发生时间\n  logs_information: string // 异常描述\n  files:{\n    list: DeviceLogItem[],\n    device_sn: string,\n    module: DOMAIN\n  }[]\n}\n\n/**\n * 上传设备日志\n * @param body\n * @returns\n */\nexport async function postDeviceUpgrade (body: UploadDeviceLogBody): Promise<IWorkspaceResponse<{}>> {\n  const resp = await request.post(`${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs`, body)\n  return resp.data\n}\n\nexport type DeviceLogUploadAction = 'cancel'\n\nexport interface CancelDeviceLogUploadBody {\n  device_sn: string\n  status: DeviceLogUploadAction\n  module_list: DOMAIN[]\n}\n\n// 取消上传\nexport async function cancelDeviceLogUpload (body: CancelDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> {\n  const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs`\n  const result = await request.delete(url, {\n    data: body\n  })\n  return result.data\n}\n\nexport interface DeleteDeviceLogUploadBody {\n  device_sn: string\n  logs_id: string\n}\n\n// 取消上传\nexport async function deleteDeviceLogUpload (body: DeleteDeviceLogUploadBody): Promise<IWorkspaceResponse<{}>> {\n  const url = `${MNG_API_PREFIX}/workspaces/${workspaceId}/devices/${body.device_sn}/logs/${body.logs_id}`\n  const result = await request.delete(url, {\n    data: body\n  })\n  return result.data\n}\n\nexport interface GetUploadDeviceLogUrlParams{\n  logs_id: string,\n  file_id: string,\n}\n\n// export interface GetUploadDeviceLogRsp{\n//   url: string\n// }\n\n/**\n * 获取设备上传日志url\n * @param params\n * @returns\n */\nexport async function getUploadDeviceLogUrl (params: GetUploadDeviceLogUrlParams): Promise<IWorkspaceResponse<string>> {\n  const resp = await request.get(`${MNG_API_PREFIX}/workspaces/${workspaceId}/logs/${params.logs_id}/url/${params.file_id}`)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/device-setting/index.ts",
    "content": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { ELocalStorageKey } from '/@/types'\nimport { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from '/@/types/device-setting'\n\nconst MNG_API_PREFIX = '/manage/api/v1'\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''\n\nexport interface PutDevicePropsBody {\n  night_lights_state?: NightLightsStateEnum;// 夜航灯开关\n  height_limit?: number;// 限高设置\n  distance_limit_status?: DistanceLimitStatus;// 限远开关\n  obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置\n}\n\n/**\n * 设置设备属性\n * @param params\n * @returns\n */\n//  /manage/api/v1/devices/{{workspace_id}}/devices/{{device_sn}}/property\nexport async function putDeviceProps (deviceSn: string, body: PutDevicePropsBody): Promise<IWorkspaceResponse<{}>> {\n  const resp = await request.put(`${MNG_API_PREFIX}/devices/${workspaceId}/devices/${deviceSn}/property`, body)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/device-upgrade/index.ts",
    "content": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { DeviceFirmwareTypeEnum } from '/@/types/device'\n\nconst MNG_API_PREFIX = '/manage/api/v1'\n\nexport interface GetDeviceUpgradeInfoParams {\n  device_name: string\n}\n\nexport interface GetDeviceUpgradeInfoRsp {\n  device_name: string\n  product_version: string\n  release_note: string\n  released_time: string\n}\n\n/**\n * 获取设备升级信息\n * @param params\n * @returns\n */\nexport async function getDeviceUpgradeInfo (params: GetDeviceUpgradeInfoParams): Promise<IWorkspaceResponse<GetDeviceUpgradeInfoRsp[]>> {\n  const resp = await request.get(`${MNG_API_PREFIX}/workspaces/firmware-release-notes/latest`, {\n    params: params\n  })\n  return resp.data\n}\n\nexport interface UpgradeDeviceInfo {\n  device_name: string,\n  sn: string,\n  product_version: string,\n  firmware_upgrade_type: DeviceFirmwareTypeEnum // 1-普通升级，2-一致性升级\n}\n\nexport type DeviceUpgradeBody = UpgradeDeviceInfo[]\n\n/**\n * 设备升级\n * @param workspace_id\n * @param body\n * @returns\n */\nexport async function postDeviceUpgrade (workspace_id: string, body: DeviceUpgradeBody): Promise<IWorkspaceResponse<{}>> {\n  const resp = await request.post(`${MNG_API_PREFIX}/devices/${workspace_id}/devices/ota`, body)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/drc.ts",
    "content": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { ELocalStorageKey } from '/@/types'\n\n// DRC 链路\nconst DRC_API_PREFIX = '/control/api/v1'\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''\n\nexport interface PostDrcBody {\n  client_id?: string // token过期时，用于续期则必填\n  expire_sec?: number // 过期时间，单位秒，默认3600\n}\n\nexport interface DrcParams {\n  address: string\n  username: string\n  password: string\n  client_id: string\n  expire_time: number // 过期时间\n  enable_tls: boolean // 是否开启tls\n}\n\n// 获取 mqtt 连接认证\nexport async function postDrc (body: PostDrcBody): Promise<IWorkspaceResponse<DrcParams>> {\n  const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/connect`, body)\n  return resp.data\n}\n\nexport interface DrcEnterBody {\n  client_id: string\n  dock_sn: string\n  expire_sec?: number // 过期时间，单位秒，默认3600\n  device_info?: {\n    osd_frequency?: number\n    hsi_frequency?: number\n  }\n}\n\nexport interface DrcEnterResp {\n  sub: string[] // 需要订阅接收的topic\n  pub: string[] // 推送的topic地址\n}\n\n// 进入飞行控制 （建立drc连接&获取云控控制权）\nexport async function postDrcEnter (body: DrcEnterBody): Promise<IWorkspaceResponse<DrcEnterResp>> {\n  const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/enter`, body)\n  return resp.data\n}\n\nexport interface DrcExitBody {\n  client_id: string\n  dock_sn: string\n}\n\n// 退出飞行控制 （退出drc连接&退出云控控制权）\nexport async function postDrcExit (body: DrcExitBody): Promise<IWorkspaceResponse<null>> {\n  const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/exit`, body)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/drone-control/drone.ts",
    "content": "import request, { IWorkspaceResponse } from '/@/api/http/request'\n// import { ELocalStorageKey } from '/@/types'\n\nconst API_PREFIX = '/control/api/v1'\n// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '\n\n// 获取飞行控制权\nexport async function postFlightAuth (sn: string): Promise<IWorkspaceResponse<null>> {\n  const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/flight`)\n  return resp.data\n}\nexport enum WaylineLostControlActionInCommandFlight {\n  CONTINUE = 0,\n  EXEC_LOST_ACTION = 1\n}\nexport enum LostControlActionInCommandFLight {\n  HOVER = 0, // 悬停\n  Land = 1, // 着陆\n  RETURN_HOME = 2, // 返航\n}\nexport enum ERthMode {\n  SMART = 0,\n  SETTING = 1\n}\nexport enum ECommanderModeLostAction {\n  CONTINUE = 0,\n  EXEC_LOST_ACTION = 1\n}\nexport enum ECommanderFlightMode {\n  SMART = 0,\n  SETTING = 1\n}\nexport interface PointBody {\n  latitude: number;\n  longitude: number;\n  height: number;\n}\nexport interface PostFlyToPointBody {\n  max_speed: number,\n  points: PointBody[]\n}\n\n// 飞向目标点\nexport async function postFlyToPoint (sn: string, body: PostFlyToPointBody): Promise<IWorkspaceResponse<null>> {\n  const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`, body)\n  return resp.data\n}\n\n// 停止飞向目标点\nexport async function deleteFlyToPoint (sn: string): Promise<IWorkspaceResponse<null>> {\n  const resp = await request.delete(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`)\n  return resp.data\n}\n\nexport interface PostTakeoffToPointBody{\n  target_height: number;\n  target_latitude: number;\n  target_longitude: number;\n  security_takeoff_height: number; // 安全起飞高\n  max_speed: number; // flyto过程中能达到的最大速度, 单位m/s 跟飞机档位有关\n  rc_lost_action: LostControlActionInCommandFLight; // 失控行为\n  rth_altitude: number; // 返航高度\n  exit_wayline_when_rc_lost: WaylineLostControlActionInCommandFlight;\n  rth_mode: ERthMode;\n  commander_mode_lost_action: ECommanderModeLostAction;\n  commander_flight_mode: ECommanderFlightMode;\n  commander_flight_height: number;\n}\n\n// 一键起飞\nexport async function postTakeoffToPoint (sn: string, body: PostTakeoffToPointBody): Promise<IWorkspaceResponse<null>> {\n  const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/takeoff-to-point`, body)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/drone-control/payload.ts",
    "content": "import request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { CameraType, CameraMode } from '/@/types/live-stream'\nimport { GimbalResetMode } from '/@/types/drone-control'\n// import { ELocalStorageKey } from '/@/types'\n\nconst API_PREFIX = '/control/api/v1'\n// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '\n\nexport interface PostPayloadAuthBody {\n  payload_index: string\n}\n\n// 获取负载控制权\nexport async function postPayloadAuth (sn: string, body: PostPayloadAuthBody): Promise<IWorkspaceResponse<null>> {\n  const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body)\n  return resp.data\n}\n\n// TODO: 画面拖动控制\nexport enum PayloadCommandsEnum {\n  CameraModeSwitch = 'camera_mode_switch',\n  CameraPhotoTake = 'camera_photo_take',\n  CameraRecordingStart = 'camera_recording_start',\n  CameraRecordingStop = 'camera_recording_stop',\n  CameraFocalLengthSet = 'camera_focal_length_set',\n  GimbalReset = 'gimbal_reset',\n  CameraAim = 'camera_aim'\n}\n\nexport interface PostCameraModeBody {\n  payload_index: string\n  camera_mode: CameraMode\n}\n\nexport interface PostCameraPhotoBody {\n  payload_index: string\n}\n\nexport interface PostCameraRecordingBody {\n  payload_index: string\n}\n\nexport interface DeleteCameraRecordingParams {\n  payload_index: string\n}\n\nexport interface PostCameraFocalLengthBody {\n  payload_index: string,\n  camera_type: CameraType,\n  zoom_factor: number\n}\n\nexport interface PostGimbalResetBody{\n  payload_index: string,\n  reset_mode: GimbalResetMode,\n}\n\nexport interface PostCameraAimBody{\n  payload_index: string,\n  camera_type: CameraType,\n  locked: boolean,\n  x: number,\n  y: number,\n}\n\nexport type PostPayloadCommandsBody = {\n  cmd: PayloadCommandsEnum.CameraModeSwitch,\n  data: PostCameraModeBody\n} | {\n  cmd: PayloadCommandsEnum.CameraPhotoTake,\n  data: PostCameraPhotoBody\n} | {\n  cmd: PayloadCommandsEnum.CameraRecordingStart,\n  data: PostCameraRecordingBody\n} | {\n  cmd: PayloadCommandsEnum.CameraRecordingStop,\n  data: DeleteCameraRecordingParams\n} | {\n  cmd: PayloadCommandsEnum.CameraFocalLengthSet,\n  data: PostCameraFocalLengthBody\n} | {\n  cmd: PayloadCommandsEnum.GimbalReset,\n  data: PostGimbalResetBody\n} | {\n  cmd: PayloadCommandsEnum.CameraAim,\n  data: PostCameraAimBody\n}\n\n// 发送负载名称\nexport async function postPayloadCommands (sn: string, body: PostPayloadCommandsBody): Promise<IWorkspaceResponse<null>> {\n  const resp = await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/flight-area/index.ts",
    "content": "import request from '../http/request'\nimport { IWorkspaceResponse } from '../http/type'\nimport { EFlightAreaType, ESyncStatus, FlightAreaContent } from './../../types/flight-area'\nimport { ELocalStorageKey } from '/@/types/enums'\nimport { GeojsonCoordinate } from '/@/utils/genjson'\n\nexport interface GetFlightArea {\n  area_id: string,\n  name: string,\n  type: EFlightAreaType,\n  content: FlightAreaContent,\n  status: boolean,\n  username: string,\n  create_time: number,\n  update_time: number,\n}\n\nexport interface PostFlightAreaBody {\n  id: string,\n  name: string,\n  type: EFlightAreaType,\n  content: {\n    properties: {\n      color: string,\n      clampToGround: boolean,\n    },\n    geometry: {\n      type: string,\n      coordinates: GeojsonCoordinate | GeojsonCoordinate[][],\n      radius?: number,\n    }\n  }\n}\n\nexport interface FlightAreaStatus {\n  sync_code: number,\n  sync_status: ESyncStatus,\n  sync_msg: string,\n\n}\nexport interface GetDeviceStatus {\n  device_sn: string,\n  nickname?: string,\n  device_name?: string,\n  online?: boolean,\n  flight_area_status: FlightAreaStatus,\n}\n\nconst MAP_API_PREFIX = '/map/api/v1'\n\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''\n\nexport async function getFlightAreaList (): Promise<IWorkspaceResponse<GetFlightArea[]>> {\n  const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-areas`)\n  return resp.data\n}\n\nexport async function changeFlightAreaStatus (area_id: string, status: boolean): Promise<IWorkspaceResponse<any>> {\n  const resp = await request.put(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`, { status })\n  return resp.data\n}\n\nexport async function saveFlightArea (body: PostFlightAreaBody): Promise<IWorkspaceResponse<any>> {\n  const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area`, body)\n  return resp.data\n}\n\nexport async function deleteFlightArea (area_id: string): Promise<IWorkspaceResponse<any>> {\n  const resp = await request.delete(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`)\n  return resp.data\n}\n\nexport async function syncFlightArea (device_sn: string[]): Promise<IWorkspaceResponse<any>> {\n  const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/sync`, { device_sn })\n  return resp.data\n}\n\nexport async function getDeviceStatus (): Promise<IWorkspaceResponse<GetDeviceStatus[]>> {\n  const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/device-status`)\n  return resp.data\n}\n"
  },
  {
    "path": "src/api/http/config.ts",
    "content": "export const CURRENT_CONFIG = {\n\n  // license\n  appId: 'Please enter the app id.', // You need to go to the development website to apply.\n  appKey: 'Please enter the app key.', // You need to go to the development website to apply.\n  appLicense: 'Please enter the app license.', // You need to go to the development website to apply.\n\n  // http\n  baseURL: 'Please enter the backend access address prefix.', // This url must end with \"/\". Example: 'http://192.168.1.1:6789/'\n  websocketURL: 'Please enter the WebSocket access address.', // Example: 'ws://192.168.1.1:6789/api/v1/ws'\n\n  // livestreaming\n  // RTMP  Note: This IP is the address of the streaming server. If you want to see livestream on web page, you need to convert the RTMP stream to WebRTC stream.\n  rtmpURL: 'Please enter the rtmp access address.', // Example: 'rtmp://192.168.1.1/live/'\n  // GB28181 Note:If you don't know what these parameters mean, you can go to Pilot2 and select the GB28181 page in the cloud platform. Where the parameters same as these parameters.\n  gbServerIp: 'Please enter the server ip.',\n  gbServerPort: 'Please enter the server port.',\n  gbServerId: 'Please enter the server id.',\n  gbAgentId: 'Please enter the agent id',\n  gbPassword: 'Please enter the agent password',\n  gbAgentPort: 'Please enter the local port.',\n  gbAgentChannel: 'Please enter the channel.',\n  // RTSP\n  rtspUserName: 'Please enter the username.',\n  rtspPassword: 'Please enter the password.',\n  rtspPort: '8554',\n  // Agora\n  agoraAPPID: 'Please enter the agora app id.',\n  agoraToken: 'Please enter the agora temporary token.',\n  agoraChannel: 'Please enter the agora channel.',\n\n  // map\n  // You can apply on the AMap website.\n  amapKey: 'Please enter the amap key.',\n\n}\n"
  },
  {
    "path": "src/api/http/request.ts",
    "content": "import axios from 'axios'\nimport { uuidv4 } from '/@/utils/uuid'\nimport { CURRENT_CONFIG } from './config'\nimport { message } from 'ant-design-vue'\nimport router from '/@/router'\nimport { ELocalStorageKey, ERouterName, EUserType } from '/@/types/enums'\nexport * from './type'\n\nconst REQUEST_ID = 'X-Request-Id'\nfunction getAuthToken () {\n  return localStorage.getItem(ELocalStorageKey.Token)\n}\n\nconst instance = axios.create({\n  // withCredentials: true,\n  headers: {\n    'Content-Type': 'application/json',\n  },\n  // timeout: 12000,\n})\n\ninstance.interceptors.request.use(\n  config => {\n    config.headers[ELocalStorageKey.Token] = getAuthToken()\n    // config.headers[REQUEST_ID] = uuidv4()\n    config.baseURL = CURRENT_CONFIG.baseURL\n    return config\n  },\n  error => {\n    return Promise.reject(error)\n  },\n)\n\ninstance.interceptors.response.use(\n  response => {\n    console.info('URL: ' + response.config.baseURL + response.config.url, '\\nData: ', response.data, '\\nResponse:', response)\n    if (response.data.code && response.data.code !== 0) {\n      message.error(response.data.message)\n    }\n    return response\n  },\n  err => {\n    const requestId = err?.config?.headers && err?.config?.headers[REQUEST_ID]\n    if (requestId) {\n      console.info(REQUEST_ID, '：', requestId)\n    }\n    console.info('url: ', err?.config?.url, `【${err?.config?.method}】 \\n>>>> err: `, err)\n\n    let description = '-'\n    if (err.response?.data && err.response.data.message) {\n      description = err.response.data.message\n    }\n    if (err.response?.data && err.response.data.result) {\n      description = err.response.data.result.message\n    }\n    // @See: https://github.com/axios/axios/issues/383\n    if (!err.response || !err.response.status) {\n      message.error('The network is abnormal, please check the backend service and try again')\n      return\n    }\n    if (err.response?.status !== 200) {\n      message.error(`ERROR_CODE: ${err.response?.status}`)\n    }\n    // if (err.response?.status === 403) {\n    //   // window.location.href = '/'\n    // }\n    if (err.response?.status === 401) {\n      console.error(err.response)\n      const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag))\n      switch (flag) {\n        case EUserType.Web:\n          router.push(ERouterName.PROJECT)\n          break\n        case EUserType.Pilot:\n          router.push(ERouterName.PILOT)\n          break\n      }\n    }\n\n    return Promise.reject(err)\n  },\n)\n\nexport default instance\n"
  },
  {
    "path": "src/api/http/type.ts",
    "content": "export interface IResult {\n code: number;\n message: string;\n}\n\nexport interface IPage {\n page: number;\n total: number;\n page_size: number;\n}\n\nexport interface IListWorkspaceResponse<T> {\n code: number;\n message: string;\n data: {\n   list: T[];\n   pagination: IPage;\n };\n}\n// Workspace\nexport interface IWorkspaceResponse<T> {\n code: number;\n data: T;\n message: string;\n}\n\nexport type IStatus = 'WAITING' | 'DOING' | 'SUCCESS' | 'FAILED';\n\nexport interface CommonListResponse<T> extends IResult {\n data: {\n   list: T[];\n   pagination: IPage;\n };\n}\n\nexport interface CommonResponse<T> extends IResult {\n data: T\n}\n"
  },
  {
    "path": "src/api/http.ts",
    "content": "/**\n * 职责声明：\n * 1.提供一个 单一的 axios 实例（方面进行统一拦截）\n * 2.允许调用方定制自己的配置（例如拦截器等），而不影响其他实例\n *\n * 暴露 API：\n * 1.一个统一的 axios 实例: singleAxiosInstance（绑定了统一的拦截器）\n * 2.创建 axios 实例的方法 createAxiosInstance，并在参数中允许配置是否绑定统一拦截器\n * 3.对外暴露统一拦截器绑定方案，允许外界进行定制: bindCommonRequestInterceptors、bindCommonResponseInterceptors\n */\n\nimport Axios, { AxiosInstance, AxiosRequestConfig } from 'axios'\n\n// 统一的 request 拦截器\nexport function bindCommonRequestInterceptors (instance: AxiosInstance): void {\n  instance.interceptors.request.use(config => {\n    return config\n  })\n}\n\n// Unified response interceptor\nexport function bindCommonResponseInterceptors (instance: AxiosInstance): void {\n  instance.interceptors.response.use(config => {\n    return config\n  }, err => {\n    return Promise.reject(err)\n  })\n}\n\nexport function createAxiosInstance (config?: AxiosRequestConfig, commonInterceptorConf: { request?: boolean, response?: boolean } = {}): AxiosInstance {\n  const instance = Axios.create(config)\n\n  // Binding a unified interceptor, binding by default\n  commonInterceptorConf.request !== false && bindCommonRequestInterceptors(instance)\n  commonInterceptorConf.response !== false && bindCommonResponseInterceptors(instance)\n\n  return instance\n}\n\nconst singleAxios = createAxiosInstance({}, { request: true, response: false })\n\nexport default singleAxios\n"
  },
  {
    "path": "src/api/layer.ts",
    "content": "import { ELocalStorageKey } from '../types/enums'\nimport request, { IWorkspaceResponse } from '/@/api/http/request'\nimport { mapLayers } from '/@/constants/mock-layers'\nimport { elementGroupsReq, PostElementsBody, PutElementsBody } from '/@/types/mapLayer'\nconst PREFIX = '/map/api/v1'\nconst workspace_id = localStorage.getItem(ELocalStorageKey.WorkspaceId)\ntype UnknownResponse = Promise<IWorkspaceResponse<unknown>>\n// get elements group\n// export const getLayers = async (reqParams: elementGroupsReq): UnknownResponse => {\n//   const url = `${PREFIX}/workspaces/${workspace_id}/element_groups`\n//   const result = await request.get(url, {\n//     params: {\n//       group_id: reqParams.groupId,\n//       is_distributed: reqParams.isDistributed\n//     },\n//   })\n//   return result.data\n// }\nexport const getLayers = async (reqParams: elementGroupsReq): UnknownResponse => {\n  return mapLayers\n}\n\n// Get elements groups request\nexport const getElementGroupsReq = async (body: elementGroupsReq): Promise<IWorkspaceResponse<any>> => {\n  const url = `${PREFIX}/workspaces/` + workspace_id + '/element-groups'\n  const result = await request.get(url, body)\n  return result.data\n}\n// add element\nexport const postElementsReq = async (pid: string, body: PostElementsBody): Promise<IWorkspaceResponse<{ id: string }>> => {\n  const url = `${PREFIX}/workspaces/` + workspace_id + `/element-groups/${pid}/elements`\n  const result = await request.post(url, body)\n  return result.data\n}\n// Update map element request\nexport const updateElementsReq = async (id: string, body: PutElementsBody): Promise<IWorkspaceResponse<{ id: string }>> => {\n  const url = `${PREFIX}/workspaces/` + workspace_id + `/elements/${id}`\n  const result = await request.put(url, body)\n  return result.data\n}\n// Delete map element\nexport const deleteElementReq = async (id: string, body: {}): Promise<any> => {\n  const url = `${PREFIX}/workspaces/` + workspace_id + `/elements/${id}`\n  const result = await request.delete(url, body)\n  return result.data\n}\n\n// Delete layer elements\nexport const deleteLayerEleReq = async (id: string, body: {}): Promise<any> => {\n  const url = `${PREFIX}/workspaces/` + workspace_id + `/element-groups/${id}/elements`\n  const result = await request.delete(url, body)\n  return result.data\n}\n"
  },
  {
    "path": "src/api/manage.ts",
    "content": "import { Firmware, FirmwareQueryParam, FirmwareUploadParam } from '/@/types/device-firmware'\nimport request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request'\nimport { Device } from '/@/types/device'\n\nconst HTTP_PREFIX = '/manage/api/v1'\n\n// login\nexport interface LoginBody {\n username: string,\n password: string,\n flag: number,\n}\nexport interface BindBody {\n  device_sn: string,\n  user_id: string,\n  workspace_id: string,\n  domain?: string\n}\nexport interface HmsQueryBody {\n  sns: string[],\n  children_sn: string,\n  device_sn: string,\n  language: string,\n  level: number | string,\n  begin_time: number,\n  end_time: number,\n  message: string,\n  domain: number,\n}\n\nexport const login = async function (body: LoginBody): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/login`\n  const result = await request.post(url, body)\n  return result.data\n}\n\n// Refresh Token\nexport const refreshToken = async function (body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/token/refresh`\n  const result = await request.post(url, body)\n  return result.data\n}\n\n// Get Platform Info\nexport const getPlatformInfo = async function (): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/workspaces/current`\n  const result = await request.get(url)\n  return result.data\n}\n\n// Get User Info\nexport const getUserInfo = async function (): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/users/current`\n  const result = await request.get(url)\n  return result.data\n}\n\n// Get Device Topo\nexport const getDeviceTopo = async function (workspace_id: string): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices`\n  const result = await request.get(url)\n  return result.data\n}\n\n// Get Livestream Capacity\nexport const getLiveCapacity = async function (body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/live/capacity`\n  const result = await request.get(url, body)\n  return result.data\n}\n\n// Start Livestream\nexport const startLivestream = async function (body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/live/streams/start`\n  const result = await request.post(url, body)\n  return result.data\n}\n\n// Stop Livestream\nexport const stopLivestream = async function (body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/live/streams/stop`\n  const result = await request.post(url, body)\n  return result.data\n}\n// Update Quality\nexport const setLivestreamQuality = async function (body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/live/streams/update`\n  const result = await request.post(url, body)\n  return result.data\n}\n\nexport const getAllUsersInfo = async function (wid: string, body: IPage): Promise<CommonListResponse<any>> {\n  const url = `${HTTP_PREFIX}/users/${wid}/users?&page=${body.page}&page_size=${body.page_size}`\n  const result = await request.get(url)\n  return result.data\n}\n\nexport const updateUserInfo = async function (wid: string, user_id: string, body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/users/${wid}/users/${user_id}`\n  const result = await request.put(url, body)\n  return result.data\n}\n\nexport const bindDevice = async function (body: BindBody): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/devices/${body.device_sn}/binding`\n  const result = await request.post(url, body)\n  return result.data\n}\n\nexport const unbindDevice = async function (device_sn: string): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/devices/${device_sn}/unbinding`\n  const result = await request.delete(url)\n  return result.data\n}\n\nexport const getDeviceBySn = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`\n  const result = await request.get(url)\n  return result.data\n}\n\n/**\n * 获取绑定设备信息\n * @param workspace_id\n * @param body\n * @param domain\n * @returns\n */\nexport const getBindingDevices = async function (workspace_id: string, body: IPage, domain: number): Promise<IListWorkspaceResponse<Device>> {\n  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}&domain=${domain}`\n  const result = await request.get(url)\n  return result.data\n}\n\nexport const updateDevice = async function (body: {}, workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`\n  const result = await request.put(url, body)\n  return result.data\n}\n\nexport const getUnreadDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`\n  const result = await request.get(url)\n  return result.data\n}\n\nexport const updateDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`\n  const result = await request.put(url)\n  return result.data\n}\n\nexport const getDeviceHms = async function (body: HmsQueryBody, workspace_id: string, pagination: IPage): Promise<IListWorkspaceResponse<any>> {\n  let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` +\n    `&level=${body.level ?? ''}&begin_time=${body.begin_time ?? ''}&end_time=${body.end_time ?? ''}&message=${body.message ?? ''}&language=${body.language}`\n  body.sns.forEach((sn: string) => {\n    if (sn !== '') {\n      url = url.concat(`&device_sn=${sn}`)\n    }\n  })\n  const result = await request.get(url)\n  return result.data\n}\n\nexport const changeLivestreamLens = async function (body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/live/streams/switch`\n  const result = await request.post(url, body)\n  return result.data\n}\n\nexport const getFirmwares = async function (workspace_id: string, page: IPage, body: FirmwareQueryParam): Promise<IListWorkspaceResponse<Firmware>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspace_id}/firmwares?page=${page.page}&page_size=${page.page_size}` +\n    `&device_name=${body.device_name}&product_version=${body.product_version}&status=${body.firmware_status ?? ''}`\n  const result = await request.get(url)\n  return result.data\n}\n\nexport const importFirmareFile = async function (workspaceId: string, param: FormData): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/file/upload`\n  const result = await request.post(url, param)\n  return result.data\n}\n\nexport const changeFirmareStatus = async function (workspaceId: string, firmwareId: string, param: {status: boolean}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/${firmwareId}`\n  const result = await request.put(url, param)\n  return result.data\n}\n"
  },
  {
    "path": "src/api/media.ts",
    "content": "import { message } from 'ant-design-vue'\nimport request, { IPage, IWorkspaceResponse } from '/@/api/http/request'\nconst HTTP_PREFIX = '/media/api/v1'\n\n// Get Media Files\nexport const getMediaFiles = async function (wid: string, pagination: IPage): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/files/${wid}/files?page=${pagination.page}&page_size=${pagination.page_size}`\n  const result = await request.get(url)\n  return result.data\n}\n// Download Media File\nexport const downloadMediaFile = async function (workspaceId: string, fileId: string): Promise<any> {\n  const url = `${HTTP_PREFIX}/files/${workspaceId}/file/${fileId}/url`\n  const result = await request.get(url, { responseType: 'blob' })\n  if (result.data.type === 'application/json') {\n    const reader = new FileReader()\n    reader.onload = function (e) {\n      const text = reader.result as string\n      const result = JSON.parse(text)\n      message.error(result.message)\n    }\n    reader.readAsText(result.data, 'utf-8')\n  } else {\n    return result.data\n  }\n}\n"
  },
  {
    "path": "src/api/pilot-bridge.ts",
    "content": "import { message } from 'ant-design-vue'\nimport { EComponentName, EPhotoType, ERouterName } from '../types'\nimport { CURRENT_CONFIG } from './http/config'\nimport { EVideoPublishType, LiveStreamStatus } from '../types/live-stream'\nimport { getRoot } from '/@/root'\n\nconst root = getRoot()\nexport const components = new Map()\ndeclare let window:any\ninterface JsResponse{\n  code:number,\n  message:string,\n  data:any\n}\n\nexport interface ThingParam {\n  host: string,\n  username: string,\n  password: string,\n  connectCallback: string\n}\n\nexport interface LiveshareParam {\n  videoPublishType: string, // video-on-demand、video-by-manual、video-demand-aux-manual\n  statusCallback: string\n}\n\nexport interface MapParam {\n  userName: string,\n  elementPreName: string\n}\n\nexport interface WsParam {\n  host: string,\n  token: string,\n  connectCallback: string\n}\n\nexport interface ApiParam {\n  host: string,\n  token: string\n}\n\nexport interface MediaParam {\n  autoUploadPhoto: boolean, // 是否自动上传图片, 非必需\n  autoUploadPhotoType: number, // 自动上传的照片类型，0：原图， 1：缩略图， 非必需\n  autoUploadVideo: boolean // 是否自动上传视频， 非必需\n}\n\nfunction returnBool (response: string): boolean {\n  const res: JsResponse = JSON.parse(response)\n  const isError = errorHint(res)\n  if (JSON.stringify(res.data) !== '{}') {\n    return isError && res.data\n  }\n  return isError\n}\n\nfunction returnString (response: string): string {\n  const res: JsResponse = JSON.parse(response)\n  return errorHint(res) ? res.data : ''\n}\n\nfunction returnNumber (response: string): number {\n  const res: JsResponse = JSON.parse(response)\n  return errorHint(res) ? res.data : -1\n}\n\nfunction errorHint (response: JsResponse): boolean {\n  if (response.code !== 0) {\n    message.error(response.message)\n    console.error(response.message)\n    return false\n  }\n  return true\n}\n\nexport default {\n  init (): Map<EComponentName, any> {\n    const thingParam: ThingParam = {\n      host: '',\n      connectCallback: '',\n      username: '',\n      password: ''\n    }\n    components.set(EComponentName.Thing, thingParam)\n    const liveshareParam: LiveshareParam = {\n      videoPublishType: EVideoPublishType.VideoDemandAuxManual,\n      statusCallback: 'liveStatusCallback'\n    }\n    components.set(EComponentName.Liveshare, liveshareParam)\n    const mapParam: MapParam = {\n      userName: '',\n      elementPreName: 'PILOT'\n    }\n    components.set(EComponentName.Map, mapParam)\n    const wsParam: WsParam = {\n      host: CURRENT_CONFIG.websocketURL,\n      token: '',\n      connectCallback: 'wsConnectCallback'\n    }\n    components.set(EComponentName.Ws, wsParam)\n    const apiParam: ApiParam = {\n      host: '',\n      token: ''\n    }\n    components.set(EComponentName.Api, apiParam)\n    components.set(EComponentName.Tsa, {})\n    const mediaParam: MediaParam = {\n      autoUploadPhoto: true,\n      autoUploadPhotoType: EPhotoType.Preview,\n      autoUploadVideo: true\n    }\n    components.set(EComponentName.Media, mediaParam)\n    components.set(EComponentName.Mission, {})\n\n    return components\n  },\n\n  getComponentParam (key:EComponentName): any {\n    return components.get(key)\n  },\n  setComponentParam (key:EComponentName, value:any) {\n    components.set(key, value)\n  },\n  loadComponent (name:string, param:any):string {\n    return returnString(window.djiBridge.platformLoadComponent(name, JSON.stringify(param)))\n  },\n  unloadComponent (name:string) :string {\n    return returnString(window.djiBridge.platformUnloadComponent(name))\n  },\n  isComponentLoaded (module:string): boolean {\n    return returnBool(window.djiBridge.platformIsComponentLoaded(module))\n  },\n  setWorkspaceId (uuid:string):string {\n    return returnString(window.djiBridge.platformSetWorkspaceId(uuid))\n  },\n  setPlatformMessage (platformName:string, title:string, desc:string): boolean {\n    return returnBool(window.djiBridge.platformSetInformation(platformName, title, desc))\n  },\n  getRemoteControllerSN () :string {\n    return returnString(window.djiBridge.platformGetRemoteControllerSN())\n  },\n  getAircraftSN ():string {\n    return returnString(window.djiBridge.platformGetAircraftSN())\n  },\n  stopwebview ():string {\n    return returnString(window.djiBridge.platformStopSelf())\n  },\n  setLogEncryptKey (key:string):string {\n    return window.djiBridge.platformSetLogEncryptKey(key)\n  },\n  clearLogEncryptKey ():string {\n    return window.djiBridge.platformClearLogEncryptKey()\n  },\n  getLogPath ():string {\n    return returnString(window.djiBridge.platformGetLogPath())\n  },\n  platformVerifyLicense (appId:string, appKey:string, appLicense:string): boolean {\n    return returnBool(window.djiBridge.platformVerifyLicense(appId, appKey, appLicense))\n  },\n  isPlatformVerifySuccess (): boolean {\n    return returnBool(window.djiBridge.platformIsVerified())\n  },\n  isAppInstalled (pkgName: string): boolean {\n    return returnBool(window.djiBridge.platformIsAppInstalled(pkgName))\n  },\n  getVersion (): string {\n    return window.djiBridge.platformGetVersion()\n  },\n\n  // thing\n  thingGetConnectState (): boolean {\n    return returnBool(window.djiBridge.thingGetConnectState())\n  },\n\n  thingGetConfigs (): ThingParam {\n    const thingParam = JSON.parse(window.djiBridge.thingGetConfigs())\n    return thingParam.code === 0 ? JSON.parse(thingParam.data) : {}\n  },\n\n  // api\n  getToken () : string {\n    return returnString(window.djiBridge.apiGetToken())\n  },\n  setToken (token:string):string {\n    return returnString(window.djiBridge.apiSetToken(token))\n  },\n  getHost (): string {\n    return returnString(window.djiBridge.apiGetHost())\n  },\n\n  // liveshare\n  /**\n   *\n   * @param type\n   * video-on-demand: 服务器点播，依赖于thing模块，具体的点播命令参见设备物模型的直播服务\n   * video-by-manual：手动点播，配置好直播类型参数之后，在图传页面可修改直播参数，停止直播\n   * video-demand-aux-manual: 混合模式，支持服务器点播，以及图传页面修改直播参数，停止直播\n   */\n  setVideoPublishType (type:string): boolean {\n    return returnBool(window.djiBridge.liveshareSetVideoPublishType(type))\n  },\n\n  /**\n   *\n   * @returns\n   * type: liveshare type， 0：unknown, 1:agora, 2:rtmp, 3:rtsp, 4:gb28181\n   */\n  getLiveshareConfig (): string {\n    return returnString(window.djiBridge.liveshareGetConfig())\n  },\n\n  setLiveshareConfig (type:number, params:string):string {\n    return window.djiBridge.liveshareSetConfig(type, params)\n  },\n\n  setLiveshareStatusCallback (callbackFunc:string) :string {\n    return window.djiBridge.liveshareSetStatusCallback(callbackFunc)\n  },\n  getLiveshareStatus (): LiveStreamStatus {\n    return JSON.parse(JSON.parse(window.djiBridge.liveshareGetStatus()).data)\n  },\n  startLiveshare (): boolean {\n    return returnBool(window.djiBridge.liveshareStartLive())\n  },\n  stopLiveshare (): boolean {\n    return returnBool(window.djiBridge.liveshareStopLive())\n  },\n  // WebSocket\n  wsGetConnectState (): boolean {\n    return returnBool(window.djiBridge.wsGetConnectState())\n  },\n  wsConnect (host: string, token: string, callback: string): string {\n    return window.djiBridge.wsConnect(host, token, callback)\n  },\n  wsDisconnect (): string {\n    return window.djiBridge.wsConnect()\n  },\n  wsSend (message: string): string {\n    return window.djiBridge.wsSend(message)\n  },\n  // media\n  setAutoUploadPhoto (auto:boolean):string {\n    return window.djiBridge.mediaSetAutoUploadPhoto(auto)\n  },\n  getAutoUploadPhoto (): boolean {\n    return returnBool(window.djiBridge.mediaGetAutoUploadPhoto())\n  },\n  setUploadPhotoType (type:number):string {\n    return window.djiBridge.mediaSetUploadPhotoType(type)\n  },\n  getUploadPhotoType (): number {\n    return returnNumber(window.djiBridge.mediaGetUploadPhotoType())\n  },\n  setAutoUploadVideo (auto:boolean):string {\n    return window.djiBridge.mediaSetAutoUploadVideo(auto)\n  },\n  getAutoUploadVideo (): boolean {\n    return returnBool(window.djiBridge.mediaGetAutoUploadVideo())\n  },\n  setDownloadOwner (rcIndex:number):string {\n    return window.djiBridge.mediaSetDownloadOwner(rcIndex)\n  },\n  getDownloadOwner (): number {\n    return returnNumber(window.djiBridge.mediaGetDownloadOwner())\n  },\n  onBackClickReg () {\n    window.djiBridge.onBackClick = () => {\n      if (root.$router.currentRoute.value.path === '/' + ERouterName.PILOT_HOME) {\n        return false\n      } else {\n        history.go(-1)\n        return true\n      }\n    }\n  },\n  onStopPlatform () {\n    window.djiBridge.onStopPlatform = () => {\n      localStorage.clear()\n    }\n  }\n}\n"
  },
  {
    "path": "src/api/wayline.ts",
    "content": "import { message } from 'ant-design-vue'\nimport request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request'\nimport { TaskType, TaskStatus, OutOfControlAction } from '/@/types/task'\nimport { WaylineType } from '/@/types/wayline'\n\nconst HTTP_PREFIX = '/wayline/api/v1'\n\n// Get Wayline Files\nexport const getWaylineFiles = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/workspaces/${wid}/waylines?order_by=${body.order_by}&page=${body.page}&page_size=${body.page_size}`\n  const result = await request.get(url)\n  return result.data\n}\n\n// Download Wayline File\nexport const downloadWaylineFile = async function (workspaceId: string, waylineId: string): Promise<any> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}/url`\n  const result = await request.get(url, { responseType: 'blob' })\n  if (result.data.type === 'application/json') {\n    const reader = new FileReader()\n    reader.onload = function (e) {\n      const text = reader.result as string\n      const result = JSON.parse(text)\n      message.error(result.message)\n    }\n    reader.readAsText(result.data, 'utf-8')\n  } else {\n    return result.data\n  }\n}\n\n// Delete Wayline File\nexport const deleteWaylineFile = async function (workspaceId: string, waylineId: string): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}`\n  const result = await request.delete(url)\n  return result.data\n}\n\nexport interface CreatePlan {\n  name: string,\n  file_id: string,\n  dock_sn: string,\n  task_type: TaskType, // 任务类型\n  wayline_type: WaylineType, // 航线类型\n  task_days: number[] // 执行任务的日期（秒）\n  task_periods: number[][] // 执行任务的时间点（秒）\n  rth_altitude: number // 相对机场返航高度 20 - 500\n  out_of_control_action: OutOfControlAction // 失控动作\n  min_battery_capacity?: number, // The minimum battery capacity of aircraft.\n  min_storage_capacity?: number, // The minimum storage capacity of dock and aircraft.\n}\n\n// Create Wayline Job\nexport const createPlan = async function (workspaceId: string, plan: CreatePlan): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks`\n  const result = await request.post(url, plan)\n  return result.data\n}\n\nexport interface Task {\n  job_id: string,\n  job_name: string,\n  task_type: TaskType, // 任务类型\n  file_id: string, // 航线文件id\n  file_name: string, // 航线名称\n  wayline_type: WaylineType, // 航线类型\n  dock_sn: string,\n  dock_name: string,\n  workspace_id: string,\n  username: string,\n  begin_time: string,\n  end_time: string,\n  execute_time: string,\n  completed_time: string,\n  status: TaskStatus, // 任务状态\n  progress: number, // 执行进度\n  code: number, // 错误码\n  rth_altitude: number // 相对机场返航高度 20 - 500\n  out_of_control_action: OutOfControlAction // 失控动作\n  media_count: number // 媒体数量\n  uploading:boolean // 是否正在上传媒体\n  uploaded_count: number // 已上传媒体数量\n}\n\n// Get Wayline Jobs\nexport const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IListWorkspaceResponse<Task>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}`\n  const result = await request.get(url)\n  return result.data\n}\n\nexport interface DeleteTaskParams {\n  job_id: string\n}\n\n//  删除机场任务\nexport async function deleteTask (workspaceId: string, params: DeleteTaskParams): Promise<IWorkspaceResponse<{}>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs`\n  const result = await request.delete(url, {\n    params: params\n  })\n  return result.data\n}\n\nexport enum UpdateTaskStatus {\n  Suspend = 0, // 暂停\n  Resume = 1, // 恢复\n}\nexport interface UpdateTaskStatusBody {\n  job_id: string\n  status: UpdateTaskStatus\n}\n\n// 更新机场任务状态\nexport async function updateTaskStatus (workspaceId: string, body: UpdateTaskStatusBody): Promise<IWorkspaceResponse<{}>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${body.job_id}`\n  const result = await request.put(url, {\n    status: body.status\n  })\n  return result.data\n}\n\n// Upload Wayline file\nexport const importKmzFile = async function (workspaceId: string, file: {}): Promise<IWorkspaceResponse<any>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/file/upload`\n  const result = await request.post(url, file, {\n    headers: {\n      'Content-Type': 'multipart/form-data',\n    }\n  })\n  return result.data\n}\n\n// 媒体立即上传\nexport const uploadMediaFileNow = async function (workspaceId: string, jobId: string): Promise<IWorkspaceResponse<{}>> {\n  const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${jobId}/media-highest`\n  const result = await request.post(url)\n  return result.data\n}\n"
  },
  {
    "path": "src/components/GMap.vue",
    "content": "<template>\n  <div class=\"g-map-wrapper\">\n    <!-- 地图区域 -->\n    <div id=\"g-container\" :style=\"{ width: '100%', height: '100%' }\" />\n    <!-- 绘制面板 -->\n    <div\n      class=\"g-action-panel\"\n      :style=\"{ right: drawVisible ? '316px' : '16px' }\"\n    >\n      <div :class=\"state.currentType === 'pin' ? 'g-action-item selection' : 'g-action-item'\" @click=\"draw('pin', true)\">\n        <a><a-image :src=\"pin\" :preview=\"false\" /></a>\n      </div>\n      <div :class=\"state.currentType === 'polyline' ? 'g-action-item selection' : 'g-action-item'\" @click=\"draw('polyline', true)\">\n        <a><LineOutlined :rotate=\"135\" class=\"fz20\"/></a>\n      </div>\n      <div :class=\"state.currentType === 'polygon' && !state.isFlightArea ? 'g-action-item selection' : 'g-action-item'\" @click=\"draw('polygon', true)\">\n        <a><BorderOutlined class=\"fz18\" /></a>\n      </div>\n      <FlightAreaActionIcon class=\"g-action-item mt10\" :class=\"{'selection': mouseMode && state.isFlightArea}\" @select-action=\"selectFlightAreaAction\" @click=\"selectFlightAreaAction\"/>\n      <div v-if=\"mouseMode\" class=\"g-action-item\" @click=\"draw('off', false)\">\n        <a style=\"color: red;\"><CloseOutlined /></a>\n      </div>\n    </div>\n    <!-- 飞机OSD -->\n    <div v-if=\"osdVisible.visible && !osdVisible.is_dock\" v-drag-window class=\"osd-panel fz12\">\n      <div class=\"drag-title pl5 pr5 flex-align-center flex-row flex-justify-between\" style=\"border-bottom: 1px solid #515151; height: 18%;\">\n        <span>{{ osdVisible.callsign }}</span>\n        <span><a class=\"fz16\" style=\"color: white;\" @click=\"() => osdVisible.visible = false\"><CloseOutlined /></a></span>\n      </div>\n      <div style=\"height: 82%;\">\n        <div class=\"flex-column flex-align-center flex-justify-center\" style=\"margin-top: -5px; padding-top: 25px; float: left; width: 60px; background: #2d2d2d;\">\n          <a-tooltip :title=\"osdVisible.model\">\n            <div style=\"width: 90%;\" class=\"flex-column flex-align-center flex-justify-center\">\n              <span><a-image :src=\"M30\" :preview=\"false\"/></span>\n              <span>{{ osdVisible.model }}</span>\n            </div>\n          </a-tooltip>\n        </div>\n        <div class=\"osd\">\n            <a-row>\n              <a-col span=\"16\" :style=\"deviceInfo.device.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'\">{{ EModeCode[deviceInfo.device.mode_code] }}</a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Signal strength\">\n                  <span>HD</span>\n                  <span class=\"ml10\">{{ deviceInfo.gateway?.transmission_signal_quality }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"RC Battery Level\">\n                  <span><ThunderboltOutlined class=\"fz14\"/></span>\n                  <span class=\"ml10\">{{ deviceInfo.gateway && deviceInfo.gateway.capacity_percent !== str ? deviceInfo.gateway?.capacity_percent + ' %' : deviceInfo.gateway?.capacity_percent }}</span>\n                </a-tooltip>\n              </a-col>\n\n              <a-col span=\"6\">\n                <a-tooltip title=\"Drone Battery Level\">\n                  <span><ThunderboltOutlined class=\"fz14\"/></span>\n                  <span class=\"ml10\">{{ deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device.battery.capacity_percent + ' %' : deviceInfo.device.battery.capacity_percent }}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-tooltip title=\"RTK Fixed\">\n                <a-col span=\"6\" class=\"flex-row flex-align-center flex-justify-start\">\n                  <span>Fixed</span>\n                  <span class=\"ml10 circle\" :style=\"deviceInfo.device.position_state.is_fixed === 1 ? 'backgroud: rgb(25,190,107);' : ' background: red;'\"></span>\n                </a-col>\n              </a-tooltip>\n              <a-col span=\"6\">\n                <a-tooltip title=\"GPS\">\n                  <span>GPS</span>\n                  <span class=\"ml10\">{{ deviceInfo.device.position_state.gps_number }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"RTK\">\n                  <span><TrademarkOutlined class=\"fz14\"/></span>\n                  <span class=\"ml10\">{{ deviceInfo.device.position_state.rtk_number }}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Flight Mode\">\n                  <span><ControlOutlined class=\"fz16\" /></span>\n                  <span class=\"ml10\">{{ EGear[deviceInfo.device.gear] }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Altitude above sea level\">\n                  <span>ASL</span>\n                  <span class=\"ml10\">{{ deviceInfo.device.height === str ? str : deviceInfo.device.height.toFixed(2) + ' m'}}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Altitude above takeoff level\">\n                  <span>ALT</span>\n                  <span class=\"ml10\">{{ deviceInfo.device.elevation === str ? str : deviceInfo.device.elevation.toFixed(2) + ' m' }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Distance to Home Point\">\n                  <span>H</span>\n                  <span class=\"ml10\">{{ deviceInfo.device.home_distance === str ? str : deviceInfo.device.home_distance.toFixed(2) + ' m' }}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Horizontal Speed\">\n                  <span>H.S</span>\n                  <span class=\"ml10\">{{ deviceInfo.device.horizontal_speed === str ? str : deviceInfo.device.horizontal_speed.toFixed(2) + ' m/s'}}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Vertical Speed\">\n                  <span>V.S</span>\n                  <span class=\"ml10\">{{ deviceInfo.device.vertical_speed === str ? str : deviceInfo.device.vertical_speed.toFixed(2) + ' m/s'}}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Wind Speed\">\n                  <span>W.S</span>\n                  <span class=\"ml10\">{{ deviceInfo.device.wind_speed === str ? str : (deviceInfo.device.wind_speed / 10).toFixed(2) + ' m/s'}}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n        </div>\n      </div>\n      <div class=\"battery-slide\" v-if=\"deviceInfo.device.battery.remain_flight_time !== 0\">\n        <div style=\"background: #535759;\" class=\"width-100\"></div>\n        <div class=\"capacity-percent\" :style=\"{ width: deviceInfo.device.battery.capacity_percent + '%'}\"></div>\n        <div class=\"return-home\" :style=\"{ width: deviceInfo.device.battery.return_home_power + '%'}\"></div>\n        <div class=\"landing\" :style=\"{ width: deviceInfo.device.battery.landing_power + '%'}\"></div>\n        <div class=\"white-point\" :style=\"{ left: deviceInfo.device.battery.landing_power + '%'}\"></div>\n        <div class=\"battery\" :style=\"{ left: deviceInfo.device.battery.capacity_percent + '%' }\">\n          {{ Math.floor(deviceInfo.device.battery.remain_flight_time / 60) }}:\n          {{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}\n        </div>\n      </div>\n    </div>\n    <!-- 机场OSD -->\n    <div v-if=\"osdVisible.visible && osdVisible.is_dock\"  v-drag-window class=\"osd-panel fz12\">\n      <div class=\"drag-title fz16 pl5 pr5 flex-align-center flex-row flex-justify-between\" style=\"border-bottom: 1px solid #515151; height: 10%;\">\n        <span>{{ osdVisible.gateway_callsign }}</span>\n      </div>\n      <span><a style=\"color: white; position: absolute; top: 5px; right: 5px;\" @click=\"() => osdVisible.visible = false\"><CloseOutlined /></a></span>\n        <!-- 机场 -->\n      <div class =\"flex-display\" style=\"border-bottom: 1px solid #515151;\">\n        <div class=\"flex-column flex-align-stretch flex-justify-center\" style=\"width: 60px; background: #2d2d2d;\">\n          <a-tooltip :title=\"osdVisible.model\">\n            <div class=\"flex-column  flex-align-center flex-justify-center\" style=\"width: 90%;\">\n              <span><RobotFilled style=\"font-size: 48px;\"/></span>\n              <span class=\"mt10\">Dock</span>\n            </div>\n          </a-tooltip>\n        </div>\n        <div class=\"osd flex-1\" style=\"flex: 1\">\n            <a-row>\n              <a-col span=\"16\" :style=\"deviceInfo.dock.basic_osd?.mode_code === EDockModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'\">\n                {{ EDockModeCode[deviceInfo.dock.basic_osd?.mode_code] }}</a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"12\">\n                <a-tooltip title=\"Accumulated Running Time\">\n                  <span><HistoryOutlined /></span>\n                  <span class=\"ml10\">\n                    <span v-if=\"deviceInfo.dock.work_osd?.acc_time >= 2592000\"> {{ Math.floor(deviceInfo.dock.work_osd?.acc_time / 2592000) }}m </span>\n                    <span v-if=\"(deviceInfo.dock.work_osd?.acc_time % 2592000) >= 86400\"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000) / 86400) }}d </span>\n                    <span v-if=\"(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400) >= 3600\"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400) / 3600) }}h </span>\n                    <span v-if=\"(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600) >= 60\"> {{ Math.floor((deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600) / 60) }}min </span>\n                    <span>{{ Math.floor(deviceInfo.dock.work_osd?.acc_time % 2592000 % 86400 % 3600 % 60) }} s</span>\n                  </span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"12\">\n                <a-tooltip title=\"Activation time\">\n                  <span><FieldTimeOutlined /></span>\n                  <span class=\"ml10\">{{ new Date((deviceInfo.dock.work_osd?.activation_time ?? 0) * 1000).toLocaleString() }}\n                  </span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Network State\">\n                  <span :style=\"qualityStyle\">\n                    <span v-if=\"deviceInfo.dock.basic_osd?.network_state?.type === NetworkStateTypeEnum.FOUR_G\"><SignalFilled /></span>\n                    <span v-else><GlobalOutlined /></span>\n                  </span>\n                  <span class=\"ml10\" >{{ deviceInfo.dock.basic_osd?.network_state?.rate }} kb/s</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"The total number of times the dock has performed missions.\">\n                  <span><CarryOutOutlined /></span>\n                  <span class=\"ml10\" >{{ deviceInfo.dock.work_osd?.job_number }} </span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Media File Remain Upload\">\n                  <span><CloudUploadOutlined class=\"fz14\"/></span>\n                  <span class=\"ml10\">{{ deviceInfo.dock.link_osd?.media_file_detail?.remain_upload }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip>\n                  <template #title>\n                    <p>total: {{ deviceInfo.dock.basic_osd?.storage?.total }}</p>\n                    <p>used: {{ deviceInfo.dock.basic_osd?.storage?.used  }}</p>\n                  </template>\n                  <span><FolderOpenOutlined /></span>\n                  <span class=\"ml10\" v-if=\"deviceInfo.dock.basic_osd?.storage?.total > 0\">\n                    <a-progress type=\"circle\" :width=\"20\" :percent=\"deviceInfo.dock.basic_osd?.storage?.used * 100/ deviceInfo.dock.basic_osd?.storage?.total\"\n                      :strokeWidth=\"20\" :showInfo=\"false\" :strokeColor=\"deviceInfo.dock.basic_osd?.storage?.used * 100 / deviceInfo.dock.basic_osd?.storage?.total > 80 ? 'red' : '#00ee8b' \"/>\n                  </span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Wind Speed\">\n                  <span>W.S</span>\n                  <span class=\"ml10\">{{ (deviceInfo.dock.basic_osd?.wind_speed ?? str) + ' m/s'}}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Rainfall\">\n                  <span>🌧</span>\n                  <span class=\"ml10\">{{ RainfallEnum[deviceInfo.dock.basic_osd?.rainfall] }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Environment Temperature\">\n                  <span>°C</span>\n                  <span class=\"ml10\">{{ deviceInfo.dock.basic_osd?.environment_temperature }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Dock Temperature\">\n                  <span>°C</span>\n                  <span class=\"ml10\">{{ deviceInfo.dock.basic_osd?.temperature }}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Dock Humidity\">\n                  <span>💦</span>\n                  <span class=\"ml10\">{{ deviceInfo.dock.basic_osd?.humidity }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Working Voltage\">\n                  <span style=\"border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 16px; text-align: center; float: left;\">V</span>\n                  <span class=\"ml10\">{{ (deviceInfo.dock.work_osd?.working_voltage ?? str) + ' mV' }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Working Current\">\n                  <span style=\"border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 15px; text-align: center; float: left;\" >A</span>\n                  <span class=\"ml10\">{{ (deviceInfo.dock.work_osd?.working_current ?? str) + ' mA' }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Drone in dock\">\n                  <span><RocketOutlined /></span>\n                  <span class=\"ml10\">{{ deviceInfo.dock.basic_osd?.drone_in_dock }}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row class=\"p5\">\n              <a-col span=\"24\">\n                <a-button type=\"primary\" :disabled=\"dockControlPanelVisible\" size=\"small\" @click=\"setDockControlPanelVisible(true)\">\n                  Actions\n                </a-button>\n              </a-col>\n            </a-row>\n            <!-- 机场控制面板 -->\n            <DockControlPanel v-if=\"dockControlPanelVisible\" :sn=\"osdVisible.gateway_sn\"  :deviceInfo=\"deviceInfo\" @close-control-panel=\"onCloseControlPanel\">\n            </DockControlPanel>\n        </div>\n      </div>\n      <!--  飞机-->\n      <div class =\"flex-display\">\n        <div class=\"flex-column flex-align-stretch flex-justify-center\" style=\"width: 60px;  background: #2d2d2d;\">\n          <a-tooltip :title=\"osdVisible.model\">\n            <div style=\"width: 90%;\" class=\"flex-column flex-align-center flex-justify-center\">\n              <span><a-image :src=\"M30\" :preview=\"false\"/></span>\n              <span>M30</span>\n            </div>\n          </a-tooltip>\n        </div>\n        <div class=\"osd flex-1\">\n            <a-row>\n              <a-col span=\"16\" :style=\"!deviceInfo.device || deviceInfo.device?.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;': 'color: rgb(25,190,107)'\">\n                {{ !deviceInfo.device ? EModeCode[EModeCode.Disconnected] : EModeCode[deviceInfo.device?.mode_code] }}</a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Upward Quality\">\n                  <span><SignalFilled /><ArrowUpOutlined style=\"font-size: 9px; vertical-align: top;\" /></span>\n                  <span class=\"ml10\">{{ deviceInfo.dock.link_osd?.sdr?.up_quality }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Downward Quality\">\n                  <span><SignalFilled /><ArrowDownOutlined style=\"font-size: 9px; vertical-align: top;\" /></span>\n                  <span class=\"ml10\">{{ deviceInfo.dock.link_osd?.sdr?.down_quality }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Drone Battery Level\">\n                  <span><ThunderboltOutlined class=\"fz14\"/></span>\n                  <span class=\"ml10\">{{ deviceInfo.device && deviceInfo.device.battery.capacity_percent !== str ? deviceInfo.device?.battery.capacity_percent + ' %' : str }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip>\n                  <template #title>\n                    <p>total: {{ deviceInfo.device?.storage?.total }}</p>\n                    <p>used: {{ deviceInfo.device?.storage?.used  }}</p>\n                  </template>\n                  <span><FolderOpenOutlined /></span>\n                  <span class=\"ml10\" v-if=\"deviceInfo.device?.storage?.total > 0\">\n                    <a-progress type=\"circle\" :width=\"20\" :percent=\"deviceInfo.device?.storage?.used * 100/ deviceInfo.device?.storage?.total\"\n                      :strokeWidth=\"20\" :showInfo=\"false\" :strokeColor=\"deviceInfo.device?.storage?.used * 100 / deviceInfo.device?.storage?.total > 80 ? 'red' : '#00ee8b' \"/>\n                  </span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-tooltip title=\"RTK Fixed\">\n                <a-col span=\"6\" class=\"flex-row flex-align-center flex-justify-start\">\n                  <span>Fixed</span>\n                  <span class=\"ml10 circle\" :style=\"deviceInfo.device?.position_state.is_fixed === 1 ? 'backgroud: rgb(25,190,107);' : ' background: red;'\"></span>\n                </a-col>\n              </a-tooltip>\n              <a-col span=\"6\">\n                <a-tooltip title=\"GPS\">\n                  <span>GPS</span>\n                  <span class=\"ml10\">{{ deviceInfo.device ? deviceInfo.device.position_state.gps_number : str }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"RTK\">\n                  <span><TrademarkOutlined class=\"fz14\"/></span>\n                  <span class=\"ml10\">{{ deviceInfo.device ? deviceInfo.device.position_state.rtk_number : str }}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Flight Mode\">\n                  <span><ControlOutlined class=\"fz16\" /></span>\n                  <span class=\"ml10\">{{ deviceInfo.device ? EGear[deviceInfo.device?.gear] : str }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Altitude above sea level\">\n                  <span>ASL</span>\n                  <span class=\"ml10\">{{ !deviceInfo.device || deviceInfo.device.height === str ? str : deviceInfo.device?.height.toFixed(2) + ' m'}}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Altitude above takeoff level\">\n                  <span>ALT</span>\n                  <span class=\"ml10\">{{ !deviceInfo.device || deviceInfo.device.elevation === str ? str : deviceInfo.device?.elevation.toFixed(2) + ' m' }}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Distance to Home Point\">\n                  <span style=\"border: 1px solid; border-radius: 50%; width: 18px; height: 18px; line-height: 15px; text-align: center;  display: block; float: left;\" >H</span>\n                  <span class=\"ml10\">{{ !deviceInfo.device || deviceInfo.device.home_distance === str ? str : deviceInfo.device?.home_distance.toFixed(2) + ' m' }}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n            <a-row>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Horizontal Speed\">\n                  <span>H.S</span>\n                  <span class=\"ml10\">{{ !deviceInfo.device || deviceInfo.device?.horizontal_speed === str ? str : deviceInfo.device?.horizontal_speed.toFixed(2) + ' m/s'}}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Vertical Speed\">\n                  <span>V.S</span>\n                  <span class=\"ml10\">{{ !deviceInfo.device || deviceInfo.device.vertical_speed === str ? str : deviceInfo.device?.vertical_speed.toFixed(2) + ' m/s'}}</span>\n                </a-tooltip>\n              </a-col>\n              <a-col span=\"6\">\n                <a-tooltip title=\"Wind Speed\">\n                  <span>W.S</span>\n                  <span class=\"ml10\">{{ !deviceInfo.device || deviceInfo.device.wind_speed === str ? str : (deviceInfo.device?.wind_speed / 10).toFixed(2) + ' m/s'}}</span>\n                </a-tooltip>\n              </a-col>\n            </a-row>\n        </div>\n      </div>\n      <div class=\"battery-slide\" v-if=\"deviceInfo.device && deviceInfo.device.battery.remain_flight_time !== 0\" style=\"border: 1px solid red\">\n        <div style=\"background: #535759;\" class=\"width-100\"></div>\n        <div class=\"capacity-percent\" :style=\"{ width: deviceInfo.device.battery.capacity_percent + '%'}\"></div>\n        <div class=\"return-home\" :style=\"{ width: deviceInfo.device.battery.return_home_power + '%'}\"></div>\n        <div class=\"landing\" :style=\"{ width: deviceInfo.device.battery.landing_power + '%'}\"></div>\n        <div class=\"white-point\" :style=\"{ left: deviceInfo.device.battery.landing_power + '%'}\"></div>\n        <div class=\"battery\" :style=\"{ left: deviceInfo.device.battery.capacity_percent + '%' }\">\n          {{ Math.floor(deviceInfo.device.battery.remain_flight_time / 60) }}:\n          {{ 10 > (deviceInfo.device.battery.remain_flight_time % 60) ? '0' : ''}}{{deviceInfo.device.battery.remain_flight_time % 60 }}\n        </div>\n      </div>\n      <!-- 飞行指令 -->\n      <DroneControlPanel :sn=\"osdVisible.gateway_sn\" :deviceInfo=\"deviceInfo\" :payloads=\"osdVisible.payloads\"></DroneControlPanel>\n    </div>\n    <!-- liveview -->\n    <div class=\"liveview\" v-if=\"livestreamOthersVisible\" v-drag-window  >\n      <div style=\"height: 40px; width: 100%\" class=\"drag-title\"></div>\n      <a style=\"position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;\" @click=\"closeLivestreamOthers\"><CloseOutlined /></a>\n      <LivestreamOthers />\n    </div>\n    <div class=\"liveview\" v-if=\"livestreamAgoraVisible\" v-drag-window  >\n      <div style=\"height: 40px; width: 100%\" class=\"drag-title\"></div>\n      <a style=\"position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;\" @click=\"closeLivestreamAgora\"><CloseOutlined /></a>\n      <LivestreamAgora />\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue'\nimport {\n  generateLineContent,\n  generatePointContent,\n  generatePolyContent\n} from '../utils/map-layer-utils'\nimport { postElementsReq } from '/@/api/layer'\nimport { MapDoodleType, MapElementEnum } from '/@/constants/map'\nimport { useGMapManage } from '/@/hooks/use-g-map'\nimport { useGMapCover } from '/@/hooks/use-g-map-cover'\nimport { useMouseTool } from '/@/hooks/use-mouse-tool'\nimport { getApp, getRoot } from '/@/root'\nimport { useMyStore } from '/@/store'\nimport { GeojsonCoordinate } from '/@/types/map'\nimport { MapDoodleEnum } from '/@/types/map-enum'\nimport { PostElementsBody } from '/@/types/mapLayer'\nimport { uuidv4 } from '/@/utils/uuid'\nimport { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform'\nimport { deviceTsaUpdate } from '/@/hooks/use-g-map-tsa'\nimport {\n  DeviceOsd, DeviceStatus, DockOsd, EGear, EModeCode, GatewayOsd, EDockModeCode,\n  NetworkStateQualityEnum, NetworkStateTypeEnum, RainfallEnum, DroneInDockEnum\n} from '/@/types/device'\nimport pin from '/@/assets/icons/pin-2d8cf0.svg'\nimport M30 from '/@/assets/icons/m30.png'\nimport {\n  BorderOutlined, LineOutlined, CloseOutlined, ControlOutlined, TrademarkOutlined, ArrowDownOutlined,\n  ThunderboltOutlined, SignalFilled, GlobalOutlined, HistoryOutlined, CloudUploadOutlined, RocketOutlined,\n  FieldTimeOutlined, CloudOutlined, CloudFilled, FolderOpenOutlined, RobotFilled, ArrowUpOutlined, CarryOutOutlined\n} from '@ant-design/icons-vue'\nimport { EDeviceTypeName } from '../types'\nimport DockControlPanel from './g-map/DockControlPanel.vue'\nimport { useDockControl } from './g-map/use-dock-control'\nimport DroneControlPanel from './g-map/DroneControlPanel.vue'\nimport { useConnectMqtt } from './g-map/use-connect-mqtt'\nimport LivestreamOthers from './livestream-others.vue'\nimport LivestreamAgora from './livestream-agora.vue'\nimport FlightAreaActionIcon from './flight-area/FlightAreaActionIcon.vue'\nimport { EFlightAreaType } from '../types/flight-area'\nimport { useFlightArea } from './flight-area/use-flight-area'\nimport { useFlightAreaDroneLocationEvent } from './flight-area/use-flight-area-drone-location-event'\n\nexport default defineComponent({\n  components: {\n    BorderOutlined,\n    LineOutlined,\n    CloseOutlined,\n    ControlOutlined,\n    TrademarkOutlined,\n    ThunderboltOutlined,\n    SignalFilled,\n    GlobalOutlined,\n    HistoryOutlined,\n    CloudUploadOutlined,\n    FieldTimeOutlined,\n    CloudOutlined,\n    CloudFilled,\n    FolderOpenOutlined,\n    RobotFilled,\n    ArrowUpOutlined,\n    ArrowDownOutlined,\n    DockControlPanel,\n    DroneControlPanel,\n    CarryOutOutlined,\n    RocketOutlined,\n    LivestreamOthers,\n    LivestreamAgora,\n    FlightAreaActionIcon,\n  },\n  name: 'GMap',\n  props: {},\n  setup () {\n    const useMouseToolHook = useMouseTool()\n    const useGMapManageHook = useGMapManage()\n    const deviceTsaUpdateHook = deviceTsaUpdate()\n    const root = getRoot()\n\n    const mouseMode = ref(false)\n    const store = useMyStore()\n    const state = reactive({\n      currentType: '',\n      coverIndex: 0,\n      isFlightArea: false,\n    })\n    const str: string = '--'\n    const deviceInfo = reactive({\n      gateway: {\n        capacity_percent: str,\n        transmission_signal_quality: str,\n      } as GatewayOsd,\n      dock: {\n\n      } as DockOsd,\n      device: {\n        gear: -1,\n        mode_code: EModeCode.Disconnected,\n        height: str,\n        home_distance: str,\n        horizontal_speed: str,\n        vertical_speed: str,\n        wind_speed: str,\n        wind_direction: str,\n        elevation: str,\n        position_state: {\n          gps_number: str,\n          is_fixed: 0,\n          rtk_number: str\n        },\n        battery: {\n          capacity_percent: str,\n          landing_power: str,\n          remain_flight_time: 0,\n          return_home_power: str,\n        },\n        latitude: 0,\n        longitude: 0,\n      } as DeviceOsd\n    })\n    const shareId = computed(() => {\n      return store.state.layerBaseInfo.share\n    })\n    const defaultId = computed(() => {\n      return store.state.layerBaseInfo.default\n    })\n    const drawVisible = computed(() => {\n      return store.state.drawVisible\n    })\n    const livestreamOthersVisible = computed(() => {\n      return store.state.livestreamOthersVisible\n    })\n    const livestreamAgoraVisible = computed(() => {\n      return store.state.livestreamAgoraVisible\n    })\n    const osdVisible = computed(() => {\n      return store.state.osdVisible\n    })\n    const qualityStyle = computed(() => {\n      if (deviceInfo.dock.basic_osd?.network_state?.type === NetworkStateTypeEnum.ETHERNET ||\n        (deviceInfo.dock.basic_osd?.network_state?.quality || 0) > NetworkStateQualityEnum.FAIR) {\n        return 'color: #00ee8b'\n      }\n      if ((deviceInfo.dock.basic_osd?.network_state?.quality || 0) === NetworkStateQualityEnum.FAIR) {\n        return 'color: yellow'\n      }\n      return 'color: red'\n    })\n    watch(() => store.state.deviceStatusEvent,\n      data => {\n        if (Object.keys(data.deviceOnline).length !== 0) {\n          deviceTsaUpdateHook.initMarker(data.deviceOnline.domain, data.deviceOnline.device_callsign, data.deviceOnline.sn)\n          store.state.deviceStatusEvent.deviceOnline = {} as DeviceStatus\n        }\n        if (Object.keys(data.deviceOffline).length !== 0) {\n          deviceTsaUpdateHook.removeMarker(data.deviceOffline.sn)\n          if ((data.deviceOffline.sn === osdVisible.value.sn) || (osdVisible.value.is_dock && data.deviceOffline.sn === osdVisible.value.gateway_sn)) {\n            osdVisible.value.visible = false\n            store.commit('SET_OSD_VISIBLE_INFO', osdVisible)\n          }\n          store.state.deviceStatusEvent.deviceOffline = {}\n        }\n      },\n      {\n        deep: true\n      }\n    )\n\n    watch(() => store.state.deviceState, data => {\n      if (data.currentType === EDeviceTypeName.Gateway && data.gatewayInfo[data.currentSn]) {\n        const coordinate = wgs84togcj02(data.gatewayInfo[data.currentSn].longitude, data.gatewayInfo[data.currentSn].latitude)\n        deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])\n        if (osdVisible.value.visible && osdVisible.value.gateway_sn !== '') {\n          deviceInfo.gateway = data.gatewayInfo[osdVisible.value.gateway_sn]\n        }\n      }\n      if (data.currentType === EDeviceTypeName.Aircraft && data.deviceInfo[data.currentSn]) {\n        const coordinate = wgs84togcj02(data.deviceInfo[data.currentSn].longitude, data.deviceInfo[data.currentSn].latitude)\n        deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])\n        if (osdVisible.value.visible && osdVisible.value.sn !== '') {\n          deviceInfo.device = data.deviceInfo[osdVisible.value.sn]\n        }\n      }\n      if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) {\n        const coordinate = wgs84togcj02(data.dockInfo[data.currentSn].basic_osd?.longitude, data.dockInfo[data.currentSn].basic_osd?.latitude)\n        deviceTsaUpdateHook.initMarker(EDeviceTypeName.Dock, EDeviceTypeName[EDeviceTypeName.Dock], data.currentSn, coordinate[0], coordinate[1])\n        if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {\n          deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn]\n          deviceInfo.device = data.deviceInfo[deviceInfo.dock.basic_osd.sub_device?.device_sn ?? osdVisible.value.sn]\n        }\n      }\n    }, {\n      deep: true\n    })\n\n    watch(\n      () => store.state.wsEvent,\n      newData => {\n        const useGMapCoverHook = useGMapCover()\n        const event = newData\n        let exist = false\n        if (Object.keys(event.mapElementCreat).length !== 0) {\n          console.log(event.mapElementCreat)\n          const ele = event.mapElementCreat\n          store.state.Layers.forEach(layer => {\n            layer.elements.forEach(e => {\n              if (e.id === ele.id) {\n                exist = true\n                console.log('true')\n              }\n            })\n          })\n          if (exist === false) {\n            setLayers({\n              id: ele.id,\n              name: ele.name,\n              resource: ele.resource\n            })\n\n            updateCoordinates('wgs84-gcj02', ele)\n            const data = { id: ele.id, name: ele.name }\n            if (MapElementEnum.PIN === ele.resource?.type) {\n              useGMapCoverHook.init2DPin(\n                ele.name,\n                ele.resource.content.geometry.coordinates,\n                ele.resource.content.properties.color,\n                data\n              )\n            } else if (MapElementEnum.LINE === ele.resource?.type) {\n              useGMapCoverHook.initPolyline(\n                ele.name,\n                ele.resource.content.geometry.coordinates,\n                ele.resource.content.properties.color,\n                data)\n            } else if (MapElementEnum.POLY === ele.resource?.type) {\n              useGMapCoverHook.initPolygon(\n                ele.name,\n                ele.resource.content.geometry.coordinates,\n                ele.resource.content.properties.color,\n                data)\n            }\n          }\n\n          store.state.wsEvent.mapElementCreat = {}\n        }\n        if (Object.keys(event.mapElementUpdate).length !== 0) {\n          console.log(event.mapElementUpdate)\n          console.log('该功能还未实现，请开发商自己增加')\n          store.state.wsEvent.mapElementUpdate = {}\n        }\n        if (Object.keys(event.mapElementDelete).length !== 0) {\n          console.log(event.mapElementDelete)\n          console.log('该功能还未实现，请开发商自己增加')\n          store.state.wsEvent.mapElementDelete = {}\n        }\n      },\n      {\n        deep: true\n      }\n    )\n\n    function draw (type: MapDoodleType, bool: boolean, flightAreaType?: EFlightAreaType) {\n      state.currentType = type\n      mouseMode.value = bool\n      state.isFlightArea = !!flightAreaType\n      useMouseToolHook.mouseTool(type, getDrawCallback, flightAreaType)\n    }\n\n    // dock 控制面板\n    const {\n      dockControlPanelVisible,\n      setDockControlPanelVisible,\n      onCloseControlPanel,\n    } = useDockControl()\n\n    // 连接或断开drc\n    useConnectMqtt()\n\n    onMounted(() => {\n      const app = getApp()\n      useGMapManageHook.globalPropertiesConfig(app)\n    })\n\n    const { getDrawFlightAreaCallback, onFlightAreaDroneLocationWs } = useFlightArea()\n    useFlightAreaDroneLocationEvent(onFlightAreaDroneLocationWs)\n\n    function selectFlightAreaAction ({ type, isCircle }: { type: EFlightAreaType, isCircle: boolean }) {\n      draw(isCircle ? MapDoodleEnum.CIRCLE : MapDoodleEnum.POLYGON, true, type)\n    }\n\n    function getDrawCallback ({ obj }: { obj : any }) {\n      if (state.isFlightArea) {\n        getDrawFlightAreaCallback(obj)\n        return\n      }\n      switch (state.currentType) {\n        case MapDoodleEnum.PIN:\n          postPinPositionResource(obj)\n          break\n        case MapDoodleEnum.POLYLINE:\n          postPolylineResource(obj)\n          break\n        case MapDoodleEnum.POLYGON:\n          postPolygonResource(obj)\n          break\n        default:\n          break\n      }\n    }\n    async function postPinPositionResource (obj) {\n      const req = getPinPositionResource(obj)\n      setLayers(req)\n      const coordinates = req.resource.content.geometry.coordinates\n      updateCoordinates('gcj02-wgs84', req);\n      (req.resource.content.geometry.coordinates as GeojsonCoordinate).push((coordinates as GeojsonCoordinate)[2])\n      const result = await postElementsReq(shareId.value, req)\n      obj.setExtData({ id: req.id, name: req.name })\n      store.state.coverMap[req.id] = [obj]\n    }\n    async function postPolylineResource (obj) {\n      const req = getPolylineResource(obj)\n      setLayers(req)\n      updateCoordinates('gcj02-wgs84', req)\n      const result = await postElementsReq(shareId.value, req)\n      obj.setExtData({ id: req.id, name: req.name })\n      store.state.coverMap[req.id] = [obj]\n    }\n    async function postPolygonResource (obj) {\n      const req = getPoygonResource(obj)\n      setLayers(req)\n      updateCoordinates('gcj02-wgs84', req)\n      const result = await postElementsReq(shareId.value, req)\n      obj.setExtData({ id: req.id, name: req.name })\n      store.state.coverMap[req.id] = [obj]\n    }\n\n    function getPinPositionResource (obj) {\n      const position = obj.getPosition()\n      const resource = generatePointContent(position)\n      const name = obj._originOpts.title\n      const id = uuidv4()\n      return {\n        id,\n        name,\n        resource\n      }\n    }\n    function getPolylineResource (obj) {\n      const path = obj.getPath()\n      const resource = generateLineContent(path)\n      const { name, id } = getBaseInfo(obj._opts)\n      return {\n        id,\n        name,\n        resource\n      }\n    }\n    function getPoygonResource (obj) {\n      const path = obj.getPath()\n      const resource = generatePolyContent(path)\n      const { name, id } = getBaseInfo(obj._opts)\n      return {\n        id,\n        name,\n        resource\n      }\n    }\n    function getBaseInfo (obj) {\n      const name = obj.title\n      const id = uuidv4()\n      return { name, id }\n    }\n    function setLayers (resource: PostElementsBody) {\n      const layers = store.state.Layers\n      const layer = layers.find(item => item.id.includes(shareId.value))\n      // layer.id = 'private_layer' + uuidv4()\n      // layer?.elements.push(resource)\n      if (layer?.elements) {\n        ;(layer?.elements as any[]).push(resource)\n      }\n      console.log('layers', layers)\n      store.commit('SET_LAYER_INFO', layers)\n    }\n    function closeLivestreamOthers () {\n      store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', false)\n    }\n    function closeLivestreamAgora () {\n      store.commit('SET_LIVESTREAM_AGORA_VISIBLE', false)\n    }\n    function updateCoordinates (transformType: string, element: any) {\n      const geoType = element.resource?.content.geometry.type\n      const type = element.resource?.type as number\n      if (element.resource) {\n        if (MapElementEnum.PIN === type) {\n          const coordinates = element.resource?.content.geometry\n            .coordinates as GeojsonCoordinate\n          if (transformType === 'wgs84-gcj02') {\n            const transResult = wgs84togcj02(\n              coordinates[0],\n              coordinates[1]\n            ) as GeojsonCoordinate\n            element.resource.content.geometry.coordinates = transResult\n          } else if (transformType === 'gcj02-wgs84') {\n            const transResult = gcj02towgs84(\n              coordinates[0],\n              coordinates[1]\n            ) as GeojsonCoordinate\n            element.resource.content.geometry.coordinates = transResult\n          }\n        } else if (MapElementEnum.LINE === type) {\n          const coordinates = element.resource?.content.geometry\n            .coordinates as GeojsonCoordinate[]\n          if (transformType === 'wgs84-gcj02') {\n            coordinates.forEach((coordinate, i, arr) => {\n              arr[i] = wgs84togcj02(\n                coordinate[0],\n                coordinate[1]\n              ) as GeojsonCoordinate\n            })\n          } else if (transformType === 'gcj02-wgs84') {\n            coordinates.forEach((coordinate, i, arr) => {\n              arr[i] = gcj02towgs84(\n                coordinate[0],\n                coordinate[1]\n              ) as GeojsonCoordinate\n            })\n          }\n          element.resource.content.geometry.coordinates = coordinates\n        } else if (MapElementEnum.POLY === type) {\n          const coordinates = element.resource?.content.geometry\n            .coordinates[0] as GeojsonCoordinate[]\n          if (transformType === 'wgs84-gcj02') {\n            coordinates.forEach((coordinate, i, arr) => {\n              arr[i] = wgs84togcj02(\n                coordinate[0],\n                coordinate[1]\n              ) as GeojsonCoordinate\n            })\n          } else if (transformType === 'gcj02-wgs84') {\n            coordinates.forEach((coordinate, i, arr) => {\n              arr[i] = gcj02towgs84(\n                coordinate[0],\n                coordinate[1]\n              ) as GeojsonCoordinate\n            })\n          }\n          element.resource.content.geometry.coordinates = [coordinates]\n        }\n      }\n    }\n    return {\n      draw,\n      mouseMode,\n      drawVisible,\n      livestreamOthersVisible,\n      livestreamAgoraVisible,\n      osdVisible,\n      pin,\n      state,\n      M30,\n      deviceInfo,\n      EGear,\n      EModeCode,\n      str,\n      EDockModeCode,\n      dockControlPanelVisible,\n      setDockControlPanelVisible,\n      onCloseControlPanel,\n      NetworkStateTypeEnum,\n      NetworkStateQualityEnum,\n      RainfallEnum,\n      DroneInDockEnum,\n      closeLivestreamOthers,\n      closeLivestreamAgora,\n      qualityStyle,\n      selectFlightAreaAction,\n    }\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n\n.g-map-wrapper {\n  height: 100%;\n  width: 100%;\n\n  .g-action-panel {\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    .g-action-item {\n      width: 28px;\n      height: 28px;\n      background: white;\n      color: $primary;\n      border-radius: 2px;\n      line-height: 28px;\n      text-align: center;\n      margin-bottom: 2px;\n    }\n    .g-action-item:hover {\n      border: 1px solid $primary;\n      border-radius: 2px;\n    }\n  }\n  .selection {\n    border: 1px solid $primary;\n    border-radius: 2px;\n  }\n\n  // antd button 光晕\n  &:deep(.ant-btn){\n    &::after {\n      display: none;\n    }\n  }\n}\n.osd-panel {\n  position: absolute;\n  margin-left: 10px;\n  left: 0;\n  top: 10px;\n  width: 480px;\n  background: #000;\n  color: #fff;\n  border-radius: 2px;\n  opacity: 0.8;\n}\n.osd > div:not(.dock-control-panel) {\n  margin-top: 5px;\n  padding-left: 5px;\n}\n\n.circle {\n  border-radius: 50%;\n  width: 10px;\n  height: 10px;\n}\n\n.battery-slide {\n  .capacity-percent {\n    background: #00ee8b;\n  }\n  .return-home {\n    background: #ff9f0a;\n  }\n  .landing {\n    background: #f5222d;\n  }\n  .white-point {\n    width: 4px;\n    height: 4px;\n    border-radius: 50%;\n    background: white;\n    bottom: -0.5px;\n  }\n  .battery {\n    background: #141414;\n    color: #00ee8b;\n    margin-top: -10px;\n    height: 20px;\n    width: auto;\n    border-left: 1px solid #00ee8b;\n    padding: 0 5px;\n  }\n}\n.battery-slide > div {\n  position: absolute;\n  min-height: 2px;\n  border-radius: 2px;\n}\n\n.liveview {\n  position: absolute;\n  color: #fff;\n  z-index: 1;\n  left: 0;\n  margin-left: 10px;\n  top: 10px;\n  text-align: center;\n  width: 800px;\n  height: 720px;\n  background: #232323;\n}\n</style>\n"
  },
  {
    "path": "src/components/LayersTree.vue",
    "content": "<template>\n  <span>\n    <a-tree\n      draggable\n      :defaultExpandAll=\"true\"\n      class=\"device-map-layers\"\n      @drop=\"onDrop\"\n      v-bind=\"$attrs\"\n    >\n      <a-tree-node\n        :title=\"layer.name\"\n        :id=\"layer.id\"\n        v-for=\"layer in getTreeData\"\n        :key=\"layer.id\"\n      >\n        <!-- <template #title>\n                {{layer.name}}\n              </template> -->\n        <template v-if=\"layer.elements\">\n          <a-tree-node\n            v-for=\"resource in layer.elements\"\n            :id=\"getLayerTreeKey('resource', resource.id)\"\n            :key=\"getLayerTreeKey('resource', resource.id)\"\n          >\n            <template #title>\n              {{ resource.name }}\n            </template>\n          </a-tree-node>\n        </template>\n      </a-tree-node>\n    </a-tree>\n  </span>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, defineProps, PropType, reactive } from 'vue'\nimport { useMyStore } from '/@/store'\nimport { DropEvent, mapLayer } from '/@/types/mapLayer'\nimport { getLayerTreeKey } from '/@/utils/layer-tree'\nconst store = useMyStore()\nconst props = defineProps({\n  layerData: Array as PropType<mapLayer[]>\n})\nconst state = reactive({\n  checkedKeys: [] as string[],\n  expandedKeys: [] as string[]\n})\nconst getTreeData = computed(() => {\n  // console.log('props.treeData', JSON.parse(JSON.stringify(props.layerData)))\n  return JSON.parse(JSON.stringify(props.layerData))\n})\nconst shareId = computed(() => {\n  return store.state.layerBaseInfo.share\n})\nconst defaultId = computed(() => {\n  return store.state.layerBaseInfo.default\n})\nasync function onDrop ({ node, dragNode, dropPosition, dropToGap }: DropEvent) {\n  let _treeData = props.layerData || []\n  let dragKey = dragNode.eventKey\n  dragKey = dragKey.replaceAll('resource__', '')\n  const dropPos = node.pos.split('-')\n  let dropKey =\n    node.eventKey.includes(shareId.value) ||\n    node.eventKey.includes(defaultId.value)\n      ? node.eventKey\n      : node.$parent.eventKey\n  if (!dragKey || !dropKey) return\n  dropKey = dropKey.replaceAll('resource__', '')\n  const loop = (data: mapLayer[], key: string, callback: Function) => {\n    data.forEach((item, index, arr) => {\n      if (item.id === key) {\n        return callback(item, index, arr)\n      }\n\n      if (item.elements) {\n        return loop(item.elements, key, callback)\n      }\n    })\n  }\n  const data = [..._treeData] as mapLayer[]\n  // Find dragObject\n  let dragObj = {} as mapLayer\n  loop(data, dragKey, (item: mapLayer, index: number, arr: mapLayer[]) => {\n    arr.splice(index, 1)\n    dragObj = item\n  })\n  if (!dropToGap) {\n    // Drop on the content\n    loop(data, dropKey, (item: mapLayer) => {\n      item.elements = item.elements || []\n      // where to insert 示例添加到尾部，可以是随意位置\n      item.elements.push(dragObj)\n    })\n  }\n  _treeData = data\n  // console.log('_treeData', _treeData)\n}\n</script>\n<style lang=\"scss\">\n$antPrefix: 'ant';\n.device-map-layers.#{$antPrefix}-tree {\n  color: #fff;\n\n  .#{$antPrefix}-tree-checkbox:not(.#{$antPrefix}-tree-checkbox-checked)\n    .#{$antPrefix}-tree-checkbox-inner {\n    background-color: unset;\n  }\n\n  .anticon {\n    font-size: 16px;\n  }\n\n  // 第一个层级的 li，有左边距 16px\n  > li {\n    padding-left: 16px;\n    padding-right: 16px;\n  }\n\n  li {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    padding-top: 0;\n    padding-bottom: 0;\n\n    &:first-child {\n      padding-top: 4px;\n    }\n\n    &.#{$antPrefix}-tree-treenode-disabled\n      > .#{$antPrefix}-tree-node-content-wrapper {\n      height: 20px;\n      span {\n        color: #fff;\n      }\n    }\n    > ul {\n      width: 100%;\n    }\n\n    .#{$antPrefix}-tree-switcher {\n      z-index: 1;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n    }\n\n    .#{$antPrefix}-tree-checkbox {\n      z-index: 1;\n    }\n    .#{$antPrefix}-tree-checkbox:hover::after,\n    .#{$antPrefix}-tree-checkbox-wrapper:hover\n      .#{$antPrefix}-tree-checkbox::after {\n      visibility: collapse;\n    }\n\n    .#{$antPrefix}-tree-title {\n      display: block;\n    }\n\n    .#{$antPrefix}-tree-node-content-wrapper {\n      color: #fff;\n      width: calc(100% - 46px);\n      flex: 1;\n      box-sizing: content-box;\n      height: 20px;\n      min-width: 0; // 解决文字溢出不会省略的问题\n      padding-right: 0;\n\n      &:not([draggable='true']) {\n        border-top: 2px transparent solid;\n        border-bottom: 2px transparent solid;\n      }\n\n      &:hover {\n        background-color: transparent;\n      }\n\n      > span {\n        &::before {\n          // position: absolute;\n          // right: 0;\n          // left: 0;\n          height: 28px;\n          transition: all 0.3s;\n          content: '';\n        }\n\n        // 进度条组件需要相对最外层定位，进度条组件的position不能设置为relative\n        > *:not(.progress-wrapper) {\n          position: relative;\n          z-index: 1;\n        }\n      }\n\n      &.#{$antPrefix}-tree-node-selected {\n        background-color: transparent;\n        color: #2d8cf0;\n        > span {\n          &::before {\n            background-color: #4f4f4f;\n          }\n        }\n      }\n    }\n  }\n  span.#{$antPrefix}-tree-switcher.#{$antPrefix}-tree-switcher_open\n    .#{$antPrefix}-tree-switcher-icon {\n    transform: rotate(0deg) !important;\n  }\n  span.#{$antPrefix}-tree-switcher.#{$antPrefix}-tree-switcher_close\n    .#{$antPrefix}-tree-switcher-icon {\n    transform: rotate(0deg) !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/MediaPanel.vue",
    "content": "<template>\n  <div class=\"header\">Media Files</div>\n  <a-spin :spinning=\"loading\" :delay=\"1000\" tip=\"downloading\" size=\"large\">\n    <div class=\"media-panel-wrapper\">\n      <a-table class=\"media-table\" :columns=\"columns\" :data-source=\"mediaData.data\" row-key=\"fingerprint\"\n        :pagination=\"paginationProp\" :scroll=\"{ x: '100%', y: 600 }\" @change=\"refreshData\">\n        <template v-for=\"col in ['name', 'path']\" #[col]=\"{ text }\" :key=\"col\">\n          <a-tooltip :title=\"text\">\n              <a v-if=\"col === 'name'\">{{ text }}</a>\n              <span v-else>{{ text }}</span>\n          </a-tooltip>\n        </template>\n        <template #original=\"{ text }\">\n          {{ text }}\n        </template>\n        <template #action=\"{ record }\">\n          <a-tooltip title=\"download\">\n            <a class=\"fz18\" @click=\"downloadMedia(record)\"><DownloadOutlined /></a>\n          </a-tooltip>\n        </template>\n      </a-table>\n    </div>\n  </a-spin>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from '@vue/reactivity'\nimport { TableState } from 'ant-design-vue/lib/table/interface'\nimport { onMounted, reactive } from 'vue'\nimport { IPage } from '../api/http/type'\nimport { ELocalStorageKey } from '../types/enums'\nimport { downloadFile } from '../utils/common'\nimport { downloadMediaFile, getMediaFiles } from '/@/api/media'\nimport { DownloadOutlined } from '@ant-design/icons-vue'\nimport { message, Pagination } from 'ant-design-vue'\nimport { load } from '@amap/amap-jsapi-loader'\n\nconst workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\nconst loading = ref(false)\n\nconst columns = [\n  {\n    title: 'File Name',\n    dataIndex: 'file_name',\n    ellipsis: true,\n    slots: { customRender: 'name' }\n  },\n  {\n    title: 'File Path',\n    dataIndex: 'file_path',\n    ellipsis: true,\n    slots: { customRender: 'path' }\n  },\n  // {\n  //   title: 'FileSize',\n  //   dataIndex: 'size',\n  // },\n  {\n    title: 'Drone',\n    dataIndex: 'drone'\n  },\n  {\n    title: 'Payload Type',\n    dataIndex: 'payload'\n  },\n  {\n    title: 'Original',\n    dataIndex: 'is_original',\n    slots: { customRender: 'original' }\n  },\n  {\n    title: 'Created',\n    dataIndex: 'create_time'\n  },\n  {\n    title: 'Action',\n    slots: { customRender: 'action' }\n  }\n]\nconst body: IPage = {\n  page: 1,\n  total: 0,\n  page_size: 50\n}\nconst paginationProp = reactive({\n  pageSizeOptions: ['20', '50', '100'],\n  showQuickJumper: true,\n  showSizeChanger: true,\n  pageSize: 50,\n  current: 1,\n  total: 0\n})\n\ntype Pagination = TableState['pagination']\n\ninterface MediaFile {\n  fingerprint: string,\n  drone: string,\n  payload: string,\n  is_original: string,\n  file_name: string,\n  file_path: string,\n  create_time: string,\n  file_id: string,\n}\n\nconst mediaData = reactive({\n  data: [] as MediaFile[]\n})\n\nonMounted(() => {\n  getFiles()\n})\n\nfunction getFiles () {\n  getMediaFiles(workspaceId, body).then(res => {\n    mediaData.data = res.data.list\n    paginationProp.total = res.data.pagination.total\n    paginationProp.current = res.data.pagination.page\n    console.info(mediaData.data[0])\n  })\n}\n\nfunction refreshData (page: Pagination) {\n  body.page = page?.current!\n  body.page_size = page?.pageSize!\n  getFiles()\n}\n\nfunction downloadMedia (media: MediaFile) {\n  loading.value = true\n  downloadMediaFile(workspaceId, media.file_id).then(res => {\n    if (!res) {\n      return\n    }\n    const data = new Blob([res])\n    downloadFile(data, media.file_name)\n  }).finally(() => {\n    loading.value = false\n  })\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.media-panel-wrapper {\n  width: 100%;\n  padding: 16px;\n  .media-table {\n    background: #fff;\n    margin-top: 10px;\n  }\n  .action-area {\n    color: $primary;\n    cursor: pointer;\n  }\n}\n.header {\n  width: 100%;\n  height: 60px;\n  background: #fff;\n  padding: 16px;\n  font-size: 20px;\n  font-weight: bold;\n  text-align: start;\n  color: #000;\n}\n</style>\n"
  },
  {
    "path": "src/components/common/sidebar.vue",
    "content": "<template>\n  <div class=\"demo-project-sidebar-wrapper flex-justify-between\">\n    <div>\n    <router-link\n      v-for=\"item in options\"\n      :key=\"item.key\"\n      :to=\"item.path\"\n      :class=\"{\n        'menu-item': true,\n        selected: selectedRoute(item),\n      }\"\n    >\n      <a-tooltip :title=\"item.label\" placement=\"right\">\n        <Icon class=\"fz20\" style=\"width: 50px;\" :icon=\"item.icon\"/>\n      </a-tooltip>\n    </router-link>\n    </div>\n    <div class=\"mb20 flex-display flex-column flex-align-center flex-justify-between\">\n      <a-tooltip title=\"Back to home\" placement=\"right\">\n        <a @click=\"goHome\"> <Icon icon=\"ImportOutlined\" style=\"font-size: 22px; color: white\"/></a>\n      </a-tooltip>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport { createVNode, defineComponent } from 'vue'\nimport { getRoot } from '/@/root'\nimport * as icons from '@ant-design/icons-vue'\nimport { ERouterName } from '/@/types'\n\ninterface IOptions {\n  key: number\n  label: string\n  path:\n    | string\n    | {\n        path: string\n        query?: any\n      }\n  icon: string\n}\n\nconst Icon = (props: {icon: string}) => {\n  return createVNode((icons as any)[props.icon])\n}\n\nexport default defineComponent({\n  components: {\n    Icon,\n  },\n  name: 'Sidebar',\n  setup () {\n    const root = getRoot()\n    const options = [\n      { key: 0, label: 'Tsa', path: '/' + ERouterName.TSA, icon: 'TeamOutlined' },\n      { key: 1, label: 'Livestream', path: '/' + ERouterName.LIVESTREAM, icon: 'VideoCameraOutlined' },\n      { key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },\n      { key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },\n      { key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },\n      { key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },\n      { key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },\n    ]\n\n    function selectedRoute (item: IOptions) {\n      const path = typeof item.path === 'string' ? item.path : item.path.path\n      return root.$route.path?.indexOf(path) === 0\n    }\n\n    function goHome () {\n      root.$router.push('/' + ERouterName.MEMBERS)\n    }\n\n    return {\n      options,\n      selectedRoute,\n      goHome,\n    }\n  }\n})\n</script>\n\n<style scoped lang=\"scss\">\n.demo-project-sidebar-wrapper {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  width: 50px;\n  border-right: 1px solid #4f4f4f;\n  color: $text-white-basic;\n  // flex: 1;\n  overflow: hidden;\n  .menu-item {\n    width: 100%;\n    padding: 16px 0px;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    color: $text-white-basic;\n    cursor: pointer;\n    &.selected {\n      background-color: #101010;\n      color: $primary;\n    }\n    &.disabled {\n      pointer-events: none;\n      opacity: 0.45;\n    }\n  }\n  .filling {\n    flex: 1;\n  }\n\n  .setting-icon {\n    font-size: 24px;\n    margin-bottom: 24px;\n    color: $text-white-basic;\n  }\n\n}\n.ant-tooltip-open {\n  border: 0;\n}\n</style>\n"
  },
  {
    "path": "src/components/common/topbar.vue",
    "content": "<template>\n  <div class=\"width-100 flex-row flex-justify-between flex-align-center\" style=\"height: 60px;\">\n    <div class=\"height-100\">\n      <a-avatar :size=\"40\" shape=\"square\" :src=\"cloudapi\" />\n      <span class=\"ml10 fontBold\">{{ workspaceName }}</span>\n    </div>\n\n    <a-space class=\"fz16 height-100\" size=\"large\">\n        <router-link\n        v-for=\"item in options\"\n        :key=\"item.key\"\n        :to=\"item.path\"\n        :class=\"{\n            'menu-item': true,\n        }\">\n          <span @click=\"selectedRoute(item.path)\" :style=\"selected === item.path ? 'color: #2d8cf0;' : 'color: white'\">{{ item.label }}</span>\n        </router-link>\n    </a-space>\n\n    <div class=\"height-100 fz16 flex-row flex-justify-between flex-align-center\">\n      <a-dropdown>\n        <div class=\"height-100\">\n          <span class=\"fz20 mt20\" style=\"border: 2px solid white; border-radius: 50%; display: inline-flex;\"><UserOutlined /></span>\n          <span class=\"ml10 mr10\" style=\"float: right;\">{{ username }}</span>\n        </div>\n        <template #overlay>\n          <a-menu theme=\"dark\" class=\"flex-column flex-justify-between flex-align-center\">\n            <a-menu-item>\n              <span class=\"mr10\" style=\"font-size: 16px;\"><ExportOutlined /></span>\n              <span @click=\"logout\">Log Out</span>\n            </a-menu-item>\n          </a-menu>\n        </template>\n      </a-dropdown>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue'\nimport { defineComponent, onMounted, ref } from 'vue'\nimport { getRoot } from '/@/root'\nimport { getPlatformInfo } from '/@/api/manage'\nimport { ELocalStorageKey, ERouterName } from '/@/types'\nimport { UserOutlined, ExportOutlined } from '@ant-design/icons-vue'\nimport cloudapi from '/@/assets/icons/cloudapi.png'\n\nconst root = getRoot()\n\ninterface IOptions {\n  key: number\n  label: string\n  path:\n    | string\n    | {\n        path: string\n        query?: any\n      }\n  icon: string\n}\nconst username = ref(localStorage.getItem(ELocalStorageKey.Username))\nconst workspaceName = ref('')\nconst options = [\n  { key: 0, label: ERouterName.WORKSPACE.charAt(0).toUpperCase() + ERouterName.WORKSPACE.substr(1), path: '/' + ERouterName.WORKSPACE },\n  { key: 1, label: ERouterName.MEMBERS.charAt(0).toUpperCase() + ERouterName.MEMBERS.substr(1), path: '/' + ERouterName.MEMBERS },\n  { key: 2, label: ERouterName.DEVICES.charAt(0).toUpperCase() + ERouterName.DEVICES.substr(1), path: '/' + ERouterName.DEVICES },\n  { key: 3, label: ERouterName.FIRMWARES.charAt(0).toUpperCase() + ERouterName.FIRMWARES.substr(1), path: '/' + ERouterName.FIRMWARES },\n]\n\nconst selected = ref<string>(root.$route.path)\n\nonMounted(() => {\n  getPlatformInfo().then(res => {\n    workspaceName.value = res.data.workspace_name\n  })\n})\n\nfunction selectedRoute (path: string) {\n  selected.value = path\n}\n\nconst logout = () => {\n  localStorage.clear()\n  root.$router.push(ERouterName.PROJECT)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n\n.fontBold {\n  font-weight: 500;\n  font-size: 18px;\n}\n\n</style>\n"
  },
  {
    "path": "src/components/devices/DeviceFirmwareStatus.vue",
    "content": "<template>\n<div>\n  <span class=\"status-tag pointer\">\n    <a-popconfirm\n      :title=\"getTitle()\"\n      ok-text=\"Yes\"\n      cancel-text=\"No\"\n      placement=\"left\"\n      @confirm=\"onFirmwareStatusClick(firmware)\"\n    >\n      <a-tag :color=\"firmware.firmware_status ? commonColor.NORMAL : commonColor.FAIL\"\n          :class=\"firmware.firmware_status ? 'border-corner ' : 'status-disable border-corner'\">\n        {{ getText(firmware.firmware_status) }}\n      </a-tag>\n    </a-popconfirm>\n  </span>\n</div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps, defineEmits, ref, watch, computed } from 'vue'\nimport { changeFirmareStatus } from '/@/api/manage'\nimport { ELocalStorageKey } from '/@/types'\nimport { Firmware, FirmwareStatusEnum } from '/@/types/device-firmware'\nimport { commonColor } from '/@/utils/color'\n\nconst props = defineProps<{\n  firmware: Firmware\n}>()\n\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\n\nfunction getTitle () {\n  return `Are you sure to set this firmware to ${getText(!props.firmware.firmware_status)}?`\n}\n\nfunction getText (status: boolean) {\n  return status ? FirmwareStatusEnum.TRUE : FirmwareStatusEnum.FALSE\n}\n\nfunction onFirmwareStatusClick (record: Firmware) {\n  changeFirmareStatus(workspaceId, record.firmware_id, { status: !record.firmware_status }).then((res) => {\n    if (res.code === 0) {\n      record.firmware_status = !record.firmware_status\n    }\n  })\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n  .status-disable{\n    opacity: 0.4;\n  }\n  .border-corner {\n    border-radius: 3px;\n  }\n  .pointer {\n    cursor: pointer;\n  }\n</style>\n"
  },
  {
    "path": "src/components/devices/device-hms/DeviceHmsDrawer.vue",
    "content": "<template>\n  <a-drawer\n    title=\"Hms Info\"\n    placement=\"right\"\n    v-model:visible=\"sVisible\"\n    @update:visible=\"onVisibleChange\"\n    :destroyOnClose=\"true\"\n    :width=\"800\">\n    <div class=\"flex-row flex-align-center\">\n      <div style=\"width: 240px;\">\n        <a-range-picker\n          v-model:value=\"time\"\n          format=\"YYYY-MM-DD\"\n          :placeholder=\"['Start Time', 'End Time']\"\n          @change=\"onTimeChange\"/>\n      </div>\n      <div class=\"ml5\">\n        <a-select\n          style=\"width: 150px\"\n          v-model:value=\"param.level\"\n          @select=\"onLevelSelect\">\n          <a-select-option\n            v-for=\"item in levels\"\n            :key=\"item.label\"\n            :value=\"item.value\"\n          >\n            {{ item.label }}\n          </a-select-option>\n        </a-select>\n      </div>\n      <div class=\"ml5\">\n        <a-select\n          v-model:value=\"param.domain\"\n          :disabled=\"!param.children_sn || !param.device_sn\"\n          style=\"width: 150px\"\n          @select=\"onDeviceTypeSelect\">\n          <a-select-option\n            v-for=\"item in deviceTypes\"\n            :key=\"item.label\"\n            :value=\"item.value\"\n          >\n            {{ item.label }}\n          </a-select-option>\n        </a-select>\n      </div>\n      <div class=\"ml5\">\n        <a-input-search\n          v-model:value=\"param.message\"\n          placeholder=\"input search message\"\n          style=\"width: 200px\"\n          @search=\"getHms\"/>\n      </div>\n    </div>\n    <div>\n      <a-table :columns=\"hmsColumns\"  :scroll=\"{ x: '100%', y: 600 }\" :data-source=\"hmsData.data\" :pagination=\"hmsPaginationProp\" @change=\"refreshHmsData\" row-key=\"hms_id\"\n        :rowClassName=\"rowClassName\" :loading=\"loading\">\n        <template #time=\"{ record }\">\n          <div>{{ record.create_time }}</div>\n          <div :style=\"record.update_time ? '' : record.level === EHmsLevel.CAUTION ? 'color: orange;' :\n            record.level === EHmsLevel.WARN ? 'color: red;' : 'color: #28d445;'\">{{ record.update_time ?? 'It is happening...' }}</div>\n        </template>\n        <template #level=\"{ text }\">\n          <div class=\"flex-row flex-align-center\">\n            <div :class=\"text === EHmsLevel.CAUTION ? 'caution' : text === EHmsLevel.WARN ? 'warn' : 'notice'\" style=\"width: 10px; height: 10px; border-radius: 50%;\"></div>\n            <div style=\"margin-left: 3px;\">{{ EHmsLevel[text] }}</div>\n          </div>\n        </template>\n        <template v-for=\"col in ['code', 'message']\" #[col]=\"{ text }\" :key=\"col\">\n          <a-tooltip :title=\"text\">\n              <div >{{ text }}</div>\n          </a-tooltip>\n        </template>\n        <template #domain=\"{text}\">\n          <a-tooltip :title=\"EDeviceTypeName[text]\">\n              <div >{{ EDeviceTypeName[text] }}</div>\n          </a-tooltip>\n        </template>\n      </a-table>\n    </div>\n  </a-drawer>\n</template>\n\n<!-- 暂时只抽取该组件 -->\n<script lang=\"ts\" setup>\nimport { watchEffect, reactive, ref, defineProps, defineEmits, watch } from 'vue'\nimport { getDeviceHms, HmsQueryBody } from '/@/api/manage'\nimport moment, { Moment } from 'moment'\nimport { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'\nimport { Device, DeviceHms } from '/@/types/device'\nimport { IPage } from '/@/api/http/type'\nimport { EDeviceTypeName, EHmsLevel, ELocalStorageKey } from '/@/types'\n\nconst props = defineProps<{\n  visible: boolean,\n  device: null | Device,\n}>()\nconst emit = defineEmits(['update:visible', 'ok', 'cancel'])\n\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''\n// 健康状态\nconst sVisible = ref(false)\n\nwatch(props, () => {\n  sVisible.value = props.visible\n  // 显示弹框时，获取设备hms信息\n  if (props.visible) {\n    showHms()\n  }\n})\n\nfunction onVisibleChange (sVisible: boolean) {\n  setVisible(sVisible)\n}\n\nfunction setVisible (v: boolean, e?: Event) {\n  sVisible.value = v\n  emit('update:visible', v, e)\n}\n\nconst loading = ref(false)\n\nconst hmsColumns: ColumnProps[] = [\n  { title: 'Alarm Begin | End Time', dataIndex: 'create_time', width: '25%', className: 'titleStyle', slots: { customRender: 'time' } },\n  { title: 'Level', dataIndex: 'level', width: '120px', className: 'titleStyle', slots: { customRender: 'level' } },\n  { title: 'Device', dataIndex: 'domain', width: '12%', className: 'titleStyle', slots: { customRender: 'domain' } },\n  { title: 'Error Code', dataIndex: 'key', width: '20%', className: 'titleStyle', ellipsis: true, slots: { customRender: 'code' } },\n  { title: 'Hms Message', dataIndex: 'message_en', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },\n  { title: 'Hms Message', dataIndex: 'message_zh', className: 'titleStyle', ellipsis: true, slots: { customRender: 'message' } },\n]\n\ninterface DeviceHmsData {\n  data: DeviceHms[]\n}\n\nconst hmsData = reactive<DeviceHmsData>({\n  data: []\n})\n\ntype Pagination = TableState['pagination']\n\nconst hmsPaginationProp = reactive({\n  pageSizeOptions: ['20', '50', '100'],\n  showQuickJumper: true,\n  showSizeChanger: true,\n  pageSize: 50,\n  current: 1,\n  total: 0\n})\n\n// 获取分页信息\nfunction getPaginationBody () {\n  return {\n    page: hmsPaginationProp.current,\n    page_size: hmsPaginationProp.pageSize\n  } as IPage\n}\n\nfunction showHms () {\n  const dock = props.device\n  if (!dock) return\n  if (dock.domain === EDeviceTypeName.Dock) {\n    getDeviceHmsBySn(dock.device_sn, dock.children?.[0].device_sn ?? '')\n  }\n  if (dock.domain === EDeviceTypeName.Aircraft) {\n    param.domain = EDeviceTypeName.Aircraft\n    getDeviceHmsBySn('', dock.device_sn)\n  }\n}\n\nfunction refreshHmsData (page: Pagination) {\n  hmsPaginationProp.current = page?.current!\n  hmsPaginationProp.pageSize = page?.pageSize!\n  getHms()\n}\n\nconst param = reactive<HmsQueryBody>({\n  sns: [],\n  device_sn: '',\n  children_sn: '',\n  language: 'en',\n  begin_time: new Date(new Date().setDate(new Date().getDate() - 7)).setHours(0, 0, 0, 0),\n  end_time: new Date().setHours(23, 59, 59, 999),\n  domain: -1,\n  level: '',\n  message: ''\n})\n\nconst levels = [\n  {\n    label: 'All',\n    value: ''\n  }, {\n    label: EHmsLevel[0],\n    value: EHmsLevel.NOTICE\n  }, {\n    label: EHmsLevel[1],\n    value: EHmsLevel.CAUTION\n  }, {\n    label: EHmsLevel[2],\n    value: EHmsLevel.WARN\n  }\n]\n\nconst deviceTypes = [\n  {\n    label: 'All',\n    value: -1\n  }, {\n    label: EDeviceTypeName[EDeviceTypeName.Aircraft],\n    value: EDeviceTypeName.Aircraft\n  }, {\n    label: EDeviceTypeName[EDeviceTypeName.Dock],\n    value: EDeviceTypeName.Dock\n  }\n]\n\nconst rowClassName = (record: any, index: number) => {\n  const className = []\n  if ((index & 1) === 0) {\n    className.push('table-striped')\n  }\n  if (record.domain !== EDeviceTypeName.Dock) {\n    className.push('child-row')\n  }\n  return className.toString().replaceAll(',', ' ')\n}\n\nconst time = ref([moment(param.begin_time), moment(param.end_time)])\n\nfunction getHms () {\n  loading.value = true\n  getDeviceHms(param, workspaceId, getPaginationBody())\n    .then(res => {\n      hmsPaginationProp.total = res.data.pagination.total\n      hmsPaginationProp.current = res.data.pagination.page\n      hmsData.data = res.data.list\n      hmsData.data.forEach(hms => {\n        hms.domain = hms.sn === param.children_sn ? EDeviceTypeName.Aircraft : EDeviceTypeName.Dock\n      })\n      loading.value = false\n    }).catch(_err => {\n      loading.value = false\n    })\n}\n\nfunction getDeviceHmsBySn (sn: string, childSn: string) {\n  param.device_sn = sn\n  param.children_sn = childSn\n  param.sns = [param.device_sn, param.children_sn]\n  getHms()\n}\n\nfunction onTimeChange (newTime: [Moment, Moment]) {\n  param.begin_time = newTime[0].valueOf()\n  param.end_time = newTime[1].valueOf()\n  getHms()\n}\n\nfunction onDeviceTypeSelect (val: number) {\n  param.sns = [param.device_sn, param.children_sn]\n  if (val === EDeviceTypeName.Dock) {\n    param.sns = [param.device_sn, '']\n  }\n  if (val === EDeviceTypeName.Aircraft) {\n    param.sns = ['', param.children_sn]\n  }\n  getHms()\n}\n\nfunction onLevelSelect (val: number) {\n  param.level = val\n  getHms()\n}\n</script>\n"
  },
  {
    "path": "src/components/devices/device-log/DeviceLogDetailModal.vue",
    "content": "<template>\n  <a-modal\n    title=\"日志上传详情\"\n    v-model:visible=\"sVisible\"\n    width=\"900px\"\n    :footer=\"null\"\n    @update:visible=\"onVisibleChange\">\n    <div class=\"device-log-detail-wrap\">\n      <div class=\"device-log-list\">\n        <div class=\"log-list-item\">\n          <a-button type=\"primary\" class=\"download-btn\" :disabled=\"!airportTableLogState.logList?.file_id || !airportTableLogState.logList?.object_key\"  size=\"small\" @click=\"onDownloadLog(airportTableLogState.logList.file_id)\">\n             下载机场日志\n          </a-button>\n          <a-table  :columns=\"airportLogColumns\"\n                    :scroll=\"{ x: '100%', y: 600 }\"\n                    :data-source=\"airportTableLogState.logList?.list\"\n                    rowKey=\"boot_index\"\n                    :pagination = \"false\"\n                    >\n            <template #log_time=\"{record}\">\n              <div>{{getLogTime(record)}}</div>\n            </template>\n            <template #size=\"{record}\">\n              <div>{{getLogSize(record.size)}}</div>\n            </template>\n          </a-table>\n        </div>\n        <div class=\"log-list-item\">\n          <a-button type=\"primary\"  class=\"download-btn\" :disabled=\"!droneTableLogState.logList?.file_id || !droneTableLogState.logList?.object_key\" size=\"small\" @click=\"onDownloadLog(droneTableLogState.logList.file_id)\">\n             下载飞行器日志\n          </a-button>\n          <a-table  :columns=\"droneLogColumns\"\n                    :scroll=\"{ x: '100%', y: 600 }\"\n                    :data-source=\"droneTableLogState.logList?.list\"\n                    rowKey=\"boot_index\"\n                    :pagination = \"false\"\n          >\n            <template #log_time=\"{record}\">\n              <div>{{getLogTime(record)}}</div>\n            </template>\n            <template #size=\"{record}\">\n              <div>{{getLogSize(record.size)}}</div>\n            </template>\n          </a-table>\n        </div>\n      </div>\n    </div>\n  </a-modal>\n</template>\n\n<script lang=\"ts\" setup>\nimport { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue'\nimport { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'\nimport { IPage } from '/@/api/http/type'\nimport { DOMAIN } from '/@/types/device'\nimport { DeviceLogFileInfo, GetDeviceUploadLogListRsp, getUploadDeviceLogUrl } from '/@/api/device-log'\nimport { useDeviceLogUploadDetail } from './use-device-log-upload-detail'\nimport { download } from '/@/utils/download'\n\nconst props = defineProps<{\n  visible: boolean,\n  deviceLog: null | GetDeviceUploadLogListRsp,\n}>()\nconst emit = defineEmits(['update:visible'])\n\nconst sVisible = ref(false)\n\nwatchEffect(() => {\n  sVisible.value = props.visible\n  if (props.visible) {\n    classifyDeviceLog()\n  }\n})\n\nfunction onVisibleChange (sVisible: boolean) {\n  setVisible(sVisible)\n}\n\nfunction setVisible (v: boolean, e?: Event) {\n  sVisible.value = v\n  emit('update:visible', v, e)\n}\n\n// 表格\nconst airportLogColumns: ColumnProps[] = [\n  { title: '机场日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } },\n  { title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } },\n]\n\nconst droneLogColumns: ColumnProps[] = [\n  { title: '飞行器日志', dataIndex: 'time', width: '70%', slots: { customRender: 'log_time' } },\n  { title: '文件大小', dataIndex: 'size', width: '30%', slots: { customRender: 'size' } },\n]\n\nconst airportTableLogState = reactive({\n  logList: {} as DeviceLogFileInfo,\n})\n\nconst droneTableLogState = reactive({\n  logList: {} as DeviceLogFileInfo,\n})\n\nfunction classifyDeviceLog () {\n  if (!props.deviceLog) return\n  const { device_logs } = props.deviceLog\n  const { files } = device_logs || {}\n  if (files && files.length > 0) {\n    files.forEach(file => {\n      if (file.module === DOMAIN.DOCK) {\n        airportTableLogState.logList = file\n      } else if (file.module === DOMAIN.DRONE) {\n        droneTableLogState.logList = file\n      }\n    })\n  }\n}\n\nconst { getLogTime, getLogSize } = useDeviceLogUploadDetail()\n\nasync function onDownloadLog (fileId: string) {\n  const { data } = await getUploadDeviceLogUrl({\n    file_id: fileId,\n    logs_id: props.deviceLog?.logs_id || ''\n  })\n  if (data) {\n    download(data)\n  // download('https:/github.com/dji-sdk/Mobile-SDK-Android-V5/archive/refs/heads/dev-sdk-main.zip')\n  }\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.device-log-detail-wrap{\n\n  .device-log-list{\n    display: flex;\n    justify-content: space-between;\n    padding: 8px 0;\n    .log-list-item{\n      width: 420px;\n\n      .download-btn{\n        margin-bottom: 10px;\n      }\n    }\n  }\n}\n</style>\n>\n"
  },
  {
    "path": "src/components/devices/device-log/DeviceLogUploadModal.vue",
    "content": "<template>\n  <a-modal\n    title=\"设备日志上传\"\n    v-model:visible=\"sVisible\"\n    width=\"900px\"\n    :footer=\"null\"\n    @update:visible=\"onVisibleChange\">\n    <div class=\"device-log-upload-wrap\">\n      <div class=\"page-action-row\">\n        <a-button type=\"primary\" :disabled=\"deviceLogUploadBtnDisabled\" @click=\"uploadDeviceLog\">上传日志</a-button>\n      </div>\n      <div class=\"device-log-list\">\n        <div class=\"log-list-item\">\n          <a-table  :columns=\"airportLogColumns\"\n                    :scroll=\"{ x: '100%', y: 600 }\"\n                    :data-source=\"airportTableLogState.logList?.list\"\n                    :loading=\"airportTableLogState.tableLoading\"\n                    :row-selection=\"airportTableLogState.rowSelection\"\n                    rowKey=\"boot_index\"\n                    :pagination = \"false\">\n            <template #log_time=\"{record}\">\n              <div>{{getLogTime(record)}}</div>\n            </template>\n            <template #size=\"{record}\">\n              <div>{{getLogSize(record.size)}}</div>\n            </template>\n          </a-table>\n        </div>\n        <div class=\"log-list-item\">\n          <a-table  :columns=\"droneLogColumns\"\n                    :scroll=\"{ x: '100%', y: 600 }\"\n                    :data-source=\"droneTableLogState.logList?.list\"\n                    :loading=\"droneTableLogState.tableLoading\"\n                    :row-selection=\"droneTableLogState.rowSelection\"\n                    rowKey=\"boot_index\"\n                    :pagination = \"false\">\n            <template #log_time=\"{record}\">\n              <div>{{getLogTime(record)}}</div>\n            </template>\n            <template #size=\"{record}\">\n              <div>{{getLogSize(record.size)}}</div>\n            </template>\n          </a-table>\n        </div>\n      </div>\n    </div>\n  </a-modal>\n</template>\n\n<script lang=\"ts\" setup>\nimport { watchEffect, reactive, ref, computed, defineProps, defineEmits } from 'vue'\nimport { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'\nimport { IPage } from '/@/api/http/type'\nimport { Device, DOMAIN } from '/@/types/device'\nimport { getDeviceLogList, postDeviceUpgrade, DeviceLogFileInfo, UploadDeviceLogBody, DeviceLogItem } from '/@/api/device-log'\nimport { message } from 'ant-design-vue'\nimport { useDeviceLogUploadDetail } from './use-device-log-upload-detail'\n\nconst props = defineProps<{\n  visible: boolean,\n  device: null | Device,\n}>()\nconst emit = defineEmits(['update:visible', 'upload-log-ok'])\n\nconst sVisible = ref(false)\n\nwatchEffect(() => {\n  sVisible.value = props.visible\n  // 显示弹框时，获取设备日志信息\n  if (props.visible) {\n    getDeviceLogInfo()\n  }\n})\n\nfunction onVisibleChange (sVisible: boolean) {\n  setVisible(sVisible)\n  if (!sVisible) {\n    resetTableLogState()\n  }\n}\n\nfunction setVisible (v: boolean, e?: Event) {\n  sVisible.value = v\n  emit('update:visible', v, e)\n}\n\n// 表格\nconst airportLogColumns: ColumnProps[] = [\n  { title: '机场日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } },\n  { title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } },\n]\n\nconst droneLogColumns: ColumnProps[] = [\n  { title: '飞行器日志', dataIndex: 'time', width: 100, slots: { customRender: 'log_time' } },\n  { title: '文件大小', dataIndex: 'size', width: 25, slots: { customRender: 'size' } },\n]\n\nconst airportTableLogState = reactive({\n  logList: {} as DeviceLogFileInfo,\n  tableLoading: false,\n  selectRow: [],\n  rowSelection: {\n    columnWidth: 15,\n    selectedRowKeys: [] as number[],\n    onChange: (selectedRowKeys:number[], selectedRows: []) => {\n      airportTableLogState.rowSelection.selectedRowKeys = selectedRowKeys\n      airportTableLogState.selectRow = selectedRows\n      console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)\n    },\n  }\n})\n\nfunction resetTableLogState () {\n  airportTableLogState.logList = {} as DeviceLogFileInfo\n  airportTableLogState.selectRow = []\n  airportTableLogState.tableLoading = false\n}\n\nconst droneTableLogState = reactive({\n  logList: {} as DeviceLogFileInfo,\n  tableLoading: false,\n  selectRow: [],\n  rowSelection: {\n    columnWidth: 15,\n    selectedRowKeys: [] as number[],\n    onChange: (selectedRowKeys: number[], selectedRows: []) => {\n      droneTableLogState.rowSelection.selectedRowKeys = selectedRowKeys\n      droneTableLogState.selectRow = selectedRows\n      console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)\n    },\n  }\n})\n\nconst deviceLogUploadBtnDisabled = computed(() => {\n  return (airportTableLogState.rowSelection.selectedRowKeys && airportTableLogState.rowSelection.selectedRowKeys.length <= 0) &&\n  (droneTableLogState.rowSelection.selectedRowKeys && droneTableLogState.rowSelection.selectedRowKeys.length <= 0)\n})\n\n// 获取设备内日志\nasync function getDeviceLogInfo () {\n  airportTableLogState.tableLoading = true\n  droneTableLogState.tableLoading = true\n  try {\n    const { code, data } = await getDeviceLogList({\n      device_sn: props.device?.device_sn || '',\n      domain: [DOMAIN.DOCK, DOMAIN.DRONE]\n    })\n    if (code === 0) {\n      const { files } = data\n      if (files && files.length > 0) {\n        files.forEach(file => {\n          if (file.module === DOMAIN.DOCK) {\n            airportTableLogState.logList = file\n          } else if (file.module === DOMAIN.DRONE) {\n            droneTableLogState.logList = file\n          }\n        })\n      }\n    }\n  } catch (err) {\n  }\n  airportTableLogState.tableLoading = false\n  droneTableLogState.tableLoading = false\n}\n\n// 日志上传\nasync function uploadDeviceLog () {\n  const body = {\n    device_sn: props.device?.device_sn || '',\n    files: [] as any\n  } as UploadDeviceLogBody\n  if (airportTableLogState.selectRow && airportTableLogState.selectRow.length > 0) {\n    body.files.push({\n      list: airportTableLogState.selectRow,\n      device_sn: airportTableLogState.logList.device_sn,\n      module: airportTableLogState.logList.module\n    })\n  }\n  if (droneTableLogState.selectRow && droneTableLogState.selectRow.length > 0) {\n    body.files.push({\n      list: droneTableLogState.selectRow,\n      device_sn: droneTableLogState.logList.device_sn,\n      module: droneTableLogState.logList.module\n    })\n  }\n  const { code } = await postDeviceUpgrade(body)\n  if (code === 0) {\n    message.success('日志上传任务执行成功')\n    emit('upload-log-ok')\n    setVisible(false)\n  }\n}\n\nconst { getLogTime, getLogSize } = useDeviceLogUploadDetail()\n\n</script>\n\n<style lang=\"scss\" scoped>\n.device-log-upload-wrap{\n\n  .device-log-list{\n    display: flex;\n    justify-content: space-between;\n    padding: 8px 0;\n    .log-list-item{\n      width: 420px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/devices/device-log/DeviceLogUploadRecordDrawer.vue",
    "content": "<template>\n  <a-drawer\n    title=\"设备日志上传记录\"\n    placement=\"right\"\n    v-model:visible=\"sVisible\"\n    @update:visible=\"onVisibleChange\"\n    :width=\"800\">\n    <!-- 设备日志上传记录 -->\n    <div class=\"device-log-upload-record-wrap\">\n      <div class=\"page-action-row\">\n        <a-button type=\"primary\" @click=\"onUploadDeviceLog\">上传日志</a-button>\n      </div>\n      <div class=\"device-log-upload-list\">\n        <a-table :columns=\"deviceLogUploadListColumns\"\n                  :scroll=\"{ x: '100%', y: 600 }\"\n                  :data-source=\"deviceUploadLogState.uploadLogList\"\n                  :loading=\"deviceUploadLogState.loading\"\n                  :pagination=\"deviceUploadLogState.paginationProp\"\n                  @change=\"onDeviceUploadLogTableChange\"\n                  rowKey=\"logs_id\">\n         <!-- 设备类型 -->\n          <template #device_type=\"{ record }\">\n            <div>\n              <div v-if=\"getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0\">{{ DEVICE_NAME[getDeviceInfo(record).parents[0].device_model.device_model_key]}}</div>\n              <div v-if=\"getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0\">{{ DEVICE_NAME[getDeviceInfo(record).hosts[0].device_model.device_model_key]}}</div>\n            </div>\n          </template>\n          <!-- 设备sn -->\n          <template #device_sn=\"{ record }\">\n            <div>\n              <div v-if=\"getDeviceInfo(record).parents && getDeviceInfo(record).parents.length > 0\">{{ getDeviceInfo(record).parents[0].sn }}</div>\n              <div v-if=\"getDeviceInfo(record).hosts && getDeviceInfo(record).hosts.length > 0\">{{ getDeviceInfo(record).hosts[0].sn }}</div>\n            </div>\n          </template>\n          <!-- 上传状态 -->\n          <template #status=\"{ record }\">\n            <div>\n              <div>\n                <span class=\"circle-icon\" :style=\"{backgroundColor: getDeviceLogUploadStatus(record).color}\"></span>\n                {{ getDeviceLogUploadStatus(record).text }}\n              </div>\n              <div v-if=\"record.status === DeviceLogUploadStatusEnum.Uploading\">\n                <a-progress :percent=\"getLogProgress(record)\" />\n              </div>\n            </div>\n          </template>\n          <!-- 操作 -->\n          <template #action=\"{ record }\">\n            <div class=\"row-action\">\n              <a-tooltip title=\"查看详情\">\n                  <FileTextOutlined  @click=\"showDeviceLogDetail(record)\"/>\n              </a-tooltip>\n              <span v-if=\"record.status === DeviceLogUploadStatusEnum.Uploading\">\n                <a-tooltip title=\"取消\">\n                  <StopOutlined @click=\"onCancelUploadDeviceLog(record)\"/>\n                </a-tooltip>\n              </span>\n              <span v-else>\n                <a-tooltip title=\"删除\">\n                  <DeleteOutlined @click=\"onDeleteUploadDeviceLog(record)\"/>\n                </a-tooltip>\n              </span>\n            </div>\n          </template>\n        </a-table>\n      </div>\n    </div>\n  </a-drawer>\n  <!-- 设备日志上传弹框 -->\n  <DeviceLogUploadModal\n     v-model:visible=\"deviceLogUploadModalVisible\"\n     :device=\"props.device\"\n     @upload-log-ok=\"onUploadLogOk\"\n  ></DeviceLogUploadModal>\n\n  <!-- 设备日志上传详情弹框 -->\n  <DeviceLogDetailModal\n     v-model:visible=\"deviceLogDetailModalVisible\"\n     :deviceLog=\"currentDeviceLog\"\n  ></DeviceLogDetailModal>\n</template>\n\n<script lang=\"ts\" setup>\nimport { watchEffect, reactive, ref, defineProps, defineEmits } from 'vue'\nimport { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'\nimport { IPage } from '/@/api/http/type'\nimport { Device, DOMAIN, DEVICE_NAME } from '/@/types/device'\nimport DeviceLogUploadModal from './DeviceLogUploadModal.vue'\nimport DeviceLogDetailModal from './DeviceLogDetailModal.vue'\nimport { getDeviceUploadLogList, GetDeviceUploadLogListRsp, cancelDeviceLogUpload, deleteDeviceLogUpload } from '/@/api/device-log'\nimport { StopOutlined, DeleteOutlined, FileTextOutlined } from '@ant-design/icons-vue'\nimport { DeviceLogUploadStatusEnum, DeviceLogUploadStatusMap, DeviceLogUploadStatusColor, DeviceLogUploadInfo, DeviceLogUploadWsStatusMap, DeviceLogProgressInfo } from '/@/types/device-log'\nimport { useDeviceLogUploadProgressEvent } from './use-device-log-upload-progress-event'\nimport { Modal } from 'ant-design-vue'\n\nconst props = defineProps<{\n  visible: boolean,\n  device: null | Device,\n}>()\nconst emit = defineEmits(['update:visible'])\n\nconst sVisible = ref(false)\n\nwatchEffect(() => {\n  sVisible.value = props.visible\n  // 显示弹框时，获取设备日志上传记录信息\n  if (props.visible) {\n    getDeviceUploadLogInfo()\n  }\n})\n\nfunction onVisibleChange (sVisible: boolean) {\n  setVisible(sVisible)\n}\n\nfunction setVisible (v: boolean, e?: Event) {\n  sVisible.value = v\n  emit('update:visible', v, e)\n}\n\n// 日志列表\nconst deviceLogUploadListColumns: ColumnProps[] = [\n  { title: '上传时间', dataIndex: 'create_time', width: 100 },\n  { title: '设备型号', dataIndex: 'device_type', width: 80, slots: { customRender: 'device_type' } },\n  { title: '设备SN', dataIndex: 'device_sn', width: 120, slots: { customRender: 'device_sn' } },\n  { title: '上传状态', dataIndex: 'status', width: 120, slots: { customRender: 'status' } },\n  { title: '操作', dataIndex: 'actions', width: 80, slots: { customRender: 'action' } },\n]\n\nconst deviceUploadLogState = reactive({\n  uploadLogList: [] as GetDeviceUploadLogListRsp[],\n  loading: false,\n  paginationProp: {\n    pageSizeOptions: ['20', '50', '100'],\n    showQuickJumper: true,\n    showSizeChanger: true,\n    pageSize: 50,\n    current: 1,\n    total: 0\n  }\n})\n\n// 获取上传的设备日志\nasync function getDeviceUploadLogInfo () {\n  deviceUploadLogState.loading = true\n  try {\n    const { code, data } = await getDeviceUploadLogList({\n      device_sn: props.device?.device_sn || '',\n      page: deviceUploadLogState.paginationProp.current,\n      page_size: deviceUploadLogState.paginationProp.pageSize\n    })\n    if (code === 0) {\n      deviceUploadLogState.uploadLogList = data.list\n      deviceUploadLogState.paginationProp.total = data.pagination.total\n      deviceUploadLogState.paginationProp.current = data.pagination.page\n      deviceUploadLogState.paginationProp.pageSize = data.pagination.page_size\n    }\n    deviceUploadLogState.loading = false\n  } catch (error) {\n    deviceUploadLogState.loading = false\n  }\n}\ntype Pagination = TableState['pagination']\n\n// 获取设备信息\nfunction getDeviceInfo (deviceLogItem: GetDeviceUploadLogListRsp) {\n  const { device_topo: deviceTopo } = deviceLogItem\n  return deviceTopo\n}\n\n// 获取上传状态\nfunction getDeviceLogUploadStatus (deviceLogItem: GetDeviceUploadLogListRsp) {\n  const statusObj = {\n    color: '',\n    text: ''\n  }\n  const { status } = deviceLogItem\n  statusObj.color = DeviceLogUploadStatusColor[status]\n  statusObj.text = DeviceLogUploadStatusMap[status]\n  return statusObj\n}\n\n// 获取上传进度\nfunction getLogProgress (deviceLogItem: GetDeviceUploadLogListRsp) {\n  let percent = 0\n  const { logs_progress } = deviceLogItem\n  if (logs_progress && logs_progress.length > 0) {\n    logs_progress.forEach(log => {\n      percent += (log.progress || 0)\n    })\n    percent = percent / logs_progress.length\n  }\n  return Math.floor(percent)\n}\n\n// 设备日志上传进度更新\nfunction onDeviceLogUploadWs (data: DeviceLogUploadInfo) {\n  const { sn, output } = data\n  if (output) {\n    const { files, status, logs_id: logId } = output || {}\n    const deviceLogItem = deviceUploadLogState.uploadLogList.find(log => log.logs_id === logId)\n    if (!deviceLogItem) return\n    if (status) {\n      deviceLogItem.status = DeviceLogUploadWsStatusMap[status]\n    }\n    if (files && files.length > 0) {\n      const logsProgress = [] as DeviceLogProgressInfo[]\n      files.forEach(file => {\n        logsProgress.push({\n          ...file,\n          status: DeviceLogUploadWsStatusMap[file.status]\n        })\n      })\n      deviceLogItem.logs_progress = logsProgress\n    }\n  }\n}\n\nuseDeviceLogUploadProgressEvent(onDeviceLogUploadWs)\n\n// 搜索\nasync function onDeviceUploadLogTableChange (page: Pagination) {\n  deviceUploadLogState.paginationProp.current = page?.current || 1\n  deviceUploadLogState.paginationProp.pageSize = page?.pageSize || 20\n  await getDeviceUploadLogInfo()\n}\n\n// 查看上传设备日志详情\nconst deviceLogDetailModalVisible = ref(false)\nconst currentDeviceLog = ref({} as GetDeviceUploadLogListRsp)\n\nfunction showDeviceLogDetail (deviceLogItem: GetDeviceUploadLogListRsp) {\n  if (!deviceLogItem) return\n  currentDeviceLog.value = deviceLogItem\n  deviceLogDetailModalVisible.value = true\n}\n\n// 取消上传设备日志\nasync function onCancelUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) {\n  Modal.confirm({\n    title: '取消日志上传',\n    content: '您确认取消设备日志上传吗？',\n    okType: 'danger',\n    onOk () {\n      cancelDeviceLogUploadOk()\n    },\n  })\n}\n\nasync function cancelDeviceLogUploadOk () {\n  const { code } = await cancelDeviceLogUpload({\n    device_sn: props.device?.device_sn || '',\n    module_list: [DOMAIN.DOCK, DOMAIN.DRONE],\n    status: 'cancel'\n  })\n  if (code === 0) {\n    await getDeviceUploadLogInfo()\n  }\n}\n\n// 删除上传的设备日志\nfunction onDeleteUploadDeviceLog (deviceLogItem: GetDeviceUploadLogListRsp) {\n  Modal.confirm({\n    title: '删除上传日志',\n    content: '您确认删除该条已上传设备日志吗？',\n    okType: 'danger',\n    onOk () {\n      deleteUploadDeviceLogOk(deviceLogItem)\n    },\n  })\n}\n\nasync function deleteUploadDeviceLogOk (deviceLogItem: GetDeviceUploadLogListRsp) {\n  const { code } = await deleteDeviceLogUpload({\n    device_sn: props.device?.device_sn || '',\n    logs_id: deviceLogItem.logs_id\n  })\n  if (code === 0) {\n    await getDeviceUploadLogInfo()\n  }\n}\n\n// 上传日志\nconst deviceLogUploadModalVisible = ref(false)\n\nfunction onUploadDeviceLog () {\n  deviceLogUploadModalVisible.value = true\n}\n\nfunction onUploadLogOk () {\n  // 刷新列表\n  getDeviceUploadLogInfo()\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.device-log-upload-record-wrap{\n  .page-action-row{\n    display: flex;\n    justify-content: space-between;\n    width: 100%;\n  }\n\n  .device-log-upload-list{\n    padding: 20px 0 10px;\n  }\n\n  .circle-icon {\n    display: inline-block;\n    width: 12px;\n    height: 12px;\n    margin-right: 3px;\n    border-radius: 50%;\n    vertical-align: middle;\n    flex-shrink: 0;\n  }\n\n  .row-action{\n    color: #2d8cf0;\n\n    & > span{\n      margin-right: 10px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/devices/device-log/use-device-log-upload-detail.ts",
    "content": "import { DeviceLogItem } from '/@/api/device-log'\nimport { bytesToSize } from '/@/utils/bytes'\nimport { formatUnixTime } from '/@/utils/time'\nimport {\n  DATE_FORMAT_MINUTE\n} from '/@/utils/constants'\n\nexport function useDeviceLogUploadDetail () {\n  function getLogTime (deviceLog: DeviceLogItem): string {\n    const startTime = formatUnixTime(deviceLog.start_time, DATE_FORMAT_MINUTE)\n    const endTime = formatUnixTime(deviceLog.end_time, DATE_FORMAT_MINUTE)\n    return `${startTime} — ${endTime}`\n  }\n\n  function getLogSize (size: number) {\n    return bytesToSize(size)\n  }\n\n  return {\n    getLogTime,\n    getLogSize\n  }\n}\n"
  },
  {
    "path": "src/components/devices/device-log/use-device-log-upload-progress-event.ts",
    "content": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { DeviceLogUploadInfo } from '/@/types/device-log'\n\nexport function useDeviceLogUploadProgressEvent (onDeviceLogUploadWs: (data: DeviceLogUploadInfo) => void): void {\n  function handleDeviceLogUploadProgress (payload: any) {\n    onDeviceLogUploadWs(payload.data)\n    // eslint-disable-next-line no-unused-expressions\n    // console.log('payload', payload.data)\n  }\n\n  onMounted(() => {\n    EventBus.on('deviceLogUploadProgress', handleDeviceLogUploadProgress)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('deviceLogUploadProgress', handleDeviceLogUploadProgress)\n  })\n}\n"
  },
  {
    "path": "src/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue",
    "content": "<template>\n<div class=\"firmware_upgrade_wrap\">\n  <!-- 版本 -->\n  <span class=\"version\"> {{ device.firmware_version }}</span>\n  <!-- tag -->\n  <span v-if=\"getTagStatus(device)\"\n        class=\"status-tag pointer\">\n    <a-tag class=\"pointer\"\n           :color=\"getFirmwareTag(device.firmware_status).color\"\n           @click=\"deviceUpgrade(device)\">\n      {{ getFirmwareTag(device.firmware_status).text }}\n    </a-tag>\n  </span>\n  <!-- 进度 -->\n  <span v-if=\"device.firmware_status === DeviceFirmwareStatusEnum.DuringUpgrade\">\n  {{ `${device.firmware_progress}`}}\n  </span>\n</div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps, defineEmits, ref, watch, computed } from 'vue'\nimport { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareStatusColor } from '/@/types/device'\n\nconst props = defineProps<{\n  device: Device,\n}>()\n\nconst emit = defineEmits(['device-upgrade'])\nconst needUpgrade = computed(() => {\n  return props.device.firmware_status === DeviceFirmwareStatusEnum.ConsistencyUpgrade ||\n         props.device.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded\n})\n\nfunction getTagStatus (record: Device) {\n  return record.firmware_status && record.firmware_status !== DeviceFirmwareStatusEnum.None\n}\n\nfunction getFirmwareTag (status: DeviceFirmwareStatusEnum) {\n  return {\n    text: DeviceFirmwareStatus[status] || '',\n    color: DeviceFirmwareStatusColor[status] || ''\n  }\n}\n\nfunction deviceUpgrade (record: Device) {\n  if (!needUpgrade.value) return\n  emit('device-upgrade', record)\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.firmware_upgrade_wrap{\n\n  .status-tag{\n    margin-left: 10px;\n  }\n\n  .pointer {\n    cursor: pointer;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue",
    "content": "<template>\n<a-modal :visible=\"sVisible\"\n         :title=\"title\"\n         :closable=\"false\"\n         centered\n         @update:visible=\"onVisibleChange\"\n         @cancel=\"onCancel\"\n         @ok=\"onConfirm\">\n         <div>\n          升级固件版本: {{ deviceUpgradeInfo?.product_version }}\n         </div>\n</a-modal>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps, defineEmits, ref, Ref, watchEffect } from 'vue'\nimport { Device, DeviceFirmwareStatusEnum, DeviceFirmwareStatus, DeviceFirmwareTypeEnum } from '/@/types/device'\nimport { getDeviceUpgradeInfo, GetDeviceUpgradeInfoRsp, DeviceUpgradeBody } from '/@/api/device-upgrade'\n\nconst props = defineProps<{\n  visible: boolean,\n  title: string,\n  device: null | Device,\n}>()\n\nconst emit = defineEmits(['update:visible', 'ok', 'cancel'])\n\nconst deviceUpgradeInfo:Ref<GetDeviceUpgradeInfoRsp> = ref({} as GetDeviceUpgradeInfoRsp)\nconst sVisible = ref(false)\n\nwatchEffect(() => {\n  sVisible.value = props.visible\n  // 显示弹框时，获取设备升级信息\n  if (props.visible) {\n    initDeviceUpgradeInfo()\n  }\n})\n\nfunction onVisibleChange (sVisible: boolean) {\n  setVisible(sVisible)\n}\n\nfunction setVisible (v: boolean, e?: Event) {\n  sVisible.value = v\n  emit('update:visible', v, e)\n}\n\n// 获取设备升级信息\nasync function initDeviceUpgradeInfo () {\n  if (!props.device?.device_name) {\n    return\n  }\n  const { code, data } = await getDeviceUpgradeInfo({ device_name: props.device?.device_name })\n  if (code === 0) {\n    deviceUpgradeInfo.value = data && data[0]\n  }\n}\n\n// 提交\nfunction checkConfirm () {\n  if (!deviceUpgradeInfo.value.product_version) {\n    return false\n  }\n  if (!props.device) {\n    return false\n  }\n  if (props.device.firmware_status !== DeviceFirmwareStatusEnum.ToUpgraded && props.device.firmware_status !== DeviceFirmwareStatusEnum.ConsistencyUpgrade) {\n    return false\n  }\n  return true\n}\n\nfunction onConfirm (e: Event) {\n  if (!checkConfirm()) {\n    return\n  }\n  setVisible(false, e)\n  emit('ok', [{\n    device_name: props.device?.device_name,\n    sn: props.device?.device_sn,\n    product_version: deviceUpgradeInfo.value.product_version,\n    firmware_upgrade_type: props.device?.firmware_status === DeviceFirmwareStatusEnum.ToUpgraded ? DeviceFirmwareTypeEnum.ToUpgraded : DeviceFirmwareTypeEnum.ConsistencyUpgrade // 1-普通升级，2-一致性升级\n  }] as DeviceUpgradeBody, e)\n}\n\nfunction onCancel (e: Event) {\n  setVisible(false, e)\n  emit('cancel', e)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "src/components/devices/device-upgrade/use-device-upgrade-event.ts",
    "content": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'\n\nexport function useDeviceUpgradeEvent (onDeviceUpgradeWs: (payload: DeviceCmdExecuteInfo) => void): void {\n  function handleDeviceUpgrade (payload: any) {\n    onDeviceUpgradeWs(payload.data)\n    // eslint-disable-next-line no-unused-expressions\n    // console.log('payload', payload.data)\n  }\n\n  onMounted(() => {\n    EventBus.on('deviceUpgrade', handleDeviceUpgrade)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('deviceUpgrade', handleDeviceUpgrade)\n  })\n}\n"
  },
  {
    "path": "src/components/devices/device-upgrade/use-device-upgrade.ts",
    "content": "import { Ref, ref } from 'vue'\nimport { Device } from '/@/types/device'\nimport { postDeviceUpgrade, DeviceUpgradeBody } from '/@/api/device-upgrade'\n\nexport function useDeviceFirmwareUpgrade (workspaceId: string) {\n  const deviceFirmwareUpgradeModalVisible = ref(false)\n  const selectedDevice: Ref<null | Device> = ref(null)\n\n  function setDeviceFirmwareUpgradeModalVisible (visible: boolean) {\n    deviceFirmwareUpgradeModalVisible.value = visible\n  }\n\n  function setSelectedDevice (device: null | Device) {\n    selectedDevice.value = device\n  }\n\n  // 点击设备升级\n  function onDeviceUpgrade (record: Device) {\n    if (!record) {\n      return\n    }\n    setSelectedDevice(record)\n    setDeviceFirmwareUpgradeModalVisible(true)\n  }\n\n  // 确认设备升级\n  async function onUpgradeDeviceOk (deviceUpgradeBody: DeviceUpgradeBody) {\n    const { code } = await postDeviceUpgrade(workspaceId, deviceUpgradeBody)\n    if (code === 0) {\n      // setDeviceFirmwareUpgradeModalVisible(false)\n    }\n  }\n\n  return {\n    deviceFirmwareUpgradeModalVisible,\n    setDeviceFirmwareUpgradeModalVisible,\n    selectedDevice,\n    setSelectedDevice,\n    onDeviceUpgrade,\n    onUpgradeDeviceOk,\n  }\n}\n"
  },
  {
    "path": "src/components/flight-area/FlightAreaActionIcon.vue",
    "content": "<template>\n  <div @click=\"selectCurrent\">\n    <a-dropdown class=\"height-100 width-100 icon-panel\">\n        <FlightAreaIcon :type=\"actionMap[selectedKey].type\" :is-circle=\"actionMap[selectedKey].isCircle\" :hide-title=\"true\"/>\n        <template #overlay>\n          <a-menu @click=\"selectAction\" mode=\"vertical-right\" :selectedKeys=\"[selectedKey]\">\n            <a-menu-item v-for=\"(v, k) in actionMap\" :key=\"k\">\n              <FlightAreaIcon :type=\"v.type\" :is-circle=\"v.isCircle\"/>\n            </a-menu-item>\n          </a-menu>\n        </template>\n      </a-dropdown>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref, defineEmits } from 'vue'\nimport { EFlightAreaType } from '../../types/flight-area'\nimport FlightAreaIcon from './FlightAreaIcon.vue'\n\nconst emit = defineEmits(['select-action', 'click'])\n\nconst actionMap: Record<string, { type: EFlightAreaType, isCircle: boolean}> = {\n  1: {\n    type: EFlightAreaType.DFENCE,\n    isCircle: true,\n  },\n  2: {\n    type: EFlightAreaType.DFENCE,\n    isCircle: false,\n  },\n  3: {\n    type: EFlightAreaType.NFZ,\n    isCircle: true,\n  },\n  4: {\n    type: EFlightAreaType.NFZ,\n    isCircle: false,\n  },\n\n}\n\nconst selectedKey = ref<string>('1')\nconst selectAction = (item: any) => {\n  selectedKey.value = item.key\n  emit('select-action', actionMap[item.key])\n}\nconst selectCurrent = () => {\n  emit('click', actionMap[selectedKey.value])\n}\n</script>\n\n<style lang=\"scss\">\n  .icon-panel {\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n  }\n</style>\n"
  },
  {
    "path": "src/components/flight-area/FlightAreaDevicePanel.vue",
    "content": "<template>\n  <div class=\"flight-area-device-panel\">\n    <Title title=\"Choose Synchronous Devices\">\n      <div style=\"position: absolute; right: 10px;\">\n        <a style=\"color: white;\" @click=\"closePanel\"><CloseOutlined /></a>\n      </div>\n    </Title>\n    <div class=\"scrollbar\">\n      <div id=\"data\" v-if=\"data.length !== 0\">\n        <div v-for=\"dock in data\" :key=\"dock.device_sn\">\n          <div class=\"pt5 panel flex-row\" @click=\"selectDock(dock)\" :style=\"{opacity: selectedDocksMap[dock.device_sn] ? 1 : 0.5 }\">\n            <div style=\"width: 88%\">\n              <div class=\"title\">\n                <RobotFilled class=\"fz20\"/>\n                <a-tooltip :title=\"dock.nickname\">\n                  <div class=\"pr10 ml5\" style=\"width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ dock.nickname }}</div>\n                </a-tooltip>\n              </div>\n              <div class=\"ml10 mr10 pr5 pl5 flex-align-center flex-row flex-justify-between\" style=\"background: #595959;\">\n                <div>\n                  Custom Flight Area\n                </div>\n                <div>\n                  <div v-if=\"!dock.status\">\n                    <a-tooltip title=\"Dock offline\">\n                      <ApiOutlined />\n                    </a-tooltip>\n                  </div>\n                  <div v-else-if=\"deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZED\">\n                    <a-tooltip title=\"Data synced\">\n                      <CheckCircleTwoTone twoToneColor=\"#28d445\"/>\n                    </a-tooltip>\n                  </div>\n                  <div v-else-if=\"deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING\n                                  || deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC\">\n                    <a-tooltip title=\"To be synced\">\n                      <SyncOutlined spin />\n                    </a-tooltip>\n                  </div>\n                  <div v-else>\n                    <a-tooltip :title=\"deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_msg || 'No synchronization'\">\n                      <ExclamationCircleTwoTone twoToneColor=\"#e70102\" />\n                    </a-tooltip>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div class=\"box\" v-if=\"selectedDocksMap[dock.device_sn]\">\n              <CheckOutlined />\n            </div>\n          </div>\n        </div>\n        <DividerLine style=\"position: absolute; bottom: 68px;\" />\n        <div class=\"flex-row flex-justify-between footer\">\n          <a-button class=\"mr10\" @click=\"closePanel\">Cancel\n          </a-button>\n          <a-button type=\"primary\" :disabled=\"confirmDisabled\" @click=\"syncDeviceFlightArea\">Sync\n          </a-button>\n        </div>\n      </div>\n      <div v-else>\n        <a-empty :image-style=\"{ height: '60px', marginTop: '60px' }\" />\n      </div>\n    </div>\n  </div>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport { CloseOutlined, RobotFilled, CheckOutlined, ApiOutlined, CheckCircleTwoTone, SyncOutlined, ExclamationCircleTwoTone } from '@ant-design/icons-vue'\nimport Title from '/@/components/workspace/Title.vue'\nimport { defineEmits, onMounted, ref, defineProps, computed } from 'vue'\nimport { getBindingDevices } from '/@/api/manage'\nimport { EDeviceTypeName, ELocalStorageKey } from '/@/types'\nimport { IPage } from '/@/api/http/type'\nimport { Device } from '/@/types/device'\nimport DividerLine from '../workspace/DividerLine.vue'\nimport { message } from 'ant-design-vue'\nimport { GetDeviceStatus, syncFlightArea } from '/@/api/flight-area'\nimport { ESyncStatus } from '/@/types/flight-area'\n\nconst props = defineProps<{\n  data: GetDeviceStatus[]\n}>()\nconst emit = defineEmits(['closePanel'])\nconst closePanel = () => {\n  emit('closePanel', false)\n}\n\nconst confirmDisabled = ref(false)\n\nconst deviceStatusMap = computed(() => props.data.reduce((obj: Record<string, GetDeviceStatus>, val: GetDeviceStatus) => {\n  obj[val.device_sn] = val\n  return obj\n}, {} as Record<string, GetDeviceStatus>))\nconst workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''\nconst body: IPage = {\n  page: 1,\n  total: 0,\n  page_size: 10,\n}\nconst data = ref<Device[]>([])\nconst selectedDocksMap = ref<Record<string, boolean>>({})\n\nconst getDocks = async () => {\n  await getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {\n    if (res.code !== 0) {\n      return\n    }\n    data.value.push(...res.data.list)\n    body.page = res.data.pagination.page\n    body.page_size = res.data.pagination.page_size\n    body.total = res.data.pagination.total\n  })\n}\n\nconst selectDock = (dock: Device) => {\n  if (!dock.status) {\n    message.info(`Dock(${dock.nickname}) is offline.`)\n    return\n  }\n  if (deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING ||\n      deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC) {\n    message.info('The dock is synchronizing.')\n    return\n  }\n  selectedDocksMap.value[dock.device_sn] = !selectedDocksMap.value[dock.device_sn]\n}\n\nonMounted(() => {\n  getDocks()\n  const key = setInterval(() => {\n    if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page) {\n      clearInterval(key)\n      return\n    }\n    body.page++\n    getDocks()\n  }, 1000)\n})\n\nconst syncDeviceFlightArea = () => {\n  const keys = Object.keys(selectedDocksMap.value)\n  if (keys.length === 0) {\n    message.warn('Please select the docks that need to be synchronized.')\n    return\n  }\n  confirmDisabled.value = true\n  Object.keys(selectedDocksMap.value).forEach(k => {\n    const device = deviceStatusMap.value[k]\n    if (device) {\n      device.flight_area_status = { sync_code: 0, sync_status: ESyncStatus.WAIT_SYNC, sync_msg: '' }\n    }\n  })\n  syncFlightArea(keys).then(res => {\n    if (res.code === 0) {\n      message.success('The devices are synchronizing...')\n      selectedDocksMap.value = {}\n    }\n  }).finally(() => setTimeout(() => {\n    confirmDisabled.value = false\n  }, 3000))\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.flight-area-device-panel {\n  position: absolute;\n  left: 285px;\n  width: 280px;\n  height: 100vh;\n  float: right;\n  top: 0;\n  z-index: 1000;\n  color: white;\n  background: #282828;\n  .footer {\n    position: absolute;\n    width: 100%;\n    bottom: 10px;\n    padding: 10px;\n    button {\n      width: 45%;\n      border: 0;\n    }\n  }\n  .scrollbar {\n    overflow-y: auto;\n    height: calc(100vh - 150px);\n  }\n  .box {\n    font-size: 22px;\n    line-height: 60px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/flight-area/FlightAreaIcon.vue",
    "content": "<template>\n  <div class=\"flex-row flex-align-center\">\n    <div class=\"shape\" :class=\"type\" :style=\"isCircle ? 'border-radius: 50%;' : ''\"></div>\n    <div class=\"ml5\" v-if=\"!hideTitle\">{{ FlightAreaTypeTitleMap[type][isCircle ? EGeometryType.CIRCLE : EGeometryType.POLYGON] }}</div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps } from 'vue'\nimport { EFlightAreaType, EGeometryType, FlightAreaTypeTitleMap } from '../../types/flight-area'\n\nconst props = defineProps<{\n  type: EFlightAreaType,\n  isCircle: boolean,\n  hideTitle?: boolean\n}>()\n\n</script>\n\n<style lang=\"scss\">\n  .nfz {\n    border-color: red;\n  }\n  .dfence {\n    border-color: $tag-green;\n  }\n  .shape {\n    width: 16px;\n    height: 16px;\n    border-width: 3px;\n    border-style: solid;\n  }\n</style>\n"
  },
  {
    "path": "src/components/flight-area/FlightAreaItem.vue",
    "content": "<template>\n  <div class=\"panel\" style=\"padding-top: 5px;\" :class=\"{disable: !flightArea.status}\">\n    <div class=\"title\">\n      <a-tooltip :title=\"flightArea.name\">\n        <div class=\"pr10\" style=\"white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ flightArea.name }}</div>\n      </a-tooltip>\n    </div>\n    <div class=\"mt5 ml10\" style=\"color: hsla(0,0%,100%,0.35);\">\n      <span class=\"mr10\">Update at {{ formatDateTime(flightArea.update_time).toLocaleString() }}</span>\n    </div>\n    <div class=\"flex-row flex-justify-between flex-align-center ml10 mt5\" style=\"color: hsla(0,0%,100%,0.65);\">\n      <FlightAreaIcon :type=\"flightArea.type\" :isCircle=\"EGeometryType.CIRCLE === flightArea.content.geometry.type\"/>\n      <div class=\"mr10 operate\">\n        <a-popconfirm v-if=\"flightArea.status\" title=\"Is it determined to disable the current area?\" okText=\"Disable\" @confirm=\"changeAreaStatus(false)\">\n          <stop-outlined />\n        </a-popconfirm>\n        <a-popconfirm v-else @confirm=\"changeAreaStatus(true)\" title=\"Is it determined to enable the current area?\" okText=\"Enable\" >\n          <check-circle-outlined />\n        </a-popconfirm>\n        <EnvironmentFilled class=\"ml10\" @click=\"clickLocation\"/>\n        <a-popconfirm title=\"Is it determined to delete the current area?\" okText=\"Delete\" okType=\"danger\" @confirm=\"deleteArea\">\n          <delete-outlined class=\"ml10\" />\n        </a-popconfirm>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps, reactive, defineEmits, computed } from 'vue'\nimport { GetFlightArea, changeFlightAreaStatus } from '../../api/flight-area'\nimport FlightAreaIcon from './FlightAreaIcon.vue'\nimport { formatDateTime } from '../../utils/time'\nimport { EGeometryType } from '../../types/flight-area'\nimport { StopOutlined, CheckCircleOutlined, DeleteOutlined, EnvironmentFilled } from '@ant-design/icons-vue'\n\nconst props = defineProps<{\n  data: GetFlightArea\n}>()\nconst emit = defineEmits(['delete', 'update', 'location'])\n\nconst flightArea = computed(() => props.data)\nconst changeAreaStatus = (status: boolean) => {\n  changeFlightAreaStatus(props.data.area_id, status).then(res => {\n    if (res.code === 0) {\n      flightArea.value.status = status\n      emit('update', flightArea)\n    }\n  })\n}\nconst deleteArea = () => {\n  emit('delete', flightArea.value.area_id)\n}\nconst clickLocation = () => {\n  emit('location', flightArea.value.area_id)\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.panel {\n  background: #3c3c3c;\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: 10px;\n  height: 90px;\n  width: 95%;\n  font-size: 13px;\n  border-radius: 2px;\n  cursor: pointer;\n\n  .title {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    height: 30px;\n    font-weight: bold;\n    margin: 0px 10px 0 10px;\n  }\n  .operate > *{\n    font-size: 16px;\n  }\n}\n\n.disable {\n  opacity: 50%;\n}\n\n</style>\n"
  },
  {
    "path": "src/components/flight-area/FlightAreaPanel.vue",
    "content": "<template>\n  <div class=\"flight-area-panel\">\n    <div v-if=\"data.length === 0\">\n      <a-empty :image-style=\"{ height: '60px', marginTop: '60px' }\" />\n    </div>\n    <div v-else v-for=\"area in flightAreaList\" :key=\"area.area_id\">\n      <FlightAreaItem :data=\"area\" @delete=\"deleteArea\" @update=\"updateArea\" @location=\"clickLocation(area)\"/>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps, defineEmits, ref, computed } from 'vue'\nimport FlightAreaItem from './FlightAreaItem.vue'\nimport { GetFlightArea } from '/@/api/flight-area'\n\nconst emit = defineEmits(['deleteArea', 'updateArea', 'locationArea'])\n\nconst props = defineProps<{\n  data: GetFlightArea[]\n}>()\n\nconst flightAreaList = computed(() => props.data)\n\nconst deleteArea = (areaId: string) => {\n  emit('deleteArea', areaId)\n}\n\nconst updateArea = (area: GetFlightArea) => {\n  emit('updateArea', area)\n}\n\nconst clickLocation = (area: GetFlightArea) => {\n  emit('locationArea', area)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n  .flight-area-panel {\n    overflow-y: auto;\n    height: calc(100vh - 150px);\n  }\n</style>\n"
  },
  {
    "path": "src/components/flight-area/FlightAreaSyncPanel.vue",
    "content": "<template>\n  <div class=\"flight-area-sync-panel p10 flex-row flex-align-center\" >\n    <RobotFilled class=\"fz30\" twoToneColor=\"red\" fill=\"#00ff00\"/>\n    <div class=\"ml20 mr10 flex-column\" @click=\"switchPanel\">\n      <div class=\"fz18\">Sync Across Devices</div>\n      <div v-if=\"syncDevicesCount > 0\"><a-spin /> Syncing to {{ syncDevicesCount }} devices</div>\n    </div>\n    <RightOutlined class=\"fz18\" @click=\"switchPanel\"/>\n    <FlightAreaDevicePanel v-if=\"visible\" @close-panel=\"closePanel\" :data=\"syncDevices\"/>\n  </div>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport { RobotFilled, RightOutlined } from '@ant-design/icons-vue'\nimport FlightAreaDevicePanel from '/@/components/flight-area/FlightAreaDevicePanel.vue'\nimport { computed, onMounted, ref, watch } from 'vue'\nimport { GetDeviceStatus, getDeviceStatus } from '/@/api/flight-area'\nimport { ESyncStatus, FlightAreaSyncProgress } from '/@/types/flight-area'\nimport { useFlightAreaSyncProgressEvent } from './use-flight-area-sync-progress-event'\n\nconst visible = ref(false)\nconst syncDevices = ref<GetDeviceStatus[]>([])\nconst syncDevicesCount = computed(() => syncDevices.value.filter(device =>\n  device.flight_area_status.sync_status === ESyncStatus.SYNCHRONIZING || device.flight_area_status.sync_status === ESyncStatus.WAIT_SYNC).length)\nconst getAllDeviceStatus = () => {\n  getDeviceStatus().then(res => {\n    if (res.code === 0) {\n      syncDevices.value = res.data\n    }\n  })\n}\n\nonMounted(() => {\n  getAllDeviceStatus()\n})\nconst switchPanel = () => {\n  visible.value = !visible.value\n}\nconst closePanel = (val: boolean) => {\n  visible.value = val\n}\n\nconst handleSyncProgress = (data: FlightAreaSyncProgress) => {\n  let has = false\n  const status = { sync_code: data.result, sync_status: data.status, sync_msg: data.message }\n  syncDevices.value.forEach(device => {\n    if (data.sn === device.device_sn) {\n      device.flight_area_status = status\n      has = true\n    }\n  })\n  if (!has) {\n    syncDevices.value.push({ device_sn: data.sn, flight_area_status: status })\n  }\n}\nuseFlightAreaSyncProgressEvent(handleSyncProgress)\n\n</script>\n\n<style lang=\"scss\" scoped>\n.flight-area-sync-panel {\n  height: 70px;\n  cursor: pointer;\n}\n</style>\n"
  },
  {
    "path": "src/components/flight-area/use-flight-area-drone-location-event.ts",
    "content": "import { FlightAreasDroneLocation } from '/@/types/flight-area'\nimport { CommonHostWs } from '/@/websocket'\nimport EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\n\nexport function useFlightAreaDroneLocationEvent (onFlightAreaDroneLocationWs: (data: CommonHostWs<FlightAreasDroneLocation>) => void): void {\n  function handleDroneLocationEvent (data: any) {\n    onFlightAreaDroneLocationWs(data.data)\n  }\n\n  onMounted(() => {\n    EventBus.on('flightAreasDroneLocationWs', handleDroneLocationEvent)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('flightAreasDroneLocationWs', handleDroneLocationEvent)\n  })\n}\n"
  },
  {
    "path": "src/components/flight-area/use-flight-area-sync-progress-event.ts",
    "content": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { FlightAreaSyncProgress } from '/@/types/flight-area'\n\nexport function useFlightAreaSyncProgressEvent (onFlightAreaSyncProgressWs: (data: FlightAreaSyncProgress) => void): void {\n  function handleSyncProgressEvent (data: FlightAreaSyncProgress) {\n    onFlightAreaSyncProgressWs(data)\n  }\n\n  onMounted(() => {\n    EventBus.on('flightAreasSyncProgressWs', handleSyncProgressEvent)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('flightAreasSyncProgressWs', handleSyncProgressEvent)\n  })\n}\n"
  },
  {
    "path": "src/components/flight-area/use-flight-area-update.ts",
    "content": "import { EFlightAreaUpdate, FlightAreaUpdate, FlightAreasDroneLocation } from '/@/types/flight-area'\nimport { CommonHostWs } from '/@/websocket'\nimport EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\n\nfunction doNothing (data: FlightAreaUpdate) {\n}\nexport function useFlightAreaUpdateEvent (addFunc = doNothing, deleteFunc = doNothing, updateFunc = doNothing): void {\n  function handleDroneLocationEvent (data: FlightAreaUpdate) {\n    switch (data.operation) {\n      case EFlightAreaUpdate.ADD:\n        addFunc(data)\n        break\n      case EFlightAreaUpdate.UPDATE:\n        updateFunc(data)\n        break\n      case EFlightAreaUpdate.DELETE:\n        deleteFunc(data)\n        break\n    }\n  }\n\n  onMounted(() => {\n    EventBus.on('flightAreasUpdateWs', handleDroneLocationEvent)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('flightAreasUpdateWs', handleDroneLocationEvent)\n  })\n}\n"
  },
  {
    "path": "src/components/flight-area/use-flight-area.ts",
    "content": "import { message, notification } from 'ant-design-vue'\nimport { MapDoodleEnum } from '/@/types/map-enum'\nimport { getRoot } from '/@/root'\nimport { PostFlightAreaBody, saveFlightArea } from '/@/api/flight-area'\nimport { generateCircleContent, generatePolyContent } from '/@/utils/map-layer-utils'\nimport { GeojsonCoordinate } from '/@/utils/genjson'\nimport { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform.js'\nimport { uuidv4 } from '/@/utils/uuid'\nimport { CommonHostWs } from '/@/websocket'\nimport { FlightAreasDroneLocation } from '/@/types/flight-area'\nimport rootStore from '/@/store'\nimport { h } from 'vue'\nimport { useGMapCover } from '/@/hooks/use-g-map-cover'\nimport moment from 'moment'\nimport { DATE_FORMAT } from '/@/utils/constants'\n\nexport function useFlightArea () {\n  const root = getRoot()\n  const store = rootStore\n  const coverMap = store.state.coverMap\n\n  let useGMapCoverHook = useGMapCover()\n\n  const MIN_RADIUS = 10\n  function checkCircle (obj: any): boolean {\n    if (obj.getRadius() < MIN_RADIUS) {\n      message.error(`The radius must be greater than ${MIN_RADIUS}m.`)\n      root.$map.remove(obj)\n      return false\n    }\n    return true\n  }\n\n  function checkPolygon (obj: any): boolean {\n    const path: any[][] = obj.getPath()\n    if (path.length < 3) {\n      message.error('The path of the polygon cannot be crossed.')\n      root.$map.remove(obj)\n      return false\n    }\n    // root.$aMap.GeometryUtil.doesLineLineIntersect()\n    return true\n  }\n\n  function setExtData (obj: any) {\n    let ext = obj.getExtData()\n    const id = uuidv4()\n    const name = `${ext.type}-${moment().format(DATE_FORMAT)}`\n    ext = Object.assign({}, ext, { id, name })\n    obj.setExtData(ext)\n    return ext\n  }\n  function createFlightArea (obj: any) {\n    const ext = obj.getExtData()\n    const data = {\n      id: ext.id,\n      type: ext.type,\n      name: ext.name,\n    }\n    let coordinates: GeojsonCoordinate | GeojsonCoordinate[][]\n    let content\n    switch (ext.mapType) {\n      case 'circle':\n        content = generateCircleContent(obj.getCenter(), obj.getRadius())\n        coordinates = getWgs84(content.geometry.coordinates as GeojsonCoordinate)\n        break\n      case 'polygon':\n        content = generatePolyContent(obj.getPath()).content\n        coordinates = [getWgs84(content.geometry.coordinates[0] as GeojsonCoordinate[])]\n        break\n      default:\n        message.error(`Invalid type: ${obj.mapType}`)\n        root.$map.remove(obj)\n        return\n    }\n    content.geometry.coordinates = coordinates\n\n    saveFlightArea(Object.assign({}, data, { content }) as PostFlightAreaBody).then(res => {\n      if (res.code !== 0) {\n        useGMapCoverHook.removeCoverFromMap(ext.id)\n      }\n    }).finally(() => root.$map.remove(obj))\n  }\n\n  function getDrawFlightAreaCallback (obj: any) {\n    useGMapCoverHook = useGMapCover()\n    const ext = setExtData(obj)\n    switch (ext.mapType) {\n      case MapDoodleEnum.CIRCLE:\n        if (!checkCircle(obj)) {\n          return\n        }\n        break\n      case MapDoodleEnum.POLYGON:\n        if (!checkPolygon(obj)) {\n          return\n        }\n        break\n      default:\n        break\n    }\n    createFlightArea(obj)\n  }\n\n  const getWgs84 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {\n    if (coordinate[0] instanceof Array) {\n      return (coordinate as GeojsonCoordinate[]).map(c => gcj02towgs84(c[0], c[1])) as T\n    }\n    return gcj02towgs84(coordinate[0], coordinate[1])\n  }\n\n  const getGcj02 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {\n    if (coordinate[0] instanceof Array) {\n      return (coordinate as GeojsonCoordinate[]).map(c => wgs84togcj02(c[0], c[1])) as T\n    }\n    return wgs84togcj02(coordinate[0], coordinate[1])\n  }\n\n  const onFlightAreaDroneLocationWs = (data: CommonHostWs<FlightAreasDroneLocation>) => {\n    const nearArea = data.host.drone_locations.filter(val => !val.is_in_area)\n    const inArea = data.host.drone_locations.filter(val => val.is_in_area)\n    notification.warning({\n      key: `flight-area-${data.sn}`,\n      message: `Drone(${data.sn}) flight area information`,\n      description: h('div',\n        [\n          h('div', [\n            h('span', { class: 'fz18' }, 'In the flight area: '),\n            h('ul', [\n              ...inArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))\n            ])\n          ]),\n          h('div', [\n            h('span', { class: 'fz18' }, 'Near the flight area: '),\n            h('ul', [\n              ...nearArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))\n            ])\n          ])\n        ]),\n      duration: null,\n      style: {\n        width: '420px',\n        marginTop: '-8px',\n        marginLeft: '-28px',\n      }\n    })\n  }\n\n  return {\n    getDrawFlightAreaCallback,\n    getGcj02,\n    getWgs84,\n    onFlightAreaDroneLocationWs,\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/DeviceSettingBox.vue",
    "content": "<template>\n  <div class=\"device-setting-wrapper\">\n    <div class=\"device-setting-header\">Device Property Set</div>\n    <div class=\"device-setting-box\">\n      <!-- 飞行器夜航灯 -->\n      <div class=\"control-setting-item\">\n        <div class=\"control-setting-item-left\">\n          <div class=\"item-label\">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}</div>\n          <div class=\"item-status\">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value }}</div>\n        </div>\n        <div class=\"control-setting-item-right\">\n          <DeviceSettingPopover\n            :visible=\"deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].popConfirm.visible\"\n            :loading=\"deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].popConfirm.loading\"\n            @confirm=\"onConfirm(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)\"\n            @cancel=\"onCancel(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <span class=\"form-label\">{{ deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].label }}:</span>\n                <a-switch checked-children=\"开\" un-checked-children=\"关\" v-model:checked=\"deviceSettingFormModel.nightLightsState\" />\n              </div>\n            </template>\n            <a @click=\"onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].settingKey)\">Edit</a>\n          </DeviceSettingPopover>\n        </div>\n      </div>\n      <!-- 限高 -->\n      <div class=\"control-setting-item\">\n        <div class=\"control-setting-item-left\">\n          <div class=\"item-label\">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}</div>\n          <div class=\"item-status\">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value }}</div>\n        </div>\n        <div class=\"control-setting-item-right\">\n          <DeviceSettingPopover\n            :visible=\"deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].popConfirm.visible\"\n            :loading=\"deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].popConfirm.loading\"\n            @confirm=\"onConfirm(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)\"\n            @cancel=\"onCancel(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <span class=\"form-label\">{{ deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].label }}:</span>\n                <a-input-number v-model:value=\"deviceSettingFormModel.heightLimit\" :min=\"20\" :max=\"1500\" />\n                m\n              </div>\n            </template>\n            <a @click=\"onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].settingKey)\">Edit</a>\n          </DeviceSettingPopover>\n        </div>\n      </div>\n      <!-- 限远 -->\n      <div class=\"control-setting-item\">\n        <div class=\"control-setting-item-left\">\n          <div class=\"item-label\">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}</div>\n          <div class=\"item-status\">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value }}</div>\n        </div>\n        <div class=\"control-setting-item-right\">\n          <DeviceSettingPopover\n            :visible=\"deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].popConfirm.visible\"\n            :loading=\"deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].popConfirm.loading\"\n            @confirm=\"onConfirm(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)\"\n            @cancel=\"onCancel(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <span class=\"form-label\">{{ deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].label }}:</span>\n                <a-switch style=\"margin-right: 10px;\" checked-children=\"开\" un-checked-children=\"关\" v-model:checked=\"deviceSettingFormModel.distanceLimitStatus.state\" />\n                <a-input-number v-model:value=\"deviceSettingFormModel.distanceLimitStatus.distanceLimit\" :min=\"15\" :max=\"8000\" />\n                m\n              </div>\n            </template>\n            <a @click=\"onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].settingKey)\">Edit</a>\n          </DeviceSettingPopover>\n        </div>\n      </div>\n      <!-- 水平避障 -->\n      <div class=\"control-setting-item\">\n        <div class=\"control-setting-item-left\">\n          <div class=\"item-label\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}</div>\n          <div class=\"item-status\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value }}</div>\n        </div>\n        <div class=\"control-setting-item-right\">\n          <DeviceSettingPopover\n            :visible=\"deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].popConfirm.visible\"\n            :loading=\"deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].popConfirm.loading\"\n            @confirm=\"onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)\"\n            @cancel=\"onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <span class=\"form-label\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].label }}:</span>\n                <a-switch checked-children=\"开\" un-checked-children=\"关\" v-model:checked=\"deviceSettingFormModel.obstacleAvoidanceHorizon\" />\n              </div>\n            </template>\n            <a @click=\"onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].settingKey)\">Edit</a>\n          </DeviceSettingPopover>\n        </div>\n      </div>\n      <!-- 上视避障 -->\n      <div class=\"control-setting-item\">\n        <div class=\"control-setting-item-left\">\n          <div class=\"item-label\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}</div>\n          <div class=\"item-status\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value }}</div>\n        </div>\n        <div class=\"control-setting-item-right\">\n          <DeviceSettingPopover\n            :visible=\"deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].popConfirm.visible\"\n            :loading=\"deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].popConfirm.loading\"\n            @confirm=\"onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)\"\n            @cancel=\"onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <span class=\"form-label\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].label }}:</span>\n                <a-switch checked-children=\"开\" un-checked-children=\"关\" v-model:checked=\"deviceSettingFormModel.obstacleAvoidanceUpside\" />\n              </div>\n            </template>\n            <a @click=\"onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].settingKey)\">Edit</a>\n          </DeviceSettingPopover>\n        </div>\n      </div>\n      <!-- 下视避障 -->\n      <div class=\"control-setting-item\">\n        <div class=\"control-setting-item-left\">\n          <div class=\"item-label\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}</div>\n          <div class=\"item-status\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value }}</div>\n        </div>\n        <div class=\"control-setting-item-right\">\n          <DeviceSettingPopover\n            :visible=\"deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].popConfirm.visible\"\n            :loading=\"deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].popConfirm.loading\"\n            @confirm=\"onConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)\"\n            @cancel=\"onCancel(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <span class=\"form-label\">{{ deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].label }}:</span>\n                <a-switch checked-children=\"开\" un-checked-children=\"关\" v-model:checked=\"deviceSettingFormModel.obstacleAvoidanceDownside\" />\n              </div>\n            </template>\n            <a @click=\"onShowPopConfirm(deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].settingKey)\">Edit</a>\n          </DeviceSettingPopover>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { defineProps, ref, watch } from 'vue'\nimport { DeviceInfoType } from '/@/types/device'\nimport { useMyStore } from '/@/store'\nimport { cloneDeep } from 'lodash'\nimport { initDeviceSetting, initDeviceSettingFormModel, DeviceSettingKeyEnum } from '/@/types/device-setting'\nimport { updateDeviceSettingInfoByOsd, updateDeviceSettingFormModelByOsd } from '/@/utils/device-setting'\nimport { useDeviceSetting } from './use-device-setting'\nimport DeviceSettingPopover from './DeviceSettingPopover.vue'\n\nconst props = defineProps<{\n  sn: string,\n  deviceInfo: DeviceInfoType,\n}>()\n\nconst store = useMyStore()\nconst deviceSetting = ref(cloneDeep(initDeviceSetting))\nconst deviceSettingFormModelFromOsd = ref(cloneDeep(initDeviceSettingFormModel))\nconst deviceSettingFormModel = ref(cloneDeep(initDeviceSettingFormModel)) // 真实使用的formModel\n\n// 根据设备osd信息更新信息\nwatch(() => props.deviceInfo, (value) => {\n  updateDeviceSettingInfoByOsd(deviceSetting.value, value)\n  updateDeviceSettingFormModelByOsd(deviceSettingFormModelFromOsd.value, value)\n  // console.log('deviceInfo', value)\n}, {\n  immediate: true,\n  deep: true\n})\n\nfunction onShowPopConfirm (settingKey: DeviceSettingKeyEnum) {\n  deviceSetting.value[settingKey].popConfirm.visible = true\n  deviceSettingFormModel.value = cloneDeep(deviceSettingFormModelFromOsd.value)\n}\n\nfunction onCancel (settingKey: DeviceSettingKeyEnum) {\n  deviceSetting.value[settingKey].popConfirm.visible = false\n}\n\nasync function onConfirm (settingKey: DeviceSettingKeyEnum) {\n  deviceSetting.value[settingKey].popConfirm.loading = true\n  const body = genDevicePropsBySettingKey(settingKey, deviceSettingFormModel.value)\n  await setDeviceProps(props.sn, body)\n  deviceSetting.value[settingKey].popConfirm.loading = false\n  deviceSetting.value[settingKey].popConfirm.visible = false\n}\n\n// 更新设备属性\nconst {\n  genDevicePropsBySettingKey,\n  setDeviceProps,\n} = useDeviceSetting()\n\n</script>\n\n<style lang='scss' scoped>\n.device-setting-wrapper{\n  border-bottom: 1px solid #515151;\n\n  .device-setting-header{\n    font-size: 14px;\n    font-weight: 600;\n    padding: 10px 10px 0px;\n  }\n\n  .device-setting-box{\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    padding: 4px 10px;\n\n    .control-setting-item{\n      width: 220px;\n      height: 58px;\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      border: 1px solid #666;\n      margin: 4px 0;\n      padding: 0 8px;\n\n      .control-setting-item-left{\n        display: flex;\n        flex-direction: column;\n\n        .item-label{\n          font-weight: 700;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/g-map/DeviceSettingPopover.vue",
    "content": "<template>\n  <a-popover :visible=\"state.sVisible\"\n             trigger=\"click\"\n             v-bind=\"$attrs\"\n             :overlay-class-name=\"overlayClassName\"\n             placement=\"bottom\"\n             @visibleChange=\";\"\n             v-on=\"$attrs\">\n    <template #content>\n      <div class=\"title-content\">\n      </div>\n      <slot name=\"formContent\" />\n      <div class=\"uranus-popconfirm-btns\">\n        <a-button size=\"sm\"\n           @click=\"onCancel\">\n           {{ cancelText || '取消'}}\n        </a-button>\n        <a-button size=\"sm\"\n          :loading=\"loading\"\n          type=\"primary\"\n          class=\"confirm-btn\"\n          @click=\"onConfirm\">\n          {{ okText || '确定' }}\n        </a-button>\n      </div>\n    </template>\n    <template v-if=\"$slots.default\">\n      <slot></slot>\n    </template>\n  </a-popover>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps, defineEmits, reactive, watch, computed } from 'vue'\n\nconst props = defineProps<{\n    visible?: boolean,\n    loading?: Boolean,\n    disabled?: Boolean,\n    title?: String,\n    okText?: String,\n    cancelText?: String,\n    width?: Number,\n}>()\n\nconst emit = defineEmits(['cancel', 'confirm'])\n\nconst state = reactive({\n  sVisible: false,\n  loading: false,\n})\n\nwatch(() => props.visible, (val) => {\n  state.sVisible = val || false\n})\n\nconst loading = computed(() => {\n  return props.loading\n})\nconst okLabel = computed(() => {\n  return props.loading ? '' : '确定'\n})\n\nconst overlayClassName = computed(() => {\n  const classList = ['device-setting-popconfirm']\n  return classList.join(' ')\n})\n\nfunction onConfirm (e: Event) {\n  if (props.disabled) {\n    return\n  }\n  emit('confirm', e)\n}\n\nfunction onCancel (e: Event) {\n  state.sVisible = false\n  emit('cancel', e)\n}\n\n</script>\n\n<style lang=\"scss\">\n.device-setting-popconfirm {\n  min-width: 300px;\n\n  .uranus-popconfirm-btns{\n    display: flex;\n    padding: 10px 0px;\n    justify-content: flex-end;\n\n    .confirm-btn{\n      margin-left: 10px;\n    }\n  }\n\n  .form-content{\n    display: inline-flex;\n    align-items: center;\n\n    .form-label{\n      padding-right: 10px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/g-map/DockControlPanel.vue",
    "content": "<template>\n<div class=\"dock-control-panel\">\n  <!-- title -->\n  <div class=\"dock-control-panel-header fz16 pl5 pr5 flex-align-center flex-row flex-justify-between\">\n    <span>Device Control<span class=\"fz12 pl15\">{{ props.sn}}</span></span>\n    <span @click=\"closeControlPanel\">\n    <CloseOutlined />\n    </span>\n  </div>\n  <!-- setting -->\n  <DeviceSettingBox :sn=\"props.sn\" :deviceInfo=\"props.deviceInfo\"></DeviceSettingBox>\n  <!-- cmd -->\n  <div class=\"control-cmd-wrapper\">\n    <div class=\"control-cmd-header\">\n      Device Remote Debug\n      <a-switch class=\"debug-btn\" checked-children=\"开\" un-checked-children=\"关\" v-model:checked=\"debugStatus\" @change=\"onDeviceStatusChange\"/>\n    </div>\n    <div class=\"control-cmd-box\">\n      <div v-for=\"(cmdItem, index) in cmdList\" :key=\"cmdItem.cmdKey\" class=\"control-cmd-item\">\n        <div class=\"control-cmd-item-left\">\n            <div class=\"item-label\">{{ cmdItem.label }}</div>\n            <div class=\"item-status\">{{ cmdItem.status }}</div>\n        </div>\n        <div class=\"control-cmd-item-right\">\n            <a-button :disabled=\"!debugStatus || cmdItem.disabled\" :loading=\"cmdItem.loading\" size=\"small\" type=\"primary\" @click=\"sendControlCmd(cmdItem, index)\">\n            {{ cmdItem.operateText }}\n            </a-button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n</template>\n\n<script setup lang=\"ts\">\nimport { defineProps, defineEmits, ref, watch } from 'vue'\nimport {\n  CloseOutlined\n} from '@ant-design/icons-vue'\nimport { useDockControl } from './use-dock-control'\nimport { DeviceInfoType, EDockModeCode } from '/@/types/device'\nimport { cmdList as baseCmdList, DeviceCmdItem } from '/@/types/device-cmd'\nimport { useMyStore } from '/@/store'\nimport { updateDeviceCmdInfoByOsd, updateDeviceCmdInfoByExecuteInfo } from '/@/utils/device-cmd'\nimport DeviceSettingBox from './DeviceSettingBox.vue'\n\nconst props = defineProps<{\n  sn: string,\n  deviceInfo: DeviceInfoType,\n}>()\n\nconst store = useMyStore()\nconst initCmdList = baseCmdList.map(cmdItem => Object.assign({}, cmdItem))\nconst cmdList = ref(initCmdList)\n\n// 根据机场指令执行状态更新信息\nwatch(() => store.state.devicesCmdExecuteInfo, (devicesCmdExecuteInfo) => {\n  if (props.sn && devicesCmdExecuteInfo[props.sn]) {\n    updateDeviceCmdInfoByExecuteInfo(cmdList.value, devicesCmdExecuteInfo[props.sn])\n  }\n}, {\n  immediate: true,\n  deep: true,\n})\n\n// 根据设备osd信息更新信息\nwatch(() => props.deviceInfo, (value) => {\n  updateDeviceCmdInfoByOsd(cmdList.value, value)\n  // console.log('deviceInfo', value)\n}, {\n  immediate: true,\n  deep: true\n})\n\n// dock 控制指令\nconst debugStatus = ref(props.deviceInfo.dock?.basic_osd?.mode_code === EDockModeCode.Remote_Debugging)\nconst emit = defineEmits(['close-control-panel'])\n\nfunction closeControlPanel () {\n  emit('close-control-panel', props.sn, debugStatus.value)\n}\n\nasync function onDeviceStatusChange (status: boolean) {\n  let result = false\n  if (status) {\n    result = await dockDebugOnOff(props.sn, true)\n  } else {\n    result = await dockDebugOnOff(props.sn, false)\n  }\n  if (!result) {\n    if (status) {\n      debugStatus.value = false\n    } else {\n      debugStatus.value = true\n    }\n  }\n}\n\nconst {\n  sendDockControlCmd,\n  dockDebugOnOff\n} = useDockControl()\n\nasync function sendControlCmd (cmdItem: DeviceCmdItem, index: number) {\n  const success = await sendDockControlCmd({\n    sn: props.sn,\n    cmd: cmdItem.cmdKey,\n    action: cmdItem.action\n  }, true)\n  if (success) {\n    // updateDeviceSingleCmdInfo(cmdList.value[index])\n  }\n}\n\n</script>\n\n<style lang='scss' scoped>\n.dock-control-panel{\n  position: absolute;\n  left: calc(100% + 10px);\n  top: 0px;\n  width: 480px;\n  padding: 0 !important;\n  background: #000;\n  color: #fff;\n  border-radius: 2px;\n\n  .dock-control-panel-header{\n    border-bottom: 1px solid #515151;\n  }\n\n  .control-cmd-wrapper{\n    .control-cmd-header{\n      font-size: 14px;\n      font-weight: 600;\n      padding: 10px 10px 0px;\n\n      .debug-btn{\n        margin-left: 10px;\n        border:1px solid #585858;\n      }\n    }\n\n    .control-cmd-box{\n      display: flex;\n      flex-wrap: wrap;\n      justify-content: space-between;\n      padding: 4px 10px;\n      .control-cmd-item{\n        width: 220px;\n        height: 58px;\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n        border: 1px solid #666;\n        margin: 4px 0;\n        padding: 0 8px;\n\n        .control-cmd-item-left{\n          display: flex;\n          flex-direction: column;\n\n          .item-label{\n            font-weight: 700;\n          }\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/g-map/DroneControlInfoPanel.vue",
    "content": "<template>\n<div class=\"drone-control-info-wrap\">\n  <a-textarea v-model:value=\"info\" placeholder=\"drc info\" :rows=\"5\" disabled/>\n</div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref, defineProps, watch } from 'vue'\n\nconst props = defineProps<{\n    message?: string,\n}>()\n\nconst info = ref('')\nwatch(() => props.message, message => {\n  info.value = message || ''\n}, {\n  immediate: true\n})\n\n// const emit = defineEmits(['cancel', 'confirm'])\n</script>\n\n<style lang=\"scss\" scoped>\n.drone-control-info-wrap {\n  &::v-deep{\n    textarea.ant-input {\n      background-color: #000;\n      color: #fff;\n      white-space: pre-wrap;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/g-map/DroneControlPanel.vue",
    "content": "<template>\n  <div class=\"drone-control-wrapper\">\n    <div class=\"drone-control-header\">Drone Flight Control</div>\n    <div class=\"drone-control-box\">\n      <div class=\"box\">\n        <div class=\"row\">\n          <div class=\"drone-control\"><Button :ghost=\"!flightController\" size=\"small\"  @click=\"onClickFightControl\">{{ flightController ? 'Exit Remote Control' : 'Enter Remote Control'}}</Button></div>\n        </div>\n        <div class=\"row\">\n          <div class=\"drone-control-direction\">\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.KEY_Q)\" @onmouseup=\"onMouseUp\">\n              <template #icon><UndoOutlined /></template><span class=\"word\">Q</span>\n            </Button>\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.KEY_W)\" @onmouseup=\"onMouseUp\">\n              <template #icon><UpOutlined/></template><span class=\"word\">W</span>\n            </Button>\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.KEY_E)\" @onmouseup=\"onMouseUp\">\n              <template #icon><RedoOutlined /></template><span class=\"word\">E</span>\n            </Button>\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.ARROW_UP)\" @onmouseup=\"onMouseUp\">\n              <template #icon><ArrowUpOutlined /></template>\n            </Button>\n            <br />\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.KEY_A)\" @onmouseup=\"onMouseUp\">\n              <template #icon><LeftOutlined/></template><span class=\"word\">A</span>\n            </Button>\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.KEY_S)\" @onmouseup=\"onMouseUp\">\n              <template #icon><DownOutlined/></template><span class=\"word\">S</span>\n            </Button>\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.KEY_D)\" @onmouseup=\"onMouseUp\">\n              <template #icon><RightOutlined/></template><span class=\"word\">D</span>\n            </Button>\n            <Button size=\"small\" ghost @mousedown=\"onMouseDown(KeyCode.ARROW_DOWN)\" @onmouseup=\"onMouseUp\">\n              <template #icon><ArrowDownOutlined /></template>\n            </Button>\n          </div>\n          <Button type=\"primary\" size=\"small\" danger ghost @click=\"handleEmergencyStop\" >\n            <template #icon><PauseCircleOutlined/></template><span>Break</span>\n          </Button>\n        </div>\n        <div class=\"row\">\n          <DroneControlPopover\n            :visible=\"flyToPointPopoverData.visible\"\n            :loading=\"flyToPointPopoverData.loading\"\n            @confirm=\"($event) => onFlyToConfirm(true)\"\n            @cancel=\"($event) =>onFlyToConfirm(false)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <div>\n                  <span class=\"form-label\">latitude:</span>\n                  <a-input-number v-model:value=\"flyToPointPopoverData.latitude\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">longitude:</span>\n                  <a-input-number v-model:value=\"flyToPointPopoverData.longitude\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">height(m):</span>\n                  <a-input-number v-model:value=\"flyToPointPopoverData.height\"/>\n                </div>\n              </div>\n            </template>\n            <Button size=\"small\" ghost @click=\"onShowFlyToPopover\" >\n              <span>Fly to</span>\n            </Button>\n          </DroneControlPopover>\n          <Button size=\"small\" ghost @click=\"onStopFlyToPoint\" >\n            <span>Stop Fly to</span>\n          </Button>\n          <DroneControlPopover\n            :visible=\"takeoffToPointPopoverData.visible\"\n            :loading=\"takeoffToPointPopoverData.loading\"\n            @confirm=\"($event) => onTakeoffToPointConfirm(true)\"\n            @cancel=\"($event) =>onTakeoffToPointConfirm(false)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <div>\n                  <span class=\"form-label\">latitude:</span>\n                  <a-input-number v-model:value=\"takeoffToPointPopoverData.latitude\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">longitude:</span>\n                  <a-input-number v-model:value=\"takeoffToPointPopoverData.longitude\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">height(m):</span>\n                  <a-input-number v-model:value=\"takeoffToPointPopoverData.height\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">Safe Takeoff Altitude(m):</span>\n                  <a-input-number v-model:value=\"takeoffToPointPopoverData.securityTakeoffHeight\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">Return-to-Home Altitude(m):</span>\n                  <a-input-number v-model:value=\"takeoffToPointPopoverData.rthAltitude\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">Lost Action:</span>\n                  <a-select\n                    v-model:value=\"takeoffToPointPopoverData.rcLostAction\"\n                    style=\"width: 120px\"\n                    :options=\"LostControlActionInCommandFLightOptions\"\n                  ></a-select>\n                </div>\n                <div>\n                  <span class=\"form-label\">Wayline Lost Action:</span>\n                  <a-select\n                    v-model:value=\"takeoffToPointPopoverData.exitWaylineWhenRcLost\"\n                    style=\"width: 120px\"\n                    :options=\"WaylineLostControlActionInCommandFlightOptions\"\n                  ></a-select>\n                </div>\n                <div>\n                  <span class=\"form-label\">Return-to-Home Mode:</span>\n                  <a-select\n                    v-model:value=\"takeoffToPointPopoverData.rthMode\"\n                    style=\"width: 120px\"\n                    :options=\"RthModeInCommandFlightOptions\"\n                  ></a-select>\n                </div>\n                <div>\n                  <span class=\"form-label\">Commander Mode Lost Action:</span>\n                  <a-select\n                    v-model:value=\"takeoffToPointPopoverData.commanderModeLostAction\"\n                    style=\"width: 120px\"\n                    :options=\"CommanderModeLostActionInCommandFlightOptions\"\n                  ></a-select>\n                </div>\n                <div>\n                  <span class=\"form-label\">Commander Flight Mode:</span>\n                  <a-select\n                    v-model:value=\"takeoffToPointPopoverData.commanderFlightMode\"\n                    style=\"width: 120px\"\n                    :options=\"CommanderFlightModeInCommandFlightOptions\"\n                  ></a-select>\n                </div>\n                <div>\n                  <span class=\"form-label\">Commander Flight Height(m):</span>\n                  <a-input-number v-model:value=\"takeoffToPointPopoverData.commanderFlightHeight\"/>\n                </div>\n              </div>\n            </template>\n            <Button size=\"small\" ghost @click=\"onShowTakeoffToPointPopover\" >\n              <span>Take off</span>\n            </Button>\n            <div v-for=\"(cmdItem) in cmdList\" :key=\"cmdItem.cmdKey\" class=\"control-cmd-item\">\n              <Button :loading=\"cmdItem.loading\" size=\"small\" ghost @click=\"sendControlCmd(cmdItem, 0)\">\n                {{ cmdItem.operateText }}\n              </Button>\n            </div>\n            <div>\n              <Button size=\"small\" ghost @click=\"openLivestreamAgora\" >\n                <span>Agora Live</span>\n              </Button>\n              <Button size=\"small\" ghost @click=\"openLivestreamOthers\" >\n                <span>RTMP/GB28181 Live</span>\n              </Button>\n            </div>\n          </DroneControlPopover>\n        </div>\n    </div>\n    <div class=\"box\">\n      <div class=\"row\">\n        <Select v-model:value=\"payloadSelectInfo.value\" style=\"width: 110px; marginRight: 5px\" :options=\"payloadSelectInfo.options\" @change=\"handlePayloadChange\"/>\n        <div class=\"drone-control\">\n          <Button type=\"primary\" size=\"small\" @click=\"onAuthPayload\">Payload Control</Button>\n        </div>\n      </div>\n      <div class=\"row\">\n        <DroneControlPopover\n          :visible=\"gimbalResetPopoverData.visible\"\n          :loading=\"gimbalResetPopoverData.loading\"\n          @confirm=\"($event) => onGimbalResetConfirm(true)\"\n          @cancel=\"($event) =>onGimbalResetConfirm(false)\"\n        >\n          <template #formContent>\n            <div class=\"form-content\">\n              <div>\n                <span class=\"form-label\">reset mode:</span>\n                <a-select\n                  v-model:value=\"gimbalResetPopoverData.resetMode\"\n                  style=\"width: 180px\"\n                  :options=\"GimbalResetModeOptions\"\n                ></a-select>\n              </div>\n            </div>\n          </template>\n          <Button size=\"small\" ghost @click=\"onShowGimbalResetPopover\">\n            <span>Gimbal Reset</span>\n          </Button>\n        </DroneControlPopover>\n        <Button size=\"small\" ghost @click=\"onSwitchCameraMode\">\n          <span>Camera Mode Switch</span>\n        </Button>\n      </div>\n      <div class=\"row\">\n        <Button size=\"small\" ghost @click=\"onStartCameraRecording\">\n          <span>Start Recording</span>\n        </Button>\n        <Button size=\"small\" ghost @click=\"onStopCameraRecording\">\n          <span>Stop Recording</span>\n        </Button>\n      </div>\n      <div class=\"row\">\n        <Button size=\"small\" ghost  @click=\"onTakeCameraPhoto\">\n          <span>Take Photo</span>\n        </Button>\n        <DroneControlPopover\n          :visible=\"zoomFactorPopoverData.visible\"\n          :loading=\"zoomFactorPopoverData.loading\"\n          @confirm=\"($event) => onZoomFactorConfirm(true)\"\n          @cancel=\"($event) =>onZoomFactorConfirm(false)\"\n        >\n          <template #formContent>\n            <div class=\"form-content\">\n              <div>\n                <span class=\"form-label\">camera type:</span>\n                <a-select\n                  v-model:value=\"zoomFactorPopoverData.cameraType\"\n                  style=\"width: 120px\"\n                  :options=\"ZoomCameraTypeOptions\"\n                ></a-select>\n              </div>\n              <div>\n                <span class=\"form-label\">zoom factor:</span>\n                <a-input-number v-model:value=\"zoomFactorPopoverData.zoomFactor\" :min=\"2\" :max=\"200\" />\n              </div>\n            </div>\n          </template>\n          <Button size=\"small\" ghost @click=\"($event) => onShowZoomFactorPopover()\">\n            <span class=\"word\" @click=\";\">Zoom</span>\n          </Button>\n        </DroneControlPopover>\n        <DroneControlPopover\n            :visible=\"cameraAimPopoverData.visible\"\n            :loading=\"cameraAimPopoverData.loading\"\n            @confirm=\"($event) => onCameraAimConfirm(true)\"\n            @cancel=\"($event) =>onCameraAimConfirm(false)\"\n          >\n            <template #formContent>\n              <div class=\"form-content\">\n                <div>\n                  <span class=\"form-label\">camera type:</span>\n                  <a-select\n                    v-model:value=\"cameraAimPopoverData.cameraType\"\n                    style=\"width: 120px\"\n                    :options=\"CameraTypeOptions\"\n                  ></a-select>\n                </div>\n                <div>\n                  <span class=\"form-label\">locked:</span>\n                  <a-switch v-model:checked=\"cameraAimPopoverData.locked\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">x:</span>\n                  <a-input-number v-model:value=\"cameraAimPopoverData.x\" :min=\"0\" :max=\"1\"/>\n                </div>\n                <div>\n                  <span class=\"form-label\">y:</span>\n                  <a-input-number v-model:value=\"cameraAimPopoverData.y\" :min=\"0\" :max=\"1\"/>\n                </div>\n              </div>\n            </template>\n            <Button size=\"small\" ghost @click=\"($event) => onShowCameraAimPopover()\">\n              <span class=\"word\" @click=\";\">AIM</span>\n            </Button>\n          </DroneControlPopover>\n      </div>\n    </div>\n    </div>\n    <!-- 信息提示 -->\n    <DroneControlInfoPanel :message=\"drcInfo\"></DroneControlInfoPanel>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { defineProps, reactive, ref, watch, computed, onMounted, watchEffect } from 'vue'\nimport { Select, message, Button } from 'ant-design-vue'\nimport { PayloadInfo, DeviceInfoType, ControlSource, DeviceOsdCamera, DrcStateEnum } from '/@/types/device'\nimport { useMyStore } from '/@/store'\nimport { postDrcEnter, postDrcExit } from '/@/api/drc'\nimport { useMqtt, DeviceTopicInfo } from './use-mqtt'\nimport { DownOutlined, UpOutlined, LeftOutlined, RightOutlined, PauseCircleOutlined, UndoOutlined, RedoOutlined, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons-vue'\nimport { useManualControl, KeyCode } from './use-manual-control'\nimport { usePayloadControl } from './use-payload-control'\nimport { CameraMode, CameraType, CameraTypeOptions, ZoomCameraTypeOptions, CameraListItem } from '/@/types/live-stream'\nimport { useDroneControlWsEvent } from './use-drone-control-ws-event'\nimport { useDroneControlMqttEvent } from './use-drone-control-mqtt-event'\nimport {\n  postFlightAuth, LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight, ERthMode,\n  ECommanderModeLostAction, ECommanderFlightMode\n} from '/@/api/drone-control/drone'\nimport { useDroneControl } from './use-drone-control'\nimport {\n  GimbalResetMode, GimbalResetModeOptions, LostControlActionInCommandFLightOptions, WaylineLostControlActionInCommandFlightOptions,\n  RthModeInCommandFlightOptions, CommanderModeLostActionInCommandFlightOptions, CommanderFlightModeInCommandFlightOptions\n} from '/@/types/drone-control'\nimport DroneControlPopover from './DroneControlPopover.vue'\nimport DroneControlInfoPanel from './DroneControlInfoPanel.vue'\nimport { noDebugCmdList as baseCmdList, DeviceCmdItem, DeviceCmd } from '/@/types/device-cmd'\nimport { useDockControl } from './use-dock-control'\n\nconst props = defineProps<{\n  sn: string,\n  deviceInfo: DeviceInfoType,\n  payloads: null | PayloadInfo[]\n}>()\n\nconst store = useMyStore()\nconst clientId = computed(() => {\n  return store.state.clientId\n})\n\nconst initCmdList = baseCmdList.map(cmdItem => Object.assign({}, cmdItem))\nconst cmdList = ref(initCmdList)\n\nconst {\n  sendDockControlCmd\n} = useDockControl()\n\nasync function sendControlCmd (cmdItem: DeviceCmdItem, index: number) {\n  cmdItem.loading = true\n  const result = await sendDockControlCmd({\n    sn: props.sn,\n    cmd: cmdItem.cmdKey,\n    action: cmdItem.action\n  }, false)\n  if (result) {\n    message.success('Return home successful')\n    if (flightController.value) {\n      exitFlightCOntrol()\n    }\n  } else {\n    message.error('Failed to return home')\n  }\n  cmdItem.loading = false\n}\n\nconst { flyToPoint, stopFlyToPoint, takeoffToPoint } = useDroneControl()\nconst MAX_SPEED = 14\n\nconst flyToPointPopoverData = reactive({\n  visible: false,\n  loading: false,\n  latitude: null as null | number,\n  longitude: null as null | number,\n  height: null as null | number,\n  maxSpeed: MAX_SPEED,\n})\n\nfunction onShowFlyToPopover () {\n  flyToPointPopoverData.visible = !flyToPointPopoverData.visible\n  flyToPointPopoverData.loading = false\n  flyToPointPopoverData.latitude = null\n  flyToPointPopoverData.longitude = null\n  flyToPointPopoverData.height = null\n}\n\nasync function onFlyToConfirm (confirm: boolean) {\n  if (confirm) {\n    if (!flyToPointPopoverData.height || !flyToPointPopoverData.latitude || !flyToPointPopoverData.longitude) {\n      message.error('Input error')\n      return\n    }\n    try {\n      await flyToPoint(props.sn, {\n        max_speed: flyToPointPopoverData.maxSpeed,\n        points: [\n          {\n            latitude: flyToPointPopoverData.latitude,\n            longitude: flyToPointPopoverData.longitude,\n            height: flyToPointPopoverData.height\n          }\n        ]\n      })\n    } catch (error) {\n    }\n  }\n  flyToPointPopoverData.visible = false\n}\n\nasync function onStopFlyToPoint () {\n  await stopFlyToPoint(props.sn)\n}\n\nconst takeoffToPointPopoverData = reactive({\n  visible: false,\n  loading: false,\n  latitude: null as null | number,\n  longitude: null as null | number,\n  height: null as null | number,\n  securityTakeoffHeight: null as null | number,\n  maxSpeed: MAX_SPEED,\n  rthAltitude: null as null | number,\n  rcLostAction: LostControlActionInCommandFLight.RETURN_HOME,\n  exitWaylineWhenRcLost: WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION,\n  rthMode: ERthMode.SETTING,\n  commanderModeLostAction: ECommanderModeLostAction.CONTINUE,\n  commanderFlightMode: ECommanderFlightMode.SETTING,\n  commanderFlightHeight: null as null | number,\n})\n\nfunction onShowTakeoffToPointPopover () {\n  takeoffToPointPopoverData.visible = !takeoffToPointPopoverData.visible\n  takeoffToPointPopoverData.loading = false\n  takeoffToPointPopoverData.latitude = null\n  takeoffToPointPopoverData.longitude = null\n  takeoffToPointPopoverData.securityTakeoffHeight = null\n  takeoffToPointPopoverData.rthAltitude = null\n  takeoffToPointPopoverData.rcLostAction = LostControlActionInCommandFLight.RETURN_HOME\n  takeoffToPointPopoverData.exitWaylineWhenRcLost = WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION\n  takeoffToPointPopoverData.rthMode = ERthMode.SETTING\n  takeoffToPointPopoverData.commanderModeLostAction = ECommanderModeLostAction.CONTINUE\n  takeoffToPointPopoverData.commanderFlightMode = ECommanderFlightMode.SETTING\n  takeoffToPointPopoverData.commanderFlightHeight = null\n}\n\nasync function onTakeoffToPointConfirm (confirm: boolean) {\n  if (confirm) {\n    if (!takeoffToPointPopoverData.height ||\n        !takeoffToPointPopoverData.latitude ||\n        !takeoffToPointPopoverData.longitude ||\n        !takeoffToPointPopoverData.securityTakeoffHeight ||\n        !takeoffToPointPopoverData.rthAltitude ||\n        !takeoffToPointPopoverData.commanderFlightHeight) {\n      message.error('Input error')\n      return\n    }\n    try {\n      await takeoffToPoint(props.sn, {\n        target_latitude: takeoffToPointPopoverData.latitude,\n        target_longitude: takeoffToPointPopoverData.longitude,\n        target_height: takeoffToPointPopoverData.height,\n        security_takeoff_height: takeoffToPointPopoverData.securityTakeoffHeight,\n        rth_altitude: takeoffToPointPopoverData.rthAltitude,\n        max_speed: takeoffToPointPopoverData.maxSpeed,\n        rc_lost_action: takeoffToPointPopoverData.rcLostAction,\n        exit_wayline_when_rc_lost: takeoffToPointPopoverData.exitWaylineWhenRcLost,\n        rth_mode: takeoffToPointPopoverData.rthMode,\n        commander_mode_lost_action: takeoffToPointPopoverData.commanderModeLostAction,\n        commander_flight_mode: takeoffToPointPopoverData.commanderFlightMode,\n        commander_flight_height: takeoffToPointPopoverData.commanderFlightHeight,\n      })\n    } catch (error) {\n    }\n  }\n  takeoffToPointPopoverData.visible = false\n}\n\nconst deviceTopicInfo: DeviceTopicInfo = reactive({\n  sn: props.sn,\n  pubTopic: '',\n  subTopic: ''\n})\n\nuseMqtt(deviceTopicInfo)\n\n// 飞行控制\n// const drcState = computed(() => {\n//   return store.state.deviceState?.dockInfo[props.sn]?.link_osd?.drc_state === DrcStateEnum.CONNECTED\n// })\nconst flightController = ref(false)\n\nasync function onClickFightControl () {\n  if (flightController.value) {\n    exitFlightCOntrol()\n    return\n  }\n  enterFlightControl()\n}\n\n// 进入飞行控制\nasync function enterFlightControl () {\n  try {\n    const { code, data } = await postDrcEnter({\n      client_id: clientId.value,\n      dock_sn: props.sn,\n    })\n    if (code === 0) {\n      flightController.value = true\n      if (data.sub && data.sub.length > 0) {\n        deviceTopicInfo.subTopic = data.sub[0]\n      }\n      if (data.pub && data.pub.length > 0) {\n        deviceTopicInfo.pubTopic = data.pub[0]\n      }\n      // 获取飞行控制权\n      if (droneControlSource.value !== ControlSource.A) {\n        await postFlightAuth(props.sn)\n      }\n      message.success('Get flight control successfully')\n    }\n  } catch (error: any) {\n  }\n}\n\n// 退出飞行控制\nasync function exitFlightCOntrol () {\n  try {\n    const { code } = await postDrcExit({\n      client_id: clientId.value,\n      dock_sn: props.sn,\n    })\n    if (code === 0) {\n      flightController.value = false\n      deviceTopicInfo.subTopic = ''\n      deviceTopicInfo.pubTopic = ''\n      message.success('Exit flight control')\n    }\n  } catch (error: any) {\n  }\n}\n\n// drc mqtt message\nconst { drcInfo, errorInfo } = useDroneControlMqttEvent(props.sn)\n\nconst {\n  handleKeyup,\n  handleEmergencyStop,\n  resetControlState,\n} = useManualControl(deviceTopicInfo, flightController)\n\nfunction onMouseDown (type: KeyCode) {\n  handleKeyup(type)\n}\n\nfunction onMouseUp () {\n  resetControlState()\n}\n\n// 负载控制\nconst payloadSelectInfo = {\n  value: null as any,\n  controlSource: undefined as undefined | ControlSource,\n  options: [] as any,\n  payloadIndex: '' as string,\n  camera: undefined as undefined | DeviceOsdCamera // 当前负载osd信息\n}\n\nconst handlePayloadChange = (value: string) => {\n  const payload = props.payloads?.find(item => item.payload_sn === value)\n  if (payload) {\n    payloadSelectInfo.payloadIndex = payload.payload_index || ''\n    payloadSelectInfo.controlSource = payload.control_source\n    payloadSelectInfo.camera = undefined\n  }\n}\n\n// function getCurrentCamera (cameraList: CameraListItem[], cameraIndex?: string):CameraListItem | null {\n//   let camera = null\n//   cameraList.forEach(item => {\n//     if (item.camera_index === cameraIndex) {\n//       camera = item\n//     }\n//   })\n//   return camera\n// }\n\n// const currentCamera = computed(() => {\n//   return getCurrentCamera(props.deviceInfo.dock.basic_osd.live_capacity?.device_list[0]?.camera_list as CameraListItem[], camera_index)\n// })\n// 更新负载信息\nwatch(() => props.payloads, (payloads) => {\n  if (payloads && payloads.length > 0) {\n    payloadSelectInfo.value = payloads[0].payload_sn\n    payloadSelectInfo.controlSource = payloads[0].control_source || ControlSource.B\n    payloadSelectInfo.payloadIndex = payloads[0].payload_index || ''\n    payloadSelectInfo.options = payloads.map(item => ({ label: item.payload_name, value: item.payload_sn }))\n    payloadSelectInfo.camera = undefined\n  } else {\n    payloadSelectInfo.value = null\n    payloadSelectInfo.controlSource = undefined\n    payloadSelectInfo.options = []\n    payloadSelectInfo.payloadIndex = ''\n    payloadSelectInfo.camera = undefined\n  }\n}, {\n  immediate: true,\n  deep: true\n})\nwatch(() => props.deviceInfo.device, (droneOsd) => {\n  if (droneOsd && droneOsd.cameras) {\n    payloadSelectInfo.camera = droneOsd.cameras.find(item => item.payload_index === payloadSelectInfo.payloadIndex)\n  } else {\n    payloadSelectInfo.camera = undefined\n  }\n}, {\n  immediate: true,\n  deep: true\n})\n\n// ws 消息通知\nconst { droneControlSource, payloadControlSource } = useDroneControlWsEvent(props.sn, payloadSelectInfo.value)\nwatch(() => payloadControlSource, (controlSource) => {\n  payloadSelectInfo.controlSource = controlSource.value\n}, {\n  immediate: true,\n  deep: true\n})\nconst {\n  checkPayloadAuth,\n  authPayload,\n  resetGimbal,\n  switchCameraMode,\n  takeCameraPhoto,\n  startCameraRecording,\n  stopCameraRecording,\n  changeCameraFocalLength,\n  cameraAim,\n} = usePayloadControl()\n\nasync function onAuthPayload () {\n  const result = await authPayload(props.sn, payloadSelectInfo.payloadIndex)\n  if (result) {\n    payloadControlSource.value = ControlSource.A\n  }\n}\n\nconst gimbalResetPopoverData = reactive({\n  visible: false,\n  loading: false,\n  resetMode: null as null | GimbalResetMode,\n})\n\nfunction onShowGimbalResetPopover () {\n  gimbalResetPopoverData.visible = !gimbalResetPopoverData.visible\n  gimbalResetPopoverData.loading = false\n  gimbalResetPopoverData.resetMode = null\n}\n\nasync function onGimbalResetConfirm (confirm: boolean) {\n  if (confirm) {\n    if (gimbalResetPopoverData.resetMode === null) {\n      message.error('Please select reset mode')\n      return\n    }\n    gimbalResetPopoverData.loading = true\n    try {\n      await resetGimbal(props.sn, {\n        payload_index: payloadSelectInfo.payloadIndex,\n        reset_mode: gimbalResetPopoverData.resetMode\n      })\n    } catch (err) {\n    }\n  }\n  gimbalResetPopoverData.visible = false\n}\n\nasync function onSwitchCameraMode () {\n  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {\n    return\n  }\n  const currentCameraMode = payloadSelectInfo.camera?.camera_mode\n  await switchCameraMode(props.sn, {\n    payload_index: payloadSelectInfo.payloadIndex,\n    camera_mode: currentCameraMode === CameraMode.Photo ? CameraMode.Video : CameraMode.Photo\n  })\n}\n\nasync function onTakeCameraPhoto () {\n  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {\n    return\n  }\n  await takeCameraPhoto(props.sn, payloadSelectInfo.payloadIndex)\n}\n\nasync function onStartCameraRecording () {\n  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {\n    return\n  }\n  await startCameraRecording(props.sn, payloadSelectInfo.payloadIndex)\n}\n\nasync function onStopCameraRecording () {\n  if (!checkPayloadAuth(payloadSelectInfo.controlSource)) {\n    return\n  }\n  await stopCameraRecording(props.sn, payloadSelectInfo.payloadIndex)\n}\n\nconst zoomFactorPopoverData = reactive({\n  visible: false,\n  loading: false,\n  cameraType: null as null | CameraType,\n  zoomFactor: null as null | number,\n})\n\nfunction onShowZoomFactorPopover () {\n  zoomFactorPopoverData.visible = !zoomFactorPopoverData.visible\n  zoomFactorPopoverData.loading = false\n  zoomFactorPopoverData.cameraType = null\n  zoomFactorPopoverData.zoomFactor = null\n}\n\nasync function onZoomFactorConfirm (confirm: boolean) {\n  if (confirm) {\n    if (!zoomFactorPopoverData.zoomFactor || zoomFactorPopoverData.cameraType === null) {\n      message.error('Please input Zoom Factor')\n      return\n    }\n    zoomFactorPopoverData.loading = true\n    try {\n      await changeCameraFocalLength(props.sn, {\n        payload_index: payloadSelectInfo.payloadIndex,\n        camera_type: zoomFactorPopoverData.cameraType,\n        zoom_factor: zoomFactorPopoverData.zoomFactor\n      })\n    } catch (err) {\n    }\n  }\n  zoomFactorPopoverData.visible = false\n}\n\nconst cameraAimPopoverData = reactive({\n  visible: false,\n  loading: false,\n  cameraType: null as null | CameraType,\n  locked: false,\n  x: null as null | number,\n  y: null as null | number,\n})\n\nfunction onShowCameraAimPopover () {\n  cameraAimPopoverData.visible = !cameraAimPopoverData.visible\n  cameraAimPopoverData.loading = false\n  cameraAimPopoverData.cameraType = null\n  cameraAimPopoverData.locked = false\n  cameraAimPopoverData.x = null\n  cameraAimPopoverData.y = null\n}\n\nfunction openLivestreamOthers () {\n  store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', true)\n}\n\nfunction openLivestreamAgora () {\n  store.commit('SET_LIVESTREAM_AGORA_VISIBLE', true)\n}\n\nasync function onCameraAimConfirm (confirm: boolean) {\n  if (confirm) {\n    if (cameraAimPopoverData.cameraType === null || cameraAimPopoverData.x === null || cameraAimPopoverData.y === null) {\n      message.error('Input error')\n      return\n    }\n    try {\n      await cameraAim(props.sn, {\n        payload_index: payloadSelectInfo.payloadIndex,\n        camera_type: cameraAimPopoverData.cameraType,\n        locked: cameraAimPopoverData.locked,\n        x: cameraAimPopoverData.x,\n        y: cameraAimPopoverData.y,\n      })\n    } catch (error) {\n    }\n  }\n  cameraAimPopoverData.visible = false\n}\n\nwatch(() => errorInfo, (errorInfo) => {\n  if (errorInfo.value) {\n    message.error(errorInfo.value)\n    console.error(errorInfo.value)\n    errorInfo.value = ''\n  }\n}, {\n  immediate: true,\n  deep: true\n})\n</script>\n\n<style lang='scss' scoped>\n.drone-control-wrapper{\n  // border-bottom: 1px solid #515151;\n\n  .drone-control-header{\n    font-size: 14px;\n    font-weight: 600;\n    padding: 10px 10px 0px;\n  }\n\n  .drone-control-box {\n    display: flex;\n    flex-wrap: 1;\n    .box {\n      width: 50%;\n      padding: 5px;\n      border: 0.5px solid rgba(255,255,255,0.3);\n\n      .row {\n        display: flex;\n        flex-wrap: wrap;\n        padding: 2px;\n\n        + .row{\n          margin-bottom: 6px;\n        }\n\n        &::v-deep{\n          .ant-btn{\n            font-size: 12px;\n            padding: 0px 4px;\n            margin-right: 5px;\n          }\n        }\n      }\n\n      .drone-control{\n         &::v-deep{\n\n          .ant-select-single:not(.ant-select-customize-input) .ant-select-selector{\n           padding: 0 2px;\n          }\n        }\n      }\n\n      .drone-control-direction{\n        margin-right: 10px;\n\n        .ant-btn {\n          // padding: 0px 1px;\n          margin-right: 0;\n        }\n\n        .word{\n          width: 12px;\n          margin-left: 2px;\n          font-size: 12px;\n          color: #aaa;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/g-map/DroneControlPopover.vue",
    "content": "<template>\n  <a-popover :visible=\"state.sVisible\"\n             trigger=\"click\"\n             v-bind=\"$attrs\"\n             :overlay-class-name=\"overlayClassName\"\n             placement=\"bottom\"\n             @visibleChange=\";\"\n             v-on=\"$attrs\">\n    <template #content>\n      <div class=\"title-content\">\n      </div>\n      <slot name=\"formContent\" />\n      <div class=\"uranus-popconfirm-btns\">\n        <a-button size=\"sm\"\n           @click=\"onCancel\">\n           {{ cancelText || 'cancel'}}\n        </a-button>\n        <a-button size=\"sm\"\n          :loading=\"loading\"\n          type=\"primary\"\n          class=\"confirm-btn\"\n          @click=\"onConfirm\">\n          {{ okText || 'ok' }}\n        </a-button>\n      </div>\n    </template>\n    <template v-if=\"$slots.default\">\n      <slot></slot>\n    </template>\n  </a-popover>\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps, defineEmits, reactive, watch, computed } from 'vue'\n\nconst props = defineProps<{\n    visible?: boolean,\n    loading?: Boolean,\n    disabled?: Boolean,\n    title?: String,\n    okText?: String,\n    cancelText?: String,\n    width?: Number,\n}>()\n\nconst emit = defineEmits(['cancel', 'confirm'])\n\nconst state = reactive({\n  sVisible: false,\n  loading: false,\n})\n\nwatch(() => props.visible, (val) => {\n  state.sVisible = val || false\n})\n\nconst loading = computed(() => {\n  return props.loading\n})\nconst okLabel = computed(() => {\n  return props.loading ? '' : '确定'\n})\n\nconst overlayClassName = computed(() => {\n  const classList = ['drone-control-popconfirm']\n  return classList.join(' ')\n})\n\nfunction onConfirm (e: Event) {\n  if (props.disabled) {\n    return\n  }\n  emit('confirm', e)\n}\n\nfunction onCancel (e: Event) {\n  state.sVisible = false\n  emit('cancel', e)\n}\n\n</script>\n\n<style lang=\"scss\">\n.drone-control-popconfirm {\n  min-width: 300px;\n\n  .uranus-popconfirm-btns{\n    display: flex;\n    padding: 10px 0px;\n    justify-content: flex-end;\n\n    .confirm-btn{\n      margin-left: 10px;\n    }\n  }\n\n  .form-content{\n    display: flex;\n    flex-direction: column;\n\n    > div {\n      display: flex;\n      margin-bottom: 5px;\n\n      .form-label {\n        flex: 1 0 60px;\n        margin-right: 10px;\n      }\n\n      > div {\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/g-map/use-connect-mqtt.ts",
    "content": "\nimport {\n  ref,\n  watch,\n  computed,\n  onUnmounted,\n} from 'vue'\nimport { useMyStore } from '/@/store'\nimport { postDrc } from '/@/api/drc'\nimport {\n  UranusMqtt,\n} from '/@/mqtt'\n\ntype StatusOptions = {\n  status: 'close';\n  event?: CloseEvent;\n} | {\n  status: 'open';\n  retryCount: number;\n} | {\n  status: 'pending';\n}\n\nexport function useConnectMqtt () {\n  const store = useMyStore()\n  const dockOsdVisible = computed(() => {\n    return store.state.osdVisible && store.state.osdVisible.visible && store.state.osdVisible.is_dock\n  })\n  const mqttState = ref<UranusMqtt | null>(null)\n\n  // 监听已打开的设备小窗 窗口数量\n  watch(() => dockOsdVisible.value, async (val) => {\n    // 1.打开小窗\n    // 2.设备拥有飞行控制权\n    // 3.请求建立mqtt连接的认证信息\n    if (val) {\n      if (mqttState.value) return\n      const result = await postDrc({})\n      if (result?.code === 0) {\n        const { address, client_id, username, password, expire_time } = result.data\n        // @TODO: 校验 expire_time\n        mqttState.value = new UranusMqtt(address, {\n          clientId: client_id,\n          username,\n          password,\n        })\n        mqttState.value?.initMqtt()\n        mqttState.value?.on('onStatus', (statusOptions: StatusOptions) => {\n          // @TODO: 异常case\n        })\n\n        store.commit('SET_MQTT_STATE', mqttState.value)\n        store.commit('SET_CLIENT_ID', client_id)\n      }\n      // @TODO: 认证失败case\n      return\n    }\n    // 关闭所有小窗后\n    // 1.销毁mqtt连接重置mqtt状态\n    if (mqttState?.value) {\n      mqttState.value?.destroyed()\n      mqttState.value = null\n      store.commit('SET_MQTT_STATE', null)\n      store.commit('SET_CLIENT_ID', '')\n    }\n  }, { immediate: true })\n\n  onUnmounted(() => {\n    mqttState.value?.destroyed()\n  })\n}\n"
  },
  {
    "path": "src/components/g-map/use-device-setting.ts",
    "content": "import { message } from 'ant-design-vue'\nimport { putDeviceProps, PutDevicePropsBody } from '/@/api/device-setting'\nimport { DeviceSettingKeyEnum, DeviceSettingFormModel, ObstacleAvoidanceStatusEnum, NightLightsStateEnum, DistanceLimitStatusEnum } from '/@/types/device-setting'\n\nexport function useDeviceSetting () {\n  // 生成参数\n  function genDevicePropsBySettingKey (key: DeviceSettingKeyEnum, fromModel: DeviceSettingFormModel) {\n    const body = {} as PutDevicePropsBody\n    if (key === DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET) {\n      body.night_lights_state = fromModel.nightLightsState ? NightLightsStateEnum.OPEN : NightLightsStateEnum.CLOSE\n    } else if (key === DeviceSettingKeyEnum.HEIGHT_LIMIT_SET) {\n      body.height_limit = fromModel.heightLimit\n    } else if (key === DeviceSettingKeyEnum.DISTANCE_LIMIT_SET) {\n      body.distance_limit_status = {}\n      if (fromModel.distanceLimitStatus.state) {\n        body.distance_limit_status.state = DistanceLimitStatusEnum.SET\n        body.distance_limit_status.distance_limit = fromModel.distanceLimitStatus.distanceLimit\n      } else {\n        body.distance_limit_status.state = DistanceLimitStatusEnum.UNSET\n      }\n    } else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON) {\n      body.obstacle_avoidance = {\n        horizon: fromModel.obstacleAvoidanceHorizon ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE\n      }\n    } else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE) {\n      body.obstacle_avoidance = {\n        upside: fromModel.obstacleAvoidanceUpside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE\n      }\n    } else if (key === DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE) {\n      body.obstacle_avoidance = {\n        downside: fromModel.obstacleAvoidanceDownside ? ObstacleAvoidanceStatusEnum.OPEN : ObstacleAvoidanceStatusEnum.CLOSE\n      }\n    }\n    return body\n  }\n\n  // 设置设备属性\n  async function setDeviceProps (sn: string, body: PutDevicePropsBody) {\n    try {\n      const { code, message: msg } = await putDeviceProps(sn, body)\n      if (code === 0) {\n        // message.success('指令发送成功')\n        return true\n      }\n      throw (msg)\n    } catch (e) {\n      message.error('设备属性设置失败')\n      return false\n    }\n  }\n\n  return {\n    genDevicePropsBySettingKey,\n    setDeviceProps\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/use-dock-control.ts",
    "content": "import { message } from 'ant-design-vue'\nimport { ref } from 'vue'\nimport { postSendCmd } from '/@/api/device-cmd'\nimport { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'\n\nexport function useDockControl () {\n  const dockControlPanelVisible = ref(false)\n\n  function setDockControlPanelVisible (visible: boolean) {\n    dockControlPanelVisible.value = visible\n  }\n\n  // 远程调试开关\n  async function dockDebugOnOff (sn: string, on: boolean) {\n    const result = await sendDockControlCmd({\n      sn: sn,\n      cmd: on ? DeviceCmd.DebugModeOpen : DeviceCmd.DebugModeClose\n    }, false)\n    return result\n  }\n\n  // 发送指令\n  async function sendDockControlCmd (params: {\n    sn: string,\n    cmd: DeviceCmd\n    action?: DeviceCmdItemAction\n  }, tip = true) {\n    try {\n      let body = undefined as any\n      if (params.action !== undefined) {\n        body = {\n          action: params.action\n        }\n      }\n      const { code, message: msg } = await postSendCmd({ dock_sn: params.sn, device_cmd: params.cmd }, body)\n      if (code === 0) {\n        tip && message.success('指令发送成功')\n        return true\n      }\n      throw (msg)\n    } catch (e) {\n      tip && message.error('指令发送失败')\n      return false\n    }\n  }\n\n  // 控制面板关闭\n  async function onCloseControlPanel (sn: string, debugging: boolean) {\n    if (debugging) {\n      await dockDebugOnOff(sn, false)\n    }\n    setDockControlPanelVisible(false)\n  }\n\n  return {\n    dockControlPanelVisible,\n    setDockControlPanelVisible,\n    sendDockControlCmd,\n    dockDebugOnOff,\n    onCloseControlPanel,\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/use-drone-control-mqtt-event.ts",
    "content": "import { ref, onMounted, onBeforeUnmount } from 'vue'\nimport EventBus from '/@/event-bus/'\nimport {\n  DRC_METHOD,\n  DRCHsiInfo,\n  DRCOsdInfo,\n  DRCDelayTimeInfo,\n  DrcResponseInfo,\n} from '/@/types/drc'\n\nexport function useDroneControlMqttEvent (sn: string) {\n  const drcInfo = ref('')\n  const hsiInfo = ref('')\n  const osdInfo = ref('')\n  const delayInfo = ref('')\n  const errorInfo = ref('')\n\n  function handleHsiInfo (data: DRCHsiInfo) {\n    hsiInfo.value = `method: ${DRC_METHOD.HSI_INFO_PUSH}\\r\\n ${JSON.stringify(data)}\\r\\n `\n  }\n\n  function handleOsdInfo (data: DRCOsdInfo) {\n    osdInfo.value = `method: ${DRC_METHOD.OSD_INFO_PUSH}\\r\\n ${JSON.stringify(data)}\\r\\n `\n  }\n\n  function handleDelayTimeInfo (data: DRCDelayTimeInfo) {\n    delayInfo.value = `method: ${DRC_METHOD.DELAY_TIME_INFO_PUSH}\\r\\n ${JSON.stringify(data)}\\r\\n `\n  }\n\n  function handleDroneControlErrorInfo (data: DrcResponseInfo) {\n    if (!data.result) {\n      return\n    }\n    errorInfo.value = `Drc error code: ${data.result}, seq: ${data.output?.seq}`\n  }\n\n  function handleDroneControlMqttEvent (payload: any) {\n    if (!payload || !payload.method) {\n      return\n    }\n\n    switch (payload.method) {\n      case DRC_METHOD.HSI_INFO_PUSH: {\n        handleHsiInfo(payload.data)\n        break\n      }\n      case DRC_METHOD.OSD_INFO_PUSH: {\n        handleOsdInfo(payload.data)\n        break\n      }\n      case DRC_METHOD.DELAY_TIME_INFO_PUSH: {\n        handleDelayTimeInfo(payload.data)\n        break\n      }\n      case DRC_METHOD.DRONE_EMERGENCY_STOP:\n      case DRC_METHOD.DRONE_CONTROL: {\n        handleDroneControlErrorInfo(payload.data)\n        break\n      }\n    }\n    drcInfo.value = hsiInfo.value + osdInfo.value + delayInfo.value\n  }\n\n  onMounted(() => {\n    EventBus.on('droneControlMqttInfo', handleDroneControlMqttEvent)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('droneControlMqttInfo', handleDroneControlMqttEvent)\n  })\n\n  return {\n    drcInfo: drcInfo,\n    errorInfo: errorInfo\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/use-drone-control-ws-event.ts",
    "content": "import { message, notification } from 'ant-design-vue'\nimport { ref, onMounted, onBeforeUnmount } from 'vue'\nimport EventBus from '/@/event-bus/'\nimport { EBizCode } from '/@/types'\nimport { ControlSource } from '/@/types/device'\nimport { ControlSourceChangeType, ControlSourceChangeInfo, FlyToPointMessage, TakeoffToPointMessage, DrcModeExitNotifyMessage, DrcStatusNotifyMessage } from '/@/types/drone-control'\n\nexport interface UseDroneControlWsEventParams {\n}\n\nexport function useDroneControlWsEvent (sn: string, payloadSn: string, funcs?: UseDroneControlWsEventParams) {\n  const droneControlSource = ref(ControlSource.A)\n  const payloadControlSource = ref(ControlSource.B)\n  function onControlSourceChange (data: ControlSourceChangeInfo) {\n    if (data.type === ControlSourceChangeType.Flight && data.sn === sn) {\n      droneControlSource.value = data.control_source\n      message.info(`Flight control is changed to ${droneControlSource.value}`)\n      return\n    }\n    if (data.type === ControlSourceChangeType.Payload && data.sn === payloadSn) {\n      payloadControlSource.value = data.control_source\n      message.info(`Payload control is changed to ${payloadControlSource.value}.`)\n    }\n  }\n\n  function handleProgress (key: string, message: string, error: number) {\n    if (error !== 0) {\n      notification.error({\n        key: key,\n        message: key + 'Error code:' + error,\n        description: message,\n        duration: null\n      })\n    } else {\n      notification.info({\n        key: key,\n        message: key,\n        description: message,\n        duration: 30\n      })\n    }\n  }\n\n  function handleDroneControlWsEvent (payload: any) {\n    if (!payload) {\n      return\n    }\n\n    switch (payload.biz_code) {\n      case EBizCode.ControlSourceChange: {\n        onControlSourceChange(payload.data)\n        break\n      }\n      case EBizCode.FlyToPointProgress: {\n        const { sn: deviceSn, result, message: msg } = payload.data as FlyToPointMessage\n        if (deviceSn !== sn) return\n        handleProgress(EBizCode.FlyToPointProgress, `device(sn: ${deviceSn}) ${msg}`, result)\n        break\n      }\n      case EBizCode.TakeoffToPointProgress: {\n        const { sn: deviceSn, result, message: msg } = payload.data as TakeoffToPointMessage\n        if (deviceSn !== sn) return\n        handleProgress(EBizCode.TakeoffToPointProgress, `device(sn: ${deviceSn}) ${msg}`, result)\n        break\n      }\n      case EBizCode.JoystickInvalidNotify: {\n        const { sn: deviceSn, result, message: msg } = payload.data as DrcModeExitNotifyMessage\n        if (deviceSn !== sn) return\n        handleProgress(EBizCode.JoystickInvalidNotify, `device(sn: ${deviceSn}) ${msg}`, result)\n        break\n      }\n      case EBizCode.DrcStatusNotify: {\n        const { sn: deviceSn, result, message: msg } = payload.data as DrcStatusNotifyMessage\n        // handleProgress(EBizCode.DrcStatusNotify, `device(sn: ${deviceSn}) ${msg}`, result)\n\n        break\n      }\n    }\n    // eslint-disable-next-line no-unused-expressions\n    // console.log('payload.biz_code', payload.data)\n  }\n\n  onMounted(() => {\n    EventBus.on('droneControlWs', handleDroneControlWsEvent)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('droneControlWs', handleDroneControlWsEvent)\n  })\n\n  return {\n    droneControlSource: droneControlSource,\n    payloadControlSource: payloadControlSource\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/use-drone-control.ts",
    "content": "import { ref } from 'vue'\nimport { postFlyToPoint, PostFlyToPointBody, deleteFlyToPoint, postTakeoffToPoint, PostTakeoffToPointBody } from '/@/api/drone-control/drone'\nimport { message } from 'ant-design-vue'\n\nexport function useDroneControl () {\n  const droneControlPanelVisible = ref(false)\n\n  function setDroneControlPanelVisible (visible: boolean) {\n    droneControlPanelVisible.value = visible\n  }\n\n  async function flyToPoint (sn: string, body: PostFlyToPointBody) {\n    const { code } = await postFlyToPoint(sn, body)\n    if (code === 0) {\n      message.success('Fly to')\n    }\n  }\n\n  async function stopFlyToPoint (sn: string) {\n    const { code } = await deleteFlyToPoint(sn)\n    if (code === 0) {\n      message.success('Stop fly to')\n    }\n  }\n\n  async function takeoffToPoint (sn: string, body: PostTakeoffToPointBody) {\n    const { code } = await postTakeoffToPoint(sn, body)\n    if (code === 0) {\n      message.success('Take off successfully')\n    }\n  }\n\n  return {\n    droneControlPanelVisible,\n    setDroneControlPanelVisible,\n    flyToPoint,\n    stopFlyToPoint,\n    takeoffToPoint\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/use-manual-control.ts",
    "content": "import {\n  ref,\n  onUnmounted,\n  watch,\n  Ref,\n} from 'vue'\nimport { message } from 'ant-design-vue'\nimport {\n  DRC_METHOD,\n  DroneControlProtocol,\n} from '/@/types/drc'\nimport {\n  useMqtt,\n  DeviceTopicInfo\n} from './use-mqtt'\n\nlet myInterval: any\n\nexport enum KeyCode {\n  KEY_W = 'KeyW',\n  KEY_A = 'KeyA',\n  KEY_S = 'KeyS',\n  KEY_D = 'KeyD',\n  KEY_Q = 'KeyQ',\n  KEY_E = 'KeyE',\n  ARROW_UP = 'ArrowUp',\n  ARROW_DOWN = 'ArrowDown',\n}\n\nexport function useManualControl (deviceTopicInfo: DeviceTopicInfo, isCurrentFlightController: Ref<boolean>) {\n  const activeCodeKey = ref(null) as Ref<KeyCode | null>\n  const mqttHooks = useMqtt(deviceTopicInfo)\n  let seq = 0\n  function handlePublish (params: DroneControlProtocol) {\n    const body = {\n      method: DRC_METHOD.DRONE_CONTROL,\n      data: params,\n    }\n    handleClearInterval()\n    myInterval = setInterval(() => {\n      body.data.seq = seq++\n      seq++\n      window.console.log('keyCode>>>>', activeCodeKey.value, body)\n      mqttHooks?.publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 0 })\n    }, 50)\n  }\n\n  function handleKeyup (keyCode: KeyCode) {\n    if (!deviceTopicInfo.pubTopic) {\n      message.error('请确保已经建立DRC链路')\n      return\n    }\n    const SPEED = 5 //  check\n    const HEIGHT = 5 //  check\n    const W_SPEED = 20 // 机头角速度\n    seq = 0\n    switch (keyCode) {\n      case 'KeyA':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ y: -SPEED })\n        activeCodeKey.value = keyCode\n        break\n      case 'KeyW':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ x: SPEED })\n        activeCodeKey.value = keyCode\n        break\n      case 'KeyS':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ x: -SPEED })\n        activeCodeKey.value = keyCode\n        break\n      case 'KeyD':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ y: SPEED })\n        activeCodeKey.value = keyCode\n        break\n      case 'ArrowUp':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ h: HEIGHT })\n        activeCodeKey.value = keyCode\n        break\n      case 'ArrowDown':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ h: -HEIGHT })\n        activeCodeKey.value = keyCode\n        break\n      case 'KeyQ':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ w: -W_SPEED })\n        activeCodeKey.value = keyCode\n        break\n      case 'KeyE':\n        if (activeCodeKey.value === keyCode) return\n        handlePublish({ w: W_SPEED })\n        activeCodeKey.value = keyCode\n        break\n      default:\n        break\n    }\n  }\n\n  function handleClearInterval () {\n    clearInterval(myInterval)\n    myInterval = undefined\n  }\n\n  function resetControlState () {\n    activeCodeKey.value = null\n    seq = 0\n    handleClearInterval()\n  }\n\n  function onKeyup () {\n    resetControlState()\n  }\n\n  function onKeydown (e: KeyboardEvent) {\n    handleKeyup(e.code as KeyCode)\n  }\n\n  function startKeyboardManualControl () {\n    window.addEventListener('keydown', onKeydown)\n    window.addEventListener('keyup', onKeyup)\n  }\n\n  function closeKeyboardManualControl () {\n    resetControlState()\n    window.removeEventListener('keydown', onKeydown)\n    window.removeEventListener('keyup', onKeyup)\n  }\n\n  watch(() => isCurrentFlightController.value, (val) => {\n    if (val && deviceTopicInfo.pubTopic) {\n      startKeyboardManualControl()\n    } else {\n      closeKeyboardManualControl()\n    }\n  }, { immediate: true })\n\n  onUnmounted(() => {\n    closeKeyboardManualControl()\n  })\n\n  function handleEmergencyStop () {\n    if (!deviceTopicInfo.pubTopic) {\n      message.error('请确保已经建立DRC链路')\n      return\n    }\n    const body = {\n      method: DRC_METHOD.DRONE_EMERGENCY_STOP,\n      data: {}\n    }\n    resetControlState()\n    window.console.log('handleEmergencyStop>>>>', deviceTopicInfo.pubTopic, body)\n    mqttHooks?.publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 1 })\n  }\n\n  return {\n    activeCodeKey,\n    handleKeyup,\n    handleEmergencyStop,\n    resetControlState,\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/use-mqtt.ts",
    "content": "import {\n  ref,\n  reactive,\n  computed,\n  watch,\n  onUnmounted,\n} from 'vue'\nimport {\n  IClientPublishOptions,\n  IPublishPacket,\n} from '/@/mqtt'\nimport { useMyStore } from '/@/store'\nimport {\n  DRC_METHOD,\n} from '/@/types/drc'\nimport EventBus from '/@/event-bus'\n\nexport interface DeviceTopicInfo{\n  sn: string\n  pubTopic: string\n  subTopic: string\n}\n\ntype MessageMqtt = (topic: string, payload: Buffer, packet: IPublishPacket) => void | Promise<void>\n\nexport function useMqtt (deviceTopicInfo: DeviceTopicInfo) {\n  let cacheSubscribeArr: {\n    topic: string;\n    callback?: MessageMqtt;\n  }[] = []\n\n  const store = useMyStore()\n\n  const mqttState = computed(() => {\n    return store.state.mqttState\n  })\n\n  function publishMqtt (topic: string, body: object, ots?: IClientPublishOptions) {\n    // const buffer = Buffer.from(JSON.stringify(body))\n    mqttState.value?.publishMqtt(topic, JSON.stringify(body), ots)\n  }\n\n  function subscribeMqtt (topic: string, handleMessageMqtt?: MessageMqtt) {\n    mqttState.value?.subscribeMqtt(topic)\n    const handler = handleMessageMqtt || onMessageMqtt\n    mqttState.value?.on('onMessageMqtt', handler)\n    cacheSubscribeArr.push({\n      topic,\n      callback: handler,\n    })\n  }\n\n  function onMessageMqtt (message: any) {\n    if (cacheSubscribeArr.findIndex(item => item.topic === message?.topic) !== -1) {\n      const payloadStr = new TextDecoder('utf-8').decode(message?.payload)\n      const payloadObj = JSON.parse(payloadStr)\n      switch (payloadObj?.method) {\n        case DRC_METHOD.HEART_BEAT:\n          break\n        case DRC_METHOD.DELAY_TIME_INFO_PUSH:\n        case DRC_METHOD.HSI_INFO_PUSH:\n        case DRC_METHOD.OSD_INFO_PUSH:\n        case DRC_METHOD.DRONE_CONTROL:\n        case DRC_METHOD.DRONE_EMERGENCY_STOP:\n          EventBus.emit('droneControlMqttInfo', payloadObj)\n          break\n        default:\n          break\n      }\n    }\n  }\n\n  function unsubscribeDrc () {\n    // 销毁已订阅事件\n    cacheSubscribeArr.forEach(item => {\n      mqttState.value?.off('onMessageMqtt', item.callback)\n      mqttState.value?.unsubscribeMqtt(item.topic)\n    })\n    cacheSubscribeArr = []\n  }\n\n  // 心跳\n  const heartBeatSeq = ref(0)\n  const state = reactive({\n    heartState: new Map<string, {\n      pingInterval: any;\n    }>(),\n  })\n\n  // 监听云控控制权\n  watch(() => deviceTopicInfo, (val, oldVal) => {\n    if (val.subTopic !== '') {\n      // 1.订阅topic\n      subscribeMqtt(deviceTopicInfo.subTopic)\n      // 2.发心跳\n      publishDrcPing(deviceTopicInfo.sn)\n    } else {\n      clearInterval(state.heartState.get(deviceTopicInfo.sn)?.pingInterval)\n      state.heartState.delete(deviceTopicInfo.sn)\n      heartBeatSeq.value = 0\n    }\n  }, { immediate: true, deep: true })\n\n  function publishDrcPing (sn: string) {\n    const body = {\n      method: DRC_METHOD.HEART_BEAT,\n      data: {\n        ts: new Date().getTime(),\n        seq: heartBeatSeq.value,\n      },\n    }\n    const pingInterval = setInterval(() => {\n      if (!mqttState.value) return\n      heartBeatSeq.value += 1\n      body.data.ts = new Date().getTime()\n      body.data.seq = heartBeatSeq.value\n      publishMqtt(deviceTopicInfo.pubTopic, body, { qos: 0 })\n    }, 1000)\n    state.heartState.set(sn, {\n      pingInterval,\n    })\n  }\n\n  onUnmounted(() => {\n    unsubscribeDrc()\n    heartBeatSeq.value = 0\n  })\n\n  return {\n    mqttState,\n    publishMqtt,\n    subscribeMqtt,\n  }\n}\n"
  },
  {
    "path": "src/components/g-map/use-payload-control.ts",
    "content": "import { message } from 'ant-design-vue'\nimport {\n  postPayloadAuth,\n  postPayloadCommands,\n  PayloadCommandsEnum,\n  PostCameraModeBody,\n  PostCameraFocalLengthBody,\n  PostGimbalResetBody,\n  PostCameraAimBody,\n} from '/@/api/drone-control/payload'\nimport { ControlSource } from '/@/types/device'\n\nexport function usePayloadControl () {\n  function checkPayloadAuth (controlSource?: ControlSource) {\n    if (controlSource !== ControlSource.A) {\n      message.error('Get Payload Control first')\n      return false\n    }\n    return true\n  }\n\n  async function authPayload (sn: string, payloadIndx: string) {\n    const { code } = await postPayloadAuth(sn, {\n      payload_index: payloadIndx\n    })\n    if (code === 0) {\n      message.success('Get Payload Control successfully')\n      return true\n    }\n    return false\n  }\n\n  async function resetGimbal (sn: string, data: PostGimbalResetBody) {\n    const { code } = await postPayloadCommands(sn, {\n      cmd: PayloadCommandsEnum.GimbalReset,\n      data: data\n    })\n    if (code === 0) {\n      message.success('Gimbal Reset successfully')\n    }\n  }\n\n  async function switchCameraMode (sn: string, data: PostCameraModeBody) {\n    const { code } = await postPayloadCommands(sn, {\n      cmd: PayloadCommandsEnum.CameraModeSwitch,\n      data: data\n    })\n    if (code === 0) {\n      message.success('Camera Mode Switch successfully')\n    }\n  }\n\n  async function takeCameraPhoto (sn: string, payloadIndx: string) {\n    const { code } = await postPayloadCommands(sn, {\n      cmd: PayloadCommandsEnum.CameraPhotoTake,\n      data: {\n        payload_index: payloadIndx\n      }\n    })\n    if (code === 0) {\n      message.success('Take Photo successfully')\n    }\n  }\n\n  async function startCameraRecording (sn: string, payloadIndx: string) {\n    const { code } = await postPayloadCommands(sn, {\n      cmd: PayloadCommandsEnum.CameraRecordingStart,\n      data: {\n        payload_index: payloadIndx\n      }\n    })\n    if (code === 0) {\n      message.success('Start Recording successfully')\n    }\n  }\n\n  async function stopCameraRecording (sn: string, payloadIndx: string) {\n    const { code } = await postPayloadCommands(sn, {\n      cmd: PayloadCommandsEnum.CameraRecordingStop,\n      data: {\n        payload_index: payloadIndx\n      }\n    })\n    if (code === 0) {\n      message.success('Stop Recording successfully')\n    }\n  }\n\n  async function changeCameraFocalLength (sn: string, data: PostCameraFocalLengthBody) {\n    const { code } = await postPayloadCommands(sn, {\n      cmd: PayloadCommandsEnum.CameraFocalLengthSet,\n      data: data,\n    })\n    if (code === 0) {\n      message.success('Zoom successfully')\n    }\n  }\n\n  async function cameraAim (sn: string, data: PostCameraAimBody) {\n    const { code } = await postPayloadCommands(sn, {\n      cmd: PayloadCommandsEnum.CameraAim,\n      data: data,\n    })\n    if (code === 0) {\n      message.success('Zoom Aim successfully')\n    }\n  }\n\n  return {\n    checkPayloadAuth,\n    authPayload,\n    resetGimbal,\n    switchCameraMode,\n    takeCameraPhoto,\n    startCameraRecording,\n    stopCameraRecording,\n    changeCameraFocalLength,\n    cameraAim,\n  }\n}\n"
  },
  {
    "path": "src/components/livestream-agora.vue",
    "content": "<template>\n  <div class=\"flex-column flex-justify-start flex-align-center\">\n    <div id=\"player\" style=\"width: 720px; height: 420px; border: 1px solid\"></div>\n    <p class=\"fz24\">Live streaming source selection</p>\n    <div class=\"flex-row flex-justify-center flex-align-center mt10\">\n      <template v-if=\"livePara.liveState && dronePara.isDockLive\">\n        <span class=\"mr10\">Lens:</span>\n        <a-radio-group v-model:value=\"dronePara.lensSelected\" button-style=\"solid\">\n          <a-radio-button v-for=\"lens in dronePara.lensList\" :key=\"lens\" :value=\"lens\">{{lens}}</a-radio-button>\n        </a-radio-group>\n      </template>\n      <template v-else>\n      <a-select\n        style=\"width:150px\"\n        placeholder=\"Select Drone\"\n        v-model:value=\"dronePara.droneSelected\"\n      >\n        <a-select-option\n          v-for=\"item in dronePara.droneList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          @click=\"onDroneSelect(item)\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select>\n      <a-select\n        class=\"ml10\"\n        style=\"width:150px\"\n        placeholder=\"Select Camera\"\n        v-model:value=\"dronePara.cameraSelected\"\n      >\n        <a-select-option\n          v-for=\"item in dronePara.cameraList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          @click=\"onCameraSelect(item)\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select>\n      <!-- <a-select\n        class=\"ml10\"\n        style=\"width:150px\"\n        placeholder=\"Select Lens\"\n        @select=\"onVideoSelect\"\n      >\n        <a-select-option\n          v-for=\"item in dronePara.videoList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select> -->\n      </template>\n      <a-select\n        class=\"ml10\"\n        style=\"width:150px\"\n        placeholder=\"Select Clarity\"\n        @select=\"onClaritySelect\"\n      >\n        <a-select-option\n          v-for=\"item in clarityList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select>\n    </div>\n    <p class=\"fz16 mt10\">\n      Note: Obtain The Following Parameters From https://console.agora.io\n    </p>\n    <div class=\"flex-row flex-justify-center flex-align-center\">\n      <span class=\"mr10\">AppId:</span>\n      <a-input v-model:value=\"agoraPara.appid\" placeholder=\"APP ID\"></a-input>\n      <span class=\"ml10\">Token:</span>\n      <a-input\n        class=\"ml10\"\n        v-model:value=\"agoraPara.token\"\n        placeholder=\"Token\"\n      ></a-input>\n      <span class=\"ml10\">Channel:</span>\n      <a-input\n        class=\"ml10\"\n        v-model:value=\"agoraPara.channel\"\n        placeholder=\"Channel\"\n      ></a-input>\n    </div>\n    <div class=\"mt20 flex-row flex-justify-center flex-align-center\">\n      <a-button v-if=\"livePara.liveState && dronePara.isDockLive\" type=\"primary\" large @click=\"onSwitch\">Switch Lens</a-button>\n      <a-button v-else type=\"primary\" large @click=\"onStart\">Play</a-button>\n      <a-button class=\"ml20\" type=\"primary\" large @click=\"onStop\"\n        >Stop</a-button\n      >\n      <a-button class=\"ml20\" type=\"primary\" large @click=\"onUpdateQuality\"\n        >Update Clarity</a-button\n      >\n      <a-button v-if=\"!livePara.liveState || !dronePara.isDockLive\" class=\"ml20\" type=\"primary\" large @click=\"onRefresh\"\n        >Refresh Live Capacity</a-button\n      >\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport AgoraRTC, { IAgoraRTCClient, IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'\nimport { message } from 'ant-design-vue'\nimport { onMounted, reactive } from 'vue'\nimport { uuidv4 } from '../utils/uuid'\nimport { CURRENT_CONFIG as config } from '/@/api/http/config'\nimport { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'\nimport { getRoot } from '/@/root'\n\nconst root = getRoot()\n\nconst clarityList = [\n  {\n    value: 0,\n    label: 'Adaptive'\n  },\n  {\n    value: 1,\n    label: 'Smooth'\n  },\n  {\n    value: 2,\n    label: 'Standard'\n  },\n  {\n    value: 3,\n    label: 'HD'\n  },\n  {\n    value: 4,\n    label: 'Super Clear'\n  }\n]\n\ninterface SelectOption {\n  value: any,\n  label: string,\n  more?: any\n}\n\nlet agoraClient = {} as IAgoraRTCClient\nconst agoraPara = reactive({\n  appid: config.agoraAPPID,\n  token: config.agoraToken,\n  channel: config.agoraChannel,\n  uid: 123456,\n  stream: {}\n})\nconst dronePara = reactive({\n  livestreamSource: [],\n  droneList: [] as SelectOption[],\n  cameraList: [] as SelectOption[],\n  videoList: [] as SelectOption[],\n  droneSelected: undefined as string | undefined,\n  cameraSelected: undefined as string | undefined,\n  videoSelected: undefined as string | undefined,\n  claritySelected: 0,\n  lensList: [] as string[],\n  lensSelected: undefined as string | undefined,\n  isDockLive: false\n})\nconst livePara = reactive({\n  url: '',\n  webrtc: {} as any,\n  videoId: '',\n  liveState: false\n})\nconst nonSwitchable = 'normal'\nconst onRefresh = async () => {\n  dronePara.droneList = []\n  dronePara.cameraList = []\n  dronePara.videoList = []\n  dronePara.droneSelected = undefined\n  dronePara.cameraSelected = undefined\n  dronePara.videoSelected = undefined\n  await getLiveCapacity({})\n    .then(res => {\n      if (res.code === 0) {\n        if (res.data === null) {\n          console.warn('warning: get live capacity is null!!!')\n          return\n        }\n        dronePara.livestreamSource = res.data\n        dronePara.droneList = []\n\n        console.log('live_capacity:', dronePara.livestreamSource)\n\n        if (dronePara.livestreamSource) {\n          dronePara.livestreamSource.forEach((ele: any) => {\n            dronePara.droneList.push({ label: ele.name + '-' + ele.sn, value: ele.sn, more: ele.cameras_list })\n          })\n        }\n      }\n    })\n    .catch(error => {\n      message.error(error)\n      console.error(error)\n    })\n}\n\nonMounted(() => {\n  onRefresh()\n  agoraClient = AgoraRTC.createClient({ mode: 'live', codec: 'vp8' })\n  agoraClient.setClientRole('audience', { level: 2 })\n  if (agoraClient.connectionState === 'DISCONNECTED') {\n    agoraClient.join(agoraPara.appid, agoraPara.channel, agoraPara.token)\n  }\n  // Subscribe when a remote user publishes a stream\n  agoraClient.on('user-joined', async (user: IAgoraRTCRemoteUser) => {\n    message.info('user[' + user.uid + '] join')\n  })\n  agoraClient.on('user-published', async (user: IAgoraRTCRemoteUser, mediaType: 'audio' | 'video') => {\n    await agoraClient.subscribe(user, mediaType)\n    if (mediaType === 'video') {\n      console.log('subscribe success')\n      // Get `RemoteVideoTrack` in the `user` object.\n      const remoteVideoTrack = user.videoTrack!\n      // Dynamically create a container in the form of a DIV element for playing the remote video track.\n      remoteVideoTrack.play(document.getElementById('player') as HTMLElement)\n    }\n  })\n  agoraClient.on('user-unpublished', async (user: any) => {\n    console.log('unpublish live:', user)\n    message.info('unpublish live')\n  })\n  agoraClient.on('exception', async (e: any) => {\n    console.log(e)\n    message.error(e.msg)\n  })\n})\nconst handleError = (err: any) => {\n  console.error(err)\n}\nconst handleJoinChannel = (uid: any) => {\n  agoraPara.uid = uid\n}\nconst onStart = async () => {\n  const that = this\n  console.log(\n    'drone parameter：',\n    dronePara.droneSelected,\n    dronePara.cameraSelected,\n    dronePara.videoSelected,\n    dronePara.claritySelected\n  )\n  const timestamp = new Date().getTime().toString()\n  const liveTimestamp = timestamp\n  if (\n    dronePara.droneSelected == null ||\n    dronePara.cameraSelected == null ||\n    dronePara.claritySelected == null\n  ) {\n    message.warn('waring: not select live para!!!')\n    return\n  }\n  agoraClient.setClientRole('audience', { level: 2 })\n  if (agoraClient.connectionState === 'DISCONNECTED') {\n    await agoraClient.join(agoraPara.appid, agoraPara.channel, agoraPara.token)\n  }\n  livePara.videoId =\n    dronePara.droneSelected +\n    '/' +\n    dronePara.cameraSelected + '/' + (dronePara.videoSelected || nonSwitchable + '-0')\n  console.log(agoraPara)\n\n  livePara.url =\n    'channel=' +\n    agoraPara.channel +\n    '&sn=' +\n    dronePara.droneSelected +\n    '&token=' +\n    encodeURIComponent(agoraPara.token) +\n    '&uid=' +\n    agoraPara.uid\n\n  startLivestream({\n    url: livePara.url,\n    video_id: livePara.videoId,\n    url_type: 0,\n    video_quality: dronePara.claritySelected\n  })\n    .then(res => {\n      if (res.code !== 0) {\n        return\n      }\n      livePara.liveState = true\n    })\n    .catch(err => {\n      console.error(err)\n    })\n}\nconst onStop = async () => {\n  if (\n    dronePara.droneSelected == null ||\n    dronePara.cameraSelected == null ||\n    dronePara.claritySelected == null\n  ) {\n    message.warn('waring: not select live para!!!')\n    return\n  }\n  livePara.videoId =\n    dronePara.droneSelected +\n    '/' +\n    dronePara.cameraSelected + '/' + (dronePara.videoSelected || nonSwitchable + '-0')\n\n  stopLivestream({\n    video_id: livePara.videoId\n  }).then(res => {\n    if (res.code === 0) {\n      message.success(res.message)\n      livePara.liveState = false\n      dronePara.lensSelected = ''\n      console.log('stop play livestream')\n    }\n  })\n}\nconst onDroneSelect = (val: SelectOption) => {\n  dronePara.cameraList = []\n  dronePara.videoList = []\n  dronePara.lensList = []\n\n  dronePara.cameraSelected = undefined\n  dronePara.videoSelected = undefined\n  dronePara.lensSelected = undefined\n  dronePara.droneSelected = val.value\n  if (!val.more) {\n    return\n  }\n  val.more.forEach((ele: any) => {\n    dronePara.cameraList.push({ label: ele.name, value: ele.index, more: ele.videos_list })\n  })\n}\nconst onCameraSelect = (val: SelectOption) => {\n  dronePara.cameraSelected = val.value\n  dronePara.videoSelected = undefined\n  dronePara.lensSelected = undefined\n  dronePara.videoList = []\n  dronePara.lensList = []\n  if (!val.more) {\n    return\n  }\n\n  val.more.forEach((ele: any) => {\n    dronePara.videoList.push({ label: ele.type, value: ele.index, more: ele.switch_video_types })\n  })\n  if (dronePara.videoList.length === 0) {\n    return\n  }\n  const firstVideo: SelectOption = dronePara.videoList[0]\n  dronePara.videoSelected = firstVideo.value\n  dronePara.lensList = firstVideo.more\n  dronePara.lensSelected = firstVideo.label\n  dronePara.isDockLive = dronePara.lensList?.length > 0\n}\nconst onVideoSelect = (val: SelectOption) => {\n  dronePara.videoSelected = val.value\n  dronePara.lensList = val.more\n  dronePara.lensSelected = val.label\n}\nconst onClaritySelect = (val: any) => {\n  dronePara.claritySelected = val\n}\nconst onUpdateQuality = () => {\n  if (!livePara.liveState) {\n    message.info('Please turn on the livestream first.')\n    return\n  }\n  setLivestreamQuality({\n    video_id: livePara.videoId,\n    video_quality: dronePara.claritySelected\n  }).then(res => {\n    if (res.code === 0) {\n      message.success('Set the clarity to ' + clarityList[dronePara.claritySelected].label)\n    }\n  })\n}\n\nconst onSwitch = () => {\n  if (dronePara.lensSelected === undefined || dronePara.lensSelected === nonSwitchable) {\n    message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8)\n    return\n  }\n  changeLivestreamLens({\n    video_id: livePara.videoId,\n    video_type: dronePara.lensSelected\n  }).then(res => {\n    if (res.code === 0) {\n      message.success('Switching live camera successfully.')\n    }\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n</style>\n"
  },
  {
    "path": "src/components/livestream-others.vue",
    "content": "<template>\n  <div class=\"flex-column flex-justify-start flex-align-center\">\n    <video\n      :style=\"{ width: '720px', height: '480px' }\"\n      id=\"video-webrtc\"\n      ref=\"videowebrtc\"\n      controls\n      autoplay\n      class=\"mt20\"\n    ></video>\n    <p class=\"fz24\">Live streaming source selection</p>\n\n    <div class=\"flex-row flex-justify-center flex-align-center mt10\">\n      <template v-if=\"liveState && isDockLive\">\n        <span class=\"mr10\">Lens:</span>\n        <a-radio-group v-model:value=\"lensSelected\" button-style=\"solid\">\n          <a-radio-button v-for=\"lens in lensList\" :key=\"lens\" :value=\"lens\">{{lens}}</a-radio-button>\n        </a-radio-group>\n      </template>\n      <template v-else>\n      <a-select\n        style=\"width: 150px\"\n        placeholder=\"Select Live Type\"\n        @select=\"onLiveTypeSelect\"\n        v-model:value=\"livetypeSelected\"\n      >\n        <a-select-option\n          v-for=\"item in liveTypeList\"\n          :key=\"item.label\"\n          :value=\"item.value\"\n        >\n          {{ item.label }}\n        </a-select-option>\n      </a-select>\n      <a-select\n        class=\"ml10\"\n        style=\"width:150px\"\n        placeholder=\"Select Drone\"\n        v-model:value=\"droneSelected\"\n      >\n        <a-select-option\n          v-for=\"item in droneList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          @click=\"onDroneSelect(item)\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select>\n      <a-select\n        class=\"ml10\"\n        style=\"width:150px\"\n        placeholder=\"Select Camera\"\n        v-model:value=\"cameraSelected\"\n      >\n        <a-select-option\n          v-for=\"item in cameraList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          @click=\"onCameraSelect(item)\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select>\n      <!-- <a-select\n        class=\"ml10\"\n        style=\"width:150px\"\n        placeholder=\"Select Lens\"\n        v-model:value=\"videoSelected\"\n      >\n        <a-select-option\n          v-for=\"item in videoList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          @click=\"onVideoSelect(item)\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select> -->\n      </template>\n      <a-select\n        class=\"ml10\"\n        style=\"width:150px\"\n        placeholder=\"Select Clarity\"\n        @select=\"onClaritySelect\"\n        v-model:value=\"claritySelected\"\n      >\n        <a-select-option\n          v-for=\"item in clarityList\"\n          :key=\"item.value\"\n          :value=\"item.value\"\n          >{{ item.label }}</a-select-option\n        >\n      </a-select>\n    </div>\n    <div class=\"mt20\">\n      <p class=\"fz10\" v-if=\"livetypeSelected == 2\">\n        Please use VLC media player to play the RTSP livestream !!!\n      </p>\n      <p class=\"fz10\" v-if=\"livetypeSelected == 2\">\n        RTSP Parameter:{{ rtspData }}\n      </p>\n    </div>\n    <div class=\"mt10 flex-row flex-justify-center flex-align-center\">\n      <a-button v-if=\"liveState && isDockLive\" type=\"primary\" large @click=\"onSwitch\">Switch Lens</a-button>\n      <a-button v-else type=\"primary\" large @click=\"onStart\">Play</a-button>\n      <a-button class=\"ml20\" type=\"primary\" large @click=\"onStop\"\n        >Stop</a-button\n      >\n      <a-button class=\"ml20\" type=\"primary\" large @click=\"onUpdateQuality\"\n        >Update Clarity</a-button\n      >\n      <a-button v-if=\"!liveState || !isDockLive\" class=\"ml20\" type=\"primary\" large @click=\"onRefresh\"\n        >Refresh Live Capacity</a-button\n      >\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue'\nimport { onMounted, reactive, ref } from 'vue'\nimport { CURRENT_CONFIG as config } from '/@/api/http/config'\nimport { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'\nimport { getRoot } from '/@/root'\nimport jswebrtc from '/@/vendors/jswebrtc.min.js'\nimport srs from '/@/vendors/srs.sdk.js'\n\nconst root = getRoot()\n\ninterface SelectOption {\n  value: any,\n  label: string,\n  more?: any\n}\n\nconst liveTypeList: SelectOption[] = [\n  {\n    value: 1,\n    label: 'RTMP'\n  },\n  {\n    value: 2,\n    label: 'RTSP'\n  },\n  {\n    value: 3,\n    label: 'GB28181'\n  },\n  {\n    value: 4,\n    label: 'WEBRTC'\n  }\n]\nconst clarityList: SelectOption[] = [\n  {\n    value: 0,\n    label: 'Adaptive'\n  },\n  {\n    value: 1,\n    label: 'Smooth'\n  },\n  {\n    value: 2,\n    label: 'Standard'\n  },\n  {\n    value: 3,\n    label: 'HD'\n  },\n  {\n    value: 4,\n    label: 'Super Clear'\n  }\n]\n\nconst videowebrtc = ref(null)\nconst livestreamSource = ref()\nconst droneList = ref()\nconst cameraList = ref()\nconst videoList = ref()\nconst droneSelected = ref()\nconst cameraSelected = ref()\nconst videoSelected = ref()\nconst claritySelected = ref()\nconst videoId = ref()\nconst liveState = ref<boolean>(false)\nconst livetypeSelected = ref()\nconst rtspData = ref()\nconst lensList = ref<string[]>([])\nconst lensSelected = ref<String>()\nconst isDockLive = ref(false)\nconst nonSwitchable = 'normal'\nlet webrtc: any = null\n\nconst onRefresh = async () => {\n  droneList.value = []\n  cameraList.value = []\n  videoList.value = []\n  droneSelected.value = null\n  cameraSelected.value = null\n  videoSelected.value = null\n  await getLiveCapacity({})\n    .then(res => {\n      console.log(res)\n      if (res.code === 0) {\n        if (res.data === null) {\n          console.warn('warning: get live capacity is null!!!')\n          return\n        }\n        const resData: Array<[]> = res.data\n        console.log('live_capacity:', resData)\n        livestreamSource.value = resData\n\n        const temp: Array<SelectOption> = []\n        if (livestreamSource.value) {\n          livestreamSource.value.forEach((ele: any) => {\n            temp.push({ label: ele.name + '-' + ele.sn, value: ele.sn, more: ele.cameras_list })\n          })\n          droneList.value = temp\n        }\n      }\n    })\n    .catch(error => {\n      message.error(error)\n      console.error(error)\n    })\n}\n\nonMounted(() => {\n  onRefresh()\n})\nconst onStart = async () => {\n  console.log(\n    'Param:',\n    livetypeSelected.value,\n    droneSelected.value,\n    cameraSelected.value,\n    videoSelected.value,\n    claritySelected.value\n  )\n  const timestamp = new Date().getTime().toString()\n  if (\n    livetypeSelected.value == null ||\n    droneSelected.value == null ||\n    cameraSelected.value == null ||\n    claritySelected.value == null\n  ) {\n    message.warn('waring: not select live para!!!')\n    return\n  }\n  videoId.value =\n    droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0')\n\n  let liveURL = ''\n  switch (livetypeSelected.value) {\n    case 1: {\n      // RTMP\n      liveURL = config.rtmpURL + timestamp\n      break\n    }\n    case 2: {\n      // RTSP\n      liveURL = `userName=${config.rtspUserName}&password=${config.rtspPassword}&port=${config.rtspPort}`\n      break\n    }\n    case 3: {\n      liveURL = `serverIP=${config.gbServerIp}&serverPort=${config.gbServerPort}&serverID=${config.gbServerId}&agentID=${config.gbAgentId}&agentPassword=${config.gbPassword}&localPort=${config.gbAgentPort}&channel=${config.gbAgentChannel}`\n      break\n    }\n    case 4: {\n      break\n    }\n    default:\n      console.warn('warning: live type is not correct!!!')\n      break\n  }\n  await startLivestream({\n    url: liveURL,\n    video_id: videoId.value,\n    url_type: livetypeSelected.value,\n    video_quality: claritySelected.value\n  })\n    .then(res => {\n      if (res.code !== 0) {\n        return\n      }\n      if (livetypeSelected.value === 3) {\n        const url = res.data.url\n        const videoElement = videowebrtc.value\n        // gb28181,it will fail if not wait.\n        message.loading({\n          content: 'Loding...',\n          duration: 4,\n          onClose () {\n            const player = new jswebrtc.Player(url, {\n              video: videoElement,\n              autoplay: true,\n              onPlay: (obj: any) => {\n                console.log('start play livestream')\n              }\n            })\n          }\n        })\n      } else if (livetypeSelected.value === 2) {\n        console.log(res)\n        rtspData.value = 'url:' + res.data.url\n      } else if (livetypeSelected.value === 1) {\n        const url = res.data.url\n        const videoElement = videowebrtc.value\n        console.log('start live:', url)\n        console.log(videoElement)\n        const player = new jswebrtc.Player(url, {\n          video: videoElement,\n          autoplay: true,\n          onPlay: (obj: any) => {\n            console.log('start play livestream')\n          }\n        })\n      } else if (livetypeSelected.value === 4) {\n        const videoElement = videowebrtc.value as unknown as HTMLMediaElement\n        videoElement.muted = true\n        playWebrtc(videoElement, res.data.url)\n      }\n      liveState.value = true\n    })\n    .catch(err => {\n      console.error(err)\n    })\n}\nconst onStop = () => {\n  videoId.value =\n    droneSelected.value + '/' + cameraSelected.value + '/' + (videoSelected.value || nonSwitchable + '-0')\n\n  stopLivestream({\n    video_id: videoId.value\n  }).then(res => {\n    if (res.code === 0) {\n      message.success(res.message)\n      liveState.value = false\n      lensSelected.value = undefined\n      console.log('stop play livestream')\n    }\n  })\n}\n\nconst onUpdateQuality = () => {\n  if (!liveState.value) {\n    message.info('Please turn on the livestream first.')\n    return\n  }\n  setLivestreamQuality({\n    video_id: videoId.value,\n    video_quality: claritySelected.value\n  }).then(res => {\n    if (res.code === 0) {\n      message.success('Set the clarity to ' + clarityList[claritySelected.value].label)\n    }\n  })\n}\n\nconst onLiveTypeSelect = (val: any) => {\n  livetypeSelected.value = val\n}\nconst onDroneSelect = (val: SelectOption) => {\n  droneSelected.value = val.value\n  const temp: Array<SelectOption> = []\n  cameraList.value = []\n  cameraSelected.value = undefined\n  videoSelected.value = undefined\n  videoList.value = []\n  lensList.value = []\n  if (!val.more) {\n    return\n  }\n  val.more.forEach((ele: any) => {\n    temp.push({ label: ele.name, value: ele.index, more: ele.videos_list })\n  })\n  cameraList.value = temp\n}\nconst onCameraSelect = (val: SelectOption) => {\n  cameraSelected.value = val.value\n  const result: Array<SelectOption> = []\n  videoSelected.value = undefined\n  videoList.value = []\n  lensList.value = []\n  if (!val.more) {\n    return\n  }\n\n  val.more.forEach((ele: any) => {\n    result.push({ label: ele.type, value: ele.index, more: ele.switch_video_types })\n  })\n  videoList.value = result\n  if (videoList.value.length === 0) {\n    return\n  }\n  const firstVideo: SelectOption = videoList.value[0]\n  videoSelected.value = firstVideo.value\n  lensList.value = firstVideo.more\n  lensSelected.value = firstVideo.label\n  isDockLive.value = lensList.value?.length > 0\n}\nconst onVideoSelect = (val: SelectOption) => {\n  videoSelected.value = val.value\n  lensList.value = val.more\n  lensSelected.value = val.label\n}\nconst onClaritySelect = (val: any) => {\n  claritySelected.value = val\n}\nconst onSwitch = () => {\n  if (lensSelected.value === undefined || lensSelected.value === nonSwitchable) {\n    message.info('The ' + nonSwitchable + ' lens cannot be switched, please select the lens to be switched.', 8)\n    return\n  }\n  changeLivestreamLens({\n    video_id: videoId.value,\n    video_type: lensSelected.value\n  }).then(res => {\n    if (res.code === 0) {\n      message.success('Switching live camera successfully.')\n    }\n  })\n}\nconst playWebrtc = (videoElement: HTMLMediaElement, url: string) => {\n  if (webrtc) {\n    webrtc.close()\n  }\n  webrtc = new srs.SrsRtcWhipWhepAsync()\n  videoElement.srcObject = webrtc.stream\n  webrtc.play(url).then(function (session: any) {\n    console.info(session)\n  }).catch(function (reason: any) {\n    webrtc.close()\n    console.error(reason)\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n</style>\n"
  },
  {
    "path": "src/components/svgIcon.vue",
    "content": "<template>\n  <svg :class=\"svgClass\" :aria-hidden=\"true\" :style=\"{color: color, width:computedWidth, height:computedWidth}\">\n    <use :xlink:href=\"iconName\" :fill=\"color\"/>\n  </svg>\n</template>\n\n<script setup>\nimport { defineProps, computed } from 'vue'\nconst props = defineProps({\n  name: {\n    type: String,\n    required: true\n  },\n  color: {\n    type: String,\n    default: ''\n  },\n  size: {\n    type: Number,\n  },\n})\nconst iconName = computed(() => `#icon-${props.name}`)\nconst svgClass = computed(() => {\n  console.log(props.name, 'props.name')\n  if (props.name) {\n    return `svg-icon icon-${props.name}`\n  }\n  return 'svg-icon'\n})\nconst computedWidth = computed(() => {\n  const result = props.width || props.size\n  return result ? result + 'px' : '1em'\n})\n</script>\n<style lang='scss'>\n.svg-icon {\n  width: 1em;\n  height: 1em;\n  fill: currentColor;\n  vertical-align: middle;\n}\n</style>\n"
  },
  {
    "path": "src/components/task/CreatePlan.vue",
    "content": "<template>\n  <div class=\"create-plan-wrapper\">\n    <div class=\"header\">\n      Create Plan\n    </div>\n    <div class=\"content\">\n      <a-form ref=\"valueRef\" layout=\"horizontal\" :hideRequiredMark=\"true\" :rules=\"rules\" :model=\"planBody\" labelAlign=\"left\">\n        <a-form-item label=\"Plan Name\" name=\"name\" :labelCol=\"{span: 23}\">\n          <a-input style=\"background: black;\"  placeholder=\"Please enter plan name\" v-model:value=\"planBody.name\"/>\n        </a-form-item>\n        <!-- 航线 -->\n        <a-form-item label=\"Flight Route\" :wrapperCol=\"{offset: 7}\" name=\"file_id\">\n          <router-link\n            :to=\"{name: 'select-plan'}\"\n            @click=\"selectRoute\"\n          >\n          Select Route\n          </router-link>\n        </a-form-item>\n        <a-form-item v-if=\"planBody.file_id\" style=\"margin-top: -15px;\">\n          <div class=\"wayline-panel\" style=\"padding-top: 5px;\">\n            <div class=\"title\">\n              <a-tooltip :title=\"wayline.name\">\n                <div class=\"pr10\" style=\"width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ wayline.name }}</div>\n              </a-tooltip>\n              <div class=\"ml10\"><UserOutlined /></div>\n              <a-tooltip :title=\"wayline.user_name\">\n                <div class=\"ml5 pr10\" style=\"width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ wayline.user_name }}</div>\n              </a-tooltip>\n            </div>\n            <div class=\"ml10 mt5\" style=\"color: hsla(0,0%,100%,0.65);\">\n              <span><RocketOutlined /></span>\n              <span class=\"ml5\">{{ DEVICE_NAME[wayline.drone_model_key] }}</span>\n              <span class=\"ml10\"><CameraFilled style=\"border-top: 1px solid; padding-top: -3px;\" /></span>\n              <span class=\"ml5\" v-for=\"payload in wayline.payload_model_keys\" :key=\"payload.id\">\n                {{ DEVICE_NAME[payload] }}\n              </span>\n            </div>\n            <div class=\"mt5 ml10\" style=\"color: hsla(0,0%,100%,0.35);\">\n              <span class=\"mr10\">Update at {{ new Date(wayline.update_time).toLocaleString() }}</span>\n            </div>\n          </div>\n        </a-form-item>\n        <!-- 设备 -->\n        <a-form-item label=\"Device\" :wrapperCol=\"{offset: 10}\" v-model:value=\"planBody.dock_sn\" name=\"dock_sn\">\n          <router-link\n            :to=\"{name: 'select-plan'}\"\n            @click=\"selectDevice\"\n          >Select Device</router-link>\n        </a-form-item>\n        <a-form-item v-if=\"planBody.dock_sn\" style=\"margin-top: -15px;\">\n          <div class=\"panel\" style=\"padding-top: 5px;\">\n            <div class=\"title\">\n              <a-tooltip :title=\"dock.nickname\">\n                <div class=\"pr10\" style=\"width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ dock.nickname }}</div>\n              </a-tooltip>\n            </div>\n            <div class=\"ml10 mt5\" style=\"color: hsla(0,0%,100%,0.65);\">\n              <span><RocketOutlined /></span>\n              <span class=\"ml5\">{{ dock.children?.nickname ?? 'No drone' }}</span>\n            </div>\n          </div>\n        </a-form-item>\n        <!-- 任务类型 -->\n        <a-form-item label=\"Plan Timer\" class=\"plan-timer-form-item\">\n          <div style=\"white-space: nowrap;\">\n            <a-radio-group v-model:value=\"planBody.task_type\" button-style=\"solid\">\n              <a-radio-button v-for=\"type in TaskTypeOptions\" :value=\"type.value\" :key=\"type.value\">{{ type.label }}</a-radio-button>\n            </a-radio-group>\n          </div>\n        </a-form-item>\n        <!-- execute date -->\n        <a-form-item label=\"Date\" v-if=\"planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition\" name=\"select_execute_date\" :labelCol=\"{span: 23}\">\n          <a-range-picker\n            v-model:value=\"planBody.select_execute_date\"\n            :disabledDate=\"(current: Moment) => current < moment().subtract(1, 'days')\"\n            format=\"YYYY-MM-DD\"\n            :placeholder=\"['Start Time', 'End Time']\"\n            style=\"width: 100%;\"\n          />\n        </a-form-item>\n        <!-- execute time -->\n        <a-form-item label=\"Time\" v-if=\"planBody.task_type === TaskType.Timed || planBody.task_type === TaskType.Condition\"\n          name=\"select_execute_time\" ref=\"select_execute_time\" :labelCol=\"{span: 23}\" :autoLink=\"false\">\n          <div class=\"mb10 flex-row flex-align-center flex-justify-around\" v-for=\"n in planBody.select_time_number\" :key=\"n\">\n            <a-time-picker\n              v-model:value=\"planBody.select_time[n - 1][0]\"\n              format=\"HH:mm:ss\"\n              show-time\n              placeholder=\"Start Time\"\n              :style=\"planBody.task_type === TaskType.Condition ? 'width: 40%' : 'width: 82%'\"\n              @change=\"() => $refs.select_execute_time.onFieldChange()\"\n            />\n            <template v-if=\"planBody.task_type === TaskType.Condition\">\n              <div><span style=\"color: white;\">-</span></div>\n              <a-time-picker\n                v-model:value=\"planBody.select_time[n - 1][1]\"\n                format=\"HH:mm:ss\"\n                show-time\n                placeholder=\"End Time\"\n                style=\"width: 40%;\"\n              />\n            </template>\n            <div class=\"ml5\" style=\"font-size:18px\">\n              <PlusCircleOutlined class=\"mr5\" style=\"color: #1890ff\" @click=\"addTime\"/>\n              <MinusCircleOutlined :style=\"planBody.select_time_number === 1 ? 'color: gray' : 'color: red;'\" @click=\"removeTime\"/>\n            </div>\n          </div>\n        </a-form-item>\n        <template v-if=\"planBody.task_type === TaskType.Condition\">\n          <!-- battery capacity -->\n          <a-form-item label=\"Start task when battery level reaches\" :labelCol=\"{span: 23}\" name=\"min_battery_capacity\">\n            <a-input-number class=\"width-100\" v-model:value=\"planBody.min_battery_capacity\" :min=\"50\" :max=\"100\"\n            :formatter=\"(value: number) => `${value}%`\" :parser=\"(value: string) => value.replace('%', '')\">\n            </a-input-number>\n          </a-form-item>\n          <!-- storage capacity -->\n          <a-form-item label=\"Start task when storage level reaches (MB)\" :labelCol=\"{span: 23}\" name=\"storage_capacity\">\n            <a-input-number v-model:value=\"planBody.min_storage_capacity\" class=\"width-100\">\n            </a-input-number>\n          </a-form-item>\n        </template>\n        <!-- RTH Altitude Relative to Dock -->\n        <a-form-item label=\"RTH Altitude Relative to Dock (m)\" :labelCol=\"{span: 23}\" name=\"rth_altitude\">\n          <a-input-number v-model:value=\"planBody.rth_altitude\" :min=\"20\" :max=\"1500\" class=\"width-100\" required>\n          </a-input-number>\n        </a-form-item>\n        <!-- Lost Action -->\n        <a-form-item label=\"Lost Action\" :labelCol=\"{span: 23}\" name=\"out_of_control_action\">\n          <div style=\"white-space: nowrap;\">\n            <a-radio-group v-model:value=\"planBody.out_of_control_action\" button-style=\"solid\">\n              <a-radio-button v-for=\"action in OutOfControlActionOptions\" :value=\"action.value\" :key=\"action.value\">\n                {{ action.label }}\n              </a-radio-button>\n            </a-radio-group>\n          </div>\n        </a-form-item>\n        <a-form-item class=\"width-100\" style=\"margin-bottom: 40px;\">\n          <div class=\"footer\">\n            <a-button class=\"mr10\" style=\"background: #3c3c3c;\" @click=\"closePlan\">Cancel\n            </a-button>\n            <a-button type=\"primary\" @click=\"onSubmit\" :disabled=\"disabled\">OK\n            </a-button>\n          </div>\n        </a-form-item>\n      </a-form>\n    </div>\n  </div>\n  <div v-if=\"drawerVisible\" style=\"position: absolute; left: 335px; width: 280px; height: 100vh; float: right; top: 0; z-index: 1000; color: white; background: #282828;\">\n    <div>\n      <router-view :name=\"routeName\"/>\n    </div>\n    <div style=\"position: absolute; top: 15px; right: 10px;\">\n      <a style=\"color: white;\" @click=\"closePanel\"><CloseOutlined /></a>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef } from 'vue'\nimport { CloseOutlined, RocketOutlined, CameraFilled, UserOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons-vue'\nimport { ELocalStorageKey, ERouterName } from '/@/types'\nimport { useMyStore } from '/@/store'\nimport { WaylineType, WaylineFile } from '/@/types/wayline'\nimport { Device, DEVICE_NAME } from '/@/types/device'\nimport { createPlan, CreatePlan } from '/@/api/wayline'\nimport { getRoot } from '/@/root'\nimport { TaskType, OutOfControlActionOptions, OutOfControlAction, TaskTypeOptions } from '/@/types/task'\nimport moment, { Moment } from 'moment'\nimport { RuleObject } from 'ant-design-vue/es/form/interface'\n\nconst root = getRoot()\nconst store = useMyStore()\n\nconst workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\n\nconst wayline = computed<WaylineFile>(() => {\n  return store.state.waylineInfo\n})\n\nconst dock = computed<Device>(() => {\n  return store.state.dockInfo\n})\n\nconst disabled = ref(false)\n\nconst routeName = ref('')\nconst planBody = reactive({\n  name: '',\n  file_id: computed(() => store.state?.waylineInfo.id),\n  dock_sn: computed(() => store.state?.dockInfo.device_sn),\n  task_type: TaskType.Immediate,\n  select_execute_date: [moment(), moment()] as Moment[],\n  select_time_number: 1,\n  select_time: [[]] as Moment[][],\n  rth_altitude: '',\n  out_of_control_action: OutOfControlAction.ReturnToHome,\n  min_battery_capacity: 90 as number,\n  min_storage_capacity: undefined as number | undefined,\n})\n\nconst drawerVisible = ref(false)\nconst valueRef = ref()\nconst rules = {\n  name: [\n    { required: true, message: 'Please enter plan name.' },\n    { max: 20, message: 'Length should be 1 to 20' }\n  ],\n  file_id: [{ required: true, message: 'Select Route' }],\n  dock_sn: [{ required: true, message: 'Select Device' }],\n  select_execute_time: [{\n    validator: async (rule: RuleObject, value: Moment[]) => {\n      validEndTime()\n      validStartTime()\n      if (planBody.select_time.length < planBody.select_time_number) {\n        throw new Error('Select time')\n      }\n      validOverlapped()\n    }\n  }],\n  select_execute_date: [{ required: true, message: 'Select date' }],\n  rth_altitude: [\n    {\n      validator: async (rule: RuleObject, value: string) => {\n        if (!/^[0-9]{1,}$/.test(value)) {\n          throw new Error('RTH Altitude Require number')\n        }\n      },\n    }\n  ],\n  min_battery_capacity: [\n    {\n      validator: async (rule: RuleObject, value: any) => {\n        if (TaskType.Condition === planBody.task_type && !value) {\n          throw new Error('Please enter battery capacity')\n        }\n      },\n    }\n  ],\n  out_of_control_action: [{ required: true, message: 'Select Lost Action' }],\n}\n\nfunction validStartTime (): Error | void {\n  for (let i = 0; i < planBody.select_time.length; i++) {\n    if (!planBody.select_time[i][0]) {\n      throw new Error('Select start time')\n    }\n  }\n}\nfunction validEndTime (): Error | void {\n  if (TaskType.Condition !== planBody.task_type) return\n  for (let i = 0; i < planBody.select_time.length; i++) {\n    if (!planBody.select_time[i][1]) {\n      throw new Error('Select end time')\n    }\n    if (planBody.select_time[i][0] && planBody.select_time[i][1].isSameOrBefore(planBody.select_time[i][0])) {\n      throw new Error('End time should be later than start time')\n    }\n  }\n}\nfunction validOverlapped (): Error | void {\n  if (TaskType.Condition !== planBody.task_type) return\n  const arr = planBody.select_time.slice()\n  arr.sort((a, b) => a[0].unix() - b[0].unix())\n  arr.forEach((v, i, arr) => {\n    if (i > 0 && v[0] < arr[i - 1][1]) {\n      throw new Error('Overlapping time periods.')\n    }\n  })\n}\n\nfunction onSubmit () {\n  console.info(dock, '12131231')\n  valueRef.value.validate().then(() => {\n    disabled.value = true\n    const createPlanBody = { ...planBody } as unknown as CreatePlan\n    if (planBody.select_execute_date.length === 2) {\n      createPlanBody.task_days = []\n      for (let i = planBody.select_execute_date[0]; i.isSameOrBefore(planBody.select_execute_date[1]); i.add(1, 'days')) {\n        createPlanBody.task_days.push(i.unix())\n      }\n    }\n    createPlanBody.task_periods = []\n    if (TaskType.Immediate !== planBody.task_type) {\n      for (let i = 0; i < planBody.select_time.length; i++) {\n        const result = []\n        result.push(planBody.select_time[i][0].unix())\n        if (TaskType.Condition === planBody.task_type) {\n          result.push(planBody.select_time[i][1].unix())\n        }\n        createPlanBody.task_periods.push(result)\n      }\n    }\n    createPlanBody.rth_altitude = Number(createPlanBody.rth_altitude)\n    if (wayline.value && wayline.value.template_types && wayline.value.template_types.length > 0) {\n      createPlanBody.wayline_type = wayline.value.template_types[0]\n    }\n    createPlan(workspaceId, createPlanBody)\n      .then(res => {\n        disabled.value = false\n      }).finally(() => {\n        closePlan()\n      })\n  }).catch((e: any) => {\n    console.log('validate err', e)\n  })\n}\n\nfunction closePlan () {\n  root.$router.push('/' + ERouterName.TASK)\n}\n\nfunction closePanel () {\n  drawerVisible.value = false\n  routeName.value = ''\n}\n\nfunction selectRoute () {\n  drawerVisible.value = true\n  routeName.value = 'WaylinePanel'\n}\n\nfunction selectDevice () {\n  drawerVisible.value = true\n  routeName.value = 'DockPanel'\n}\n\nfunction addTime () {\n  valueRef.value.validateFields(['select_execute_time']).then(() => {\n    planBody.select_time_number++\n    planBody.select_time.push([])\n  })\n}\nfunction removeTime () {\n  if (planBody.select_time_number === 1) return\n  planBody.select_time_number--\n  planBody.select_time.splice(planBody.select_time_number)\n}\n</script>\n\n<style lang=\"scss\">\n.create-plan-wrapper {\n  background-color: #232323;\n  color: fff;\n  padding-bottom: 0;\n  height: 100vh;\n  display: flex;\n  flex-direction: column;\n  width: 285px;\n\n  .header {\n    height: 52px;\n    border-bottom: 1px solid #4f4f4f;\n    font-weight: 700;\n    font-size: 16px;\n    padding-left: 10px;\n    display: flex;\n    align-items: center;\n  }\n\n  ::-webkit-scrollbar {\n    display: none;\n  }\n\n  .content {\n    height: calc(100% - 54px);\n    overflow-y: auto;\n\n    form {\n      margin: 10px;\n    }\n\n    form label, input, .ant-input, .ant-calendar-range-picker-separator,\n    .ant-input:hover, .ant-time-picker .anticon, .ant-calendar-picker .anticon {\n      background-color: #232323;\n      color: #fff;\n    }\n\n    .ant-input-suffix {\n      color: #fff;\n    }\n\n    .plan-timer-form-item {\n\n      .ant-radio-button-wrapper{\n        background-color: #232323;\n        color: #fff;\n        width: 33%;\n        text-align: center;\n        &.ant-radio-button-wrapper-checked{\n          background-color: #1890ff;\n        }\n      }\n    }\n  }\n\n  .footer {\n    display: flex;\n    padding:10px 0;\n\n    button {\n      width: 45%;\n      color: #fff ;\n      border: 0;\n    }\n  }\n}\n\n.wayline-panel {\n  background: #3c3c3c;\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: 10px;\n  height: 90px;\n  width: 95%;\n  font-size: 13px;\n  border-radius: 2px;\n  cursor: pointer;\n  .title {\n    display: flex;\n    color: white;\n    flex-direction: row;\n    align-items: center;\n    height: 30px;\n    font-weight: bold;\n    margin: 0px 10px 0 10px;\n  }\n}\n\n.panel {\n  background: #3c3c3c;\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: 10px;\n  height: 70px;\n  width: 95%;\n  font-size: 13px;\n  border-radius: 2px;\n  cursor: pointer;\n  .title {\n    display: flex;\n    color: white;\n    flex-direction: row;\n    align-items: center;\n    height: 30px;\n    font-weight: bold;\n    margin: 0px 10px 0 10px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/task/TaskPanel.vue",
    "content": "<template>\n  <div class=\"header\">Task Plan Library</div>\n  <div class=\"plan-panel-wrapper\">\n    <a-table class=\"plan-table\" :columns=\"columns\" :data-source=\"plansData.data\" row-key=\"job_id\"\n      :pagination=\"paginationProp\" :scroll=\"{ x: '100%', y: 600 }\" @change=\"refreshData\">\n      <!-- 执行时间 -->\n      <template #duration=\"{ record }\">\n        <div class=\"flex-row\" style=\"white-space: pre-wrap\">\n          <div>\n            <div>{{ formatTaskTime(record.begin_time) }}</div>\n            <div>{{ formatTaskTime(record.end_time) }}</div>\n          </div>\n          <div class=\"ml10\">\n            <div>{{ formatTaskTime(record.execute_time) }}</div>\n            <div>{{ formatTaskTime(record.completed_time) }}</div>\n          </div>\n        </div>\n      </template>\n      <!-- 状态 -->\n      <template #status=\"{ record }\">\n        <div>\n          <div class=\"flex-display flex-align-center\">\n            <span class=\"circle-icon\" :style=\"{backgroundColor: formatTaskStatus(record).color}\"></span>\n            {{ formatTaskStatus(record).text }}\n            <a-tooltip v-if=\"!!record.code\" placement=\"bottom\" arrow-point-at-center >\n              <template #title>\n              <div>{{ getCodeMessage(record.code) }}</div>\n              </template>\n              <exclamation-circle-outlined class=\"ml5\" :style=\"{color: commonColor.WARN, fontSize: '16px' }\"/>\n            </a-tooltip>\n          </div>\n          <div v-if=\"record.status === TaskStatus.Carrying\">\n            <a-progress :percent=\"record.progress || 0\" />\n          </div>\n        </div>\n      </template>\n      <!-- 任务类型 -->\n      <template #taskType=\"{ record }\">\n        <div>{{ formatTaskType(record) }}</div>\n      </template>\n      <!-- 失控动作 -->\n      <template #lostAction=\"{ record }\">\n        <div>{{ formatLostAction(record) }}</div>\n      </template>\n     <!-- 媒体上传状态 -->\n      <template #media_upload=\"{ record }\">\n        <div>\n          <div class=\"flex-display flex-align-center\">\n            <span class=\"circle-icon\" :style=\"{backgroundColor: formatMediaTaskStatus(record).color}\"></span>\n            {{ formatMediaTaskStatus(record).text }}\n          </div>\n          <div class=\"pl15\">\n            {{ formatMediaTaskStatus(record).number }}\n            <a-tooltip v-if=\"formatMediaTaskStatus(record).status === MediaStatus.ToUpload\" placement=\"bottom\" arrow-point-at-center >\n              <template #title>\n              <div>Upload now</div>\n              </template>\n              <UploadOutlined class=\"ml5\" :style=\"{color: commonColor.BLUE, fontSize: '16px' }\"  @click=\"onUploadMediaFileNow(record.job_id)\"/>\n            </a-tooltip>\n          </div>\n        </div>\n      </template>\n      <!-- 操作 -->\n      <template #action=\"{ record }\">\n        <div class=\"action-area\">\n          <a-popconfirm\n            v-if=\"record.status === TaskStatus.Wait\"\n            title=\"Are you sure you want to delete flight task?\"\n            ok-text=\"Yes\"\n            cancel-text=\"No\"\n            @confirm=\"onDeleteTask(record.job_id)\"\n          >\n            <a-button type=\"primary\" size=\"small\">Delete</a-button>\n          </a-popconfirm>\n          <a-popconfirm\n            v-if=\"record.status === TaskStatus.Carrying\"\n            title=\"Are you sure you want to suspend?\"\n            ok-text=\"Yes\"\n            cancel-text=\"No\"\n            @confirm=\"onSuspendTask(record.job_id)\"\n          >\n            <a-button type=\"primary\" size=\"small\">Suspend</a-button>\n          </a-popconfirm>\n          <a-popconfirm\n            v-if=\"record.status === TaskStatus.Paused\"\n            title=\"Are you sure you want to resume?\"\n            ok-text=\"Yes\"\n            cancel-text=\"No\"\n            @confirm=\"onResumeTask(record.job_id)\"\n          >\n            <a-button type=\"primary\" size=\"small\">Resume</a-button>\n          </a-popconfirm>\n        </div>\n      </template>\n    </a-table>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { reactive, ref } from '@vue/reactivity'\nimport { message } from 'ant-design-vue'\nimport { TableState } from 'ant-design-vue/lib/table/interface'\nimport { onMounted } from 'vue'\nimport { IPage } from '/@/api/http/type'\nimport { deleteTask, updateTaskStatus, UpdateTaskStatus, getWaylineJobs, Task, uploadMediaFileNow } from '/@/api/wayline'\nimport { useMyStore } from '/@/store'\nimport { ELocalStorageKey } from '/@/types/enums'\nimport { useFormatTask } from './use-format-task'\nimport { TaskStatus, TaskProgressInfo, TaskProgressStatus, TaskProgressWsStatusMap, MediaStatus, MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task'\nimport { useTaskWsEvent } from './use-task-ws-event'\nimport { getErrorMessage } from '/@/utils/error-code/index'\nimport { commonColor } from '/@/utils/color'\nimport { ExclamationCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'\n\nconst store = useMyStore()\nconst workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\n\nconst body: IPage = {\n  page: 1,\n  total: 0,\n  page_size: 50\n}\nconst paginationProp = reactive({\n  pageSizeOptions: ['20', '50', '100'],\n  showQuickJumper: true,\n  showSizeChanger: true,\n  pageSize: 50,\n  current: 1,\n  total: 0\n})\n\nconst columns = [\n  {\n    title: 'Planned/Actual Time',\n    dataIndex: 'duration',\n    width: 200,\n    slots: { customRender: 'duration' },\n  },\n  {\n    title: 'Status',\n    key: 'status',\n    width: 150,\n    slots: { customRender: 'status' }\n  },\n  {\n    title: 'Plan Name',\n    dataIndex: 'job_name',\n    width: 100,\n  },\n  {\n    title: 'Type',\n    dataIndex: 'taskType',\n    width: 100,\n    slots: { customRender: 'taskType' },\n  },\n  {\n    title: 'Flight Route Name',\n    dataIndex: 'file_name',\n    width: 100,\n  },\n  {\n    title: 'Dock Name',\n    dataIndex: 'dock_name',\n    width: 100,\n    ellipsis: true\n  },\n  {\n    title: 'RTH Altitude Relative to Dock (m)',\n    dataIndex: 'rth_altitude',\n    width: 120,\n  },\n  {\n    title: 'Lost Action',\n    dataIndex: 'out_of_control_action',\n    width: 120,\n    slots: { customRender: 'lostAction' },\n  },\n  {\n    title: 'Creator',\n    dataIndex: 'username',\n    width: 120,\n  },\n  {\n    title: 'Media File Upload',\n    key: 'media_upload',\n    width: 160,\n    slots: { customRender: 'media_upload' }\n  },\n  {\n    title: 'Action',\n    width: 120,\n    slots: { customRender: 'action' }\n  }\n]\ntype Pagination = TableState['pagination']\n\nconst plansData = reactive({\n  data: [] as Task[]\n})\n\nconst { formatTaskType, formatTaskTime, formatLostAction, formatTaskStatus, formatMediaTaskStatus } = useFormatTask()\n\n// 设备任务执行进度更新\nfunction onTaskProgressWs (data: TaskProgressInfo) {\n  const { bid, output } = data\n  if (output) {\n    const { status, progress } = output || {}\n    const taskItem = plansData.data.find(task => task.job_id === bid)\n    if (!taskItem) return\n    if (status) {\n      taskItem.status = TaskProgressWsStatusMap[status]\n      // 执行中，更新进度\n      if (status === TaskProgressStatus.Sent || status === TaskProgressStatus.inProgress) {\n        taskItem.progress = progress?.percent || 0\n      } else if ([TaskProgressStatus.Rejected, TaskProgressStatus.Canceled, TaskProgressStatus.Timeout, TaskProgressStatus.Failed, TaskProgressStatus.OK].includes(status)) {\n        getPlans()\n      }\n    }\n  }\n}\n\n// 媒体上传进度更新\nfunction onTaskMediaProgressWs (data: MediaStatusProgressInfo) {\n  const { media_count: mediaCount, uploaded_count: uploadedCount, job_id: jobId } = data\n  if (isNaN(mediaCount) || isNaN(uploadedCount) || !jobId) {\n    return\n  }\n  const taskItem = plansData.data.find(task => task.job_id === jobId)\n  if (!taskItem) return\n  if (mediaCount === uploadedCount) {\n    taskItem.uploading = false\n  } else {\n    taskItem.uploading = true\n  }\n  taskItem.media_count = mediaCount\n  taskItem.uploaded_count = uploadedCount\n}\n\nfunction onoTaskMediaHighestPriorityWS (data: TaskMediaHighestPriorityProgressInfo) {\n  const { pre_job_id: preJobId, job_id: jobId } = data\n  const preTaskItem = plansData.data.find(task => task.job_id === preJobId)\n  const taskItem = plansData.data.find(task => task.job_id === jobId)\n  if (preTaskItem) {\n    preTaskItem.uploading = false\n  }\n  if (taskItem) {\n    taskItem.uploading = true\n  }\n}\n\nfunction getCodeMessage (code: number) {\n  return getErrorMessage(code) + `（code: ${code}）`\n}\n\nuseTaskWsEvent({\n  onTaskProgressWs,\n  onTaskMediaProgressWs,\n  onoTaskMediaHighestPriorityWS,\n})\n\nonMounted(() => {\n  getPlans()\n})\n\nfunction getPlans () {\n  getWaylineJobs(workspaceId, body).then(res => {\n    if (res.code !== 0) {\n      return\n    }\n    plansData.data = res.data.list\n    paginationProp.total = res.data.pagination.total\n    paginationProp.current = res.data.pagination.page\n  })\n}\n\nfunction refreshData (page: Pagination) {\n  body.page = page?.current!\n  body.page_size = page?.pageSize!\n  getPlans()\n}\n\n// 删除任务\nasync function onDeleteTask (jobId: string) {\n  const { code } = await deleteTask(workspaceId, {\n    job_id: jobId\n  })\n  if (code === 0) {\n    message.success('Deleted successfully')\n    getPlans()\n  }\n}\n\n// 挂起任务\nasync function onSuspendTask (jobId: string) {\n  const { code } = await updateTaskStatus(workspaceId, {\n    job_id: jobId,\n    status: UpdateTaskStatus.Suspend\n  })\n  if (code === 0) {\n    message.success('Suspended successfully')\n    getPlans()\n  }\n}\n\n// 解除挂起任务\nasync function onResumeTask (jobId: string) {\n  const { code } = await updateTaskStatus(workspaceId, {\n    job_id: jobId,\n    status: UpdateTaskStatus.Resume\n  })\n  if (code === 0) {\n    message.success('Resumed successfully')\n    getPlans()\n  }\n}\n\n// 立即上传媒体\nasync function onUploadMediaFileNow (jobId: string) {\n  const { code } = await uploadMediaFileNow(workspaceId, jobId)\n  if (code === 0) {\n    message.success('Upload Media File successfully')\n    getPlans()\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.plan-panel-wrapper {\n  width: 100%;\n  padding: 16px;\n  .plan-table {\n    background: #fff;\n    margin-top: 10px;\n  }\n  .action-area {\n\n    &::v-deep {\n      .ant-btn {\n        margin-right: 10px;\n        margin-bottom: 10px;\n      }\n    }\n  }\n\n  .circle-icon {\n    display: inline-block;\n    width: 12px;\n    height: 12px;\n    margin-right: 3px;\n    border-radius: 50%;\n    vertical-align: middle;\n    flex-shrink: 0;\n  }\n}\n.header {\n  width: 100%;\n  height: 60px;\n  background: #fff;\n  padding: 16px;\n  font-size: 20px;\n  font-weight: bold;\n  text-align: start;\n  color: #000;\n}\n</style>\n"
  },
  {
    "path": "src/components/task/use-format-task.ts",
    "content": "import { DEFAULT_PLACEHOLDER } from '/@/utils/constants'\nimport { Task } from '/@/api/wayline'\nimport { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap, MediaStatusMap, MediaStatusColorMap, MediaStatus } from '/@/types/task'\nimport { isNil } from 'lodash'\n\nexport function useFormatTask () {\n  function formatTaskType (task: Task) {\n    return TaskTypeMap[task.task_type] || DEFAULT_PLACEHOLDER\n  }\n\n  function formatTaskTime (time: string) {\n    return time || DEFAULT_PLACEHOLDER\n  }\n\n  function formatLostAction (task: Task) {\n    return OutOfControlActionMap[task.out_of_control_action] || DEFAULT_PLACEHOLDER\n  }\n\n  function formatTaskStatus (task: Task) {\n    const statusObj = {\n      text: '',\n      color: ''\n    }\n    const { status } = task\n    statusObj.text = TaskStatusMap[status]\n    statusObj.color = TaskStatusColor[status]\n    return statusObj\n  }\n\n  function formatMediaTaskStatus (task: Task) {\n    const statusObj = {\n      text: '',\n      color: '',\n      number: '',\n      status: MediaStatus.Empty,\n    }\n    const { media_count, uploaded_count, uploading } = task\n    if (isNil(media_count) || isNaN(media_count)) {\n      return statusObj\n    }\n    const expectedFileCount = media_count || 0\n    const uploadedFileCount = uploaded_count || 0\n    if (media_count === 0) {\n      statusObj.text = MediaStatusMap[MediaStatus.Empty]\n      statusObj.color = MediaStatusColorMap[MediaStatus.Empty]\n    } else if (media_count === uploaded_count) {\n      statusObj.text = MediaStatusMap[MediaStatus.Success]\n      statusObj.color = MediaStatusColorMap[MediaStatus.Success]\n      statusObj.number = `(${uploadedFileCount}/${expectedFileCount})`\n      statusObj.status = MediaStatus.Success\n    } else {\n      if (uploading) {\n        statusObj.text = MediaStatusMap[MediaStatus.Uploading]\n        statusObj.color = MediaStatusColorMap[MediaStatus.Uploading]\n        statusObj.status = MediaStatus.Uploading\n      } else {\n        statusObj.text = MediaStatusMap[MediaStatus.ToUpload]\n        statusObj.color = MediaStatusColorMap[MediaStatus.ToUpload]\n        statusObj.status = MediaStatus.ToUpload\n      }\n      statusObj.number = `(${uploadedFileCount}/${expectedFileCount})`\n    }\n    return statusObj\n  }\n\n  return {\n    formatTaskType,\n    formatTaskTime,\n    formatLostAction,\n    formatTaskStatus,\n    formatMediaTaskStatus,\n  }\n}\n"
  },
  {
    "path": "src/components/task/use-task-ws-event.ts",
    "content": "import EventBus from '/@/event-bus/'\nimport { onMounted, onBeforeUnmount } from 'vue'\nimport { TaskProgressInfo, MediaStatusProgressInfo, TaskMediaHighestPriorityProgressInfo } from '/@/types/task'\nimport { EBizCode } from '/@/types'\n\nexport interface UseTaskWsEventParams {\n  onTaskProgressWs: (data: TaskProgressInfo) => void,\n  onTaskMediaProgressWs: (data: MediaStatusProgressInfo) => void\n  onoTaskMediaHighestPriorityWS: (data: TaskMediaHighestPriorityProgressInfo) => void\n}\n\nexport function useTaskWsEvent (funcs: UseTaskWsEventParams): void {\n  function handleTaskWsEvent (payload: any) {\n    if (!payload) {\n      return\n    }\n\n    switch (payload.biz_code) {\n      case EBizCode.FlightTaskProgress: {\n        funcs?.onTaskProgressWs(payload.data)\n        break\n      }\n      case EBizCode.FlightTaskMediaProgress: {\n        funcs?.onTaskMediaProgressWs(payload.data)\n        break\n      }\n      case EBizCode.FlightTaskMediaHighestPriority: {\n        funcs?.onoTaskMediaHighestPriorityWS(payload.data)\n        break\n      }\n    }\n    // eslint-disable-next-line no-unused-expressions\n    // console.log('payload', payload.data)\n  }\n\n  onMounted(() => {\n    EventBus.on('flightTaskWs', handleTaskWsEvent)\n  })\n\n  onBeforeUnmount(() => {\n    EventBus.off('flightTaskWs', handleTaskWsEvent)\n  })\n}\n"
  },
  {
    "path": "src/components/workspace/DividerLine.vue",
    "content": "<template>\n  <Divider class=\"divider\" />\n</template>\n<script lang=\"ts\" setup>\nimport { Divider } from 'ant-design-vue'\n\n</script>\n\n<style lang=\"scss\" scoped>\n.divider {\n  margin: 10px 0;\n  height: 1px;\n  background-color: #4f4f4f;\n}\n</style>\n"
  },
  {
    "path": "src/components/workspace/Title.vue",
    "content": "<template>\n  <div style=\"height: 40px; line-height: 50px; font-weight: 450;\">\n    <a-row>\n      <a-col :span=\"1\"></a-col>\n      <a-col :span=\"(23 - (extSpan || 0))\">{{ title }}</a-col>\n      <a-col :span=\"extSpan\"><slot /></a-col>\n    </a-row>\n  </div>\n  <DividerLine />\n</template>\n\n<script lang=\"ts\" setup>\nimport { defineProps } from 'vue'\nimport DividerLine from '/@/components/workspace/DividerLine.vue'\n\nconst props = defineProps < {\n  extSpan?: number,\n  title: string,\n} >()\n\n</script>\n"
  },
  {
    "path": "src/constants/index.ts",
    "content": "import { CURRENT_CONFIG } from '/@/api/http/config'\n\nexport const AMapConfig = {\n  key: CURRENT_CONFIG.amapKey,\n  version: '2.0',\n  plugins: [\n    'AMap.Scale',\n    'AMap.ToolBar',\n    'AMap.ControlBar',\n    'AMap.ElasticMarker',\n    'AMap.MapType',\n    'AMap.Geocoder',\n    'AMap.CircleEditor',\n    'AMap.PolygonEditor',\n    'AMap.PolylineEditor',\n    'AMap.PolyEditor',\n    'AMap.RangingTool',\n    'AMap.Weather',\n    'AMap.MouseTool',\n    'AMap.MoveAnimation'\n  ]\n}\n"
  },
  {
    "path": "src/constants/map.ts",
    "content": "\nexport enum MapElementColor {\n    Blue = '#2D8CF0',\n    Green = '#19BE6B',\n    Yellow = '#FFBB00',\n    Red = '#E23C39',\n    Orange = '#B620E0',\n    Default = '#212121'\n  }\nexport const MapElementDefaultColor = MapElementColor.Default\n\nexport enum MapDoodleColor {\n  PinColor = '#2D8CF0',\n  PolylineColor = '#2D8CF0',\n  PolygonColor = '#2D8CF0'\n}\n\nexport enum MapElementEnum {\n  PIN = 0,\n  LINE = 1,\n  POLY = 2\n}\nexport type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off' | 'circle'\n"
  },
  {
    "path": "src/constants/mock-layers.ts",
    "content": "export const mapLayers = {\n  code: 0,\n  message: 'success',\n  data: {\n    list: [{\n      id: 'private_layer',\n      name: 'Private Layer',\n      order: 0,\n      is_distributed: false,\n      type: 1,\n      is_lock: false,\n      create_time: 1634268707424,\n      elements: [{\n        id: 'b2370d29-be65-42b0-9224-4d816e86dc64',\n        name: 'xuejia n. 1',\n        order: 0,\n        status: 1,\n        display: 1,\n        resource: {\n          content: {\n            type: 'Feature',\n            properties: {\n              color: '#2D8CF0'\n            },\n            geometry: {\n              type: 'Polygon',\n              coordinates: [\n                [\n                  [114.156671, 38.468249],\n                  [114.139517, 37.372177],\n                  [115.52899, 37.712212]\n                ]\n              ]\n            }\n          },\n          type: 4,\n          user_name: 'xuejia n.',\n          user_id: '1402914943455727616'\n        },\n        update_time: 1636966336566,\n        create_time: 1636966325700,\n        elevation_load_status: 0,\n        icon: 'area'\n      }, {\n        id: '768e9fcd-121f-47a6-96b9-f1aee27d32f0',\n        name: 'xuejia n. 1',\n        order: 0,\n        status: 1,\n        display: 1,\n        resource: {\n          content: {\n            type: 'Feature',\n            properties: {\n              color: '#2D8CF0'\n            },\n            geometry: {\n              type: 'LineString',\n              coordinates: [\n                [116.263962, 40.234929],\n                [116.503006, 40.237026],\n                [116.335465, 40.155206],\n                [116.541458, 40.12371]\n              ]\n            }\n          },\n          type: 4,\n          user_name: 'xuejia n.',\n          user_id: '1402914943455727616'\n        },\n        update_time: 1636966322636,\n        create_time: 1636966316803,\n        elevation_load_status: 0,\n        icon: 'line'\n      }, {\n        id: '4e741a76-3600-4af5-ace8-d805e7cd31fa',\n        name: 'xuejia n. 2',\n        order: 0,\n        status: 1,\n        display: 1,\n        resource: {\n          content: {\n            type: 'Feature',\n            properties: {\n              color: '#2D8CF0',\n              clampToGround: true\n            },\n            geometry: {\n              type: 'Point',\n              coordinates: [116.098223, 39.976538, 104]\n            }\n          },\n          type: 6,\n          user_name: 'xuejia n.',\n          user_id: '1402914943455727616'\n        },\n        update_time: 1636966305229,\n        create_time: 1636966305229,\n        elevation_load_status: 0,\n        icon: 'pin'\n      }, {\n        id: 'efff2b5d-de22-4d48-8d92-4f53170668f6',\n        name: 'xuejia n. 1',\n        order: 0,\n        status: 1,\n        display: 1,\n        resource: {\n          content: {\n            type: 'Feature',\n            properties: {\n              color: '#19BE6B',\n              clampToGround: true\n            },\n            geometry: {\n              type: 'Point',\n              coordinates: [113.35367028239645, 23.755194000519843, 22]\n            }\n          },\n          type: 6,\n          user_name: 'xuejia n.',\n          user_id: '1402914943455727616'\n        },\n        update_time: 1636966304432,\n        create_time: 1636966299455,\n        elevation_load_status: 0,\n        icon: 'pin'\n      }]\n    }, {\n      id: 'share_layer',\n      name: 'Share Layer',\n      order: 0,\n      is_distributed: true,\n      type: 2,\n      is_lock: false,\n      create_time: 1634268707414,\n      elements: []\n    }]\n  }\n}\n"
  },
  {
    "path": "src/directives/drag-window.ts",
    "content": "import { nextTick, App } from 'vue'\n\nexport default function useDragWindowDirective (app: App): void {\n  app.directive('drag-window', async (el) => {\n    await nextTick()\n\n    const modal = el\n    const header = el.getElementsByClassName('drag-title')[0]\n    let left = 0\n    let top = 0\n    header.style.cursor = 'move'\n    top = top || modal.offsetTop\n\n    header.onpointerdown = (e: { clientX: number; clientY: number; pointerId: number }) => {\n      const startX = e.clientX\n      const startY = e.clientY\n      header.left = header.offsetLeft\n      header.top = header.offsetTop\n      header.setPointerCapture(e.pointerId)\n\n      el.onpointermove = (event: { clientX: number; clientY: number }) => {\n        const endX = event.clientX\n        const endY = event.clientY\n        modal.left = header.left + (endX - startX) + left\n        modal.top = header.top + (endY - startY) + top\n        modal.style.left = modal.left + 'px'\n        modal.style.top = modal.top + 'px'\n      }\n      el.onpointerup = () => {\n        left = modal.left || 0\n        top = modal.top || 0\n        el.onpointermove = null\n        el.onpointerup = null\n        header.releasePointerCapture(e.pointerId)\n      }\n    }\n  })\n}\n"
  },
  {
    "path": "src/directives/index.ts",
    "content": "import { App } from 'vue'\nimport useDragWindowDirective from './drag-window'\n\nexport function useDirectives (app: App): void {\n  useDragWindowDirective(app)\n}\n"
  },
  {
    "path": "src/env.d.ts",
    "content": "// Environment variable definition\n// https://cn.vitejs.dev/guide/env-and-mode.html#env-files\n\ninterface ImportMetaEnv {\n  VITE_APP_ENVIRONMENT: 'DEV' | 'STAG' | 'UAT' | 'PROD',\n  // api gateway\n  VITE_APP_APIGATEWAY_BACKEND_HOST: string\n  // More environment variables...\n}\n"
  },
  {
    "path": "src/event-bus/index.ts",
    "content": "import mitt, { Emitter } from 'mitt'\n\ntype Events = {\n  deviceUpgrade: any; // 设备升级\n  deviceLogUploadProgress: any // 设备日志上传\n  flightTaskWs: any // 机场任务消息\n  droneControlWs: any // 飞行指令信息\n  droneControlMqttInfo: any // drc 链路通知\n  flightAreasDroneLocationWs: any\n  flightAreasSyncProgressWs: any\n  flightAreasUpdateWs: any\n};\n\nconst emitter: Emitter<Events> = mitt<Events>()\n\nexport default emitter\n"
  },
  {
    "path": "src/hooks/use-connect-websocket.ts",
    "content": "import { onMounted, onUnmounted } from 'vue'\nimport ReconnectingWebSocket from 'reconnecting-websocket'\nimport ConnectWebSocket, { MessageHandler } from '/@/websocket'\nimport { getWebsocketUrl } from '/@/websocket/util/config'\n\n/**\n * 接收一个message函数\n * @param messageHandler\n */\nexport function useConnectWebSocket (messageHandler: MessageHandler) {\n  const webSocket = new ConnectWebSocket(getWebsocketUrl())\n\n  onMounted(() => {\n    webSocket?.registerMessageHandler(messageHandler)\n    webSocket?.initSocket()\n  })\n\n  onUnmounted(() => {\n    webSocket?.close()\n  })\n}\n"
  },
  {
    "path": "src/hooks/use-g-map-cover.ts",
    "content": "import { EFlightAreaType } from '../types/flight-area'\nimport pin19be6b from '/@/assets/icons/pin-19be6b.svg'\nimport pin212121 from '/@/assets/icons/pin-212121.svg'\nimport pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg'\nimport pinb620e0 from '/@/assets/icons/pin-b620e0.svg'\nimport pine23c39 from '/@/assets/icons/pin-e23c39.svg'\nimport pineffbb00 from '/@/assets/icons/pin-ffbb00.svg'\nimport { getRoot } from '/@/root'\nimport rootStore from '/@/store'\nimport { GeojsonCoordinate } from '/@/types/map'\n\nexport function useGMapCover () {\n  const root = getRoot()\n  const AMap = root.$aMap\n\n  const normalColor = '#2D8CF0'\n  const store = rootStore\n  const coverMap = store.state.coverMap\n  const flightAreaColorMap = {\n    [EFlightAreaType.DFENCE]: '#19be6b',\n    [EFlightAreaType.NFZ]: '#ff0000',\n  }\n  const disableColor = '#b3b3b3'\n\n  function AddCoverToMap (cover :any) {\n    root.$map.add(cover)\n    coverMap[cover.getExtData().id] = [cover]\n  }\n\n  function getPinIcon (color?:string) {\n    // console.log('color', color)\n    const colorObj: {\n      [key: number| string]: any\n    } = {\n      '2d8cf0': pin2d8cf0,\n      '19be6b': pin19be6b,\n      212121: pin212121,\n      b620e0: pinb620e0,\n      e23c39: pine23c39,\n      ffbb00: pineffbb00,\n    }\n    const iconName = (color?.replaceAll('#', '') || '').toLocaleLowerCase()\n    return new AMap.Icon({\n      // size: new AMap.Size(40, 50),\n      image: colorObj[iconName],\n      // imageOffset: new AMap.Pixel(0, -60),\n      // imageSize: new AMap.Size(40, 50)\n    })\n  }\n\n  function init2DPin (name: string, coordinates:GeojsonCoordinate, color?:string, data?:{}) {\n    const pin = new AMap.Marker({\n      position: new AMap.LngLat(coordinates[0], coordinates[1]),\n      title: name,\n      icon: getPinIcon(color),\n      // strokeColor: color || normalColor,\n      // fillColor: color || normalColor,\n      extData: data\n    })\n    // console.log('coordinates pin', pin)\n    AddCoverToMap(pin)\n  }\n\n  function AddOverlayGroup (overlayGroup) {\n    root.$map.add(overlayGroup)\n    const id = overlayGroup.getExtData().id\n    coverMap[id] = [...(coverMap[id] || []), overlayGroup]\n  }\n  function initPolyline (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) {\n    const path = [] as GeojsonCoordinate[]\n    coordinates.forEach(coordinate => {\n      path.push(new AMap.LngLat(coordinate[0], coordinate[1]))\n    })\n    const polyline = new AMap.Polyline({\n      path: path,\n      strokeColor: color || normalColor,\n      strokeOpacity: 1,\n      strokeWeight: 2,\n      strokeStyle: 'solid',\n      extData: data\n      // draggable: true,\n    })\n    AddOverlayGroup(polyline)\n  }\n\n  function initPolygon (name: string, coordinates:GeojsonCoordinate[][], color?:string, data?:{}) {\n    const path = [] as GeojsonCoordinate[]\n    coordinates[0].forEach(coordinate => {\n      path.push(new AMap.LngLat(coordinate[0], coordinate[1]))\n    })\n    // console.log('Polygon', path)\n    const Polygon = new AMap.Polygon({\n      path: path,\n      strokeOpacity: 1,\n      strokeWeight: 2,\n      fillColor: color || normalColor,\n      fillOpacity: 0.4,\n      // draggable: true,\n      strokeColor: color || normalColor,\n      extData: data\n    })\n    AddOverlayGroup(Polygon)\n  }\n\n  function removeCoverFromMap (id:string) {\n    coverMap[id].forEach(cover => root.$map.remove(cover))\n    coverMap[id] = []\n  }\n\n  function getElementFromMap (id:string): any[] {\n    return coverMap[id]\n  }\n\n  function updatePinElement (id:string, name: string, coordinates:GeojsonCoordinate, color?:string) {\n    const elements = getElementFromMap(id)\n    if (elements && elements.length > 0) {\n      const element = elements[0]\n      const icon = getPinIcon(color)\n      element.setPosition(new AMap.LngLat(coordinates[0], coordinates[1]))\n      element.setIcon(icon)\n      element.setTitle(name)\n    } else {\n      // console.log('into init PIN')\n      init2DPin(name, coordinates, color, {\n        id: id,\n        name: name\n      })\n    }\n  }\n\n  function updatePolylineElement (id:string, name: string, coordinates:GeojsonCoordinate[], color?:string) {\n    const elements = getElementFromMap(id)\n    if (elements && elements.length > 0) {\n      const element = elements[0]\n      const options = element.getOptions()\n      options.strokeColor = color || normalColor\n      element.setOptions(options)\n    } else {\n      initPolyline(name, coordinates, color, {\n        id: id,\n        name: name\n      })\n    }\n  }\n\n  function updatePolygonElement (id:string, name: string, coordinates:GeojsonCoordinate[][], color?:string) {\n    const elements = getElementFromMap(id)\n    if (elements && elements.length > 0) {\n      const element = elements[0]\n      const options = element.getOptions()\n      options.fillColor = color || normalColor\n      options.strokeColor = color || normalColor\n      element.setOptions(options)\n    } else {\n      initPolygon(name, coordinates, color, {\n        id: id,\n        name: name\n      })\n    }\n  }\n\n  function initTextInfo (content: string, coordinates: GeojsonCoordinate, id: string) {\n    const info = new AMap.Text({\n      text: content,\n      position: new AMap.LngLat(coordinates[0], coordinates[1]),\n      extData: { id: id, type: 'text' },\n      anchor: 'top-center',\n      style: {\n        background: 'none',\n        borderStyle: 'none',\n        fontSize: '16px',\n      },\n    })\n    AddOverlayGroup(info)\n  }\n\n  function initFlightAreaCircle (name: string, radius: number, position: GeojsonCoordinate, data: { id: string, type: EFlightAreaType, enable: boolean }) {\n    const circle = new AMap.Circle({\n      strokeColor: data.enable ? flightAreaColorMap[data.type] : disableColor,\n      strokeOpacity: 1,\n      strokeWeight: 6,\n      extData: data,\n      strokeStyle: 'dashed',\n      strokeDasharray: EFlightAreaType.NFZ === data.type ? [10, 2] : [10, 1, 2],\n      fillColor: flightAreaColorMap[data.type],\n      fillOpacity: EFlightAreaType.NFZ === data.type && data.enable ? 0.3 : 0,\n      radius: radius,\n      center: new AMap.LngLat(position[0], position[1]),\n    })\n    AddOverlayGroup(circle)\n    initTextInfo(name, position, data.id)\n  }\n\n  function updateFlightAreaCircle (id: string, name: string, radius: number, position: GeojsonCoordinate, enable: boolean, type: EFlightAreaType) {\n    const elements = getElementFromMap(id)\n    if (elements && elements.length > 0) {\n      let textIndex = elements.findIndex(ele => ele.getExtData()?.type === 'text')\n      if (textIndex === -1) {\n        textIndex = 1\n        initTextInfo(name, position, id)\n      } else {\n        const text = elements[textIndex]\n        text.setText(name)\n        text.setPosition(position)\n      }\n      const element = elements[textIndex ^ 1]\n      const options = element.getOptions()\n\n      options.fillOpacity = EFlightAreaType.NFZ === type && enable ? 0.3 : 0\n      options.strokeColor = enable ? flightAreaColorMap[type] : disableColor\n      options.radius = radius\n      options.center = new AMap.LngLat(position[0], position[1])\n      element.setOptions(options)\n    } else {\n      initFlightAreaCircle(name, radius, position, { id, type, enable })\n    }\n  }\n\n  function calcPolygonPosition (coordinate: GeojsonCoordinate[]): GeojsonCoordinate {\n    const index = coordinate.length - 1\n    return [(coordinate[0][0] + coordinate[index][0]) / 2.0, (coordinate[0][1] + coordinate[index][1]) / 2]\n  }\n\n  function initFlightAreaPolygon (name: string, coordinates: GeojsonCoordinate[], data: { id: string, type: EFlightAreaType, enable: boolean }) {\n    const path = [] as GeojsonCoordinate[]\n    coordinates.forEach(coordinate => {\n      path.push(new AMap.LngLat(coordinate[0], coordinate[1]))\n    })\n    const polygon = new AMap.Polygon({\n      path: path,\n      strokeColor: data.enable ? flightAreaColorMap[data.type] : disableColor,\n      strokeOpacity: 1,\n      strokeWeight: 4,\n      draggable: true,\n      extData: data,\n      strokeStyle: 'dashed',\n      strokeDasharray: EFlightAreaType.NFZ === data.type ? [10, 2] : [10, 1, 2],\n      fillColor: flightAreaColorMap[data.type],\n      fillOpacity: EFlightAreaType.NFZ === data.type && data.enable ? 0.3 : 0,\n    })\n    AddOverlayGroup(polygon)\n    initTextInfo(name, calcPolygonPosition(coordinates), data.id)\n  }\n\n  function updateFlightAreaPolygon (id: string, name: string, coordinates: GeojsonCoordinate[], enable: boolean, type: EFlightAreaType) {\n    const elements = getElementFromMap(id)\n    if (elements && elements.length > 0) {\n      let textIndex = elements.findIndex(ele => ele.getExtData()?.type === 'text')\n      if (textIndex === -1) {\n        textIndex = 1\n        initTextInfo(name, calcPolygonPosition(coordinates), id)\n      } else {\n        const text = elements[textIndex]\n        text.setText(name)\n        text.setPosition(calcPolygonPosition(coordinates))\n      }\n      const element = elements[textIndex ^ 1]\n      const options = element.getOptions()\n      const path = [] as GeojsonCoordinate[]\n      coordinates.forEach(coordinate => {\n        path.push(new AMap.LngLat(coordinate[0], coordinate[1]))\n      })\n      options.path = path\n      options.fillOpacity = EFlightAreaType.NFZ === type && enable ? 0.3 : 0\n      options.strokeColor = enable ? flightAreaColorMap[type] : disableColor\n      element.setOptions(options)\n    } else {\n      initFlightAreaPolygon(name, coordinates, { id, type, enable })\n    }\n  }\n\n  return {\n    init2DPin,\n    initPolyline,\n    initPolygon,\n    removeCoverFromMap,\n    getElementFromMap,\n    updatePinElement,\n    updatePolylineElement,\n    updatePolygonElement,\n    initFlightAreaCircle,\n    initFlightAreaPolygon,\n    updateFlightAreaPolygon,\n    updateFlightAreaCircle,\n    calcPolygonPosition,\n  }\n}\n"
  },
  {
    "path": "src/hooks/use-g-map-tsa.ts",
    "content": "import store from '/@/store'\nimport { getRoot } from '/@/root'\nimport { ELocalStorageKey, EDeviceTypeName } from '/@/types'\nimport { getDeviceBySn } from '/@/api/manage'\nimport { message } from 'ant-design-vue'\nimport dockIcon from '/@/assets/icons/dock.png'\nimport rcIcon from '/@/assets/icons/rc.png'\nimport droneIcon from '/@/assets/icons/drone.png'\n\nexport function deviceTsaUpdate () {\n  const root = getRoot()\n  let AMap = root.$aMap\n\n  const icons = new Map([\n    [EDeviceTypeName.Aircraft, droneIcon],\n    [EDeviceTypeName.Gateway, rcIcon],\n    [EDeviceTypeName.Dock, dockIcon]\n  ])\n  const markers = store.state.markerInfo.coverMap\n  const paths = store.state.markerInfo.pathMap\n\n  let trackLine = null as any\n  function getTrackLineInstance () {\n    if (!trackLine) {\n      trackLine = new AMap.Polyline({\n        map: root.$map,\n        strokeColor: '#939393' // 线颜色\n      })\n    }\n    return trackLine\n  }\n\n  function initIcon (type: number) {\n    return new AMap.Icon({\n      image: icons.get(type),\n      imageSize: new AMap.Size(40, 40),\n      size: new AMap.Size(40, 40)\n    })\n  }\n\n  function initMarker (type: number, name: string, sn: string, lng?: number, lat?: number) {\n    if (markers[sn]) {\n      return\n    }\n    if (root.$aMap === undefined) {\n      return\n    }\n    AMap = root.$aMap\n    markers[sn] = new AMap.Marker({\n      position: new AMap.LngLat(lng || 113.943225499, lat || 22.577673716),\n      icon: initIcon(type),\n      title: name,\n      anchor: 'top-center',\n      offset: [0, -20],\n    })\n    root.$map.add(markers[sn])\n    // markers[sn].on('moving', function (e: any) {\n    //   let path = paths[sn]\n    //   if (!path) {\n    //     paths[sn] = e.passedPath\n    //     return\n    //   }\n    //   path.push(e.passedPath[0])\n    //   path.push(e.passedPath[1])\n    //   getTrackLineInstance().setPath(path)\n    // })\n  }\n\n  function removeMarker (sn: string) {\n    if (!markers[sn]) {\n      return\n    }\n    root.$map.remove(markers[sn])\n    getTrackLineInstance().setPath([])\n    delete markers[sn]\n    delete paths[sn]\n  }\n\n  function addMarker (sn: string, lng?: number, lat?: number) {\n    getDeviceBySn(localStorage.getItem(ELocalStorageKey.WorkspaceId)!, sn)\n      .then(data => {\n        if (data.code !== 0) {\n          message.error(data.message)\n          return\n        }\n        initMarker(data.data.domain, data.data.nickname, sn, lng, lat)\n      })\n  }\n\n  function moveTo (sn: string, lng: number, lat: number) {\n    let marker = markers[sn]\n    if (!marker) {\n      addMarker(sn, lng, lat)\n      marker = markers[sn]\n      return\n    }\n    marker.moveTo([lng, lat], {\n      duration: 1800,\n      autoRotation: true\n    })\n  }\n\n  return {\n    marker: markers,\n    initMarker,\n    removeMarker,\n    moveTo\n  }\n}\n"
  },
  {
    "path": "src/hooks/use-g-map.ts",
    "content": "import AMapLoader from '@amap/amap-jsapi-loader'\nimport { App, reactive } from 'vue'\nimport { AMapConfig } from '/@/constants/index'\n\nexport function useGMapManage () {\n  const state = reactive({\n    aMap: null, // Map类\n    map: null, // 地图对象\n    mouseTool: null,\n  })\n\n  async function initMap (container: string, app:App) {\n    AMapLoader.load({\n      ...AMapConfig\n    }).then((AMap) => {\n      state.aMap = AMap\n      state.map = new AMap.Map(container, {\n        center: [113.943225499, 22.577673716],\n        zoom: 20\n      })\n      state.mouseTool = new AMap.MouseTool(state.map)\n\n      // 挂在到全局\n      app.config.globalProperties.$aMap = state.aMap\n      app.config.globalProperties.$map = state.map\n      app.config.globalProperties.$mouseTool = state.mouseTool\n    }).catch(e => {\n      console.log(e)\n    })\n  }\n\n  function globalPropertiesConfig (app:App) {\n    initMap('g-container', app)\n  }\n\n  return {\n    globalPropertiesConfig,\n  }\n}\n"
  },
  {
    "path": "src/hooks/use-map-tool.ts",
    "content": "import { GeojsonCoordinate } from '../utils/genjson'\nimport { getRoot } from '/@/root'\n\nexport function useMapTool () {\n  const root = getRoot()\n  const map = root.$map\n  const AMap = root.$aMap\n\n  function panTo (coordinate: GeojsonCoordinate) {\n    map.panTo(coordinate, 100)\n    map.setZoom(18, false, 100)\n  }\n  return {\n    panTo,\n  }\n}\n"
  },
  {
    "path": "src/hooks/use-mouse-tool.ts",
    "content": "import { reactive } from 'vue'\nimport pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg'\nimport { MapDoodleType } from '/@/constants/map'\nimport { getRoot } from '/@/root'\nimport { MapDoodleEnum } from '/@/types/map-enum'\nimport { EFlightAreaType } from '../types/flight-area'\nimport { message } from 'ant-design-vue'\n\nexport function useMouseTool () {\n  const root = getRoot()\n\n  const state = reactive({\n    pinNum: 0,\n    polylineNum: 0,\n    PolygonNum: 0,\n    currentType: '',\n  })\n  const flightAreaColorMap = {\n    [EFlightAreaType.DFENCE]: '#19be6b',\n    [EFlightAreaType.NFZ]: '#ff0000',\n  }\n  function drawPin (type:MapDoodleType, getDrawCallback:Function) {\n    root?.$mouseTool.marker({\n      title: type + state.pinNum,\n      icon: pin2d8cf0,\n    })\n    state.pinNum++\n    root?.$mouseTool.on('draw', getDrawCallback)\n  }\n\n  function drawPolyline (type:MapDoodleType, getDrawCallback:Function) {\n    root?.$mouseTool.polyline({\n      strokeColor: '#2d8cf0',\n      strokeOpacity: 1,\n      strokeWeight: 2,\n      strokeStyle: 'solid',\n      title: type + state.polylineNum++\n    })\n    root?.$mouseTool.on('draw', getDrawCallback)\n  }\n\n  function drawPolygon (type:MapDoodleType, getDrawCallback:Function) {\n    root?.$mouseTool.polygon({\n      strokeColor: '#2d8cf0',\n      strokeOpacity: 1,\n      strokeWeight: 2,\n      fillColor: '#1791fc',\n      fillOpacity: 0.4,\n      title: type + state.PolygonNum++\n    })\n    root?.$mouseTool.on('draw', getDrawCallback)\n  }\n\n  function drawOff (type:MapDoodleType) {\n    root?.$mouseTool.close()\n    root?.$mouseTool.off('draw')\n  }\n\n  function drawFlightAreaPolygon (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {\n    root?.$mouseTool.polygon({\n      strokeColor: flightAreaColorMap[type],\n      strokeOpacity: 1,\n      strokeWeight: 4,\n      extData: {\n        type: type,\n        mapType: 'polygon',\n      },\n      strokeStyle: 'dashed',\n      strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],\n      fillColor: flightAreaColorMap[type],\n      fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,\n    })\n    root?.$mouseTool.on('draw', getDrawFlightAreaCallback)\n  }\n\n  function drawFlightAreaCircle (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {\n    root?.$mouseTool.circle({\n      strokeColor: flightAreaColorMap[type],\n      strokeOpacity: 1,\n      strokeWeight: 6,\n      extData: {\n        type: type,\n        mapType: 'circle',\n      },\n      strokeStyle: 'dashed',\n      strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],\n      fillColor: flightAreaColorMap[type],\n      fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,\n    })\n    root?.$mouseTool.on('draw', getDrawFlightAreaCallback)\n  }\n\n  function mouseTool (type: MapDoodleType, getDrawCallback: Function, flightAreaType?: EFlightAreaType) {\n    state.currentType = type\n    if (flightAreaType) {\n      switch (type) {\n        case MapDoodleEnum.POLYGON:\n          drawFlightAreaPolygon(flightAreaType, getDrawCallback)\n          return\n        case MapDoodleEnum.CIRCLE:\n          drawFlightAreaCircle(flightAreaType, getDrawCallback)\n          return\n        default:\n          message.error(`Invalid type: ${flightAreaType}`)\n          return\n      }\n    }\n    switch (type) {\n      case MapDoodleEnum.PIN:\n        drawPin(type, getDrawCallback)\n        break\n      case MapDoodleEnum.POLYLINE:\n        drawPolyline(type, getDrawCallback)\n        break\n      case MapDoodleEnum.POLYGON:\n        drawPolygon(type, getDrawCallback)\n        break\n      case MapDoodleEnum.Close:\n        drawOff(type)\n        break\n    }\n  }\n\n  return {\n    mouseTool\n  }\n}\n"
  },
  {
    "path": "src/main.ts",
    "content": "import App from './App.vue'\nimport router from './router'\nimport { antComponents } from './antd'\nimport { CommonComponents } from './use-common-components'\nimport 'virtual:svg-icons-register'\nimport store, { storeKey } from './store'\nimport { createInstance } from '/@/root'\nimport { useDirectives } from './directives'\n\nimport '/@/styles/index.scss'\nconst app = createInstance(App)\n\napp.use(store, storeKey)\napp.use(router)\napp.use(CommonComponents)\napp.use(antComponents)\napp.use(useDirectives)\napp.mount('#demo-app')\n"
  },
  {
    "path": "src/mqtt/config.ts",
    "content": "\nimport {\n  IClientOptions,\n} from 'mqtt'\n\nexport const OPTIONS: IClientOptions = {\n  clean: true, // true: 清除会话, false: 保留会话\n  connectTimeout: 10000, // mqtt 超时时间\n  resubscribe: true, // 断开重连后，再次订阅原订阅\n  reconnectPeriod: 10000, // 重连间隔时间： 5s\n  keepalive: 1, // 心跳间隔时间：1s\n}\n"
  },
  {
    "path": "src/mqtt/index.ts",
    "content": "import EventEmitter from 'eventemitter3'\nimport {\n  OPTIONS,\n} from './config'\nimport {\n  connect,\n  MqttClient,\n  IClientPublishOptions,\n  IPublishPacket,\n  Packet,\n  ISubscriptionGrant,\n  IClientOptions,\n} from 'mqtt/dist/mqtt.min'\n\nexport class UranusMqtt extends EventEmitter {\n  _url: string\n  _options?: IClientOptions\n  _client: MqttClient | null\n  _hasInit: boolean\n\n  constructor (url?: string, options?: IClientOptions) {\n    super()\n    this._url = url || ''\n    this._options = options\n    this._client = null\n    this._hasInit = false\n  }\n\n  initMqtt = () => {\n    // 仅初始化一次\n    if (this._hasInit) return\n    // 建立连接\n    this._client = connect(this._url, {\n      ...OPTIONS,\n      ...this._options,\n    })\n    this._hasInit = true\n    if (this._client) {\n      this._client.on('reconnect', this._onReconnect)\n\n      // 消息监听\n      this._client.on('message', this._onMessage)\n\n      // 连接关闭\n      this._client.on('close', this._onClose)\n\n      // 连接异常\n      this._client.on('error', this._onError)\n    }\n  }\n\n  // 发布\n  publishMqtt = (topic: string, body: string | Buffer, opts?: IClientPublishOptions) => {\n    if (!this._client?.connected) {\n      this.initMqtt()\n    }\n    this._client?.publish(topic, body, opts || {}, (error?: Error, packet?: Packet) => {\n      if (error) {\n        window.console.error('mqtt publish error,', error, packet)\n      }\n    })\n  }\n\n  // 订阅\n  subscribeMqtt = (topic: string) => {\n    if (!this._client?.connected) {\n      this.initMqtt()\n    }\n    window.console.log('subscribeMqtt>>>>>', topic)\n    this._client?.subscribe(topic, (error: Error, granted: ISubscriptionGrant[]) => {\n      window.console.log('mqtt subscribe,', error, granted)\n    })\n  }\n\n  // 取消订阅\n  unsubscribeMqtt = (topic: string) => {\n    window.console.log('mqtt unsubscribeMqtt,', topic)\n    this._client?.unsubscribe(topic)\n  }\n\n  // 关闭 mqtt 客户端\n  destroyed = () => {\n    window.console.log('mqtt destroyed')\n    this._client?.end()\n  }\n\n  _onReconnect = () => {\n    if (this._client) { window.console.error('mqtt reconnect,') }\n  }\n\n  _onMessage = (topic: string, payload: Buffer, packet: IPublishPacket) => {\n    this.emit('onMessageMqtt', { topic, payload, packet })\n  }\n\n  _onClose = () => {\n    // 连接异常关闭会自动重连\n    window.console.error('mqtt close,')\n    this.emit('onStatus', {\n      status: 'close',\n    })\n  }\n\n  _onError = (error: Error) => {\n    // 连接错误会自动重连\n    window.console.error('mqtt error,', error)\n    this.emit('onStatus', {\n      status: 'error',\n      data: error,\n    })\n  }\n}\n\nexport {\n  IClientOptions,\n  IPublishPacket,\n  IClientPublishOptions,\n}\n"
  },
  {
    "path": "src/pages/page-pilot/pilot-bind.vue",
    "content": "<template>\n  <a-layout class=\"flex-display\" style=\"height: 100vh; background-color: white;\">\n  <div class=\"height100 width100 flex-column flex-justify-start flex-align-start\">\n    <a-row class=\"pt20 pl20\" style=\"height: 45px; width: 100vw\" align=\"middle\">\n      <a-col :span=\"1\">\n        <span style=\"color: #1fa3f6\" class=\"fz26\"><SendOutlined rotate=\"90\" /></span>\n      </a-col>\n      <a-col :span=\"20\">\n        <span class=\"fz20 pl5\">{{ drone.data.model }}</span>\n      </a-col>\n      <a-col :span=\"3\">\n        <span class=\"fz16\" v-if=\"drone.data.bound_status\" style=\"color: #737373\">Bound</span>\n        <a-button type=\"primary\" @click=\"onBindDevice\" v-else>Bind</a-button>\n      </a-col>\n    </a-row>\n  </div>\n  </a-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { SendOutlined } from '@ant-design/icons-vue'\nimport { message } from 'ant-design-vue'\nimport { onMounted, reactive, ref } from 'vue'\nimport { BindBody, bindDevice } from '/@/api/manage'\nimport apiPilot from '/@/api/pilot-bridge'\nimport { getRoot } from '/@/root'\nimport { ELocalStorageKey } from '/@/types'\nimport { DeviceStatus } from '/@/types/device'\n\nconst root = getRoot()\ninterface DeviceStatusData {\n  data: DeviceStatus\n}\nconst drone = reactive<DeviceStatusData>({\n  data: JSON.parse(localStorage.getItem(ELocalStorageKey.Device)!)\n})\n\nfunction onBindDevice () {\n  const bindParam: BindBody = {\n    device_sn: drone.data.sn,\n    user_id: localStorage.getItem(ELocalStorageKey.UserId)!,\n    workspace_id: localStorage.getItem(ELocalStorageKey.WorkspaceId)!\n  }\n  bindDevice(bindParam).then(bindRes => {\n    if (bindRes.code !== 0) {\n      message.error('bind failed:' + bindRes.message)\n      console.error(bindRes.message)\n      return\n    }\n    drone.data.bound_status = true\n    localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(drone.data))\n  })\n}\n</script>\n"
  },
  {
    "path": "src/pages/page-pilot/pilot-home.vue",
    "content": "<template>\n  <a-layout class=\"page\">\n    <a-layout-sider class=\"left\" width=\"40%\" style=\"border-radius: 4px;\">\n      <div style=\"width:90%; height: 90%; margin: 4vh\">\n        <a-layout style=\"height: 20%; margin-top: 3vh; background-color: white; \">\n          <a-layout-sider width=\"25%\" theme=\"light\" align=\"center\">\n            <a-avatar :size=\"60\" :src=\"cloudapi\">\n            </a-avatar>\n          </a-layout-sider>\n          <a-layout-content style=\"margin-left: 1vw;\" @click=\"showStatus\">\n            <div style=\"height: 50%;\">\n              <span style=\"font-size: 16px; font-weight: bolder\">{{ workspaceName }}</span>\n              <RightOutlined style=\"float: right; margin-top: 5px; color: #8894a0\" />\n            </div>\n            <div style=\"height: 50%;\">\n              <CloudSyncOutlined v-if=\"thingState === EStatusValue.CONNECTED\" style=\"color: #75c5f6\" />\n              <SyncOutlined spin v-else/>\n              <span style=\"color: #737373; margin-left: 3px;\">{{ thingState }}</span>\n            </div>\n            <a-drawer  placement=\"right\" v-model:visible=\"drawerVisible\" width=\"340px\">\n              <div class=\"mb10 flex-row flex-justify-center flex-align-center\">\n                <p class=\"fz14\" style=\"font-weight: 100;\">Module State</p>\n              </div>\n              <div class= \"width-100 mb10 flex-align-start\" v-for=\"m in modules\" :key=\"m.name\" style=\"height: 30px;\">\n\n                <div class=\"ml5\" style=\"float: left; color: #000000;\">{{m.name}}：</div>\n                <div class=\"ml10\" style=\"float: right; margin-bottom: 8px;\">\n                  <span :key=\"m.state\" :class=\"m.state.value === EStatusValue.CONNECTED ? 'green' : 'red'\">{{ m.state.value }}&nbsp;</span>\n                  <a-button-group >\n                  <a-button class=\"ml5\" type=\"primary\" size=\"small\" @click.stop=\"moduleInstall(m)\">install</a-button>\n                  <a-button class=\"ml5 mr5\" type=\"danger\" size=\"small\" @click.stop=\"moduleUninstall(m)\">uninstall</a-button>\n                  </a-button-group>\n                </div>\n                <a-divider />\n\n              </div>\n            </a-drawer>\n          </a-layout-content>\n\n        </a-layout>\n        <a-divider  style=\"height: 2px; background-color: #f5f5f5; margin-top: 3vh;\" />\n\n        <a-button id=\"exitBtn\" class=\"fz18\" @click=\"confirmAgain\"\n        style=\"width: 10vw; height: 10vh; position: fixed; bottom: 13vh; left: 15vw; background-color: #e6e6e6; color: red; border: 0;\"\n        type=\"primary\">Exit\n        </a-button>\n        <a-modal v-model:visible=\"exitVisible\" width=\"300px\" :closable=\"false\">\n          <template #footer>\n            <a-button type=\"text\" style=\"width: 48%; float: left;\" @click=\"onBack\">Cancel</a-button>\n            <a-button type=\"text\" style=\"width: 48%;\" @click=\"onExit\">Exit</a-button>\n          </template>\n          <p>Data will not be synchronized between DJI Pilot and this server after exiting.</p>\n        </a-modal>\n      </div>\n    </a-layout-sider>\n    <a-layout-content class=\"right flex-column\">\n      <div class=\"mb5\">\n        <span class=\"ml5\" style=\"color: #939393;\">Serial Number</span>\n      </div>\n      <div class=\"fz16\" style=\"background-color: white; border-radius: 4px;\">\n        <a-row style=\"border-bottom: 1px solid #f4f8f9; height: 45px;\" align=\"middle\">\n          <a-col :span=\"1\"></a-col>\n            <a-col :span=\"9\">\n            Remote Control Sn\n            </a-col>\n          <a-col :span=\"13\" class=\"flex-align-end flex-column\">\n            <span style=\"color: #737373\">{{ device.data.gateway_sn }}</span>\n          </a-col>\n        </a-row>\n        <a-row style=\"border-bottom: 1px solid #f4f8f9; height: 45px;\" align=\"middle\" v-if=\"device.data.online_status && device.data.sn\">\n          <a-col :span=\"1\"></a-col>\n          <a-col :span=\"9\">Aircraft Sn</a-col>\n          <a-col :span=\"13\" class=\"flex-align-end flex-column\" >\n            <span style=\"color: #737373\">{{ device.data.sn }}</span>\n          </a-col>\n        </a-row>\n      </div>\n      <div class=\"mt5 mb5\">\n        <span class=\"ml5\" style=\"color: #939393;\">Settings</span>\n      </div>\n      <div class=\"fz16\" style=\"background-color: white; border-radius: 4px;\">\n        <a-row v-if=\"device.data.online_status && device.data.sn\" style=\"border-bottom: 1px solid #f4f8f9; height: 45px;\" align=\"middle\" @click=\"bindingDevice\">\n          <a-col :span=\"1\"></a-col>\n          <a-col :span=\"11\">\n            Device Binding\n          </a-col>\n          <a-col :span=\"10\" style=\"text-align: right\">\n            <span v-if=\"device.data.bound_status\" style=\"color: #737373\">Aircraft bound</span>\n            <span v-else style=\"color: #737373\">Aircraft not bound</span>\n          </a-col>\n          <a-col :span=\"2\" class=\"flex-align-center flex-column\" >\n            <RightOutlined style=\"color: #8894a0; font-size: 20px;\" />\n          </a-col>\n        </a-row>\n        <a-row style=\"border-bottom: 1px solid #f4f8f9; height: 45px;\" align=\"middle\" @click=\"onMediaSetting\">\n          <a-col :span=\"1\"></a-col>\n          <a-col :span=\"21\">\n            Media File Upload\n          </a-col>\n          <a-col :span=\"2\" class=\"flex-align-center flex-column\" >\n            <RightOutlined style=\"color: #8894a0; font-size: 20px;\" />\n          </a-col>\n        </a-row>\n        <a-row style=\"border-bottom: 1px solid #f4f8f9; height: 45px;\" align=\"middle\" @click=\"onLiveshareSetting\">\n          <a-col :span=\"1\"></a-col>\n          <a-col :span=\"21\">Livestream Manually</a-col>\n          <a-col :span=\"2\" class=\"flex-align-center flex-column\">\n            <RightOutlined style=\"color: #8894a0; font-size: 20px;\" />\n          </a-col>\n        </a-row>\n        <a-row style=\"border-bottom: 1px solid #f4f8f9; height: 45px;\" align=\"middle\" @click=\"onOpen3rdApp\">\n          <a-col :span=\"1\"></a-col>\n          <a-col :span=\"21\">Open 3rd Party APP</a-col>\n          <a-col :span=\"2\" class=\"flex-align-center flex-column\">\n            <RightOutlined style=\"color: #8894a0; font-size: 20px;\" />\n          </a-col>\n        </a-row>\n      </div>\n    </a-layout-content>\n  </a-layout>\n</template>\n<script lang=\"ts\" setup>\nimport { message, Popconfirm } from 'ant-design-vue'\nimport { onMounted, onUnmounted, reactive, ref, watch } from 'vue'\nimport { CURRENT_CONFIG } from '/@/api/http/config'\nimport { BindBody, bindDevice, getDeviceBySn, getPlatformInfo, getUserInfo } from '/@/api/manage'\nimport apiPilot, { ApiParam, MapParam, ThingParam, WsParam } from '/@/api/pilot-bridge'\nimport { getRoot } from '/@/root'\nimport { EBizCode, EComponentName, EDownloadOwner, ELocalStorageKey, ERouterName, EStatusValue } from '/@/types'\nimport cloudapi from '/@/assets/icons/cloudapi.png'\nimport { RightOutlined, CloudOutlined, CloudSyncOutlined, SyncOutlined } from '@ant-design/icons-vue'\nimport { useMyStore } from '/@/store'\nimport { DeviceStatus } from '/@/types/device'\nimport { useConnectWebSocket } from '/@/hooks/use-connect-websocket'\n\nconst root = getRoot()\nconst gatewayState = ref<boolean>(localStorage.getItem(ELocalStorageKey.GatewayOnline) === 'true')\nconst thingState = ref(EStatusValue.DISCONNECT)\nconst apiState = ref(EStatusValue.DISCONNECT)\nconst liveState = ref(EStatusValue.DISCONNECT)\nconst wsState = ref(EStatusValue.DISCONNECT)\nconst mapState = ref(EStatusValue.DISCONNECT)\nconst tsaState = ref(EStatusValue.DISCONNECT)\nconst mediaState = ref(EStatusValue.DISCONNECT)\nconst waylineState = ref(EStatusValue.DISCONNECT)\nconst workspaceName = ref<string>(localStorage.getItem(ELocalStorageKey.WorkspaceName)!)\nconst username = ref(localStorage.getItem(ELocalStorageKey.Username)!)\nconst wsId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)\nconst components = apiPilot.init()\nconst exitVisible = ref(false)\nconst drawerVisible = ref(false)\nlet minitor: any\n\ninterface DeviceInfoData {\n  data: DeviceStatus\n}\nconst device = reactive<DeviceInfoData>({\n  data: {\n    sn: '',\n    online_status: false,\n    device_callsign: '',\n    user_id: '',\n    user_callsign: '',\n    bound_status: false,\n    model: '',\n    gateway_sn: EStatusValue.DISCONNECT,\n    domain: -1\n  }\n})\nconst bindParam: BindBody = {\n  device_sn: '',\n  user_id: '',\n  workspace_id: wsId.value\n}\n\nconst modules = [{\n  name: 'Cloud',\n  state: thingState,\n  module: EComponentName.Thing\n}, {\n  name: 'Api',\n  state: apiState,\n  module: EComponentName.Api\n}, {\n  name: 'Live',\n  state: liveState,\n  module: EComponentName.Liveshare\n}, {\n  name: 'Ws',\n  state: wsState,\n  module: EComponentName.Ws\n}, {\n  name: 'Map',\n  state: mapState,\n  module: EComponentName.Map\n}, {\n  name: 'Tsa',\n  state: tsaState,\n  module: EComponentName.Tsa\n}, {\n  name: 'Media',\n  state: mediaState,\n  module: EComponentName.Media\n}, {\n  name: 'Wayline',\n  state: waylineState,\n  module: EComponentName.Mission\n}]\n\nconst store = useMyStore()\n\nconst messageHandler = async (payload: any) => {\n  if (!payload) {\n    return\n  }\n  switch (payload.biz_code) {\n    case EBizCode.DeviceOnline: {\n      console.info('online: ', payload)\n      if (payload.data.sn === device.data.gateway_sn) {\n        localStorage.setItem(ELocalStorageKey.GatewayOnline, gatewayState.value.toString())\n        break\n      }\n      if (payload.data.gateway_sn === device.data.gateway_sn) {\n        device.data = payload.data\n        localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))\n      }\n      break\n    }\n    case EBizCode.DeviceOffline: {\n      console.info('offline: ', payload)\n      if (payload.data.sn === device.data.sn) {\n        device.data.online_status = payload.data.online_status\n        localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))\n      }\n      break\n    }\n\n    default:\n      break\n  }\n}\n\n// 监听ws 消息\nuseConnectWebSocket(messageHandler)\n\nlet bindNum: any\n\nonMounted(() => {\n  apiPilot.onBackClickReg()\n  apiPilot.onStopPlatform()\n  const oldDevice = localStorage.getItem(ELocalStorageKey.Device)\n  if (oldDevice) {\n    device.data = JSON.parse(oldDevice)\n  }\n\n  window.connectCallback = (arg: any) => {\n    connectCallback(arg)\n  }\n  window.wsConnectCallback = (arg: any) => {\n    wsConnectCallback(arg)\n  }\n  device.data.gateway_sn = apiPilot.getRemoteControllerSN()\n  if (device.data.gateway_sn === EStatusValue.DISCONNECT.toString()) {\n    message.warn('Data is not available, please restart the remote control.')\n    return\n  }\n\n  device.data.sn = apiPilot.getAircraftSN()\n  getDeviceInfo()\n\n  const isLoaded = apiPilot.isComponentLoaded(EComponentName.Thing)\n  if (isLoaded) {\n    username.value = '' + localStorage.getItem(ELocalStorageKey.Username)\n    workspaceName.value = '' + localStorage.getItem(ELocalStorageKey.WorkspaceName)\n    refreshStatus()\n    apiPilot.setPlatformMessage(\n      '' + localStorage.getItem(ELocalStorageKey.PlatformName),\n      workspaceName.value,\n      '' + localStorage.getItem(ELocalStorageKey.WorkspaceDesc)\n    )\n    return\n  }\n\n  setWorkspaceInfo()\n\n  getUserInfo().then(res => {\n    username.value = res.data.username\n    localStorage.setItem(ELocalStorageKey.Username, username.value)\n    // thing\n    const param: ThingParam = {\n      host: res.data.mqtt_addr,\n      username: res.data.mqtt_username,\n      password: res.data.mqtt_password,\n      connectCallback: 'connectCallback'\n    }\n    components.set(EComponentName.Thing, param)\n    apiPilot.loadComponent(EComponentName.Thing, components.get(EComponentName.Thing))\n\n    bindParam.device_sn = device.data.gateway_sn\n    bindParam.user_id = res.data.user_id\n    bindParam.workspace_id = res.data.workspace_id\n  })\n})\n\nconst connectCallback = async (arg: any) => {\n  if (arg) {\n    thingState.value = EStatusValue.CONNECTED\n    // liveshare\n    apiPilot.loadComponent(EComponentName.Liveshare, components.get(EComponentName.Liveshare))\n\n    // ws\n    const wsParam: WsParam = components.get(EComponentName.Ws)\n    wsParam.token = apiPilot.getToken()\n    apiPilot.loadComponent(EComponentName.Ws, components.get(EComponentName.Ws))\n\n    // map\n    const mapParam: MapParam = components.get(EComponentName.Map)\n    mapParam.userName = username.value\n    apiPilot.loadComponent(EComponentName.Map, components.get(EComponentName.Map))\n\n    // tsa\n    apiPilot.loadComponent(EComponentName.Tsa, components.get(EComponentName.Tsa))\n\n    // media\n    apiPilot.loadComponent(EComponentName.Media, components.get(EComponentName.Media))\n    apiPilot.setDownloadOwner(EDownloadOwner.Mine.valueOf())\n\n    // mission\n    apiPilot.loadComponent(EComponentName.Mission, {})\n\n    bindNum = setInterval(() => {\n      if (!bindParam.device_sn) {\n        device.data.gateway_sn = apiPilot.getRemoteControllerSN()\n        bindParam.device_sn = device.data.gateway_sn\n        return\n      }\n      bindDevice(bindParam).then(bindRes => {\n        if (bindRes.code !== 0) {\n          message.error(bindRes.message)\n          console.error(bindRes.message)\n        } else {\n          clearInterval(bindNum)\n        }\n      })\n    }, 2000)\n    setTimeout(getDeviceInfo, 3000)\n  } else {\n    thingState.value = EStatusValue.DISCONNECT\n  }\n  refreshStatus()\n}\nconst wsConnectCallback = async (arg: any) => {\n  if (arg) {\n    wsState.value = EStatusValue.CONNECTED\n  } else {\n    wsState.value = EStatusValue.DISCONNECT\n  }\n}\n\nconst confirmAgain = () => {\n  exitVisible.value = true\n}\n\nconst onBack = () => {\n  exitVisible.value = false\n}\n\nconst onExit = () => {\n  localStorage.clear()\n  apiPilot.stopwebview()\n}\n\nconst bindingDevice = async () => {\n  root.$router.push(ERouterName.PILOT_BIND)\n}\n\nconst onMediaSetting = async (e: any) => {\n  root.$router.push(ERouterName.PILOT_MEDIA)\n}\nconst onLiveshareSetting = async (e: any) => {\n  root.$router.push(ERouterName.PILOT_LIVESHARE)\n}\nconst onOpen3rdApp = () => {\n  const packageName = 'com.dji.sample'\n  const isInstalled = apiPilot.isAppInstalled(packageName)\n  if (isInstalled) {\n    window.open('https://www.dji.com')\n  } else {\n    message.error(packageName + ' is not installed.')\n  }\n}\n\nconst showStatus = async () => {\n  minitor = setInterval(() => {\n    refreshStatus()\n    if (!drawerVisible.value) {\n      clearInterval(minitor)\n    }\n  }, 2000)\n  drawerVisible.value = true\n}\n\nfunction setWorkspaceInfo () {\n  if (localStorage.getItem(ELocalStorageKey.WorkspaceName)) {\n    apiPilot.setPlatformMessage(\n      '' + localStorage.getItem(ELocalStorageKey.PlatformName),\n      workspaceName.value,\n      '' + localStorage.getItem(ELocalStorageKey.WorkspaceDesc)\n    )\n    apiPilot.setWorkspaceId(wsId.value)\n\n    return\n  }\n  getPlatformInfo().then(res => {\n    console.log(res)\n    workspaceName.value = res.data.workspace_name\n    wsId.value = res.data.workspace_id\n    localStorage.setItem(ELocalStorageKey.PlatformName, res.data.platform_name)\n    localStorage.setItem(ELocalStorageKey.WorkspaceName, workspaceName.value)\n    localStorage.setItem(ELocalStorageKey.WorkspaceDesc, res.data.workspace_desc)\n    apiPilot.setPlatformMessage(\n      res.data.platform_name,\n      workspaceName.value,\n      res.data.workspace_desc\n    )\n    apiPilot.setWorkspaceId(wsId.value)\n  })\n}\n\nfunction refreshStatus () {\n  thingState.value = apiPilot.thingGetConnectState() ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT\n  apiState.value = apiPilot.isComponentLoaded(EComponentName.Api) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT\n  liveState.value = apiPilot.isComponentLoaded(EComponentName.Liveshare) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT\n  wsState.value = apiPilot.isComponentLoaded(EComponentName.Ws) && apiPilot.wsGetConnectState()\n    ? EStatusValue.CONNECTED\n    : EStatusValue.DISCONNECT\n  mapState.value = apiPilot.isComponentLoaded(EComponentName.Map) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT\n  tsaState.value = apiPilot.isComponentLoaded(EComponentName.Tsa) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT\n  mediaState.value = apiPilot.isComponentLoaded(EComponentName.Media) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT\n  waylineState.value = apiPilot.isComponentLoaded(EComponentName.Mission) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT\n}\n\nfunction moduleInstall (m: any) {\n  let param\n  switch (m.module) {\n    case EComponentName.Thing:\n      param = apiPilot.thingGetConfigs()\n      break\n    case EComponentName.Api: {\n      const apiParam: ApiParam = {\n        host: apiPilot.getHost(),\n        token: apiPilot.getToken()\n      }\n      param = apiParam\n      break\n    }\n    case EComponentName.Map: {\n      const mapParam: MapParam = components.get(EComponentName.Map)\n      mapParam.userName = '' + localStorage.getItem(ELocalStorageKey.Username)\n      param = mapParam\n      break\n    }\n    case EComponentName.Ws: {\n      const wsParam: WsParam = components.get(EComponentName.Ws)\n      wsParam.token = '' + localStorage.getItem(ELocalStorageKey.Token)\n      param = wsParam\n      break\n    }\n    default:\n      param = components.get(m.module)\n  }\n\n  components.set(m.module, param)\n  console.info(components.get(m.module))\n  apiPilot.loadComponent(m.module, components.get(m.module))\n  refreshStatus()\n}\n\nfunction moduleUninstall (m: any) {\n  message.info('uninstall ' + m.module)\n  apiPilot.unloadComponent(m.module)\n  refreshStatus()\n}\n\nfunction getDeviceInfo () {\n  if (!device.data.sn || device.data.sn === EStatusValue.DISCONNECT) {\n    return\n  }\n  getDeviceBySn(bindParam.workspace_id, device.data.sn).then(res => {\n    if (res.code !== 0) {\n      return\n    }\n    device.data.online_status = res.data.status\n    device.data.bound_status = res.data.bound_status\n    device.data.device_callsign = res.data.nickname\n    device.data.model = res.data.device_name\n    localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n.page {\n  display: flex;\n  position: absolute;\n  transition: width 0.2s ease;\n  height: 100%;\n  width: 100%;\n  .left {\n    height: 90%;\n    background-color: white;\n    margin-top: 6vh;\n    margin-left: 2vh;\n  }\n  .right {\n    height: 90%;\n    margin-top: 6vh;\n    margin-left: 5vh;\n    margin-right: 5vh;\n  }\n}\n.green {\n  color: green\n}\n.red {\n  color: red;\n}\n#exitBtn:hover :active {\n  background-color: rgb(77, 75, 75);\n  width: 10vw;\n  height: 10vh;\n  position: fixed;\n  bottom: 13vh;\n  left: 15vw;\n  line-height: 10vh;\n}\n\n</style>\n"
  },
  {
    "path": "src/pages/page-pilot/pilot-index.vue",
    "content": "<template>\n  <div class=\"login flex-column flex-justify-center flex-align-center m0 b0\">\n    <a-image\n      style=\"width: 17vw; height: 10vw; margin-bottom: 50px\"\n      :src=\"djiLogo\"\n    />\n    <p class=\"logo fz35 pb50\">Pilot Cloud API Demo</p>\n    <a-form\n      layout=\"inline\"\n      :model=\"formState\"\n      class=\"flex-row flex-justify-center flex-align-center\"\n    >\n      <a-form-item>\n        <a-input v-model:value=\"formState.username\" placeholder=\"Username\">\n          <template #prefix\n            ><UserOutlined style=\"color: rgba(0, 0, 0, 0.25)\"\n          /></template>\n        </a-input>\n      </a-form-item>\n      <a-form-item>\n        <a-input\n          v-model:value=\"formState.password\"\n          type=\"password\"\n          placeholder=\"Password\"\n        >\n          <template #prefix\n            ><LockOutlined style=\"color: rgba(0, 0, 0, 0.25)\"\n          /></template>\n        </a-input>\n      </a-form-item>\n      <a-form-item>\n        <a-button\n          class=\"m0\"\n          type=\"primary\"\n          html-type=\"submit\"\n          :disabled=\"formState.user === '' || formState.password === ''\"\n          @click=\"onSubmit\"\n        >\n          Login\n        </a-button>\n      </a-form-item>\n    </a-form>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue'\nimport { onMounted, reactive, ref, UnwrapRef } from 'vue'\nimport { CURRENT_CONFIG } from '/@/api/http/config'\nimport { login, LoginBody, refreshToken } from '/@/api/manage'\nimport apiPilot from '/@/api/pilot-bridge'\nimport { getRoot } from '/@/root'\nimport router from '/@/router'\nimport { EComponentName, ELocalStorageKey, ERouterName, EUserType } from '/@/types'\nimport { UserOutlined, LockOutlined } from '@ant-design/icons-vue'\nimport djiLogo from '/@/assets/icons/dji_logo.png'\n\nconst root = getRoot()\n\nconst formState: UnwrapRef<LoginBody> = reactive({\n  username: 'pilot',\n  password: 'pilot123',\n  flag: EUserType.Pilot,\n})\nconst isVerified = ref<boolean>(false)\nonMounted(async () => {\n  verifyLicense()\n  if (!isVerified.value) {\n    return\n  }\n\n  apiPilot.setPlatformMessage('Cloud Api Platform', '', '')\n\n  const token = localStorage.getItem(ELocalStorageKey.Token)\n  if (token) {\n    await refreshToken({})\n      .then(res => {\n        apiPilot.setComponentParam(EComponentName.Api, {\n          host: CURRENT_CONFIG.baseURL,\n          token: res.data.access_token\n        })\n        const jsres = apiPilot.loadComponent(EComponentName.Api, apiPilot.getComponentParam(EComponentName.Api))\n        if (!jsres) {\n          message.error('Failed to load api module.')\n          return\n        }\n        apiPilot.setToken(res.data.access_token)\n        localStorage.setItem(ELocalStorageKey.Token, res.data.access_token)\n        root.$router.push(ERouterName.PILOT_HOME)\n      })\n      .catch(err => {\n        message.error(err)\n      })\n  }\n})\nconst onSubmit = async (e: any) => {\n  await login(formState)\n    .then(res => {\n      if (!isVerified.value) {\n        message.error('Please verify the license firstly.')\n        return\n      }\n      console.log('login res:', res)\n      if (res.code === 0) {\n        apiPilot.setComponentParam(EComponentName.Api, {\n          host: CURRENT_CONFIG.baseURL,\n          token: res.data.access_token\n        })\n        const jsres = apiPilot.loadComponent(\n          EComponentName.Api,\n          apiPilot.getComponentParam(EComponentName.Api)\n        )\n        console.log('load api module res:', jsres)\n        apiPilot.setToken(res.data.access_token)\n        localStorage.setItem(ELocalStorageKey.Token, res.data.access_token)\n        localStorage.setItem(ELocalStorageKey.WorkspaceId, res.data.workspace_id)\n        localStorage.setItem(ELocalStorageKey.UserId, res.data.user_id)\n        localStorage.setItem(ELocalStorageKey.Username, res.data.username)\n        localStorage.setItem(ELocalStorageKey.Flag, EUserType.Pilot.toString())\n        message.success('Login Success')\n        root.$router.push(ERouterName.PILOT_HOME)\n      }\n    })\n    .catch(err => {\n      message.error(err)\n    })\n}\n\nfunction verifyLicense () {\n  isVerified.value = apiPilot.platformVerifyLicense(CURRENT_CONFIG.appId, CURRENT_CONFIG.appKey, CURRENT_CONFIG.appLicense) &&\n    apiPilot.isPlatformVerifySuccess()\n  if (isVerified.value) {\n    message.success('The license verification is successful.')\n  } else {\n    message.error('Filed to verify the license. Please check license whether the license is correct, or apply again.')\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n.login {\n  // background-color: $dark-highlight;\n  height: 100vh;\n}\n.logo {\n  color: $primary;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-pilot/pilot-liveshare.vue",
    "content": "<template>\n    <div class=\"width100 flex-column flex-justify-start flex-align-start\" style=\"background-color: white;\">\n\n      <p class=\"fz16 ml10 mt15 mb10 color-text-title color-font-bold\" style=\"color: #939393\">\n        Before starting manually, please select the publish mode and livestream type\n      </p>\n    <div\n      class=\"mt15 flex-row flex-align-center flex-justify-between\"\n      style=\"width: 100%;\">\n      <p class=\"ml10 mb0 fz16\" style=\"color: black\">\n        Select Video Publish Mode:\n      </p>\n      <a-select\n        style=\"width: 200px; margin-right: 20px;\"\n        placeholder=\"Select Mode\"\n        @select=\"onPublishModeSelect\"\n      >\n        <a-select-option\n          v-for=\"item in publishModeList\"\n          :key=\"item.label\"\n          :value=\"item.value\"\n        >\n          {{ item.label }}\n        </a-select-option>\n      </a-select>\n    </div>\n\n    <div class=\"ml10 mr10\" style=\"width: 96%; margin-top: -10px;\">\n      <a-divider />\n    </div>\n    <div\n      class=\"flex-row flex-align-center flex-justify-between\"\n      style=\"width: 100%; margin-top: -10px;\"\n    >\n      <p class=\"ml10 mb0 fz16\">Select Livestream Type:</p>\n      <a-select\n        style=\"width: 200px; margin-right: 20px;\"\n        placeholder=\"Select Live Type\"\n        :value=\"liveStreamStatus.type\"\n        @select=\"onLiveTypeSelect\"\n      >\n        <a-select-option\n          v-for=\"item in liveTypeList\"\n          :key=\"item.label\"\n          :value=\"item.value\"\n        >\n          {{ item.label }}\n        </a-select-option>\n      </a-select>\n    </div>\n    <div class=\"ml10 mr10\" style=\"width: 96%; margin-top: -10px;\">\n      <a-divider />\n    </div>\n    <div class=\"width-100\" style=\"margin-top: -10px;\">\n      <div class=\"ml10\" style=\"width: 97%;\">\n        <span class=\"fz16\">Param: </span>\n        <span v-if=\"liveStreamStatus.type === ELiveTypeValue.Agora\" style=\"word-break: break-all; color: #75c5f6;\">\n          <div class=\"flex-col flex-justify-center flex-align-center\">\n            <div>\n              <span class=\"ml10\">Token:</span>\n              <a-input\n                class=\"ml10\"\n                v-model:value=\"agoraParam.token\"\n                placeholder=\"Token\"\n              ></a-input>\n            </div>\n            <div>\n              <span class=\"ml10\">Channel:</span>\n              <a-input\n                class=\"ml10\"\n                v-model:value=\"agoraParam.channelId\"\n                placeholder=\"Channel\"\n              ></a-input>\n            </div>\n          </div>\n        </span>\n        <span v-else-if=\"liveStreamStatus.type === ELiveTypeValue.RTMP\" style=\"word-break: break-all; color: #75c5f6;\">{{ rtmpParam }}</span>\n        <span v-else-if=\"liveStreamStatus.type === ELiveTypeValue.RTSP\" style=\"word-break: break-all; color: #75c5f6;\">{{ rtspParam }}</span>\n        <span v-else-if=\"liveStreamStatus.type === ELiveTypeValue.GB28181\" style=\"word-break: break-all; color: #75c5f6;\">{{ gb28181Param }}</span>\n        <span v-else></span>\n      </div>\n\n    </div>\n    <div class=\"ml10 mr10\" style=\"width: 96%; margin-top: -10px;\">\n      <a-divider />\n    </div>\n    <div class=\"mb20 flex-row flex-align-center flex-justify-center\"\n      style=\"width: 100%; \">\n      <a-button class=\"flex-column fz20 flex-align-center flex-justify-center\" style=\"width: 100px;\" type=\"ghost\" @click=\"onPlay\">Play</a-button>\n      <a-button class=\"flex-column fz20 flex-align-center flex-justify-center ml40\" style=\"width: 100px;\" type=\"ghost\" @click=\"onStop\">Stop</a-button>\n    </div>\n    <a-button v-if=\"playVisiable\" class=\"flex-column flex-align-center\" shape=\"circle\" @click=\"showLivingStatus\"\n      style=\"position: fixed; top: 13vh; left: 5vw; opacity: 0.8; background-color: rgb(0,0,0,0)\">\n      <template #icon><CaretRightFilled style=\"font-size: 26px; color: \" /></template>\n    </a-button>\n\n    <a-drawer  placement=\"right\" v-model:visible=\"drawerVisible\" width=\"280px\" :mask=\"false\" @close=\"closeDrawer\">\n      <div class=\"fz16 width-100\">\n        <div class=\"mt20\" style=\" margin-bottom: -10px;\">\n          <span class=\"fz20 flex-row flex-align-center flex-justify-center\">\n            <font :color=\"liveState === EStatusValue.LIVING ? 'green' : liveState === EStatusValue.CONNECTED ? 'blue' : 'red'\">{{ liveState }}</font></span>\n        </div>\n        <a-divider />\n        <div style=\" margin-top: -10px; margin-bottom: -15px;\">\n          <span>Frame Rate:</span><span style=\"float: right; color: #75c5f6;\">{{ liveStreamStatus.fps }}<span v-if=\"liveStreamStatus.fps != -1\"> fps</span></span><br/>\n        </div>\n        <a-divider />\n        <div style=\" margin-top: -10px; margin-bottom: -10px;\">\n          <span>Video Bit Rate:</span><span style=\"float: right; color: #75c5f6;\">{{ liveStreamStatus.videoBitRate }}<span v-if=\"liveStreamStatus.videoBitRate != -1\"> kbps</span></span><br/>\n        </div>\n        <a-divider />\n        <div style=\" margin-top: -10px; margin-bottom: -10px;\">\n          <span>Audio Bit Rate:</span><span style=\"float: right; color: #75c5f6;\">{{ liveStreamStatus.audioBitRate }}<span v-if=\"liveStreamStatus.audioBitRate != -1\"> kbps</span></span><br/>\n        </div>\n        <a-divider />\n        <div style=\" margin-top: -10px; margin-bottom: -10px;\">\n          <span>Packet Loss Rate:</span><span style=\"float: right; color: #75c5f6;\">{{ liveStreamStatus.dropRate }}<span v-if=\"liveStreamStatus.dropRate != -1\"> %</span></span><br/>\n        </div>\n        <a-divider />\n        <div style=\" margin-top: -10px; margin-bottom: -10px;\">\n          <span>RTT:</span><span style=\"float: right; color: #75c5f6;\">{{ liveStreamStatus.rtt }}<span v-if=\"liveStreamStatus.rtt != -1\"> ms</span></span><br/>\n        </div>\n        <a-divider />\n        <div style=\" margin-top: -10px;\">\n          <span >Jitter:</span><span style=\"float: right; color: #75c5f6;\">{{ liveStreamStatus.jitter }}</span><br/>\n        </div>\n      </div>\n    </a-drawer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue'\nimport { onMounted, reactive, ref, UnwrapRef } from 'vue'\nimport { CURRENT_CONFIG as config, CURRENT_CONFIG } from '/@/api/http/config'\nimport { ELiveTypeName, ELiveTypeValue, GB28181Param, LiveConfigParam, LiveStreamStatus, RTSPParam, EVideoPublishType } from '/@/types/live-stream'\nimport apiPilot from '/@/api/pilot-bridge'\nimport { getRoot } from '/@/root'\nimport { ELiveStatusValue, EStatusValue } from '/@/types'\nimport { CaretRightFilled } from '@ant-design/icons-vue'\n\nconst root = getRoot()\n\nconst publishModeList = [\n  {\n    value: EVideoPublishType.VideoOnDemand,\n    label: EVideoPublishType.VideoOnDemand\n  },\n  {\n    value: EVideoPublishType.VideoByManual,\n    label: EVideoPublishType.VideoByManual\n  },\n  {\n    value: EVideoPublishType.VideoDemandAuxManual,\n    label: EVideoPublishType.VideoDemandAuxManual\n  }\n]\nconst liveTypeList = [\n  {\n    value: ELiveTypeValue.Agora,\n    label: ELiveTypeName.Agora\n  },\n  {\n    value: ELiveTypeValue.RTMP,\n    label: ELiveTypeName.RTMP\n  },\n  {\n    value: ELiveTypeValue.RTSP,\n    label: ELiveTypeName.RTSP\n  },\n  {\n    value: ELiveTypeValue.GB28181,\n    label: ELiveTypeName.GB28181\n  }\n]\nconst agoraParam = reactive({\n  uid: '2892130292',\n  token: config.agoraToken,\n  channelId: config.agoraChannel\n})\nconst rtmpParam = {\n  url: config.rtmpURL + new Date().getTime()\n}\nconst rtspParam: RTSPParam = {\n  userName: CURRENT_CONFIG.rtspUserName,\n  password: CURRENT_CONFIG.rtspPassword,\n  port: CURRENT_CONFIG.rtspPort\n}\nconst gb28181Param: GB28181Param = {\n  serverIp: CURRENT_CONFIG.gbServerIp,\n  serverPort: CURRENT_CONFIG.gbServerPort,\n  serverId: CURRENT_CONFIG.gbServerId,\n  agentId: CURRENT_CONFIG.gbAgentId,\n  password: CURRENT_CONFIG.gbPassword,\n  agentPort: CURRENT_CONFIG.gbAgentPort,\n  agentChannel: CURRENT_CONFIG.gbAgentChannel,\n}\n\nconst playVisiable = ref(false)\nconst drawerVisible = ref(false)\nconst liveState = ref(EStatusValue.DISCONNECT)\nconst liveTypeSelected = ref<string>()\nconst publishModeSelected = ref<string>()\nconst liveStreamStatus: LiveStreamStatus = reactive({\n  audioBitRate: -1,\n  dropRate: -1,\n  fps: -1,\n  jitter: -1,\n  quality: -1,\n  rtt: -1,\n  status: -1,\n  type: -1,\n  videoBitRate: -1\n})\n\nonMounted(() => {\n  const config: LiveConfigParam = JSON.parse(apiPilot.getLiveshareConfig())\n  liveStreamStatus.type = config.type\n  refreshLiveType()\n\n  window.liveStatusCallback = arg => {\n    liveStatusCallback(arg)\n  }\n})\n\nconst liveStatusCallback = async (arg: LiveStreamStatus) => {\n  liveStreamStatus.fps = arg.fps\n  liveStreamStatus.audioBitRate = arg.audioBitRate\n  liveStreamStatus.dropRate = arg.dropRate\n  liveStreamStatus.jitter = arg.jitter\n  liveStreamStatus.rtt = arg.rtt\n  liveStreamStatus.videoBitRate = arg.videoBitRate\n  liveStreamStatus.quality = arg.quality\n  liveStreamStatus.type = arg.type\n  liveStreamStatus.status = arg.status\n\n  switch (liveStreamStatus.status) {\n    case ELiveStatusValue.LIVING:\n      liveState.value = EStatusValue.LIVING\n      break\n    case ELiveStatusValue.CONNECTED:\n      liveState.value = EStatusValue.CONNECTED\n      break\n    default:\n      liveState.value = EStatusValue.DISCONNECT\n  }\n}\nfunction refreshLiveType () {\n  switch (liveStreamStatus.type) {\n    case ELiveTypeValue.Agora:\n      liveTypeSelected.value = ELiveTypeName.Agora\n      break\n    case ELiveTypeValue.RTMP:\n      liveTypeSelected.value = ELiveTypeName.RTMP\n      break\n    case ELiveTypeValue.RTSP:\n      liveTypeSelected.value = ELiveTypeName.RTSP\n      break\n    case ELiveTypeValue.GB28181:\n      liveTypeSelected.value = ELiveTypeName.GB28181\n      break\n    default:\n      liveTypeSelected.value = ELiveTypeName.Unknown\n  }\n}\nconst onLiveTypeSelect = (val: number) => {\n  liveStreamStatus.type = val\n  refreshLiveType()\n}\nconst onPublishModeSelect = (val: string) => {\n  publishModeSelected.value = val\n  apiPilot.setVideoPublishType(publishModeSelected.value)\n}\nconst onPlay = () => {\n  console.info(JSON.stringify(agoraParam))\n  if (!publishModeSelected.value) {\n    message.warn('Please select publish mode!')\n    return\n  }\n  if (liveTypeSelected.value === ELiveTypeName.Unknown) {\n    message.warn('Please select livestream type!')\n    return\n  }\n  switch (liveStreamStatus.type) {\n    case 1: {\n      apiPilot.setLiveshareConfig(ELiveTypeValue.Agora, JSON.stringify(agoraParam))\n      break\n    }\n    case 2: {\n      apiPilot.setLiveshareConfig(ELiveTypeValue.RTMP, JSON.stringify(rtmpParam))\n      break\n    }\n    case 3: {\n      apiPilot.setLiveshareConfig(ELiveTypeValue.RTSP, JSON.stringify(rtspParam))\n      break\n    }\n    case 4: {\n      apiPilot.setLiveshareConfig(ELiveTypeValue.GB28181, JSON.stringify(gb28181Param))\n      break\n    }\n  }\n  const status = apiPilot.startLiveshare()\n  if (status) {\n    playVisiable.value = true\n    drawerVisible.value = true\n    message.success('success')\n  }\n}\n\nconst showLivingStatus = () => {\n  drawerVisible.value = !drawerVisible.value\n}\n\nconst onStop = () => {\n  const status = apiPilot.stopLiveshare()\n  if (status) {\n    message.success('success')\n    playVisiable.value = false\n    drawerVisible.value = false\n    setTimeout(() => {\n      let key: (keyof LiveStreamStatus)\n      for (key in liveStreamStatus) {\n        if (key === 'type') {\n          continue\n        }\n        liveStreamStatus[key] = -1\n      }\n    }, 2000)\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n// @import '/@/styles/index.scss';\n</style>\n"
  },
  {
    "path": "src/pages/page-pilot/pilot-media.vue",
    "content": "<template>\n  <a-layout>\n  <div class=\"width100 flex-column flex-justify-start flex-align-start\" style=\"background-color: white;\">\n\n    <p class=\"fz16 ml10 mt15 mb10 color-text-title color-font-bold\" style=\"color: #939393\">\n      When enabled, photos and videos will be automatically uploaded to this server\n    </p>\n    <div\n      class=\"flex-row flex-align-center mt20\"\n      style=\"width: 100%;\"\n    >\n      <p class=\"ml10 mb0 fz16\" style=\"margin-right: 73vw;\">Auto Photo Upload</p>\n      <a-switch\n        v-model:checked=\"enablePhotoUpload\"\n        @change=\"onPhotoUpload\"\n      ></a-switch>\n    </div>\n    <div\n      class=\"flex-row flex-align-center flex-justify-between\"\n      style=\"width: 100%\"\n    >\n      <a-radio-group\n        class=\"mt10 ml20\"\n        v-if=\"enablePhotoUpload === true\"\n        v-model:value=\"photoType\"\n        defaultChecked=\"0\"\n        @change=\"onPhototype\"\n      >\n        <a-radio :value=\"EPhotoType.Original\">Original Photo</a-radio>\n        <a-radio class=\"ml20\" :value=\"EPhotoType.Preview\">Preview Photo</a-radio>\n      </a-radio-group>\n    </div>\n    <div class=\"ml10 mr10\" style=\"width: 96%; margin-top: -10px;\">\n      <a-divider />\n    </div>\n    <div\n      class=\"flex-row flex-align-center\"\n      style=\"width: 100%; margin-top: -10px;\"\n    >\n      <p class=\"ml10 mb0 fz16\" style=\"margin-right: 73vw;\">Auto Video Upload</p>\n      <a-switch\n        @change=\"onVideoUpload\"\n        v-model:checked=\"enableVideoUpload\"\n      ></a-switch>\n    </div>\n    <div class=\"ml10 mr10\" style=\"width: 96%; margin-top: -10px;\">\n      <a-divider />\n    </div>\n    <div\n      class=\"flex-row flex-align-center flex-justify-between mb15\"\n      style=\"width: 100%; margin-top: -10px;\"\n    >\n      <p class=\"ml10 mb0 fz16 color-font-bold\">\n        Path for uploading media resources in dual-controller mode\n      </p>\n      <a-radio-group\n        class=\"mt0 mb0\"\n        v-model:value=\"uploadPath\"\n        button-style=\"solid\"\n        @change=\"onUploadPath\"\n      >\n        <a-radio-button :value=\"EDownloadOwner.Mine\">Mine</a-radio-button>\n        <a-radio-button :value=\"EDownloadOwner.Others\">Another</a-radio-button>\n      </a-radio-group>\n    </div>\n  </div>\n  </a-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue'\nimport { onMounted, ref } from 'vue'\nimport apiPilot from '/@/api/pilot-bridge'\nimport { getRoot } from '/@/root'\nimport { EComponentName, EPhotoType, EDownloadOwner } from '/@/types'\n\nconst root = getRoot()\n\nconst enablePhotoUpload = ref<boolean>(apiPilot.getAutoUploadPhoto())\nconst enableVideoUpload = ref<boolean>(apiPilot.getAutoUploadVideo())\nconst photoType = ref<number>(apiPilot.getUploadPhotoType())\nconst uploadPath = ref<number>(apiPilot.getDownloadOwner())\n\nconst onPhotoUpload = () => {\n  apiPilot.setAutoUploadPhoto(enablePhotoUpload.value)\n}\nconst onVideoUpload = () => {\n  apiPilot.setAutoUploadVideo(enableVideoUpload.value)\n}\nconst onPhototype = () => {\n  apiPilot.setUploadPhotoType(photoType.value)\n}\nconst onUploadPath = (e: any) => {\n  apiPilot.setDownloadOwner(uploadPath.value)\n}\nonMounted(() => {\n  console.error(apiPilot.getUploadPhotoType())\n  console.error(apiPilot.getAutoUploadVideo())\n})\n</script>\n\n<style lang=\"scss\" scoped>\n// @import '/@/styles/index.scss';\n</style>\n"
  },
  {
    "path": "src/pages/page-web/home.vue",
    "content": "<template>\n  <a-layout class=\"width-100 flex-display\" style=\"height: 100vh\">\n    <a-layout-header class=\"header\">\n      <Topbar />\n    </a-layout-header>\n    <a-layout-content>\n      <router-view />\n    </a-layout-content>\n\n  </a-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport Topbar from '/@/components/common/topbar.vue'\nimport { onMounted, reactive, ref, UnwrapRef, watch } from 'vue'\nimport { getRoot } from '/@/root'\nimport { EBizCode, ELocalStorageKey, ERouterName } from '/@/types'\nimport { useConnectWebSocket } from '/@/hooks/use-connect-websocket'\nimport EventBus from '/@/event-bus'\n\ninterface FormState {\n  user: string\n  password: string\n}\n\nconst root = getRoot()\n\nconst messageHandler = async (payload: any) => {\n  if (!payload) {\n    return\n  }\n  switch (payload.biz_code) {\n    case EBizCode.DeviceUpgrade: {\n      EventBus.emit('deviceUpgrade', payload)\n      break\n    }\n    case EBizCode.DeviceLogUploadProgress: {\n      EventBus.emit('deviceLogUploadProgress', payload)\n      break\n    }\n  }\n}\n\n// 监听ws 消息\nuseConnectWebSocket(messageHandler)\n\nonMounted(() => {\n  const token = localStorage.getItem(ELocalStorageKey.Token)\n  if (!token) {\n    root.$router.push(ERouterName.PROJECT)\n  }\n})\n\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n\n.fontBold {\n  font-weight: 500;\n  font-size: 18px;\n}\n\n.header {\n  background-color: black;\n  color: white;\n  height: 60px;\n  font-size: 15px;\n  padding: 0 20px;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/index.vue",
    "content": "<template>\n  <div\n    class=\"login flex-column flex-justify-center flex-align-center m0 b0\">\n    <a-image\n      style=\"width: 17vw; height: 10vw; margin-bottom: 50px\"\n      :src=\"djiLogo\"\n    />\n    <p class=\"fz35 pb50\" style=\"color: #2d8cf0\">Cloud API Demo</p>\n    <a-form\n      layout=\"inline\"\n      :model=\"formState\"\n      class=\"flex-row flex-justify-center flex-align-center\"\n    >\n      <a-form-item>\n        <a-input v-model:value=\"formState.username\" placeholder=\"Username\">\n          <template #prefix\n            ><UserOutlined style=\"color: rgba(0, 0, 0, 0.25)\"\n          /></template>\n        </a-input>\n      </a-form-item>\n      <a-form-item>\n        <a-input\n          v-model:value=\"formState.password\"\n          type=\"password\"\n          placeholder=\"Password\"\n        >\n          <template #prefix\n            ><LockOutlined style=\"color: rgba(0, 0, 0, 0.25)\"\n          /></template>\n        </a-input>\n      </a-form-item>\n      <a-form-item>\n        <a-button\n          class=\"m0\"\n          type=\"primary\"\n          html-type=\"submit\"\n          :disabled=\"loginBtnDisabled\"\n          @click=\"onSubmit\"\n        >\n          Login\n        </a-button>\n      </a-form-item>\n    </a-form>\n  </div>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport djiLogo from '/@/assets/icons/dji_logo.png'\nimport { LockOutlined, UserOutlined } from '@ant-design/icons-vue'\nimport { message } from 'ant-design-vue'\nimport { reactive, computed, UnwrapRef } from 'vue'\nimport { login, LoginBody } from '/@/api/manage'\nimport { getRoot } from '/@/root'\nimport { ELocalStorageKey, ERouterName, EUserType } from '/@/types'\nimport router from '/@/router'\n\nconst root = getRoot()\n\nconst formState: UnwrapRef<LoginBody> = reactive({\n  username: 'adminPC',\n  password: 'adminPC',\n  flag: EUserType.Web,\n})\n\nconst loginBtnDisabled = computed(() => {\n  return !formState.username || !formState.password\n})\n\nconst onSubmit = async (e: any) => {\n  const result = await login(formState)\n  if (result.code === 0) {\n    localStorage.setItem(ELocalStorageKey.Token, result.data.access_token)\n    localStorage.setItem(ELocalStorageKey.WorkspaceId, result.data.workspace_id)\n    localStorage.setItem(ELocalStorageKey.Username, result.data.username)\n    localStorage.setItem(ELocalStorageKey.UserId, result.data.user_id)\n    localStorage.setItem(ELocalStorageKey.Flag, EUserType.Web.toString())\n    root.$router.push(ERouterName.MEMBERS)\n  } else {\n    message.error(result.message)\n  }\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n.login {\n  background-color: $dark-highlight;\n  height: 100vh;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/Firmwares.vue",
    "content": "\n<template>\n  <div class=\"ml20 mt20 mr20 flex-row flex-align-center flex-justify-between\">\n    <div class=\"flex-row\">\n      <a-button type=\"primary\" @click=\"sVisible = true\">\n        Click to Upload\n      </a-button>\n      <a-modal :visible=\"sVisible\"\n         title=\"Import Firmware File\"\n         :closable=\"false\"\n         @cancel=\"onCancel\"\n         @ok=\"uploadFile\"\n         centered>\n         <a-form :rules=\"rules\" ref=\"formRef\" :model=\"uploadParam\" :label-col=\"{ span: 6 }\">\n          <a-form-item name=\"status\" label=\"Avaliable\" required>\n            <a-switch v-model:checked=\"uploadParam.status\" />\n          </a-form-item>\n          <a-form-item name=\"device_name\" label=\"Device Name\" required>\n            <a-select\n              style=\"width: 220px\"\n              mode=\"multiple\"\n              placeholder=\"can choose multiple\"\n              v-model:value=\"uploadParam.device_name\">\n              <a-select-option\n                v-for=\"k in DeviceNameEnum\"\n                :key=\"k\"\n                :value=\"k\"\n              >\n                {{ k }}\n              </a-select-option>\n            </a-select>\n          </a-form-item>\n          <a-form-item name=\"release_note\" label=\"Release Note\" required>\n            <a-textarea v-model:value=\"uploadParam.release_note\" showCount :maxlength=\"300\" />\n          </a-form-item>\n          <a-form-item label=\"File\" required>\n            <a-upload\n              :multiple=\"false\"\n              :before-upload=\"beforeUpload\"\n              :show-upload-list=\"true\"\n              :file-list=\"fileList\"\n              :remove=\"removeFile\"\n             >\n              <a-button type=\"primary\">\n                <UploadOutlined />\n                Import Firmware File\n              </a-button>\n            </a-upload>\n          </a-form-item>\n         </a-form>\n      </a-modal>\n    </div>\n    <div class=\"flex-row\">\n      <div class=\"ml5\">\n        <a-select\n          style=\"width: 150px\"\n          v-model:value=\"param.firmware_status\"\n          @select=\"getAllFirmwares(pageParam)\">\n          <a-select-option\n            v-for=\"(key, value) in FirmwareStatusEnum\"\n            :key=\"key\"\n            :value=\"value\"\n          >\n            {{ key }}\n          </a-select-option>\n        </a-select>\n      </div>\n      <div class=\"ml5\">\n        <a-select\n          style=\"width: 150px\"\n          v-model:value=\"param.device_name\"\n          @select=\"getAllFirmwares(pageParam)\">\n          <a-select-option\n            v-for=\"item in deviceNameList\"\n            :key=\"item.label\"\n            :value=\"item.value\"\n          >\n            {{ item.label }}\n          </a-select-option>\n        </a-select>\n      </div>\n      <div class=\"ml5\">\n        <a-input-search\n          :enter-button=\"true\"\n          v-model:value=\"param.product_version\"\n          placeholder=\"input search verison\"\n          style=\"width: 250px\"\n          @search=\"getAllFirmwares(pageParam)\"/>\n      </div>\n    </div>\n  </div>\n  <div class=\"table flex-display flex-column\">\n    <a-table :columns=\"columns\" :data-source=\"data.firmware\" :pagination=\"paginationProp\" @change=\"refreshData\" row-key=\"firmware_id\"\n     :rowClassName=\"(record, index) => ((index % 2) === 0 ? 'table-striped' : null)\" :scroll=\"{ x: '100%', y: 600 }\">\n      <template #device_name=\"{ record }\">\n        <div v-for=\"text in record.device_name\" :key=\"text\">\n          {{ text }}\n        </div>\n      </template>\n      <template #file_size=\"{ record }\">\n        <div>{{ bytesToSize(record.file_size) }}</div>\n      </template>\n      <template #firmware_status=\"{ record }\">\n        <DeviceFirmwareStatus :firmware=\"record\" />\n      </template>\n      <template v-for=\"col in ['file_name', 'release_note']\" #[col]=\"{ text }\" :key=\"col\">\n        <a-tooltip :title=\"text\">\n            <span>{{ text }}</span>\n        </a-tooltip>\n      </template>\n\n    </a-table>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { message, notification, PaginationProps } from 'ant-design-vue'\nimport { TableState } from 'ant-design-vue/lib/table/interface'\nimport { onMounted, reactive, Ref, ref, UnwrapRef } from 'vue'\nimport { IPage } from '/@/api/http/type'\nimport { getFirmwares, importFirmareFile } from '/@/api/manage'\nimport DeviceFirmwareStatus from '/@/components/devices/DeviceFirmwareStatus.vue'\nimport { ELocalStorageKey } from '/@/types'\nimport { UploadOutlined } from '@ant-design/icons-vue'\nimport { Firmware, FirmwareQueryParam, FirmwareStatusEnum, DeviceNameEnum, FirmwareUploadParam } from '/@/types/device-firmware'\nimport { commonColor } from '/@/utils/color'\nimport { bytesToSize } from '/@/utils/bytes'\nimport moment from 'moment'\n\ninterface FirmwareData {\n  firmware: Firmware[]\n}\nconst columns = [\n  { title: 'Model', dataIndex: 'device_name', width: 120, ellipsis: true, className: 'titleStyle', slots: { customRender: 'device_name' } },\n  { title: 'File Name', dataIndex: 'file_name', width: 220, ellipsis: true, className: 'titleStyle', slots: { customRender: 'file_name' } },\n  { title: 'Firmware Version', dataIndex: 'product_version', width: 180, className: 'titleStyle' },\n  { title: 'File Size', dataIndex: 'file_size', width: 150, className: 'titleStyle', slots: { customRender: 'file_size' } },\n  { title: 'Creator', dataIndex: 'username', width: 100, className: 'titleStyle' },\n  { title: 'Release Date', dataIndex: 'released_time', width: 160, sorter: (a: Firmware, b: Firmware) => a.released_time.localeCompare(b.released_time), className: 'titleStyle' },\n  { title: 'Release Note', dataIndex: 'release_note', width: 300, ellipsis: true, className: 'titleStyle', slots: { customRender: 'release_note' } },\n  { title: 'Status', dataIndex: 'firmware_status', width: 100, className: 'titleStyle', slots: { customRender: 'firmware_status' } },\n]\n\nconst data = reactive<FirmwareData>({\n  firmware: []\n})\n\nconst paginationProp = reactive({\n  pageSizeOptions: ['20', '50', '100'],\n  showQuickJumper: true,\n  showSizeChanger: true,\n  pageSize: 50,\n  current: 1,\n  total: 0\n})\n\nconst deviceNameList = ref<any[]>([{ label: 'All', value: '' }])\n\ntype Pagination = TableState['pagination']\n\nconst pageParam: IPage = {\n  page: 1,\n  total: 0,\n  page_size: 50\n}\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\n\nconst param = reactive<FirmwareQueryParam>({\n  product_version: '',\n  device_name: '',\n  firmware_status: FirmwareStatusEnum.NONE\n})\n\nonMounted(() => {\n  getAllFirmwares(pageParam)\n  for (const key in DeviceNameEnum) {\n    const value = DeviceNameEnum[key]\n    deviceNameList.value.push({ label: value, value: value })\n  }\n})\n\nfunction refreshData (page: Pagination) {\n  pageParam.page = page?.current!\n  pageParam.page_size = page?.pageSize!\n  getAllFirmwares(pageParam)\n}\n\nfunction getAllFirmwares (page: IPage) {\n  getFirmwares(workspaceId, page, param).then(res => {\n    const firmwareList: Firmware[] = res.data.list\n    data.firmware = firmwareList\n    paginationProp.total = res.data.pagination.total\n    paginationProp.current = res.data.pagination.page\n  })\n}\n\nconst sVisible = ref(false)\nconst uploadParam = reactive<FirmwareUploadParam>({\n  device_name: [],\n  release_note: '',\n  status: true\n})\n\nconst rules = {\n  status: [{ required: true }],\n  release_note: [{ required: true, message: 'Please input release note.' }],\n  device_name: [{ required: true, message: 'Please select which models this firmware belongs to.' }]\n}\ninterface FileItem {\n  uid: string;\n  name?: string;\n  status?: string;\n  response?: string;\n  url?: string;\n}\n\ninterface FileInfo {\n  file: FileItem;\n  fileList: FileItem[];\n}\nconst fileList = ref<FileItem[]>([])\n\nfunction beforeUpload (file: FileItem) {\n  if (!file.name || !file.name?.endsWith('.zip')) {\n    message.error('Format error. Please select zip file.')\n    return false\n  }\n  fileList.value = [file]\n  return false\n}\n\nconst formRef = ref()\nfunction removeFile (file: FileItem) {\n  fileList.value = []\n}\nfunction onCancel () {\n  formRef.value.resetFields()\n  fileList.value = []\n  sVisible.value = false\n}\n\nconst uploadFile = async () => {\n  if (fileList.value.length === 0) {\n    message.error('Please select at least one file.')\n  }\n  let uploading: string\n  formRef.value.validate().then(async () => {\n    const file: FileItem = fileList.value[0]\n    const fileData = new FormData()\n    fileData.append('file', file as any, file.name)\n    Object.keys(uploadParam).forEach((key) => {\n      const val = uploadParam[key as keyof FirmwareUploadParam]\n      if (val instanceof Array) {\n        val.forEach((value) => {\n          fileData.append(key, value)\n        })\n      } else {\n        fileData.append(key, val.toString())\n      }\n    })\n    const timestamp = new Date().getTime()\n    uploading = (file.name ?? 'uploding') + timestamp\n    notification.open({\n      key: uploading,\n      message: `Uploading  ${moment().format()}`,\n      description: `[${file.name}] is uploading... `,\n      duration: null\n    })\n    importFirmareFile(workspaceId, fileData).then((res) => {\n      if (res.code === 0) {\n        notification.success({\n          message: `Uploaded  ${moment().format()}`,\n          description: `[${file.name}] file uploaded successfully. Duration: ${moment.duration(new Date().getTime() - timestamp).asSeconds()}`,\n          duration: null\n        })\n        getAllFirmwares(pageParam)\n      } else {\n        notification.error({\n          message: `Failed to upload [${file.name}]. Check and try again.`,\n          description: `Error message: ${res.message} ${moment().format()}`,\n          style: { color: commonColor.FAIL },\n          duration: null,\n        })\n      }\n    }).finally(() => {\n      notification.close(uploading)\n    })\n    fileList.value = []\n    formRef.value.resetFields()\n    sVisible.value = false\n  })\n}\n\n</script>\n<style>\n\n.table {\n    background-color: white;\n    margin: 20px;\n    padding: 20px;\n    height: 88vh;\n}\n.table-striped {\n  background-color: #f7f9fa;\n}\n.ant-table {\n  border-top: 1px solid rgb(0,0,0,0.06);\n  border-bottom: 1px solid rgb(0,0,0,0.06);\n}\n.ant-table-tbody tr td {\n  border: 0;\n}\n.ant-table td {\n  white-space: nowrap;\n}\n.ant-table-thead tr th {\n  background: white !important;\n  border: 0;\n}\nth.ant-table-selection-column {\n  background-color: white !important;\n}\n.ant-table-header {\n  background-color: white !important;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/devices.vue",
    "content": "\n<template>\n  <a-menu v-model:selectedKeys=\"current\" mode=\"horizontal\" @select=\"select\">\n    <a-menu-item :key=\"EDeviceTypeName.Aircraft\" class=\"ml20\">\n      Aircraft\n    </a-menu-item>\n    <a-menu-item :key=\"EDeviceTypeName.Dock\">\n      Dock\n    </a-menu-item>\n  </a-menu>\n  <div class=\"device-table-wrap table flex-display flex-column\">\n    <a-table :columns=\"columns\" :data-source=\"data.device\" :pagination=\"paginationProp\" @change=\"refreshData\" row-key=\"device_sn\" :expandedRowKeys=\"expandRows\"\n    :row-selection=\"rowSelection\" :rowClassName=\"rowClassName\" :scroll=\"{ x: '100%', y: 600 }\"\n      :expandIcon=\"expandIcon\" :loading=\"loading\">\n      <template v-for=\"col in ['nickname']\" #[col]=\"{ text, record }\" :key=\"col\">\n        <div>\n          <a-input\n            v-if=\"editableData[record.device_sn]\"\n            v-model:value=\"editableData[record.device_sn][col]\"\n            style=\"margin: -5px 0\"\n          />\n          <template v-else>\n            <a-tooltip :title=\"text\">\n              {{ text }}\n            </a-tooltip>\n          </template>\n        </div>\n      </template>\n      <template v-for=\"col in ['sn', 'workspace']\" #[col]=\"{ text }\" :key=\"col\">\n        <a-tooltip :title=\"text\">\n            <span>{{ text }}</span>\n        </a-tooltip>\n      </template>\n      <!-- 固件版本 -->\n      <template #firmware_version=\"{ record }\">\n        <span v-if=\"judgeCurrentType(EDeviceTypeName.Dock)\">\n          <DeviceFirmwareUpgrade :device=\"record\"\n                                  class=\"table-flex-col\"\n                                  @device-upgrade=\"onDeviceUpgrade\"\n                                 />\n        </span>\n        <span v-else>\n          {{ record.firmware_version }}\n        </span>\n      </template>\n      <!-- 状态 -->\n      <template #status=\"{ text }\">\n        <span v-if=\"text\" class=\"flex-row flex-align-center\">\n            <span class=\"mr5\" style=\"width: 12px; height: 12px; border-radius: 50%; background-color: green;\" />\n            <span>Online</span>\n        </span>\n        <span class=\"flex-row flex-align-center\" v-else>\n            <span class=\"mr5\" style=\"width: 12px; height: 12px; border-radius: 50%; background-color: red;\" />\n            <span>Offline</span>\n        </span>\n      </template>\n      <!-- 操作 -->\n      <template #action=\"{ record }\">\n        <div class=\"editable-row-operations\">\n          <!-- 编辑态操作 -->\n          <div v-if=\"editableData[record.device_sn]\">\n            <a-tooltip title=\"Confirm changes\">\n              <span @click=\"save(record)\" style=\"color: #28d445;\"><CheckOutlined /></span>\n            </a-tooltip>\n            <a-tooltip title=\"Modification canceled\">\n              <span @click=\"() => delete editableData[record.device_sn]\" style=\"color: #e70102;\"><CloseOutlined /></span>\n            </a-tooltip>\n          </div>\n          <!-- 非编辑态操作 -->\n          <div v-else class=\"flex-align-center flex-row\" style=\"color: #2d8cf0\">\n            <a-tooltip v-if=\"current.indexOf(EDeviceTypeName.Dock) !== -1\" title=\"设备日志\">\n              <CloudServerOutlined @click=\"showDeviceLogUploadRecord(record)\"/>\n            </a-tooltip>\n            <a-tooltip v-if=\"current.indexOf(EDeviceTypeName.Dock) !== -1\" title=\"Hms Info\">\n              <FileSearchOutlined @click=\"showHms(record)\"/>\n            </a-tooltip>\n            <a-tooltip title=\"Edit\">\n              <EditOutlined @click=\"edit(record)\"/>\n            </a-tooltip>\n            <a-tooltip title=\"Delete\">\n              <DeleteOutlined @click=\"() => { deleteTip = true, deleteSn = record.device_sn }\"/>\n            </a-tooltip>\n          </div>\n        </div>\n      </template>\n\n    </a-table>\n    <a-modal v-model:visible=\"deleteTip\" width=\"450px\" :closable=\"false\" centered :okButtonProps=\"{ danger: true }\" @ok=\"unbind\">\n        <p class=\"pt10 pl20\" style=\"height: 50px;\">Delete device from workspace?</p>\n        <template #title>\n            <div class=\"flex-row flex-justify-center\">\n                <span>Delete devices</span>\n            </div>\n        </template>\n    </a-modal>\n\n    <!-- 设备升级 -->\n    <DeviceFirmwareUpgradeModal title=\"设备升级\"\n      v-model:visible=\"deviceFirmwareUpgradeModalVisible\"\n      :device=\"selectedDevice\"\n      @ok=\"onUpgradeDeviceOk\"\n    ></DeviceFirmwareUpgradeModal>\n\n    <!-- 设备日志上传记录 -->\n    <DeviceLogUploadRecordDrawer\n      v-model:visible=\"deviceLogUploadRecordVisible\"\n      :device=\"currentDevice\"\n    ></DeviceLogUploadRecordDrawer>\n\n    <!-- hms 信息 -->\n    <DeviceHmsDrawer\n       v-model:visible=\"hmsVisible\"\n      :device=\"currentDevice\">\n    </DeviceHmsDrawer>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'\nimport { h, onMounted, reactive, ref, UnwrapRef } from 'vue'\nimport { IPage } from '/@/api/http/type'\nimport { BindBody, bindDevice, getBindingDevices, unbindDevice, updateDevice } from '/@/api/manage'\nimport { EDeviceTypeName, ELocalStorageKey } from '/@/types'\nimport { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue'\nimport { Device, DeviceFirmwareStatusEnum } from '/@/types/device'\nimport DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue'\nimport DeviceFirmwareUpgradeModal from '/@/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue'\nimport { useDeviceFirmwareUpgrade } from '/@/components/devices/device-upgrade/use-device-upgrade'\nimport { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-device-upgrade-event'\nimport { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'\nimport DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue'\nimport DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue'\nimport { message, notification } from 'ant-design-vue'\n\ninterface DeviceData {\n  device: Device[]\n}\n\nconst loading = ref(true)\nconst deleteTip = ref<boolean>(false)\nconst deleteSn = ref<string>()\nconst columns: ColumnProps[] = [\n  { title: 'Model', dataIndex: 'device_name', width: 100, className: 'titleStyle' },\n  { title: 'SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } },\n  {\n    title: 'Name',\n    dataIndex: 'nickname',\n    width: 100,\n    sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),\n    className: 'titleStyle',\n    ellipsis: true,\n    slots: { customRender: 'nickname' }\n  },\n  { title: 'Firmware Version', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } },\n  { title: 'Status', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } },\n  {\n    title: 'Workspace',\n    dataIndex: 'workspace_name',\n    width: 100,\n    className: 'titleStyle',\n    ellipsis: true,\n    slots: { customRender: 'workspace' },\n    customRender: ({ text, record, index }) => {\n      const obj = {\n        children: text,\n        props: {} as any,\n      }\n      if (current.value.indexOf(EDeviceTypeName.Dock) !== -1) {\n        if (record.domain === EDeviceTypeName.Aircraft) {\n          obj.children = ''\n        }\n      }\n      return obj\n    }\n  },\n  { title: 'Joined', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' },\n  { title: 'Last Online', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' },\n  {\n    title: 'Actions',\n    dataIndex: 'actions',\n    width: 100,\n    className: 'titleStyle',\n    slots: { customRender: 'action' }\n  },\n]\n\nconst expandIcon = (props: any) => {\n  if (judgeCurrentType(EDeviceTypeName.Dock) && !props.expanded) {\n    return h('div',\n      {\n        style: 'border-left: 2px solid rgb(200,200,200); border-bottom: 2px solid rgb(200,200,200); height: 16px; width: 16px; float: left;',\n        class: 'mt-5 ml0',\n      })\n  }\n}\n\nconst rowClassName = (record: any, index: number) => {\n  const className = []\n  if ((index & 1) === 0) {\n    className.push('table-striped')\n  }\n  if (record.domain !== EDeviceTypeName.Dock) {\n    className.push('child-row')\n  }\n  return className.toString().replaceAll(',', ' ')\n}\n\nconst expandRows = ref<string[]>([])\nconst data = reactive<DeviceData>({\n  device: []\n})\n\nconst paginationProp = reactive({\n  pageSizeOptions: ['20', '50', '100'],\n  showQuickJumper: true,\n  showSizeChanger: true,\n  pageSize: 50,\n  current: 1,\n  total: 0\n})\n\n// 获取分页信息\nfunction getPaginationBody () {\n  return {\n    page: paginationProp.current,\n    page_size: paginationProp.pageSize\n  } as IPage\n}\n\nconst rowSelection = {\n  onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => {\n    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)\n  },\n  onSelect: (record: any, selected: boolean, selectedRows: []) => {\n    console.log(record, selected, selectedRows)\n  },\n  onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => {\n    console.log(selected, selectedRows, changeRows)\n  },\n  getCheckboxProps: (record: any) => ({\n    disabled: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock,\n    style: judgeCurrentType(EDeviceTypeName.Dock) && record.domain !== EDeviceTypeName.Dock ? 'display: none' : ''\n  }),\n}\ntype Pagination = TableState['pagination']\n\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''\nconst editableData: UnwrapRef<Record<string, Device>> = reactive({})\nconst current = ref([EDeviceTypeName.Aircraft])\n\nfunction judgeCurrentType (type: EDeviceTypeName): boolean {\n  return current.value.indexOf(type) !== -1\n}\n\n// 设备升级\nconst {\n  deviceFirmwareUpgradeModalVisible,\n  selectedDevice,\n  onDeviceUpgrade,\n  onUpgradeDeviceOk\n} = useDeviceFirmwareUpgrade(workspaceId)\n\nfunction onDeviceUpgradeWs (payload: DeviceCmdExecuteInfo) {\n  updateDevicesByWs(data.device, payload)\n}\n\nfunction updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) {\n  if (!devices || devices.length <= 0) {\n    return\n  }\n  for (let i = 0; i < devices.length; i++) {\n    if (devices[i].device_sn === payload.sn) {\n      if (!payload.output) return\n      const { status, progress, ext } = payload.output\n      if (status === DeviceCmdExecuteStatus.Sent || status === DeviceCmdExecuteStatus.InProgress) { // 升级中\n        const rate = ext?.rate ? (ext.rate / 1024).toFixed(2) + 'kb/s' : ''\n        devices[i].firmware_status = DeviceFirmwareStatusEnum.DuringUpgrade\n        devices[i].firmware_progress = (progress?.percent || 0) + '% ' + rate\n      } else { // 终态：成功，失败，超时\n        if (status === DeviceCmdExecuteStatus.Failed || status === DeviceCmdExecuteStatus.Timeout) {\n          notification.error({\n            message: `(${payload.sn}) Upgrade failed`,\n            description: `Error Code: ${payload.result}`,\n            duration: null\n          })\n        }\n        // 拉取列表\n        getDevices(current.value[0], true)\n      }\n      return\n    }\n    if (devices[i].children) {\n      updateDevicesByWs(devices[i].children || [], payload)\n    }\n  }\n}\n\nuseDeviceUpgradeEvent(onDeviceUpgradeWs)\n\n// 获取设备列表信息\nfunction getDevices (domain: number, closeLoading?: boolean) {\n  if (!closeLoading) {\n    loading.value = true\n  }\n  getBindingDevices(workspaceId, getPaginationBody(), domain).then(res => {\n    if (res.code !== 0) {\n      return\n    }\n    const resData: Device[] = res.data.list\n    expandRows.value = []\n    resData.forEach((val: any) => {\n      if (val.children) {\n        val.children = [val.children]\n      }\n      if (judgeCurrentType(EDeviceTypeName.Dock)) {\n        expandRows.value.push(val.device_sn)\n      }\n    })\n    data.device = resData\n    paginationProp.total = res.data.pagination.total\n    paginationProp.current = res.data.pagination.page\n    paginationProp.pageSize = res.data.pagination.page_size\n    loading.value = false\n  })\n}\n\nfunction refreshData (page: Pagination) {\n  paginationProp.current = page?.current!\n  paginationProp.pageSize = page?.pageSize!\n  getDevices(current.value[0])\n}\n\n// 编辑\nfunction edit (record: Device) {\n  editableData[record.device_sn] = record\n}\n\n// 保存\nfunction save (record: Device) {\n  delete editableData[record.device_sn]\n  updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn)\n}\n\n// 删除\nfunction showDeleteTip (sn: any) {\n  deleteTip.value = true\n}\n\n// 解绑\nfunction unbind () {\n  deleteTip.value = false\n  unbindDevice(deleteSn.value?.toString()!).then(res => {\n    if (res.code !== 0) {\n      return\n    }\n    getDevices(current.value[0])\n  })\n}\n\n// 选择设备\nfunction select (item: any) {\n  getDevices(item.key)\n}\n\nconst currentDevice = ref({} as Device)\n// 设备日志\nconst deviceLogUploadRecordVisible = ref(false)\nfunction showDeviceLogUploadRecord (dock: Device) {\n  deviceLogUploadRecordVisible.value = true\n  currentDevice.value = dock\n}\n\n// 健康状态\nconst hmsVisible = ref<boolean>(false)\n\nfunction showHms (dock: Device) {\n  hmsVisible.value = true\n  currentDevice.value = dock\n}\n\nonMounted(() => {\n  getDevices(current.value[0])\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.device-table-wrap{\n  .editable-row-operations{\n    div > span {\n      margin-right: 10px;\n    }\n  }\n}\n</style>\n\n<style lang=\"scss\">\n.table {\n  background-color: white;\n  margin: 20px;\n  padding: 20px;\n  height: 88vh;\n}\n.table-striped {\n  background-color: #f7f9fa;\n}\n.ant-table {\n  border-top: 1px solid rgb(0,0,0,0.06);\n  border-bottom: 1px solid rgb(0,0,0,0.06);\n}\n.ant-table-tbody tr td {\n  border: 0;\n}\n.ant-table td {\n  white-space: nowrap;\n}\n.ant-table-thead tr th {\n  background: white !important;\n  border: 0;\n}\nth.ant-table-selection-column {\n  background-color: white !important;\n}\n.ant-table-header {\n  background-color: white !important;\n}\n.child-row {\n  height: 70px;\n}\n.notice {\n  background: $success;\n  overflow: hidden;\n  cursor: pointer;\n}\n.caution {\n  background: orange;\n  cursor: pointer;\n  overflow: hidden;\n}\n.warn {\n  background: red;\n  cursor: pointer;\n  overflow: hidden;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/dock.vue",
    "content": "<template>\n  <div class=\"height-100\">\n    <div style=\"height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;\">\n      <a-row>\n        <a-col :span=\"1\"></a-col>\n        <a-col :span=\"22\">Devices</a-col>\n        <a-col :span=\"1\"></a-col>\n      </a-row>\n    </div>\n    <div class=\"scrollbar height-100\" :style=\"{ height: scorllHeight + 'px'}\">\n      <div id=\"data\" class=\" uranus-scrollbar\" v-if=\"docksData.data.length !== 0\" @scroll=\"onScroll\">\n        <div v-for=\"dock in docksData.data\" :key=\"dock.device_sn\">\n          <div class=\"panel\" style=\"padding-top: 5px;\" @click=\"selectDock(dock)\">\n            <div class=\"title\">\n              <a-tooltip :title=\"dock.nickname\">\n                <div class=\"pr10\" style=\"width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ dock.nickname }}</div>\n              </a-tooltip>\n            </div>\n            <div class=\"ml10 mt5\" style=\"color: hsla(0,0%,100%,0.65);\">\n              <span><RocketOutlined /></span>\n              <span class=\"ml5\">{{ dock.children?.nickname ?? 'No drone' }}</span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div v-else>\n        <a-empty :image-style=\"{ height: '60px', marginTop: '60px' }\" />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { reactive } from '@vue/reactivity'\nimport { message } from 'ant-design-vue'\nimport { onMounted, ref } from 'vue'\nimport { deleteWaylineFile, downloadWaylineFile, getWaylineFiles } from '/@/api/wayline'\nimport { EDeviceTypeName, ELocalStorageKey } from '/@/types'\nimport { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'\nimport { Device } from '/@/types/device'\nimport { useMyStore } from '/@/store'\nimport { getBindingDevices } from '/@/api/manage'\nimport { IPage } from '/@/api/http/type'\n\nconst store = useMyStore()\n\nconst docksData = reactive({\n  data: [] as Device[]\n})\n\nconst workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\nconst scorllHeight = ref()\nconst canRefresh = ref(true)\n\nonMounted(() => {\n  const parent = document.getElementsByClassName('scrollbar').item(0)?.parentNode as HTMLDivElement\n  scorllHeight.value = document.body.clientHeight - parent.firstElementChild!.clientHeight\n  getDocks()\n  const key = setInterval(() => {\n    const data = document.getElementById('data')?.lastElementChild as HTMLDivElement\n    if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page || scorllHeight.value + 50 <= data?.clientHeight + data?.offsetTop) {\n      clearInterval(key)\n      return\n    }\n    body.page++\n    getDocks()\n  }, 1000)\n})\nconst body: IPage = {\n  page: 1,\n  total: -1,\n  page_size: 10,\n}\n\nasync function getDocks () {\n  if (!canRefresh.value) {\n    return\n  }\n  canRefresh.value = false\n\n  await getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {\n    if (res.code !== 0) {\n      return\n    }\n    docksData.data.push(...res.data.list)\n    body.page = res.data.pagination.page\n    body.page_size = res.data.pagination.page_size\n    body.total = res.data.pagination.total\n  }).finally(() => {\n    canRefresh.value = true\n  })\n}\n\nfunction onScroll (e: any) {\n  const element = e.srcElement\n  if (element.scrollTop + element.clientHeight >= element.scrollHeight - 5 && Math.ceil(body.total / body.page_size) > body.page && canRefresh.value) {\n    body.page++\n    getDocks()\n  }\n}\n\nfunction selectDock (dock: Device) {\n  store.commit('SET_SELECT_DOCK_INFO', dock)\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.panel {\n  background: #3c3c3c;\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: 10px;\n  height: 70px;\n  width: 95%;\n  font-size: 13px;\n  border-radius: 2px;\n  cursor: pointer;\n  .title {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    height: 30px;\n    font-weight: bold;\n    margin: 0px 10px 0 10px;\n  }\n}\n.uranus-scrollbar {\n  overflow: auto;\n  scrollbar-width: thin;\n  scrollbar-color: #c5c8cc transparent;\n  height: 100%;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/flight-area.vue",
    "content": "<template>\n  <div class=\"project-flight-area-wrapper height-100\">\n    <a-spin :spinning=\"loading\" :delay=\"300\" tip=\"loading\" size=\"large\" class=\"height-100\">\n      <Title title=\"Custom Flight Area\" />\n      <FlightAreaPanel :data=\"flightAreaList\" @location-area=\"clickArea\" @delete-area=\"deleteAreaById\"/>\n      <DividerLine />\n      <FlightAreaSyncPanel />\n    </a-spin>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref } from 'vue'\nimport Title from '/@/components/workspace/Title.vue'\nimport DividerLine from '/@/components/workspace/DividerLine.vue'\nimport FlightAreaPanel from '/@/components/flight-area/FlightAreaPanel.vue'\nimport FlightAreaSyncPanel from '/@/components/flight-area/FlightAreaSyncPanel.vue'\nimport { GetFlightArea, deleteFlightArea, getFlightAreaList } from '/@/api/flight-area'\nimport { useGMapCover } from '/@/hooks/use-g-map-cover'\nimport { useMapTool } from '/@/hooks/use-map-tool'\nimport { EFlightAreaType, EGeometryType, FlightAreaUpdate } from '/@/types/flight-area'\nimport { useFlightArea } from '/@/components/flight-area/use-flight-area'\nimport { useFlightAreaUpdateEvent } from '/@/components/flight-area/use-flight-area-update'\n\nconst loading = ref(false)\nconst flightAreaList = ref<GetFlightArea[]>([])\nlet useGMapCoverHook = useGMapCover()\nlet useMapToolHook = useMapTool()\n\nonMounted(() => {\n  getDataList()\n})\nconst { getGcj02 } = useFlightArea()\n\nconst initMapFlightArea = () => {\n  useMapToolHook = useMapTool()\n  useGMapCoverHook = useGMapCover()\n  flightAreaList.value.forEach(area => {\n    updateMapFlightArea(area)\n  })\n}\n\nconst updateMapFlightArea = (area: GetFlightArea) => {\n  switch (area.content.geometry.type) {\n    case EGeometryType.CIRCLE:\n      useGMapCoverHook.updateFlightAreaCircle(area.area_id, area.name, area.content.geometry.radius, getGcj02(area.content.geometry.coordinates), area.status, area.type)\n      break\n    case 'Polygon':\n      useGMapCoverHook.updateFlightAreaPolygon(area.area_id, area.name, getGcj02(area.content.geometry.coordinates[0]), area.status, area.type)\n      break\n  }\n}\n\nconst getDataList = () => {\n  loading.value = true\n  getFlightAreaList().then(res => {\n    flightAreaList.value = res.data\n    setTimeout(initMapFlightArea, 2000)\n  }).finally(() => {\n    loading.value = false\n  })\n}\n\nconst deleteAreaById = (areaId: string) => {\n  deleteFlightArea(areaId)\n}\n\nconst deleteArea = (area: FlightAreaUpdate) => {\n  flightAreaList.value = flightAreaList.value.filter(data => data.area_id !== area.area_id)\n  useGMapCoverHook.removeCoverFromMap(area.area_id)\n}\n\nconst updateArea = (area: FlightAreaUpdate) => {\n  flightAreaList.value = flightAreaList.value.map(data => data.area_id === area.area_id ? area : data)\n  updateMapFlightArea(area as GetFlightArea)\n}\n\nconst addArea = (area: FlightAreaUpdate) => {\n  flightAreaList.value.push(area as GetFlightArea)\n  updateMapFlightArea(area as GetFlightArea)\n}\n\nuseFlightAreaUpdateEvent(addArea, deleteArea, updateArea)\n\nconst clickArea = (area: GetFlightArea) => {\n  console.info(area)\n  let coordinate\n  switch (area.content.geometry.type) {\n    case EGeometryType.CIRCLE:\n      coordinate = getGcj02(area.content.geometry.coordinates)\n      break\n    case 'Polygon':\n      coordinate = useGMapCoverHook.calcPolygonPosition(getGcj02(area.content.geometry.coordinates[0]))\n      break\n  }\n  useMapToolHook.panTo(coordinate)\n}\n</script>\n"
  },
  {
    "path": "src/pages/page-web/projects/layer.vue",
    "content": "<template>\n  <div class=\"project-layer-wrapper height-100\">\n    <div style=\"height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;\">\n      <a-row>\n        <a-col :span=\"1\"></a-col>\n        <a-col :span=\"22\">Annotations</a-col>\n        <a-col :span=\"1\"></a-col>\n      </a-row>\n    </div>\n    <div class=\"scrollbar\" :style=\"{ height: scorllHeight + 'px'}\">\n    <LayersTree\n      :layer-data=\"mapLayers\"\n      class=\"project-layer-content\"\n      @check=\"checkLayer\"\n      @select=\"selectLayer\"\n      v-model:selectedKeys=\"selectedKeys\"\n      v-model:checkedKeys=\"checkedKeys\"\n    />\n    </div>\n    <a-drawer\n      title=\"Map Element\"\n      placement=\"right\"\n      :closable=\"true\"\n      v-model:visible=\"visible\"\n      :mask=\"false\"\n      wrapClassName=\"drawer-element-wrapper\"\n      @close=\"closeDrawer\"\n      width=\"300\"\n    >\n      <div class=\"drawer-element-content\">\n        <div class=\"name element-item\">\n          <span class=\"title\">Name:</span>\n          <a-input\n            v-model:value=\"layerState.layerName\"\n            style=\"width:120px\"\n            placeholder=\"element name\"\n            @change=\"changeLayer\"\n          />\n        </div>\n        <div\n          class=\"longitude element-item\"\n          v-if=\"layerState.currentType === geoType.Point\"\n        >\n          <span class=\"title\">Longitude:</span>\n          <a-input\n            v-model:value=\"layerState.longitude\"\n            style=\"width:120px\"\n            placeholder=\"longitude\"\n            @change=\"changeLayer\"\n          />\n        </div>\n        <div\n          class=\"latitude element-item\"\n          v-if=\"layerState.currentType === geoType.Point\"\n        >\n          <span class=\"title\">Latitude:</span>\n          <a-input\n            v-model:value=\"layerState.latitude\"\n            style=\"width:120px\"\n            placeholder=\"latitude\"\n            @change=\"changeLayer\"\n          />\n        </div>\n        <div class=\"color-content\">\n          <span class=\"mr30\">Color: </span>\n          <div\n            v-for=\"item in colors\"\n            :key=\"item.id\"\n            class=\"color-item\"\n            :style=\"'background:' + item.color\"\n            @click=\"changeColor(item)\"\n          >\n            <svg-icon\n              v-if=\"item.color === layerState.color\"\n              :size=\"18\"\n              name=\"check\"\n            ></svg-icon>\n          </div>\n        </div>\n      </div>\n      <div class=\"flex-row flex-justify-around flex-align-center mt20\">\n        <a-button type=\"primary\" @click=\"deleteElement\">Delete</a-button>\n      </div>\n    </a-drawer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, reactive, ref, watch } from 'vue'\nimport {\n  deleteElementReq,\n  getElementGroupsReq,\n  updateElementsReq\n} from '/@/api/layer'\nimport LayersTree from '/@/components/LayersTree.vue'\nimport { MapDoodleColor, MapElementEnum } from '/@/constants/map'\nimport { useGMapCover } from '/@/hooks/use-g-map-cover'\nimport { getRoot } from '/@/root'\nimport { useMyStore } from '/@/store'\nimport { GeojsonCoordinate, LayerResource } from '/@/types/map'\nimport { Color, GeoType } from '/@/types/mapLayer'\nimport { generatePoint } from '/@/utils/genjson'\nimport { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform'\n\nconst root = getRoot()\nconst store = useMyStore()\nlet useGMapCoverHook = useGMapCover(store)\nconsole.log('store', store)\nconst mapLayers = ref(store.state.Layers)\nconst checkedKeys = ref<string[]>([])\nconst selectedKeys = ref<string[]>([])\nconst selectedKey = ref<string>('')\nconst selectedLayer = ref<any>(null)\nconst visible = ref<boolean>(false)\nstore.commit('SET_DRAW_VISIBLE_INFO', visible.value)\nconst geoType = GeoType\nconst layerState = reactive({\n  layerName: '',\n  layerId: '',\n  longitude: 0,\n  latitude: 0,\n  currentType: '', // “LineString”,\"Polygon\",\"Point\"\n  color: '#212121'\n})\nconst colors = ref<Color[]>([\n  { id: 1, name: 'BLUE', color: '#2D8CF0', selected: true },\n  { id: 2, name: 'GREEN', color: '#19BE6B', selected: false },\n  { id: 3, name: 'YELLOW', color: '#FFBB00', selected: false },\n  { id: 4, name: 'ORANGE', color: '#B620E0', selected: false },\n  { id: 5, name: 'RED', color: '#E23C39', selected: false },\n  { id: 6, name: 'NAME_DEFAULT', color: '#212121', selected: false }\n])\nconst scorllHeight = ref()\n\nasync function getAllElement () {\n  getElementGroups('init')\n  setTimeout(() => {\n    useGMapCoverHook = useGMapCover()\n    initMapCover()\n  }, 1000)\n}\nfunction initMapCover () {\n  mapLayers.value.forEach(item => {\n    if (item.elements) {\n      setMapCoverByElement(item.elements)\n    }\n  })\n}\nwatch(\n  () => store.state.Layers,\n  newData => {\n    mapLayers.value = newData\n  },\n  {\n    deep: true\n  }\n)\nfunction setMapCoverByElement (elements: LayerResource[]) {\n  elements.forEach(element => {\n    const name = element.name\n    const color = element.resource?.content.properties.color\n    const type = element.resource?.type as number\n    updateMapElement(element, name, color)\n  })\n}\nfunction updateMapElement (\n  element: LayerResource,\n  name: string,\n  color: string | undefined\n) {\n  const geoType = element.resource?.content.geometry.type\n  const id = element.id\n  const type = element.resource?.type as number\n  if (MapElementEnum.PIN === type) {\n    const coordinates = element.resource?.content.geometry\n      .coordinates as GeojsonCoordinate\n    useGMapCoverHook.updatePinElement(id, name, coordinates, color)\n  } else if (MapElementEnum.LINE === type && geoType === 'LineString') {\n    const coordinates = element.resource?.content.geometry\n      .coordinates as GeojsonCoordinate[]\n    useGMapCoverHook.updatePolylineElement(id, name, coordinates, color)\n  } else if (MapElementEnum.POLY === type && geoType === 'Polygon') {\n    const coordinates = element.resource?.content.geometry\n      .coordinates as GeojsonCoordinate[][]\n    useGMapCoverHook.updatePolygonElement(id, name, coordinates, color)\n  }\n}\nfunction checkLayer (keys: string[]) {\n  console.log('checkLayer', keys, selectedKeys.value, checkedKeys.value)\n}\nfunction selectLayer (keys: string[], e) {\n  // console.log('selectLayer', e.node.eventKey, e.selected)\n  if (e.selected) {\n    selectedKey.value = e.node.eventKey\n    selectedLayer.value = getCurrentLayer(selectedKey.value)\n    setBaseInfo()\n  }\n  visible.value = e.selected\n  store.commit('SET_DRAW_VISIBLE_INFO', visible.value)\n  // store.dispatch('updateElement', { type: 'is_select', id: e.node.eventKey, bool: e.selected })\n}\nfunction getCurrentLayer (id: string) {\n  const Layers = store.state.Layers\n  const key = id.replaceAll('resource__', '')\n  // console.log('selectedKey.value', selectedKey.value)\n  let layer = null\n  const findCan = function (V) {\n    V.forEach(item => {\n      if (item.id === key) {\n        layer = item\n      }\n      if (item.elements) {\n        findCan(item.elements)\n      }\n    })\n  }\n  findCan(Layers)\n  // const layer = Layers.find(item => item.elements.find(el => el.id === key))\n  console.log('layer', layer)\n  return layer\n}\nfunction setBaseInfo () {\n  const layer = selectedLayer.value\n  if (layer) {\n    const geoType = layer.resource?.content.geometry.type\n    // “LineString”,\"Polygon\",\"Point\"\n    layerState.currentType = geoType\n    layerState.layerName = layer.name\n    layerState.layerId = layer.id\n    layerState.color = layer.resource?.content.properties.color\n    let coordinate: GeojsonCoordinate\n    switch (geoType) {\n      case GeoType.Point:\n        coordinate = gcj02towgs84(layer.resource?.content.geometry.coordinates[0], layer.resource?.content.geometry.coordinates[1]) as GeojsonCoordinate\n        layerState.longitude = coordinate[0]\n        layerState.latitude = coordinate[1]\n        break\n      case GeoType.LineString:\n        break\n      case GeoType.Polygon:\n        break\n    }\n  }\n}\nonMounted(() => {\n  const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement\n  const parent = element?.parentNode as HTMLDivElement\n  scorllHeight.value = parent?.clientHeight - parent.firstElementChild!.clientHeight\n  getAllElement()\n})\nfunction closeDrawer () {\n  store.commit('SET_DRAW_VISIBLE_INFO', false)\n  selectedKeys.value = []\n}\nfunction changeColor (color: Color) {\n  layerState.color = color.color\n\n  updateElements()\n}\nfunction changeLayer (val: string) {\n  updateElements()\n}\nasync function deleteElement () {\n  const elementid = selectedLayer.value.id\n\n  await deleteElementReq(elementid, {}).then(async (res: any) => {\n    // console.log('delete element res:', res)\n    if (res.code !== 0) {\n      console.warn(res)\n      return\n    }\n    visible.value = false\n    store.commit('SET_DRAW_VISIBLE_INFO', visible.value)\n    useGMapCoverHook.removeCoverFromMap(elementid)\n    getElementGroups()\n  })\n}\nasync function getElementGroups (type?: string) {\n  const result = await getElementGroupsReq({\n    groupId: '',\n    isDistributed: true\n  })\n  mapLayers.value = result.data\n  mapLayers.value = updateWgs84togcj02()\n  if (type && type === 'init') {\n    store.dispatch('setLayerInfo', mapLayers.value)\n  }\n  store.commit('SET_LAYER_INFO', mapLayers.value)\n}\nasync function updateElements () {\n  let content = null\n  if (layerState.currentType === GeoType.Point) {\n    const position = {\n      height: 0,\n      latitude: Number(layerState.latitude || 0),\n      longitude: Number(layerState.longitude || 0)\n    }\n    const cxt = generatePoint(position, {\n      color: layerState.color || MapDoodleColor.PinColor,\n      clampToGround: true\n    })\n    content = {\n      type: MapElementEnum.PIN,\n      geometry: cxt.geometry,\n      properties: cxt.properties\n    }\n    const currentLayer = selectedLayer.value\n    currentLayer.resource.content = content\n    selectedLayer.value = currentLayer\n  } else {\n    const currentLayer = selectedLayer.value\n    content = currentLayer.resource.content\n    content.properties.color = layerState.color\n  }\n  updateMapElement(selectedLayer.value, layerState.layerName, layerState.color)\n  const result = await updateElementsReq(layerState.layerId, {\n    name: layerState.layerName,\n    content: content\n  })\n  getElementGroups()\n}\n\nfunction updateWgs84togcj02 () {\n  const layers = mapLayers.value\n  layers.forEach(item => {\n    if (item.elements) {\n      item.elements.forEach(ele => {\n        updateCoordinates('wgs84-gcj02', ele)\n      })\n    }\n  })\n  return layers\n}\nfunction updateCoordinates (transformType: string, element: LayerResource) {\n  const geoType = element.resource?.content.geometry.type\n  const type = element.resource?.type as number\n  if (element.resource) {\n    if (MapElementEnum.PIN === type) {\n      const coordinates = element.resource?.content.geometry\n        .coordinates as GeojsonCoordinate\n      if (transformType === 'wgs84-gcj02') {\n        const transResult = wgs84togcj02(\n          coordinates[0],\n          coordinates[1]\n        ) as GeojsonCoordinate\n        element.resource.content.geometry.coordinates = transResult\n      } else if (transformType === 'gcj02-wgs84') {\n        const transResult = gcj02towgs84(\n          coordinates[0],\n          coordinates[1]\n        ) as GeojsonCoordinate\n        element.resource.content.geometry.coordinates = transResult\n      }\n    } else if (MapElementEnum.LINE === type) {\n      const coordinates = element.resource?.content.geometry\n        .coordinates as GeojsonCoordinate[]\n      if (transformType === 'wgs84-gcj02') {\n        coordinates.forEach((coordinate, i, arr) => {\n          arr[i] = wgs84togcj02(\n            coordinate[0],\n            coordinate[1]\n          ) as GeojsonCoordinate\n        })\n      } else if (transformType === 'gcj02-wgs84') {\n        coordinates.forEach((coordinate, i, arr) => {\n          arr[i] = gcj02towgs84(\n            coordinate[0],\n            coordinate[1]\n          ) as GeojsonCoordinate\n        })\n      }\n      element.resource.content.geometry.coordinates = coordinates\n    } else if (MapElementEnum.POLY === type) {\n      const coordinates = element.resource?.content.geometry\n        .coordinates[0] as GeojsonCoordinate[]\n\n      if (transformType === 'wgs84-gcj02') {\n        coordinates.forEach((coordinate, i, arr) => {\n          arr[i] = wgs84togcj02(\n            coordinate[0],\n            coordinate[1]\n          ) as GeojsonCoordinate\n        })\n      } else if (transformType === 'gcj02-wgs84') {\n        coordinates.forEach((coordinate, i, arr) => {\n          arr[i] = gcj02towgs84(\n            coordinate[0],\n            coordinate[1]\n          ) as GeojsonCoordinate\n        })\n      }\n      element.resource.content.geometry.coordinates = [coordinates]\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n</style>\n<style lang=\"scss\">\n.drawer-element-wrapper {\n  .ant-drawer-content {\n    background-color: $dark-highlight;\n    color: $text-white-basic;\n    .ant-drawer-header {\n      background-color: $dark-highlight;\n      .ant-drawer-title {\n        color: $text-white-basic;\n      }\n      .ant-drawer-close {\n        color: $text-white-basic;\n      }\n    }\n    .ant-input {\n      background-color: #101010;\n      border-color: $dark-border;\n      color: $text-white-basic;\n    }\n  }\n  .color-content {\n    display: flex;\n    align-items: center;\n    margin-top: 8px;\n    .color-item {\n      cursor: pointer;\n      width: 18px;\n      height: 18px;\n      line-height: 18px;\n      display: flex;\n      align-items: center;\n      margin-left: 5px;\n    }\n  }\n  .title {\n    display: inline-flex;\n    width: 80px;\n  }\n  .element-item {\n    margin-bottom: 10px;\n  }\n}\n.scrollbar {\n  overflow: auto;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/livestream.vue",
    "content": "<template>\n  <div class=\"flex-column flex-justify-start flex-align-center\">\n    <router-link\n      style=\"width: 90%; margin: auto;\"\n      v-for=\"item in options\"\n      :key=\"item.key\"\n      :to=\"item.path\"\n      :class=\"{\n        'menu-item': true,\n      }\"\n    >\n    <a-button\n      class=\"mt10\"\n      style=\"width:100%;\"\n      type=\"primary\"\n      @click=\"selectLivestream(item.routeName)\"\n      >{{ item.label }}</a-button\n    >\n    </router-link>\n  </div>\n  <div class=\"live\" v-if=\"showLive\" v-drag-window>\n    <div style=\"height: 40px; width: 100%\" class=\"drag-title\"></div>\n    <a style=\"position: absolute; right: 10px; top: 10px; font-size: 16px; color: white;\" @click=\"() => root.$router.push('/' + ERouterName.LIVESTREAM)\"><CloseOutlined /></a>\n    <router-view :name=\"routeName\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue'\nimport { onMounted, ref, watch } from 'vue'\nimport { CloseOutlined } from '@ant-design/icons-vue'\nimport { getRoot } from '/@/root'\nimport { ERouterName } from '/@/types'\nconst root = getRoot()\nconst routeName = ref<string>('LiveOthers')\nconst showLive = ref<boolean>(root.$route.name === ERouterName.LIVING)\n\nconst options = [\n  { key: 0, label: 'Agora Live', path: '/' + ERouterName.LIVESTREAM + '/' + ERouterName.LIVING, routeName: 'LiveAgora' },\n  { key: 1, label: 'RTMP/GB28181 Live', path: '/' + ERouterName.LIVESTREAM + '/' + ERouterName.LIVING, routeName: 'LiveOthers' }\n]\n\nconst selectLivestream = (route: string) => {\n  showLive.value = root.$route.name === ERouterName.LIVING\n  routeName.value = route\n}\n\nonMounted(() => {\n  watch(() => root.$route.name, data => {\n    showLive.value = data === ERouterName.LIVING\n  },\n  {\n    deep: true\n  })\n})\n</script>\n\n<style lang=\"scss\">\n.full-modal {\n  .ant-modal {\n    max-width: 100%;\n    top: 0;\n    padding-bottom: 0;\n    margin: 0;\n  }\n  .ant-modal-content {\n    display: flex;\n    flex-direction: column;\n    height: calc(100vh);\n  }\n  .ant-modal-body {\n    flex: 1;\n  }\n}\n.live {\n  position: absolute;\n  z-index: 1;\n  left: 0;\n  top: 10px;\n  margin-left: 345px;\n\n  text-align: center;\n  width: 800px;\n  height: 720px;\n  background: #232323;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/media.vue",
    "content": "<template>\n  <div class=\"project-media-wrapper\">\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\n</script>\n\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/members.vue",
    "content": "\n<template>\n  <div class=\"table flex-display flex-column\">\n    <a-table :columns=\"columns\" :data-source=\"data.member\" :pagination=\"paginationProp\" @change=\"refreshData\" row-key=\"user_id\"\n    :row-selection=\"rowSelection\" :rowClassName=\"(record, index) => ((index % 2) === 0 ? 'table-striped' : null)\" :scroll=\"{ x: '100%', y: 600 }\">\n      <template v-for=\"col in ['mqtt_username', 'mqtt_password']\" #[col]=\"{ text, record }\" :key=\"col\">\n        <div>\n          <a-input\n            v-if=\"editableData[record.user_id]\"\n            v-model:value=\"editableData[record.user_id][col]\"\n            style=\"margin: -5px 0\"\n          />\n          <template v-else>\n            {{ text }}\n          </template>\n        </div>\n      </template>\n      <template #action=\"{ record }\">\n        <div class=\"editable-row-operations\">\n          <span v-if=\"editableData[record.user_id]\">\n            <a-tooltip title=\"Confirm changes\">\n              <span @click=\"save(record)\" style=\"color: #28d445;\"><CheckOutlined /></span>\n            </a-tooltip>\n            <a-tooltip title=\"Modification canceled\">\n              <span @click=\"() => delete editableData[record.user_id]\" class=\"ml15\" style=\"color: #e70102;\"><CloseOutlined /></span>\n            </a-tooltip>\n          </span>\n          <span v-else class=\"fz18 flex-align-center flex-row\" style=\"color: #2d8cf0\">\n            <EditOutlined @click=\"edit(record)\" />\n          </span>\n        </div>\n      </template>\n\n    </a-table>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { message, PaginationProps } from 'ant-design-vue'\nimport { TableState } from 'ant-design-vue/lib/table/interface'\nimport { onMounted, reactive, Ref, ref, UnwrapRef } from 'vue'\nimport { IPage } from '/@/api/http/type'\nimport { getAllUsersInfo, updateUserInfo } from '/@/api/manage'\nimport { ELocalStorageKey } from '/@/types'\nimport { EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue'\n\nexport interface Member {\n    user_id: string\n    username: string\n    user_type: string\n    workspace_name: string\n    create_time: string\n    mqtt_username: string\n    mqtt_password: string\n}\n\ninterface MemberData {\n  member: Member[]\n}\nconst columns = [\n  { title: 'Account', dataIndex: 'username', width: 150, sorter: (a: Member, b: Member) => a.username.localeCompare(b.username), className: 'titleStyle' },\n  { title: 'User Type', dataIndex: 'user_type', width: 150, className: 'titleStyle' },\n  { title: 'Workspace Name', dataIndex: 'workspace_name', width: 150, className: 'titleStyle' },\n  { title: 'Mqtt Username', dataIndex: 'mqtt_username', width: 150, className: 'titleStyle', slots: { customRender: 'mqtt_username' } },\n  { title: 'Mqtt Password', dataIndex: 'mqtt_password', width: 150, className: 'titleStyle', slots: { customRender: 'mqtt_password' } },\n  { title: 'Joined', dataIndex: 'create_time', width: 150, sorter: (a: Member, b: Member) => a.create_time.localeCompare(b.create_time), className: 'titleStyle' },\n  { title: 'Action', dataIndex: 'action', width: 100, className: 'titleStyle', slots: { customRender: 'action' } },\n]\n\nconst data = reactive<MemberData>({\n  member: []\n})\n\nconst editableData: UnwrapRef<Record<string, Member>> = reactive({})\n\nconst paginationProp = reactive({\n  pageSizeOptions: ['20', '50', '100'],\n  showQuickJumper: true,\n  showSizeChanger: true,\n  pageSize: 50,\n  current: 1,\n  total: 0\n})\n\nconst rowSelection = {\n  onChange: (selectedRowKeys: (string | number)[], selectedRows: []) => {\n    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)\n  },\n  onSelect: (record: any, selected: boolean, selectedRows: []) => {\n    console.log(record, selected, selectedRows)\n  },\n  onSelectAll: (selected: boolean, selectedRows: [], changeRows: []) => {\n    console.log(selected, selectedRows, changeRows)\n  },\n}\ntype Pagination = TableState['pagination']\n\nconst body: IPage = {\n  page: 1,\n  total: 0,\n  page_size: 50\n}\nconst workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\n\nonMounted(() => {\n  getAllUsers(workspaceId, body)\n})\n\nfunction refreshData (page: Pagination) {\n  body.page = page?.current!\n  body.page_size = page?.pageSize!\n  getAllUsers(workspaceId, body)\n}\n\nfunction getAllUsers (workspaceId: string, page: IPage) {\n  getAllUsersInfo(workspaceId, page).then(res => {\n    const userList: Member[] = res.data.list\n    data.member = userList\n    paginationProp.total = res.data.pagination.total\n    paginationProp.current = res.data.pagination.page\n  })\n}\n\nfunction edit (record: Member) {\n  editableData[record.user_id] = record\n}\n\nfunction save (record: Member) {\n  delete editableData[record.user_id]\n  updateUserInfo(workspaceId, record.user_id, record).then(res => {\n    if (res.code !== 0) {\n      message.error(res.message)\n    }\n  })\n}\n\n</script>\n<style>\n\n.table {\n    background-color: white;\n    margin: 20px;\n    padding: 20px;\n    height: 88vh;\n}\n.table-striped {\n  background-color: #f7f9fa;\n}\n.ant-table {\n  border-top: 1px solid rgb(0,0,0,0.06);\n  border-bottom: 1px solid rgb(0,0,0,0.06);\n}\n.ant-table-tbody tr td {\n  border: 0;\n}\n.ant-table td {\n  white-space: nowrap;\n}\n.ant-table-thead tr th {\n  background: white !important;\n  border: 0;\n}\nth.ant-table-selection-column {\n  background-color: white !important;\n}\n.ant-table-header {\n  background-color: white !important;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/task.vue",
    "content": "<template>\n  <div>\n    <div style=\"height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;\">\n      <a-row>\n        <a-col :span=\"1\"></a-col>\n        <a-col :span=\"20\">Task Plan Library</a-col>\n        <a-col :span=\"2\">\n          <span v-if=\"taskRoute\">\n            <router-link :to=\"{ name: ERouterName.CREATE_PLAN}\">\n              <PlusOutlined class=\"route-icon\"/>\n            </router-link>\n          </span>\n          <span v-else>\n            <router-link :to=\"{ name: ERouterName.TASK}\">\n              <MinusOutlined class=\"route-icon\"/>\n            </router-link>\n          </span>\n        </a-col>\n        <a-col :span=\"1\"></a-col>\n      </a-row>\n    </div>\n    <div v-if=\"!taskRoute\">\n      <router-view/>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { PlusOutlined, MinusOutlined } from '@ant-design/icons-vue'\nimport { computed, ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { ERouterName } from '/@/types/enums'\n\nconst route = useRoute()\n\nconst taskRoute = computed(() => {\n  return route.name === ERouterName.TASK\n})\n</script>\n\n<style lang=\"scss\">\n.route-icon {\n  color: #fff;\n  font-size: 16px;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/tsa.vue",
    "content": "<template>\n  <div class=\"project-tsa-wrapper \">\n    <div>\n      <a-row>\n        <a-col :span=\"1\"></a-col>\n        <a-col :span=\"11\">My Username</a-col>\n        <a-col :span=\"11\" align=\"right\" style=\"font-weight: 700\">{{ username }}</a-col>\n        <a-col :span=\"1\"></a-col>\n      </a-row>\n    </div>\n    <div class=\"scrollbar\" :style=\"{ height: scorllHeight + 'px'}\">\n      <a-collapse :bordered=\"false\" expandIconPosition=\"right\" accordion style=\"background: #232323;\">\n        <a-collapse-panel :key=\"EDeviceTypeName.Dock\" header=\"Dock\" style=\"border-bottom: 1px solid #4f4f4f;\">\n          <div v-if=\"onlineDocks.data.length === 0\" style=\"height: 150px; color: white;\">\n            <a-empty :image=\"noData\" :image-style=\"{ height: '60px' }\" />\n          </div>\n          <div v-else class=\"fz12\" style=\"color: white;\">\n            <div v-for=\"dock in onlineDocks.data\" :key=\"dock.sn\" style=\"background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;\">\n              <div style=\"border-radius: 2px; height: 100%; width: 100%;\" class=\"flex-row flex-justify-between flex-align-center\">\n                <div style=\"float: left; padding: 0px 5px 8px 8px; width: 88%\">\n                  <div style=\"width: 80%; height: 30px; line-height: 30px; font-size: 16px;\">\n                    <a-tooltip :title=\"`${dock.gateway.callsign} - ${dock.callsign ?? 'No Drone'}`\">\n                      <div class=\"text-hidden\" style=\"max-width: 200px;\">{{ dock.gateway.callsign }} - {{ dock.callsign ?? 'No Drone' }}</div>\n                    </a-tooltip>\n                  </div>\n                  <div class=\"mt5 flex-align-center flex-row flex-justify-between\" style=\"background: #595959;\">\n                    <div class=\"flex-align-center flex-row\">\n                      <span class=\"ml5 mr5\"><RobotOutlined /></span>\n                      <div class=\"font-bold text-hidden\" style=\"max-width: 80px;\" :style=\"dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'\">\n                        {{ dockInfo[dock.gateway.sn] ? EDockModeCode[dockInfo[dock.gateway.sn].basic_osd?.mode_code] : EDockModeCode[EDockModeCode.Disconnected] }}\n                      </div>\n                    </div>\n                    <div class=\"mr5 flex-align-center flex-row\" style=\"width: 85px; margin-right: 0; height: 18px;\">\n                      <div v-if=\"hmsInfo[dock.gateway.sn]\" class=\"flex-align-center flex-row\">\n                          <div :class=\"hmsInfo[dock.gateway.sn][0].level === EHmsLevel.CAUTION ? 'caution-blink' :\n                            hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'\" style=\"width: 18px; height: 16px; text-align: center;\">\n                            <span :style=\"hmsInfo[dock.gateway.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'\">{{ hmsInfo[dock.gateway.sn].length }}</span>\n                            <span class=\"fz10\">{{ hmsInfo[dock.gateway.sn].length > 99 ? '+' : ''}}</span>\n                          </div>\n                        <a-popover trigger=\"click\" placement=\"bottom\" color=\"black\" v-model:visible=\"hmsVisible[dock.gateway.sn]\"\n                          @visibleChange=\"readHms(hmsVisible[dock.gateway.sn], dock.gateway.sn)\"\n                          :overlayStyle=\"{width: '200px', height: '300px'}\">\n                          <div :class=\"hmsInfo[dock.gateway.sn][0].level === EHmsLevel.CAUTION ? 'caution' :\n                            hmsInfo[dock.gateway.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'\" style=\"margin-left: 3px; width: 62px; height: 16px;\">\n                            <span class=\"word-loop\">{{ hmsInfo[dock.gateway.sn][0].message_en }}</span>\n                          </div>\n                          <template #content>\n                            <a-collapse style=\"background: black; height: 300px; overflow-y: auto;\" :bordered=\"false\" expand-icon-position=\"right\" :accordion=\"true\">\n                              <a-collapse-panel v-for=\"hms in hmsInfo[dock.gateway.sn]\" :key=\"hms.hms_id\" :showArrow=\"false\"\n                                style=\" margin: 0 auto 3px auto; border: 0; width: 140px; border-radius: 3px\"\n                                :class=\"hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'\"\n                                >\n                                <template #header=\"{ isActive }\">\n                                  <div class=\"flex-row flex-align-center\" style=\"width: 130px;\">\n                                    <div style=\"width: 110px;\">\n                                      <span class=\"word-loop\">{{ hms.message_en }}</span>\n                                    </div>\n                                    <div style=\"width: 20px; height: 15px; font-size: 10px; z-index: 2 \" class=\"flex-row flex-align-center flex-justify-center\"\n                                      :class=\"hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'\"\n                                    >\n                                      <DoubleRightOutlined :rotate=\"isActive ? 90 : 0\" />\n                                    </div>\n                                  </div>\n                                </template>\n\n                                <a-tooltip :title=\"hms.create_time\">\n                                  <div style=\"color: white;\" class=\"text-hidden\">{{ hms.create_time }}</div>\n                                </a-tooltip>\n                              </a-collapse-panel>\n                            </a-collapse>\n                          </template>\n                        </a-popover>\n                      </div>\n                      <div v-else class=\"width-100\" style=\"height: 90%; background: rgba(0, 0, 0, 0.35)\"></div>\n                    </div>\n                  </div>\n                  <div class=\"mt5 flex-align-center flex-row flex-justify-between\" style=\"background: #595959;\">\n                    <div class=\"flex-row\">\n                      <span class=\"ml5 mr5\"><RocketOutlined /></span>\n                      <div class=\"font-bold text-hidden\" style=\"max-width: 80px\" :style=\"deviceInfo[dock.sn] && deviceInfo[dock.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'\">\n                        {{ deviceInfo[dock.sn] ? EModeCode[deviceInfo[dock.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}\n                      </div>\n                    </div>\n                    <div class=\"mr5 flex-align-center flex-row\" style=\"width: 85px; margin-right: 0; height: 18px;\">\n                      <div v-if=\"hmsInfo[dock.sn]\" class=\"flex-align-center flex-row\">\n                        <div :class=\"hmsInfo[dock.sn][0].level === EHmsLevel.CAUTION ? 'caution-blink' :\n                          hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn-blink' : 'notice-blink'\" style=\"width: 18px; height: 16px; text-align: center;\">\n                          <span :style=\"hmsInfo[dock.sn].length > 99 ? 'font-size: 11px' : 'font-size: 12px'\">{{ hmsInfo[dock.sn].length }}</span>\n                          <span class=\"fz10\">{{ hmsInfo[dock.sn].length > 99 ? '+' : ''}}</span>\n                        </div>\n                        <a-popover trigger=\"click\" placement=\"bottom\" color=\"black\" v-model:visible=\"hmsVisible[dock.sn]\" @visibleChange=\"readHms(hmsVisible[dock.sn], dock.sn)\"\n                          :overlayStyle=\"{width: '200px', height: '300px'}\">\n                          <div :class=\"hmsInfo[dock.sn][0].level === EHmsLevel.CAUTION ? 'caution' :\n                            hmsInfo[dock.sn][0].level === EHmsLevel.WARN ? 'warn' : 'notice'\" style=\"margin-left: 3px; width: 62px; height: 16px;\">\n                            <span class=\"word-loop\">{{ hmsInfo[dock.sn][0].message_en }}</span>\n                          </div>\n                          <template #content>\n                            <a-collapse style=\"background: black; height: 300px; overflow-y: auto;\" :bordered=\"false\" expand-icon-position=\"right\" :accordion=\"true\">\n                              <a-collapse-panel v-for=\"hms in hmsInfo[dock.sn]\" :key=\"hms.hms_id\" :showArrow=\"false\"\n                                style=\" margin: 0 auto 3px auto; border: 0; width: 140px; border-radius: 3px\"\n                                :class=\"hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'\"\n                                >\n                                <template #header=\"{ isActive }\">\n                                  <div class=\"flex-row flex-align-center\" style=\"width: 130px;\">\n                                    <div style=\"width: 110px;\">\n                                      <span class=\"word-loop\">{{ hms.message_en }}</span>\n                                    </div>\n                                    <div style=\"width: 20px; height: 15px; font-size: 10px; z-index: 2 \" class=\"flex-row flex-align-center flex-justify-center\"\n                                      :class=\"hms.level === EHmsLevel.CAUTION ? 'caution' : hms.level === EHmsLevel.WARN ? 'warn' : 'notice'\"\n                                    >\n                                      <DoubleRightOutlined :rotate=\"isActive ? 90 : 0\" />\n                                    </div>\n                                  </div>\n                                </template>\n\n                                <a-tooltip :title=\"hms.create_time\">\n                                  <div style=\"color: white;\" class=\"text-hidden\">{{ hms.create_time }}</div>\n                                </a-tooltip>\n                              </a-collapse-panel>\n                            </a-collapse>\n                          </template>\n                        </a-popover>\n                      </div>\n                      <div v-else class=\"width-100\" style=\"height: 90%; background: rgba(0, 0, 0, 0.35)\"></div>\n                    </div>\n                  </div>\n                </div>\n                <div style=\"float: right; background: #595959; height: 100%; width: 40px;\" class=\"flex-row flex-justify-center flex-align-center\">\n                  <div class=\"fz16\" @click=\"switchVisible($event, dock, true, dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected)\">\n                    <a v-if=\"osdVisible.gateway_sn === dock.gateway.sn && osdVisible.visible\"><EyeOutlined /></a>\n                    <a v-else><EyeInvisibleOutlined /></a>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </a-collapse-panel>\n      </a-collapse>\n      <a-collapse :bordered=\"false\" expandIconPosition=\"right\" accordion style=\"background: #232323;\">\n        <a-collapse-panel :key=\"EDeviceTypeName.Aircraft\" header=\"Online Devices\" style=\"border-bottom: 1px solid #4f4f4f;\">\n          <div v-if=\"onlineDevices.data.length === 0\" style=\"height: 150px; color: white;\">\n            <a-empty :image=\"noData\" :image-style=\"{ height: '60px' }\" />\n          </div>\n          <div v-else class=\"fz12\" style=\"color: white;\">\n            <div v-for=\"device in onlineDevices.data\" :key=\"device.sn\" style=\"background: #3c3c3c; height: 90px; width: 250px; margin-bottom: 10px;\">\n              <div class=\"battery-slide\" v-if=\"deviceInfo[device.sn]\">\n                <div style=\"background: #535759; width: 100%;\"></div>\n                <div class=\"capacity-percent\" :style=\"{ width: deviceInfo[device.sn].battery.capacity_percent + '%'}\"></div>\n                <div class=\"return-home\" :style=\"{ width: deviceInfo[device.sn].battery.return_home_power + '%'}\"></div>\n                <div class=\"landing\" :style=\"{ width: deviceInfo[device.sn].battery.landing_power + '%'}\"></div>\n                <div class=\"battery\" :style=\"{ left: deviceInfo[device.sn].battery.capacity_percent + '%' }\"></div>\n              </div>\n              <div style=\"border-bottom: 1px solid #515151; border-radius: 2px; height: 50px; width: 100%;\" class=\"flex-row flex-justify-between flex-align-center\">\n                <div style=\"float: left; padding: 5px 5px 8px 8px; width: 88%\">\n                  <div style=\"width: 100%; height: 100%;\">\n                    <a-tooltip>\n                      <template #title>{{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone'}}</template>\n                      <span class=\"text-hidden\" style=\"max-width: 200px; display: block; height: 20px;\">{{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone'}}</span>\n                    </a-tooltip>\n                  </div>\n                  <div class=\"mt5\" style=\"background: #595959;\">\n                    <span class=\"ml5 mr5\"><RocketOutlined /></span>\n                    <span class=\"font-bold\" :style=\"deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' :  'color: red;'\">\n                      {{ deviceInfo[device.sn] ? EModeCode[deviceInfo[device.sn].mode_code] : EModeCode[EModeCode.Disconnected] }}\n                    </span>\n                  </div>\n                </div>\n                <div style=\"float: right; background: #595959; height: 50px; width: 40px;\" class=\"flex-row flex-justify-center flex-align-center\">\n                  <div class=\"fz16\" @click=\"switchVisible($event, device, false, deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected)\">\n                    <a v-if=\"osdVisible.sn === device.sn && osdVisible.visible\"><EyeOutlined /></a>\n                    <a v-else><EyeInvisibleOutlined /></a>\n                  </div>\n                </div>\n              </div>\n              <div class=\"flex-row flex-justify-center flex-align-center\" style=\"height: 40px;\">\n                <div class=\"flex-row\" style=\"height: 20px; background: #595959; width: 94%;\" >\n                  <span class=\"mr5\"><a-image style=\"margin-left: 2px; margin-top: -2px; height: 20px; width: 20px;\" :src=\"rc\" /></span>\n                  <a-tooltip>\n                    <template #title>{{ device.gateway.model }} - {{ device.gateway.callsign }} </template>\n                    <div class=\"text-hidden\" style=\"max-width: 200px;\">{{ device.gateway.model }} - {{ device.gateway.callsign }}</div>\n                  </a-tooltip>\n                </div>\n              </div>\n            </div>\n          </div>\n        </a-collapse-panel>\n      </a-collapse>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, onMounted, reactive, ref, watch, WritableComputedRef } from 'vue'\nimport { EDeviceTypeName, ELocalStorageKey } from '/@/types'\nimport noData from '/@/assets/icons/no-data.png'\nimport rc from '/@/assets/icons/rc.png'\nimport { OnlineDevice, EModeCode, OSDVisible, EDockModeCode, DeviceOsd } from '/@/types/device'\nimport { useMyStore } from '/@/store'\nimport { getDeviceTopo, getUnreadDeviceHms, updateDeviceHms } from '/@/api/manage'\nimport { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'\nimport { EHmsLevel } from '/@/types/enums'\n\nconst store = useMyStore()\nconst username = ref(localStorage.getItem(ELocalStorageKey.Username))\nconst workspaceId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)\nconst osdVisible = computed(() => store.state.osdVisible)\nconst hmsVisible = new Map<string, boolean>()\nconst scorllHeight = ref()\n\nconst onlineDevices = reactive({\n  data: [] as OnlineDevice[]\n})\n\nconst onlineDocks = reactive({\n  data: [] as OnlineDevice[]\n})\n\nconst deviceInfo = computed(() => store.state.deviceState.deviceInfo)\nconst dockInfo = computed(() => store.state.deviceState.dockInfo)\nconst hmsInfo = computed({\n  get: () => store.state.hmsInfo,\n  set: (val) => {\n    return val\n  }\n})\n\nonMounted(() => {\n  getOnlineTopo()\n  setTimeout(() => {\n    watch(() => store.state.deviceStatusEvent,\n      data => {\n        getOnlineTopo()\n        if (data.deviceOnline.sn) {\n          getUnreadHms(data.deviceOnline.sn)\n        }\n      },\n      {\n        deep: true\n      }\n    )\n    getOnlineDeviceHms()\n  }, 3000)\n  const element = document.getElementsByClassName('scrollbar').item(0) as HTMLDivElement\n  const parent = element?.parentNode as HTMLDivElement\n  scorllHeight.value = parent?.clientHeight - parent?.firstElementChild?.clientHeight\n})\n\nfunction getOnlineTopo () {\n  getDeviceTopo(workspaceId.value).then((res) => {\n    if (res.code !== 0) {\n      return\n    }\n    onlineDevices.data = []\n    onlineDocks.data = []\n    res.data.forEach((gateway: any) => {\n      const child = gateway.children\n      const device: OnlineDevice = {\n        model: child?.device_name,\n        callsign: child?.nickname,\n        sn: child?.device_sn,\n        mode: EModeCode.Disconnected,\n        gateway: {\n          model: gateway?.device_name,\n          callsign: gateway?.nickname,\n          sn: gateway?.device_sn,\n          domain: gateway?.domain\n        },\n        payload: []\n      }\n      child?.payloads_list.forEach((payload: any) => {\n        device.payload.push({\n          index: payload.index,\n          model: payload.model,\n          payload_name: payload.payload_name,\n          payload_sn: payload.payload_sn,\n          control_source: payload.control_source,\n          payload_index: payload.payload_index\n        })\n      })\n      if (EDeviceTypeName.Dock === gateway.domain) {\n        hmsVisible.set(device.sn, false)\n        hmsVisible.set(device.gateway.sn, false)\n        onlineDocks.data.push(device)\n      }\n      if (gateway.status && EDeviceTypeName.Gateway === gateway.domain) {\n        onlineDevices.data.push(device)\n      }\n    })\n  })\n}\n\nfunction switchVisible (e: any, device: OnlineDevice, isDock: boolean, isClick: boolean) {\n  if (!isClick) {\n    e.target.style.cursor = 'not-allowed'\n    return\n  }\n  if (device.sn === osdVisible.value.sn) {\n    osdVisible.value.visible = !osdVisible.value.visible\n  } else {\n    osdVisible.value.sn = device.sn\n    osdVisible.value.callsign = device.callsign\n    osdVisible.value.model = device.model\n    osdVisible.value.visible = true\n    osdVisible.value.gateway_sn = device.gateway.sn\n    osdVisible.value.is_dock = isDock\n    osdVisible.value.gateway_callsign = device.gateway.callsign\n    osdVisible.value.payloads = device.payload\n  }\n  store.commit('SET_OSD_VISIBLE_INFO', osdVisible)\n}\n\nfunction getUnreadHms (sn: string) {\n  getUnreadDeviceHms(workspaceId.value, sn).then(res => {\n    if (res.data.length !== 0) {\n      hmsInfo.value[sn] = res.data\n    }\n  })\n  console.info(hmsInfo.value)\n}\n\nfunction getOnlineDeviceHms () {\n  const snList = Object.keys(dockInfo.value)\n  if (snList.length === 0) {\n    return\n  }\n  snList.forEach(sn => {\n    getUnreadHms(sn)\n  })\n  const deviceSnList = Object.keys(deviceInfo.value)\n  if (deviceSnList.length === 0) {\n    return\n  }\n  deviceSnList.forEach(sn => {\n    getUnreadHms(sn)\n  })\n}\n\nfunction readHms (visiable: boolean, sn: string) {\n  if (!visiable) {\n    updateDeviceHms(workspaceId.value, sn).then(res => {\n      if (res.code === 0) {\n        delete hmsInfo.value[sn]\n      }\n    })\n  }\n}\n\nfunction openLivestreamOthers () {\n  store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', true)\n}\n\nfunction openLivestreamAgora () {\n  store.commit('SET_LIVESTREAM_AGORA_VISIBLE', true)\n}\n\n</script>\n\n<style lang=\"scss\">\n.project-tsa-wrapper > :first-child {\n  height: 50px;\n  line-height: 50px;\n  align-items: center;\n  border-bottom: 1px solid #4f4f4f;\n}\n.project-tsa-wrapper {\n  height: 100%;\n  .scrollbar {\n    overflow: auto;\n  }\n  ::-webkit-scrollbar {\n    display: none;\n  }\n}\n.ant-collapse > .ant-collapse-item > .ant-collapse-header {\n  color: white;\n  border: 0;\n  padding-left: 14px;\n}\n\n.text-hidden {\n  overflow: hidden !important;\n  text-overflow: ellipsis !important;\n  white-space: nowrap;\n  -o-text-overflow: ellipsis;\n}\n.font-bold {\n  font-weight: 700;\n}\n\n.battery-slide {\n  width: 100%;\n  .capacity-percent {\n    background: #00ee8b;\n  }\n  .return-home {\n    background: #ff9f0a;\n  }\n  .landing {\n    background: #f5222d;\n  }\n  .battery {\n    background: white;\n    border-radius: 1px;\n    width: 8px;\n    height: 4px;\n    margin-top: -3px;\n  }\n}\n.battery-slide > div {\n  position: relative;\n  margin-top: -2px;\n  min-height: 2px;\n  border-radius: 2px;\n  white-space: nowrap;\n}\n.disable {\n  cursor: not-allowed;\n}\n\n.notice-blink {\n  background: $success;\n  animation: blink 500ms infinite;\n}\n.caution-blink {\n  background: orange;\n  animation: blink 500ms infinite;\n}\n.warn-blink {\n  background: red;\n  animation: blink 500ms infinite;\n}\n.notice {\n  background: $success;\n  overflow: hidden;\n  cursor: pointer;\n}\n.caution {\n  background: orange;\n  cursor: pointer;\n  overflow: hidden;\n}\n.warn {\n  background: red;\n  cursor: pointer;\n  overflow: hidden;\n}\n.word-loop {\n  white-space: nowrap;\n  display: inline-block;\n  animation: 10s loop linear infinite normal;\n}\n@keyframes blink {\n  from {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0.35;\n  }\n  to {\n    opacity: 1;\n  }\n}\n@keyframes loop {\n  0% {\n    transform: translateX(20px);\n    -webkit-transform: translateX(20px);\n  }\n  100% {\n    transform: translateX(-100%);\n    -webkit-transform: translateX(-100%);\n  }\n}\n\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/wayline.vue",
    "content": "<template>\n  <div class=\"project-wayline-wrapper height-100\">\n    <a-spin :spinning=\"loading\" :delay=\"300\" tip=\"downloading\" size=\"large\">\n    <div style=\"height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;\">\n      <a-row>\n        <a-col :span=\"1\"></a-col>\n        <a-col :span=\"15\">Flight Route Library</a-col>\n        <a-col :span=\"8\" v-if=\"importVisible\" class=\"flex-row flex-justify-end flex-align-center\">\n          <a-upload\n            name=\"file\"\n            :multiple=\"false\"\n            :before-upload=\"beforeUpload\"\n            :show-upload-list=\"false\"\n            :customRequest=\"uploadFile\"\n          >\n            <a-button type=\"text\" style=\"color: white;\">\n              <SelectOutlined />\n            </a-button>\n          </a-upload>\n        </a-col>\n      </a-row>\n    </div>\n    <div :style=\"{ height : height + 'px'}\" class=\"scrollbar\">\n      <div id=\"data\" class=\"height-100 uranus-scrollbar\" v-if=\"waylinesData.data.length !== 0\" @scroll=\"onScroll\">\n        <div v-for=\"wayline in waylinesData.data\" :key=\"wayline.id\">\n          <div class=\"wayline-panel\" style=\"padding-top: 5px;\" @click=\"selectRoute(wayline)\">\n            <div class=\"title\">\n              <a-tooltip :title=\"wayline.name\">\n                <div class=\"pr10\" style=\"width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ wayline.name }}</div>\n              </a-tooltip>\n              <div class=\"ml10\"><UserOutlined /></div>\n              <a-tooltip :title=\"wayline.user_name\">\n                <div class=\"ml5 pr10\" style=\"width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\">{{ wayline.user_name }}</div>\n              </a-tooltip>\n              <div class=\"fz20\">\n                <a-dropdown>\n                  <a style=\"color: white;\">\n                    <EllipsisOutlined />\n                  </a>\n                  <template #overlay>\n                    <a-menu theme=\"dark\" class=\"more\" style=\"background: #3c3c3c;\">\n                      <a-menu-item @click=\"downloadWayline(wayline.id, wayline.name)\">\n                        <span>Download</span>\n                      </a-menu-item>\n                      <a-menu-item @click=\"showWaylineTip(wayline.id)\">\n                        <span>Delete</span>\n                      </a-menu-item>\n                    </a-menu>\n                  </template>\n                </a-dropdown>\n              </div>\n            </div>\n            <div class=\"ml10 mt5\" style=\"color: hsla(0,0%,100%,0.65);\">\n              <span><RocketOutlined /></span>\n              <span class=\"ml5\">{{ DEVICE_NAME[wayline.drone_model_key] }}</span>\n              <span class=\"ml10\"><CameraFilled style=\"border-top: 1px solid; padding-top: -3px;\" /></span>\n              <span class=\"ml5\" v-for=\"payload in wayline.payload_model_keys\" :key=\"payload.id\">\n                {{ DEVICE_NAME[payload] }}\n              </span>\n            </div>\n            <div class=\"mt5 ml10\" style=\"color: hsla(0,0%,100%,0.35);\">\n              <span class=\"mr10\">Update at {{ new Date(wayline.update_time).toLocaleString() }}</span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div v-else>\n        <a-empty :image-style=\"{ height: '60px', marginTop: '60px' }\" />\n      </div>\n      <a-modal v-model:visible=\"deleteTip\" width=\"450px\" :closable=\"false\" :maskClosable=\"false\" centered :okButtonProps=\"{ danger: true }\" @ok=\"deleteWayline\">\n          <p class=\"pt10 pl20\" style=\"height: 50px;\">Wayline file is unrecoverable once deleted. Continue?</p>\n          <template #title>\n              <div class=\"flex-row flex-justify-center\">\n                  <span>Delete</span>\n              </div>\n          </template>\n      </a-modal>\n    </div>\n    </a-spin>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { reactive } from '@vue/reactivity'\nimport { message } from 'ant-design-vue'\nimport { onMounted, onUpdated, ref } from 'vue'\nimport { deleteWaylineFile, downloadWaylineFile, getWaylineFiles, importKmzFile } from '/@/api/wayline'\nimport { ELocalStorageKey, ERouterName } from '/@/types'\nimport { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined, SelectOutlined } from '@ant-design/icons-vue'\nimport { DEVICE_NAME } from '/@/types/device'\nimport { useMyStore } from '/@/store'\nimport { WaylineFile } from '/@/types/wayline'\nimport { downloadFile } from '/@/utils/common'\nimport { IPage } from '/@/api/http/type'\nimport { CURRENT_CONFIG } from '/@/api/http/config'\nimport { load } from '@amap/amap-jsapi-loader'\nimport { getRoot } from '/@/root'\n\nconst loading = ref(false)\nconst store = useMyStore()\nconst pagination :IPage = {\n  page: 1,\n  total: -1,\n  page_size: 10\n}\n\nconst waylinesData = reactive({\n  data: [] as WaylineFile[]\n})\n\nconst root = getRoot()\nconst workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!\nconst deleteTip = ref(false)\nconst deleteWaylineId = ref<string>('')\nconst canRefresh = ref(true)\nconst importVisible = ref<boolean>(root.$router.currentRoute.value.name === ERouterName.WAYLINE)\nconst height = ref()\n\nonMounted(() => {\n  const parent = document.getElementsByClassName('scrollbar').item(0)?.parentNode as HTMLDivElement\n  height.value = document.body.clientHeight - parent.firstElementChild!.clientHeight\n  getWaylines()\n\n  const key = setInterval(() => {\n    const data = document.getElementById('data')?.lastElementChild as HTMLDivElement\n    if (pagination.total === 0 || Math.ceil(pagination.total / pagination.page_size) <= pagination.page || height.value <= data?.clientHeight + data?.offsetTop) {\n      clearInterval(key)\n      return\n    }\n    pagination.page++\n    getWaylines()\n  }, 1000)\n})\n\nfunction getWaylines () {\n  if (!canRefresh.value) {\n    return\n  }\n  canRefresh.value = false\n  getWaylineFiles(workspaceId, {\n    page: pagination.page,\n    page_size: pagination.page_size,\n    order_by: 'update_time desc'\n  }).then(res => {\n    if (res.code !== 0) {\n      return\n    }\n    waylinesData.data = [...waylinesData.data, ...res.data.list]\n    pagination.total = res.data.pagination.total\n    pagination.page = res.data.pagination.page\n  }).finally(() => {\n    canRefresh.value = true\n  })\n}\n\nfunction showWaylineTip (waylineId: string) {\n  deleteWaylineId.value = waylineId\n  deleteTip.value = true\n}\n\nfunction deleteWayline () {\n  deleteWaylineFile(workspaceId, deleteWaylineId.value).then(res => {\n    if (res.code === 0) {\n      message.success('Wayline file deleted')\n    }\n    deleteWaylineId.value = ''\n    deleteTip.value = false\n    pagination.total = 0\n    pagination.page = 1\n    waylinesData.data = []\n    getWaylines()\n  })\n}\n\nfunction downloadWayline (waylineId: string, fileName: string) {\n  loading.value = true\n  downloadWaylineFile(workspaceId, waylineId).then(res => {\n    if (!res) {\n      return\n    }\n    const data = new Blob([res], { type: 'application/zip' })\n    downloadFile(data, fileName + '.kmz')\n  }).finally(() => {\n    loading.value = false\n  })\n}\n\nfunction selectRoute (wayline: WaylineFile) {\n  store.commit('SET_SELECT_WAYLINE_INFO', wayline)\n}\n\nfunction onScroll (e: any) {\n  const element = e.srcElement\n  if (element.scrollTop + element.clientHeight >= element.scrollHeight - 5 && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {\n    pagination.page++\n    getWaylines()\n  }\n}\n\ninterface FileItem {\n  uid: string;\n  name?: string;\n  status?: string;\n  response?: string;\n  url?: string;\n}\n\ninterface FileInfo {\n  file: FileItem;\n  fileList: FileItem[];\n}\nconst fileList = ref<FileItem[]>([])\n\nfunction beforeUpload (file: FileItem) {\n  fileList.value = [file]\n  loading.value = true\n  return true\n}\nconst uploadFile = async () => {\n  fileList.value.forEach(async (file: FileItem) => {\n    const fileData = new FormData()\n    fileData.append('file', file, file.name)\n    await importKmzFile(workspaceId, fileData).then((res) => {\n      if (res.code === 0) {\n        message.success(`${file.name} file uploaded successfully`)\n        canRefresh.value = true\n        pagination.total = 0\n        pagination.page = 1\n        waylinesData.data = []\n        getWaylines()\n      }\n    }).finally(() => {\n      loading.value = false\n      fileList.value = []\n    })\n  })\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.wayline-panel {\n  background: #3c3c3c;\n  margin-left: auto;\n  margin-right: auto;\n  margin-top: 10px;\n  height: 90px;\n  width: 95%;\n  font-size: 13px;\n  border-radius: 2px;\n  cursor: pointer;\n  .title {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    height: 30px;\n    font-weight: bold;\n    margin: 0px 10px 0 10px;\n  }\n}\n.uranus-scrollbar {\n  overflow: auto;\n  scrollbar-width: thin;\n  scrollbar-color: #c5c8cc transparent;\n}\n</style>\n"
  },
  {
    "path": "src/pages/page-web/projects/workspace.vue",
    "content": "<template>\n  <div class=\"project-app-wrapper\">\n    <div class=\"left\">\n      <Sidebar />\n      <div class=\"main-content uranus-scrollbar dark\">\n        <router-view />\n      </div>\n    </div>\n    <div class=\"right\">\n      <div class=\"map-wrapper\">\n        <GMap />\n      </div>\n      <div class=\"media-wrapper\" v-if=\"root.$route.name === ERouterName.MEDIA\">\n        <MediaPanel />\n      </div>\n      <div class=\"task-wrapper\" v-if=\"root.$route.name === ERouterName.TASK\">\n        <TaskPanel />\n      </div>\n    </div>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport Sidebar from '/@/components/common/sidebar.vue'\nimport MediaPanel from '/@/components/MediaPanel.vue'\nimport TaskPanel from '/@/components/task/TaskPanel.vue'\nimport GMap from '/@/components/GMap.vue'\nimport { EBizCode, ERouterName } from '/@/types'\nimport { getRoot } from '/@/root'\nimport { useMyStore } from '/@/store'\nimport { useConnectWebSocket } from '/@/hooks/use-connect-websocket'\nimport EventBus from '/@/event-bus'\n\nconst root = getRoot()\nconst store = useMyStore()\n\nconst messageHandler = async (payload: any) => {\n  if (!payload) {\n    return\n  }\n\n  switch (payload.biz_code) {\n    case EBizCode.GatewayOsd: {\n      store.commit('SET_GATEWAY_INFO', payload.data)\n      break\n    }\n    case EBizCode.DeviceOsd: {\n      store.commit('SET_DEVICE_INFO', payload.data)\n      break\n    }\n    case EBizCode.DockOsd: {\n      store.commit('SET_DOCK_INFO', payload.data)\n      break\n    }\n    case EBizCode.MapElementCreate: {\n      store.commit('SET_MAP_ELEMENT_CREATE', payload.data)\n      break\n    }\n    case EBizCode.MapElementUpdate: {\n      store.commit('SET_MAP_ELEMENT_UPDATE', payload.data)\n      break\n    }\n    case EBizCode.MapElementDelete: {\n      store.commit('SET_MAP_ELEMENT_DELETE', payload.data)\n      break\n    }\n    case EBizCode.DeviceOnline: {\n      store.commit('SET_DEVICE_ONLINE', payload.data)\n      break\n    }\n    case EBizCode.DeviceOffline: {\n      store.commit('SET_DEVICE_OFFLINE', payload.data)\n      break\n    }\n    case EBizCode.FlightTaskProgress:\n    case EBizCode.FlightTaskMediaProgress:\n    case EBizCode.FlightTaskMediaHighestPriority: {\n      EventBus.emit('flightTaskWs', payload)\n      break\n    }\n    case EBizCode.DeviceHms: {\n      store.commit('SET_DEVICE_HMS_INFO', payload.data)\n      break\n    }\n    case EBizCode.DeviceReboot:\n    case EBizCode.DroneOpen:\n    case EBizCode.DroneClose:\n    case EBizCode.CoverOpen:\n    case EBizCode.CoverClose:\n    case EBizCode.PutterOpen:\n    case EBizCode.PutterClose:\n    case EBizCode.ChargeOpen:\n    case EBizCode.ChargeClose:\n    case EBizCode.DeviceFormat:\n    case EBizCode.DroneFormat:\n    {\n      store.commit('SET_DEVICES_CMD_EXECUTE_INFO', {\n        biz_code: payload.biz_code,\n        timestamp: payload.timestamp,\n        ...payload.data,\n      })\n      break\n    }\n    case EBizCode.ControlSourceChange:\n    case EBizCode.FlyToPointProgress:\n    case EBizCode.TakeoffToPointProgress:\n    case EBizCode.JoystickInvalidNotify:\n    case EBizCode.DrcStatusNotify:\n    {\n      EventBus.emit('droneControlWs', payload)\n      break\n    }\n    case EBizCode.FlightAreasSyncProgress: {\n      EventBus.emit('flightAreasSyncProgressWs', payload.data)\n      break\n    }\n    case EBizCode.FlightAreasDroneLocation: {\n      EventBus.emit('flightAreasDroneLocationWs', payload)\n      break\n    }\n    case EBizCode.FlightAreasUpdate: {\n      EventBus.emit('flightAreasUpdateWs', payload.data)\n      break\n    }\n    default:\n      break\n  }\n}\n\n// 监听ws 消息\nuseConnectWebSocket(messageHandler)\n\n</script>\n<style lang=\"scss\" scoped>\n@import '/@/styles/index.scss';\n\n.project-app-wrapper {\n  display: flex;\n  transition: width 0.2s ease;\n  height: 100%;\n  width: 100%;\n\n  .left {\n    display: flex;\n    width: 335px;\n    flex: 0 0 335px;\n    background-color: #232323;\n\n    .main-content {\n      flex: 1;\n      color: $text-white-basic;\n      width: 285px;\n    }\n  }\n\n  .right {\n    flex-grow: 1;\n    position: relative;\n\n    .map-wrapper{\n      width: 100%;\n      height: 100%;\n    }\n\n    .media-wrapper,\n    .task-wrapper {\n      position: absolute;\n      top: 0;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      z-index: 100;\n      background: #f6f8fa;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/plugins/svgBuilder.ts",
    "content": "import { readFileSync, readdirSync } from 'fs'\n\nlet idPerfix = ''\nconst svgTitle = /<svg([^>+].*?)>/\nconst clearHeightWidth = /(width|height)=\"([^>+].*?)\"/g\nconst hasViewBox = /(viewBox=\"[^>+].*?\")/g\nconst clearReturn = /(\\r)|(\\n)/g\n\n// Find the svg file\nfunction svgFind(e) {\n  const arr = []\n  const dirents = readdirSync(e, { withFileTypes: true })\n  for (const dirent of dirents) {\n    if (dirent.isDirectory()) arr.push(...svgFind(e + dirent.name + '/'))\n    else {\n      const svg = readFileSync(e + dirent.name)\n        .toString()\n        .replace(clearReturn, '')\n        .replace(svgTitle, ($1, $2) => {\n          let width = 0\n          let height = 0\n          let content = $2.replace(clearHeightWidth, (s1, s2, s3) => {\n            if (s2 === 'width') width = s3\n            else if (s2 === 'height') height = s3\n            return ''\n          })\n          if (!hasViewBox.test($2)) content += `viewBox=\"0 0 ${width} ${height}\"`\n          return `<symbol id=\"${idPerfix}-${dirent.name.replace('.svg', '')}\" ${content}>`\n        }).replace('</svg>', '</symbol>')\n      arr.push(svg)\n    }\n  }\n  return arr\n}\n\nexport const svgBuilder = (path: any, perfix = 'icon') => {\n  if (path === '') return\n  idPerfix = perfix\n  const res = svgFind(path)\n  console.log(res)\n  return {\n    name: 'svg-transform',\n    transformIndexHtml (dom: String) {\n      return dom.replace(\n        '<body>',\n          `<body><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" style=\"position: absolute; width: 0; height: 0\" version=\"1.1\">${res.join('')}</svg>`\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "src/root.ts",
    "content": "import { createApp, ComponentCustomProperties, App as VueApp } from 'vue'\ndeclare module '@vue/runtime-core' {\n  interface ComponentCustomProperties {\n    $aMap: any // Map类\n    $map: any // 地图对象\n    $mouseTool: any\n  }\n}\nlet root: ComponentCustomProperties\nlet app = null as any\n\nexport function createInstance (App: any): VueApp {\n  app = createApp(App)\n  root = app.config.globalProperties as ComponentCustomProperties\n  return app\n}\n\nexport function getRoot (): ComponentCustomProperties {\n  return root\n}\n\nexport function getApp (): VueApp {\n  return app\n}\n"
  },
  {
    "path": "src/router/index.ts",
    "content": "import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'\nimport { ERouterName } from '/@/types/index'\nimport CreatePlan from '/@/components/task/CreatePlan.vue'\nimport WaylinePanel from '/@/pages/page-web/projects/wayline.vue'\nimport DockPanel from '/@/pages/page-web/projects/dock.vue'\nimport LiveAgora from '/@/components/livestream-agora.vue'\nimport LiveOthers from '/@/components/livestream-others.vue'\n\nconst routes: Array<RouteRecordRaw> = [\n  {\n    path: '/',\n    redirect: '/' + ERouterName.PROJECT\n  },\n  // 首页\n  {\n    path: '/' + ERouterName.PROJECT,\n    name: ERouterName.PROJECT,\n    component: () => import('/@/pages/page-web/index.vue')\n  },\n  // members, devices\n  {\n    path: '/' + ERouterName.HOME,\n    name: ERouterName.HOME,\n    component: () => import('/@/pages/page-web/home.vue'),\n    children: [\n      {\n        path: '/' + ERouterName.MEMBERS,\n        name: ERouterName.MEMBERS,\n        component: () => import('/@/pages/page-web/projects/members.vue')\n      },\n      {\n        path: '/' + ERouterName.DEVICES,\n        name: ERouterName.DEVICES,\n        component: () => import('/@/pages/page-web/projects/devices.vue')\n      },\n      {\n        path: '/' + ERouterName.FIRMWARES,\n        name: ERouterName.FIRMWARES,\n        component: () => import('../pages/page-web/projects/Firmwares.vue')\n      }\n    ]\n  },\n  // workspace\n  {\n    path: '/' + ERouterName.WORKSPACE,\n    name: ERouterName.WORKSPACE,\n    component: () => import('/@/pages/page-web/projects/workspace.vue'),\n    redirect: '/' + ERouterName.TSA,\n    children: [\n      {\n        path: '/' + ERouterName.TSA,\n        component: () => import('/@/pages/page-web/projects/tsa.vue')\n      },\n      {\n        path: '/' + ERouterName.LIVESTREAM,\n        name: ERouterName.LIVESTREAM,\n        component: () => import('/@/pages/page-web/projects/livestream.vue'),\n        children: [\n          {\n            path: ERouterName.LIVING,\n            name: ERouterName.LIVING,\n            components: {\n              LiveAgora,\n              LiveOthers\n            }\n          }\n        ]\n      },\n      {\n        path: '/' + ERouterName.LAYER,\n        name: ERouterName.LAYER,\n        component: () => import('/@/pages/page-web/projects/layer.vue')\n      },\n      {\n        path: '/' + ERouterName.MEDIA,\n        name: ERouterName.MEDIA,\n        component: () => import('/@/pages/page-web/projects/media.vue')\n      },\n      {\n        path: '/' + ERouterName.WAYLINE,\n        name: ERouterName.WAYLINE,\n        component: () => import('/@/pages/page-web/projects/wayline.vue')\n      },\n      {\n        path: '/' + ERouterName.TASK,\n        name: ERouterName.TASK,\n        component: () => import('/@/pages/page-web/projects/task.vue'),\n        children: [\n          {\n            path: ERouterName.CREATE_PLAN,\n            name: ERouterName.CREATE_PLAN,\n            component: CreatePlan,\n            children: [\n              {\n                path: ERouterName.SELECT_PLAN,\n                name: ERouterName.SELECT_PLAN,\n                components: {\n                  WaylinePanel,\n                  DockPanel\n                }\n              }\n            ]\n          }\n\n        ]\n      },\n      {\n        path: '/' + ERouterName.FLIGHT_AREA,\n        name: ERouterName.FLIGHT_AREA,\n        component: () => import('/@/pages/page-web/projects/flight-area.vue')\n      },\n    ]\n  },\n  // pilot\n  {\n    path: '/' + ERouterName.PILOT,\n    name: ERouterName.PILOT,\n    component: () => import('/@/pages/page-pilot/pilot-index.vue'),\n  },\n  {\n    path: '/' + ERouterName.PILOT_HOME,\n    component: () => import('/@/pages/page-pilot/pilot-home.vue')\n  },\n  {\n    path: '/' + ERouterName.PILOT_MEDIA,\n    component: () => import('/@/pages/page-pilot/pilot-media.vue')\n  },\n  {\n    path: '/' + ERouterName.PILOT_LIVESHARE,\n    component: () => import('/@/pages/page-pilot/pilot-liveshare.vue')\n  },\n  {\n    path: '/' + ERouterName.PILOT_BIND,\n    component: () => import('/@/pages/page-pilot/pilot-bind.vue')\n  }\n]\n\nconst router = createRouter({\n  history: createWebHistory(import.meta.env.BASE_URL),\n  routes\n})\n\nexport default router\n"
  },
  {
    "path": "src/shims-mqtt.d.ts",
    "content": "declare module 'mqtt/dist/mqtt.min' {\n  import MQTT from 'mqtt'\n  export = MQTT\n}\n"
  },
  {
    "path": "src/shims-vue.d.ts",
    "content": "declare module '*.vue' {\n  import { DefineComponent } from 'vue'\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}\n"
  },
  {
    "path": "src/store/index.ts",
    "content": "import { InjectionKey } from 'vue'\nimport { ActionTree, createStore, GetterTree, MutationTree, Store, StoreOptions, useStore } from 'vuex'\nimport { EDeviceTypeName } from '../types'\nimport { Device, DeviceHms, DeviceOsd, DeviceStatus, DockOsd, GatewayOsd, OSDVisible } from '../types/device'\nimport { getLayers } from '/@/api/layer'\nimport { LayerType } from '/@/types/mapLayer'\nimport { WaylineFile } from '/@/types/wayline'\nimport { DevicesCmdExecuteInfo } from '/@/types/device-cmd'\nimport { FlightAreaStatus } from '../api/flight-area'\n\nconst initStateFunc = () => ({\n  Layers: [\n    {\n      name: 'default',\n      id: '',\n      is_distributed: true,\n      elements: [],\n      is_check: false,\n      is_select: false,\n      type: 1\n    },\n    {\n      name: 'share',\n      id: '',\n      is_distributed: true,\n      elements: [],\n      is_check: false,\n      is_select: false,\n      type: 2\n    }\n  ],\n  layerBaseInfo: {} as {\n    [key:string]:string\n  },\n  drawVisible: false,\n  livestreamOthersVisible: false,\n  livestreamAgoraVisible: false,\n  coverMap: {} as {\n    [key: string]: any[]\n  },\n  wsEvent: {\n    mapElementCreat: {},\n    mapElementUpdate: {},\n    mapElementDelete: {}\n  },\n  deviceStatusEvent: {\n    deviceOnline: {} as DeviceStatus,\n    deviceOffline: {}\n  },\n  markerInfo: {\n    coverMap: {} as {\n      [sn: string]: any\n    },\n    pathMap: {} as {\n      [sn: string]: any[]\n    }\n  },\n  deviceState: {\n    // remote controller, dock\n    gatewayInfo: {} as {\n      [sn: string]: GatewayOsd\n    },\n    // drone\n    deviceInfo: {} as {\n      [sn: string]: DeviceOsd\n    },\n    dockInfo: {} as {\n      [sn: string]: DockOsd\n    },\n    currentSn: '',\n    currentType: -1\n  },\n  osdVisible: { // osd 显示设备相关信息\n    sn: '',\n    callsign: '',\n    model: '',\n    visible: false,\n    gateway_sn: '',\n    is_dock: false,\n    payloads: null\n  } as OSDVisible,\n  waylineInfo: {\n\n  } as WaylineFile,\n  dockInfo: {\n\n  } as Device,\n  hmsInfo: {} as {\n    [sn: string]: DeviceHms[]\n  },\n  // 机场指令执行状态信息\n  devicesCmdExecuteInfo: {\n  } as DevicesCmdExecuteInfo,\n  mqttState: null as any, // mqtt 实例\n  clientId: '', // mqtt 连接 唯一客户端id\n})\n\nexport type RootStateType = ReturnType<typeof initStateFunc>\n\nconst getters: GetterTree<RootStateType, RootStateType> = {\n}\nconst mutations: MutationTree<RootStateType> = {\n  SET_LAYER_INFO (state, info) {\n    state.Layers = info\n  },\n  SET_DEVICE_INFO (state, info) {\n    state.deviceState.deviceInfo[info.sn] = info.host\n    state.deviceState.currentSn = info.sn\n    state.deviceState.currentType = EDeviceTypeName.Aircraft\n  },\n  SET_GATEWAY_INFO (state, info) {\n    state.deviceState.gatewayInfo[info.sn] = info.host\n    state.deviceState.currentSn = info.sn\n    state.deviceState.currentType = EDeviceTypeName.Gateway\n  },\n  SET_DOCK_INFO (state, info) {\n    if (Object.keys(info.host).length === 0) {\n      return\n    }\n    if (!state.deviceState.dockInfo[info.sn]) {\n      state.deviceState.dockInfo[info.sn] = { } as DockOsd\n    }\n    state.deviceState.currentSn = info.sn\n    state.deviceState.currentType = EDeviceTypeName.Dock\n    const dock = state.deviceState.dockInfo[info.sn]\n    if (info.host.mode_code !== undefined) {\n      dock.basic_osd = info.host\n      return\n    }\n    if (info.host.wireless_link) {\n      dock.link_osd = info.host\n      return\n    }\n    if (info.host.job_number !== undefined) {\n      dock.work_osd = info.host\n    }\n  },\n  SET_DRAW_VISIBLE_INFO (state, bool) {\n    state.drawVisible = bool\n  },\n  SET_LIVESTREAM_OTHERS_VISIBLE (state, bool) {\n    state.livestreamOthersVisible = bool\n  },\n  SET_LIVESTREAM_AGORA_VISIBLE (state, bool) {\n    state.livestreamAgoraVisible = bool\n  },\n  SET_MAP_ELEMENT_CREATE (state, info) {\n    state.wsEvent.mapElementCreat = info\n  },\n  SET_MAP_ELEMENT_UPDATE (state, info) {\n    state.wsEvent.mapElementUpdate = info\n  },\n  SET_MAP_ELEMENT_DELETE (state, info) {\n    state.wsEvent.mapElementDelete = info\n  },\n  SET_DEVICE_ONLINE (state, info) {\n    state.deviceStatusEvent.deviceOnline = info\n  },\n  SET_DEVICE_OFFLINE (state, info) {\n    state.deviceStatusEvent.deviceOffline = info\n    delete state.deviceState.gatewayInfo[info.sn]\n    delete state.deviceState.deviceInfo[info.sn]\n    delete state.deviceState.dockInfo[info.sn]\n    delete state.hmsInfo[info.sn]\n    // delete state.markerInfo.coverMap[info.sn]\n    // delete state.markerInfo.pathMap[info.sn]\n  },\n  SET_OSD_VISIBLE_INFO (state, info) {\n    state.osdVisible = info\n  },\n  SET_SELECT_WAYLINE_INFO (state, info) {\n    state.waylineInfo = info\n  },\n  SET_SELECT_DOCK_INFO (state, info) {\n    state.dockInfo = info\n  },\n  SET_DEVICE_HMS_INFO (state, info) {\n    const hmsList: Array<DeviceHms> = state.hmsInfo[info.sn]\n    state.hmsInfo[info.sn] = info.host.concat(hmsList ?? [])\n  },\n  SET_DEVICES_CMD_EXECUTE_INFO (state, info) { // 保存设备指令ws消息推送\n    if (!info.sn) {\n      return\n    }\n    if (state.devicesCmdExecuteInfo[info.sn]) {\n      const index = state.devicesCmdExecuteInfo[info.sn].findIndex(cmdExecuteInfo => cmdExecuteInfo.biz_code === info.biz_code)\n      if (index >= 0) {\n        // 丢弃前面的消息\n        if (state.devicesCmdExecuteInfo[info.sn][index].timestamp > info.timestamp) {\n          return\n        }\n        state.devicesCmdExecuteInfo[info.sn][index] = info\n      } else {\n        state.devicesCmdExecuteInfo[info.sn].push(info)\n      }\n    } else {\n      state.devicesCmdExecuteInfo[info.sn] = [info]\n    }\n  },\n  SET_MQTT_STATE (state, mqttState) {\n    state.mqttState = mqttState\n  },\n  SET_CLIENT_ID (state, clientId) {\n    state.clientId = clientId\n  },\n}\n\nconst actions: ActionTree<RootStateType, RootStateType> = {\n  async getAllElement ({ commit }) {\n    const result = await getLayers({\n      groupId: '',\n      isDistributed: true\n    })\n    commit('SET_LAYER_INFO', result.data?.list)\n    console.log(result)\n  },\n  updateElement ({ state }, content: {type: 'is_check' | 'is_select', id: string, bool:boolean}) {\n    const key = content.id.replaceAll('resource__', '')\n    const type = content.type\n    const layers = state.Layers\n    const layer = layers.find(item => item.id === key)\n    if (layer) {\n      layer[type] = content.bool\n    }\n  },\n  setLayerInfo ({ state }, layers) {\n    // const layers = state.Layers\n    const obj:{\n      [key:string]:string\n    } = {}\n    layers.forEach(layer => {\n      if (layer.type === LayerType.Default) {\n        obj.default = layer.id\n      } else {\n        if (layer.type === LayerType.Share) {\n          obj.share = layer.id\n        }\n      }\n    })\n    state.layerBaseInfo = obj\n    console.log('state.layerBaseInfo', state.layerBaseInfo)\n  },\n  getLayerInfo ({ state }, id:string) {\n    return state.layerBaseInfo[id]\n  }\n}\n\nconst storeOptions: StoreOptions<RootStateType> = {\n  state: initStateFunc,\n  getters,\n  mutations,\n  actions\n}\n\nconst rootStore = createStore(storeOptions)\n\nexport default rootStore\n\nexport const storeKey: InjectionKey<Store<RootStateType>> = Symbol('')\n\ntype AllStateStoreTypes = RootStateType & {\n  // moduleName: moduleType\n}\n\nexport function useMyStore<T = AllStateStoreTypes> () {\n  return useStore<T>(storeKey)\n}\n"
  },
  {
    "path": "src/styles/common.scss",
    "content": "\nhtml, body, #app, #my-app {\n  height: 100%;\n  overflow: hidden;\n}\n\nbody {\n  background-color: #f7f9fa;\n  -webkit-font-smoothing: antialiased;\n  // Prevent font enlargement in horizontal screen\n  text-size-adjust: 100%;\n\n  font-family: sans-serif, Roboto, sans-serif-medium, Arial;\n  font-feature-settings: normal;\n  color: $main-text-color;\n  font-size: 14px;\n\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  ::-webkit-scrollbar {\n    width: 8px;\n    height: 8px;\n    background: transparent;\n  }\n  \n  ::-webkit-scrollbar-thumb {\n    border-radius: 4px;\n    border: none;\n    background: rgb(89, 89, 89);\n  }\n}"
  },
  {
    "path": "src/styles/flex.style.scss",
    "content": ".flex-display {\n  display: flex;\n}\n\n.flex-column {\n  @extend .flex-display;\n  flex-direction: column;\n}\n\n.flex-row {\n  @extend .flex-display;\n  flex-direction: row;\n}\n\n.flex-align-start {\n  align-items: flex-start;\n}\n.flex-align-end {\n  align-items: flex-end;\n}\n.flex-align-baseline {\n  align-items: baseline;\n}\n.flex-align-stretch {\n  align-items: stretch;\n}\n.flex-align-center {\n  align-items: center;\n}\n\n.flex-justify-start {\n  justify-content: flex-start;\n}\n.flex-justify-end {\n  justify-content: flex-end;\n}\n.flex-justify-center {\n  justify-content: center;\n}\n.flex-justify-between {\n  justify-content: space-between;\n}\n.flex-justify-around {\n  justify-content: space-around;\n}\n\n.flex-1 {\n  flex: 1;\n}\n\n.flex-shrink-0 {\n  flex-shrink: 0;\n}\n\n.flex-shrink-1 {\n  flex-shrink: 1;\n}\n\n//width\n.width-100vw {\n  width: 100vw;\n}\n.width-100 {\n  width: 100%;\n}\n\n//height\n.height-100vh {\n  height: 100vh;\n}\n.height-100 {\n  height: 100%;\n}\n\n//margin\nm-5 {\n  margin: -5px !important;\n}\n.mt-5 {\n  margin-top: -5px !important;\n}\n\n.mt100 {\n  margin-top: 100px !important;\n}\n\n.mt110 {\n  margin-top: 110px !important;\n}\n\n.mb-5 {\n  margin-bottom: -5px !important;\n}\n.ml-5 {\n  margin-left: -5px !important;\n}\n.mr-5 {\n  margin-right: -5px !important;\n}\n.m0 {\n  margin: 0px !important;\n}\n.mt0 {\n  margin-top: 0px !important;\n}\n.mb0 {\n  margin-bottom: 0px !important;\n}\n.ml0 {\n  margin-left: 0px !important;\n}\n.mr0 {\n  margin-right: 0px !important;\n}\n.m5 {\n  margin: 5px !important;\n}\n.mt5 {\n  margin-top: 5px !important;\n}\n.mb5 {\n  margin-bottom: 5px !important;\n}\n.ml5 {\n  margin-left: 5px !important;\n}\n.mr5 {\n  margin-right: 5px !important;\n}\n.m10 {\n  margin: 10px !important;\n}\n.mt10 {\n  margin-top: 10px !important;\n}\n.mb10 {\n  margin-bottom: 10px !important;\n}\n.ml10 {\n  margin-left: 10px !important;\n}\n.mr10 {\n  margin-right: 10px !important;\n}\n.m15 {\n  margin: 15px !important;\n}\n.mt15 {\n  margin-top: 15px !important;\n}\n.mb15 {\n  margin-bottom: 15px !important;\n}\n.ml15 {\n  margin-left: 15px !important;\n}\n.mr15 {\n  margin-right: 15px !important;\n}\n.m20 {\n  margin: 20px !important;\n}\n.mt20 {\n  margin-top: 20px !important;\n}\n.mb20 {\n  margin-bottom: 20px !important;\n}\n.ml20 {\n  margin-left: 20px !important;\n}\n.mr20 {\n  margin-right: 20px !important;\n}\n.m25 {\n  margin: 25px !important;\n}\n.mt25 {\n  margin-top: 25px !important;\n}\n.mb25 {\n  margin-bottom: 25px !important;\n}\n.ml25 {\n  margin-left: 25px !important;\n}\n.mr25 {\n  margin-right: 25px !important;\n}\n.m30 {\n  margin: 30px !important;\n}\n.mt30 {\n  margin-top: 30px !important;\n}\n.mb30 {\n  margin-bottom: 30px !important;\n}\n.ml30 {\n  margin-left: 30px !important;\n}\n.ml40 {\n  margin-left: 40px !important;\n}\n.mr30 {\n  margin-right: 30px !important;\n}\n.m50 {\n  margin: 50px !important;\n}\n.mt50 {\n  margin-top: 50px !important;\n}\n.mb50 {\n  margin-bottom: 50px !important;\n}\n.ml50 {\n  margin-left: 50px !important;\n}\n.mr50 {\n  margin-right: 50px !important;\n}\n// padding值\n.p0 {\n  padding: 0 !important;\n}\n.pt0 {\n  padding-top: 0 !important;\n}\n.pr0 {\n  padding-right: 0 !important;\n}\n.pb0 {\n  padding-bottom: 0 !important;\n}\n.pl0 {\n  padding-left: 0 !important;\n}\n.p5 {\n  padding: 5px;\n}\n.pt5 {\n  padding-top: 5px;\n}\n.pr5 {\n  padding-right: 5px;\n}\n.pb5 {\n  padding-bottom: 5px;\n}\n.pl5 {\n  padding-left: 5px;\n}\n.p10 {\n  padding: 10px;\n}\n.pt10 {\n  padding-top: 10px;\n}\n.pr10 {\n  padding-right: 10px;\n}\n.pb10 {\n  padding-bottom: 10px;\n}\n.pl10 {\n  padding-left: 10px;\n}\n.p15 {\n  padding: 15px;\n}\n.pt15 {\n  padding-top: 15px;\n}\n.pr15 {\n  padding-right: 15px;\n}\n.pb15 {\n  padding-bottom: 15px;\n}\n.pl15 {\n  padding-left: 15px;\n}\n.p20 {\n  padding: 20px;\n  box-sizing: border-box;\n}\n.pt20 {\n  padding-top: 20px;\n}\n.pr20 {\n  padding-right: 20px;\n}\n.pb20 {\n  padding-bottom: 20px;\n}\n.pl20 {\n  padding-left: 20px;\n}\n.p30 {\n  padding: 30px;\n  box-sizing: border-box;\n}\n.pt30 {\n  padding-top: 30px;\n}\n.pr30 {\n  padding-right: 30px;\n}\n.pb30 {\n  padding-bottom: 30px;\n}\n.pl30 {\n  padding-left: 30px;\n}\n.pb50 {\n  padding-bottom: 50px;\n}\n.pl50 {\n  padding-left: 50px;\n}\n.pl120 {\n  padding-left: 120px;\n}\n.pl150 {\n  padding-left: 150px;\n}\n.pt50 {\n  padding-top: 50px;\n}\n"
  },
  {
    "path": "src/styles/fonts.scss",
    "content": "$font-family-sans-serif: 'Open Sans', BlinkMacSystemFont, 'Segoe UI', Roboto,\n  'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',\n  'Microsoft YaHei', SimSun, sans-serif;\n\n$line-heights: (\n  12: 20px,\n  14: 22px,\n  16: 24px,\n  18: 26px\n);\n\n// 用法: @include text(12)\n@mixin text($size) {\n  font-size: #{$size}px;\n  line-height: map-get($line-heights, $size);\n}\n\n// 常规体\n@mixin text-regular {\n  font-weight: 400;\n}\n\n// 中粗体\n@mixin text-semibold {\n  font-weight: 600;\n}\n\n@mixin ellipsis {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.fz10 {\n  font-size: 10px;\n}\n.fz12 {\n  font-size: 12px;\n}\n.fz14 {\n  font-size: 14px;\n}\n.fz16 {\n  font-size: 16px;\n}\n.fz18 {\n  font-size: 18px;\n}\n.fz20 {\n  font-size: 20px;\n}\n.fz22 {\n  font-size: 22px;\n}\n.fz24 {\n  font-size: 24px;\n}\n.fz26 {\n  font-size: 26px;\n}\n.fz28 {\n  font-size: 28px;\n}\n.fz30 {\n  font-size: 30px;\n}\n.fz32 {\n  font-size: 32px;\n}\n.fz35 {\n  font-size: 35px;\n}\n"
  },
  {
    "path": "src/styles/index.scss",
    "content": "@import './common.scss';\n@import 'flex.style.scss';\n@import 'fonts.scss';\n"
  },
  {
    "path": "src/styles/reset.scss",
    "content": "html,\nbody,\ndiv,\nspan,\napplet,\nobject,\niframe,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\na,\nabbr,\nacronym,\naddress,\nbig,\ncite,\ncode,\ndel,\ndfn,\nem,\nimg,\nins,\nkbd,\nq,\ns,\nsamp,\nsmall,\nstrike,\nstrong,\nsub,\nsup,\ntt,\nvar,\nb,\nu,\ni,\ncenter,\ndl,\ndt,\ndd,\nol,\nul,\nli,\nfieldset,\nform,\nlabel,\nlegend,\ntable,\ncaption,\ntbody,\ntfoot,\nthead,\ntr,\nth,\ntd,\narticle,\naside,\ncanvas,\ndetails,\nembed,\nfigure,\nfigcaption,\nfooter,\nheader,\nhgroup,\nmenu,\nnav,\noutput,\nruby,\nsection,\nsummary,\ntime,\nmark,\naudio,\nvideo {\n  margin        : 0;\n  padding       : 0;\n  border        : 0;\n  font          : inherit;\n  font-size     : 100%;\n  vertical-align: baseline;\n}\n\nhtml {\n  line-height                : 1;\n  // -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nol,\nul {\n  list-style: none;\n}\n\ntable {\n  border-collapse: collapse;\n  border-spacing : 0;\n}\n\ncaption,\nth,\ntd {\n  font-weight   : normal;\n  vertical-align: middle;\n}\n\nq,\nblockquote {\n  quotes: none;\n}\n\nq::before,\nq::after,\nblockquote::before,\nblockquote::after {\n  content: '';\n  content: none;\n}\n\na img {\n  border: none;\n}\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\n\n* {\n  box-sizing: content-box;\n}\n\na {\n  background     : transparent;\n  text-decoration: none;\n}\n\nbutton,\ninput[type='number'],\ninput[type='text'],\ninput[type='password'],\ninput[type='email'],\ninput[type='search'],\nselect,\ntextarea {\n  font-family       : inherit;\n  margin            : 0;\n  -webkit-appearance: none;\n}"
  },
  {
    "path": "src/styles/variables.scss",
    "content": "$main-text-color: #000;\n$header-height: 52px;\n// Auxiliary color\n$info: #1fa3f6;\n$success: #28d445;\n$danger: #e70102;\n$alarm: #ffcc00;\n$warning: #ffcc00;\n$error: #e70102;\n\n// 品牌色\n$primary: #2d8cf0;\n$primary-click: #2b85e4;\n$primary-hover: #5cadff;\n$primary-hover-dropdown: rgba($primary-hover, 0.2);\n$primary-disabled: #274d75;\n// 辅助色拓展\n$danger-hover: #ff4d4e;\n$danger-active: #d40001;\n$menu-primary: #464c5b; // tab菜单主题色\n// 图标颜色\n$ic-white-normal: #fff;\n$ic-black-normal: #4e4e4e;\n$ic-hover: #a7a7a7;\n$ic-disabled: #5f5f5f;\n$ic-selected: #1088f2;\n// 中性色-黑\n$dark-bg-light: #868688; // 背景色浅色\n$dark-btn-hover: #5d5f61; // 按钮 hover 色\n$dark-border: #4f4f4f; // 边框色\n$dark-btn-disabled: #3c3c3c; // 按钮主色禁用\n$dark-border-secondary: #393939; // 第二边框色\n$dark-basic-primary: #232323; // 第一基础底色\n$dark-basic-secondary: #282828; // 第二基础底色\n$dark-highlight: #232323; // 高亮色\n$dark-disable: #444444; // 置灰底色\n$dark-project-disabled: #292929;\n// 中性色-白\n$light-bg-primary: #fff; // 1 背景\n$light-bg-secondary: #f7f9fa; // 2 背景\n$light-divider: #e8eaec; // 3 分割线\n$light-border: #dcdee2; // 4 边框\n$light-disabled: #c5c8ce; // 5 失效\n$light-auxiliary: #808695; // 6 辅助图标\n$light-main-text: #515a6e; // 7 正文 ？用处\n$light-title: #17233d; // 8 标题 ？用处\n$light-bg-menu: #3b3e40; // 9 菜单栏背景色  ？用处\n$light-border-secondary: #e8e8e8; // 第二边框色\n\n// 字体\n// 白色\n$text-white-basic: #fff; // 基础色\n$text-white-main: rgba($text-white-basic, 1); // 正文\n$text-white-secondary: rgba($text-white-basic, 0.45); // 次级\n$text-white-disabled: rgba($text-white-basic, 0.25); // 置灰\n// 黑色\n$text-black-basic: #000000; // 基础色\n$text-black-emphasize: rgba($text-black-basic, 0.85); // 强调\n$text-black-main: rgba($text-black-basic, 0.65); // 正文\n$text-black-secondary: rgba($text-black-basic, 0.45); // 次要\n$text-black-disabled: rgba($text-black-basic, 0.25); // 置灰\n$text-link: $primary;\n$text-danger: $danger;\n// 标签\n$tag-green: #19be6b;\n// 滚动条等颜色\n$scroll-bar: #c5c8ce;\n$scroll-bar-dark: #5f5f5f;\n\n// 选择框\n\n$select-disabled: #d8d8d8;\n"
  },
  {
    "path": "src/types/airport-tsa.ts",
    "content": "// 机场存储容量：总容量（单位：KB）、已使用（单位：KB）\nexport interface AirportStorage {\n  total: number, // 单位：KB\n  used: number\n}\n\n// 舱盖状态\nexport enum CoverStateEnum {\n  Close = 0, // 关闭\n  Open = 1, // 打开\n  HalfOpen = 2, // 半打开\n  Failed = 3 // 失败\n}\n\n// 推杆状态\nexport enum PutterStateEnum {\n  Close = 0, // 关闭\n  Open = 1, // 打开\n  HalfOpen = 2, // 半打开\n  Failed = 3 // 失败\n}\n\n// 充电状态\nexport enum ChargeStateEnum {\n  NotCharge = 0, // 空闲\n  Charge = 1, // 正在充电\n}\n\nexport interface DroneChargeState {\n  state: ChargeStateEnum,\n  capacity_percent: string,\n}\n\n// 补光灯状态\nexport enum SupplementLightStateEnum {\n  Close = 0, // 关闭\n  Open = 1, // 打开\n}\n\n// 机场声光报警状态\nexport enum AlarmModeEnum {\n  CLOSE = 0, // 关闭\n  OPEN = 1, // 开启\n}\n\n// 电池保养\nexport enum BatteryStoreModeEnum {\n  BATTERY_PLAN_STORE = 1, // 电池计划存储策略\n  BATTERY_EMERGENCY_STORE = 2, // 电池应急存储策略\n}\n\n// 飞行器电池保养\nexport enum DroneBatteryStateEnum {\n  NoMaintenanceRequired = 0, // 0-无需保养\n  MaintenanceRequired = 1, // 1-待保养\n  MaintenanceInProgress = 2, // 2-正在保养\n}\n\nexport enum DroneBatteryModeEnum {\n  CLOSE = 0, // 关闭\n  OPEN = 1, // 开启\n}\n\n// 4g链路连接状态\nexport enum FourGLinkStateEnum {\n  CLOSE = 0, // 断开\n  OPEN = 1, // 连接\n}\n\n//  Sdr链路连接状态\nexport enum SdrLinkStateEnum {\n  CLOSE = 0, // 断开\n  OPEN = 1, // 连接\n}\n\n// 机场的图传链路模式\nexport enum LinkWorkModeEnum {\n  SDR = 0, // sdr模式\n  FourG_FUSION_MODE = 1, // 4G融合模式\n}\n"
  },
  {
    "path": "src/types/device-cmd.ts",
    "content": "import { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryModeEnum, LinkWorkModeEnum } from '/@/types/airport-tsa'\n// 机场指令集\nexport enum DeviceCmd {\n  // 简单指令\n  DebugModeOpen = 'debug_mode_open', // 调试模式开启\n  DebugModeClose = 'debug_mode_close', // 调试模式关闭\n  SupplementLightOpen = 'supplement_light_open', // 打开补光灯\n  SupplementLightClose = 'supplement_light_close', // 关闭补光灯\n  ReturnHome = 'return_home', // 一键返航\n  ReturnHomeCancel = 'return_home_cancel', // 取消返航\n  // 复杂指令\n  DeviceReboot = 'device_reboot', // 机场重启\n  DroneOpen = 'drone_open', // 飞行器开机\n  DroneClose = 'drone_close', // 飞行器关机\n  // DeviceCheck = 'device_check', // 一键排障（一键起飞自检）\n  DeviceFormat = 'device_format', // 机场数据格式化\n  DroneFormat = 'drone_format', // 飞行器数据格式化\n  CoverOpen = 'cover_open', // 打开舱盖\n  CoverClose = 'cover_close', // 关闭舱盖\n  PutterOpen = 'putter_open', // 推杆展开\n  PutterClose = 'putter_close', // 推杆闭合\n  ChargeOpen = 'charge_open', // 打开充电\n  ChargeClose = 'charge_close', // 关闭充电\n  AlarmStateSwitch = 'alarm_state_switch', // 机场声光报警\n  BatteryStoreModeSwitch = 'battery_store_mode_switch', // 电池保养\n  DroneBatteryModeSwitch = 'battery_maintenance_switch', // 飞行器电池保养\n  SdrWorkModeSwitch = 'sdr_workmode_switch', // 增强图传\n}\n\nexport type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum | LinkWorkModeEnum\n\nexport interface DeviceCmdItem{\n  label: string, // 标题\n  status: string, // 当前状态\n  operateText: string, // 按钮文字\n  cmdKey: DeviceCmd, // 请求指令\n  oppositeCmdKey?: DeviceCmd, // 相反状态指令\n  action?: DeviceCmdItemAction, // 参数\n  func: string, // 处理函数\n  loading: boolean // 按钮loading\n  disabled?: boolean // 按钮disabled\n}\nexport const noDebugCmdList: DeviceCmdItem[] = [\n  {\n    label: 'Return Home',\n    status: '--',\n    operateText: 'Return Home',\n    cmdKey: DeviceCmd.ReturnHome,\n    func: 'returnHome',\n    loading: false,\n  },\n  {\n    label: 'Return Home Cancel',\n    status: '--',\n    operateText: 'Return Home Cancel',\n    cmdKey: DeviceCmd.ReturnHomeCancel,\n    func: 'returnHomeCancel',\n    loading: false,\n  }\n]\n\n// 机场指令\nexport const cmdList: DeviceCmdItem[] = [\n  {\n    // iconName: ,\n    label: '机场系统',\n    status: '工作中',\n    operateText: '重启',\n    cmdKey: DeviceCmd.DeviceReboot,\n    func: 'deviceReboot',\n    loading: false,\n    // btnAnimationIconName: '',\n    // operateTips: '',\n    // statusColor: '',\n  },\n  {\n    label: '飞行器',\n    status: '关机',\n    operateText: '开机',\n    cmdKey: DeviceCmd.DroneOpen,\n    oppositeCmdKey: DeviceCmd.DroneClose,\n    func: 'droneStatus',\n    loading: false,\n  },\n  {\n    label: '舱盖',\n    status: '关',\n    operateText: '开启',\n    cmdKey: DeviceCmd.CoverOpen,\n    oppositeCmdKey: DeviceCmd.CoverClose,\n    func: 'coverStatus',\n    loading: false,\n  },\n  {\n    label: '推杆',\n    status: '闭合',\n    operateText: '展开',\n    cmdKey: DeviceCmd.PutterOpen,\n    oppositeCmdKey: DeviceCmd.PutterClose,\n    func: 'putterStatus',\n    loading: false,\n  },\n  {\n    label: '充电状态',\n    status: '未充电',\n    operateText: '充电',\n    cmdKey: DeviceCmd.ChargeOpen,\n    oppositeCmdKey: DeviceCmd.ChargeClose,\n    func: 'chargeStatus',\n    loading: false,\n  },\n  {\n    label: '机场存储',\n    status: '--',\n    operateText: '格式化',\n    cmdKey: DeviceCmd.DeviceFormat,\n    func: 'deviceFormat',\n    loading: false,\n  },\n  {\n    label: '飞行器存储',\n    status: '--',\n    operateText: '格式化',\n    cmdKey: DeviceCmd.DroneFormat,\n    func: 'droneFormat',\n    loading: false,\n  },\n  {\n    label: '补光灯',\n    status: '关',\n    operateText: '打开',\n    cmdKey: DeviceCmd.SupplementLightOpen,\n    oppositeCmdKey: DeviceCmd.SupplementLightClose,\n    func: 'supplementLightStatus',\n    loading: false,\n  },\n  {\n    label: '机场声光报警',\n    status: '关',\n    operateText: '打开',\n    cmdKey: DeviceCmd.AlarmStateSwitch,\n    action: AlarmModeEnum.OPEN,\n    func: 'alarmState',\n    loading: false,\n  },\n  {\n    label: '机场电池存储模式',\n    status: '计划',\n    operateText: '应急',\n    cmdKey: DeviceCmd.BatteryStoreModeSwitch,\n    action: BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE,\n    func: 'batteryStoreMode',\n    loading: false,\n  },\n  {\n    label: '飞机电池保养',\n    status: '--',\n    operateText: '保养',\n    cmdKey: DeviceCmd.DroneBatteryModeSwitch,\n    action: DroneBatteryModeEnum.OPEN,\n    func: 'droneBatteryMode',\n    loading: false,\n    disabled: true,\n  },\n  {\n    label: '4g 增强',\n    status: '--',\n    operateText: '开启',\n    cmdKey: DeviceCmd.SdrWorkModeSwitch,\n    action: LinkWorkModeEnum.FourG_FUSION_MODE,\n    func: 'sdrWorkMode',\n    loading: false,\n  },\n]\n\nexport enum DeviceCmdStatusText {\n  DeviceRebootNormalText = '工作中',\n  DeviceRebootInProgressText = '重启中...',\n  DeviceRebootFailedText = '重启失败',\n\n  DroneStatusOpenNormalText = '开',\n  DroneStatusOpenInProgressText = '开机中...',\n  DroneStatusOpenFailedText = '关',\n  DroneStatusOpenBtnText = '关机',\n\n  DroneStatusCloseNormalText = '关',\n  DroneStatusCloseInProgressText = '关机中...',\n  DroneStatusCloseFailedText = '开',\n  DroneStatusCloseBtnText = '开机',\n\n  DeviceCoverOpenNormalText = '开',\n  DeviceCoverOpenInProgressText = '开启中...',\n  DeviceCoverOpenFailedText = '关',\n  DeviceCoverOpenBtnText = '关闭',\n\n  DeviceCoverCloseNormalText = '关',\n  DeviceCoverCloseInProgressText = '关闭中...',\n  DeviceCoverCloseFailedText = '开',\n  DeviceCoverCloseBtnText = '开启',\n\n  DevicePutterOpenNormalText = '展开',\n  DevicePutterOpenBtnText = '闭合',\n  DevicePutterOpenInProgressText = '推杆展开中',\n  DevicePutterOpenFailedText = '闭合',\n\n  DevicePutterCloseNormalText = '闭合',\n  DevicePutterCloseInProgressText = '推杆闭合中',\n  DevicePutterCloseFailedText = '展开',\n  DevicePutterCloseBtnText = '展开',\n\n  DeviceChargeOpenNormalText = '充电',\n  DeviceChargeOpenInProgressText = '充电中...',\n  DeviceChargeOpenFailedText = '未充电',\n  DeviceChargeOpenBtnText = '断电',\n\n  DeviceChargeCloseNormalText = '断电',\n  DeviceChargeCloseInProgressText = '断电中...',\n  DeviceChargeCloseFailedText = '充电',\n  DeviceChargeCloseBtnText = '充电',\n\n  DeviceFormatInProgressText = '格式化...',\n  DeviceFormatFailedText = '格式化失败',\n\n  DroneFormatInProgressText = '格式化...',\n  DroneFormatFailedText = '格式化失败',\n\n  DeviceSupplementLightOpenNormalText = '开',\n  DeviceSupplementLightOpenInProgressText = '开启中...',\n  DeviceSupplementLightOpenFailedText = '关',\n  DeviceSupplementLightOpenBtnText = '关闭',\n\n  DeviceSupplementLightCloseNormalText = '关',\n  DeviceSupplementLightCloseText = '关闭中...',\n  DeviceSupplementLightCloseFailedText = '开',\n  DeviceSupplementLightCloseBtnText = '打开',\n\n  AlarmStateOpenNormalText = '开',\n  AlarmStateOpenText = '开启中...',\n  AlarmStateOpenFailedText = '关',\n  AlarmStateOpenBtnText = '关闭',\n\n  AlarmStateCloseNormalText = '关',\n  AlarmStateCloseText = '关闭中...',\n  AlarmStateCloseFailedText = '开',\n  AlarmStateCloseBtnText = '打开',\n\n  BatteryStoreModePlanNormalText = '计划',\n  BatteryStoreModePlanText = '切换中...',\n  BatteryStoreModePlanFailedText = '应急',\n  BatteryStoreModePlanBtnText = '应急',\n\n  BatteryStoreModeEmergencyNormalText = '应急',\n  BatteryStoreModeEmergencyText = '切换中...',\n  BatteryStoreModeEmergencyFailedText = '计划',\n  BatteryStoreModeEmergencyBtnText = '计划',\n\n  DroneBatteryModeMaintenanceInProgressText = '保养中',\n  DroneBatteryModeMaintenanceNotNeedText = '无需保养',\n  DroneBatteryModeMaintenanceNeedText = '需保养',\n  DroneBatteryModeOpenBtnText = '保养',\n  DroneBatteryModeCloseBtnText = '关闭保养',\n\n  SdrWorkModeFourGOpenNormalText = '开',\n  SdrWorkModeFourGOpenText = '开启中...',\n  SdrWorkModeFourGOpenFailedText = '--',\n  SdrWorkModeFourGOpenBtnText = '关闭',\n\n  SdrWorkModeFourGCloseNormalText = '--',\n  SdrWorkModeFourGCloseText = '关闭中...',\n  SdrWorkModeFourGCloseFailedText = '开',\n  SdrWorkModeFourCloseBtnText = '开启',\n}\n\n// cmd ws 消息状态\nexport enum DeviceCmdExecuteStatus {\n  Sent = 'sent', // 已下发\n  InProgress = 'in_progress', // 执行中\n  OK = 'ok', // 执行成功\n  Failed = 'failed', // 失败\n  Canceled = 'canceled', // 取消\n  Timeout = 'timeout' // 超时\n}\n\nexport interface DeviceCmdExecuteInfo {\n  biz_code: string,\n  timestamp: number,\n  sn: string,\n  bid: string,\n  output:{\n    status: DeviceCmdExecuteStatus,\n    progress?: {\n      percent: number,\n      step_key: string,\n      step_result: number\n    },\n    ext?: {\n      rate?: number\n    }\n  }\n  result: number,\n}\n\n// 所有机场的指令执行状态\nexport interface DevicesCmdExecuteInfo {\n  [key: string]: DeviceCmdExecuteInfo[], // sn --- DeviceCmdExecuteInfo\n}\n"
  },
  {
    "path": "src/types/device-firmware.ts",
    "content": "export interface Firmware {\n  firmware_id: string\n  file_name: string\n  product_version: string\n  file_size: number\n  device_name: string[]\n  username: string\n  release_note: string\n  released_time: string\n  firmware_status: boolean\n}\n\nexport enum FirmwareStatusEnum {\n  NONE = 'All',\n  FALSE = 'Disabled',\n  TRUE = 'Available'\n}\n\nexport interface FirmwareQueryParam {\n  product_version: string\n  device_name: string\n  firmware_status: FirmwareStatusEnum\n}\n\nexport interface FirmwareUploadParam {\n  device_name: string[]\n  release_note: string\n  status: boolean\n}\n\nexport enum DeviceNameEnum {\n  DJI_DOCK = 'DJI Dock',\n  DJI_DOCK2 = 'DJI Dock2',\n  MATRICE_30 = 'Matrice 30',\n  MATRICE_30T = 'Matrice 30T',\n  M3D = 'M3D',\n  M3TD = 'M3TD',\n}\n"
  },
  {
    "path": "src/types/device-log.ts",
    "content": "import { DOMAIN } from '/@/types/device'\nimport { commonColor } from '/@/utils/color'\n\n// 日志上传状态\nexport enum DeviceLogUploadStatusEnum {\n  Uploading = 1, //  上传中\n  Done = 2, // 完成\n  Canceled = 3, // 取消\n  Failed = 4, // 失败\n}\n\nexport const DeviceLogUploadStatusMap = {\n  [DeviceLogUploadStatusEnum.Uploading]: '上传中',\n  [DeviceLogUploadStatusEnum.Done]: '上传成功',\n  [DeviceLogUploadStatusEnum.Canceled]: '取消上传',\n  [DeviceLogUploadStatusEnum.Failed]: '上传失败',\n}\n\nexport const DeviceLogUploadStatusColor = {\n  [DeviceLogUploadStatusEnum.Uploading]: commonColor.BLUE,\n  [DeviceLogUploadStatusEnum.Done]: commonColor.NORMAL,\n  [DeviceLogUploadStatusEnum.Canceled]: commonColor.WARN,\n  [DeviceLogUploadStatusEnum.Failed]: commonColor.FAIL,\n}\n\n// 设备日志上传 ws 消息状态\nexport enum DeviceLogUploadStatus {\n  FilePull = 'file_pull', // 拉取日志 可以作为 正在处理中\n  FileZip = 'file_zip', // 拉取日志,日志压缩可以作为 正在处理中\n  FileUploading = 'file_uploading', // 正在上传\n  Canceled = 'canceled', // 取消\n  Timeout = 'timeout', // 超时\n  Failed = 'failed', // 失败\n  OK = 'ok', // 上传成功\n  // Paused = 'paused' // 暂停\n}\n\nexport interface DeviceLogUploadInfo {\n  sn: string,\n  bid: string,\n  output:{\n    logs_id: string\n    status: DeviceLogUploadStatus,\n    files: {\n        device_sn: string,\n        device_model_domain: DOMAIN,\n        progress: number,\n        result: number,\n        upload_rate: number,\n        status: DeviceLogUploadStatus\n    }[]\n  }\n  result: number,\n}\n\n// ws status => log status\nexport const DeviceLogUploadWsStatusMap = {\n  [DeviceLogUploadStatus.FilePull]: DeviceLogUploadStatusEnum.Uploading,\n  [DeviceLogUploadStatus.FileZip]: DeviceLogUploadStatusEnum.Uploading,\n  [DeviceLogUploadStatus.FileUploading]: DeviceLogUploadStatusEnum.Uploading,\n  [DeviceLogUploadStatus.OK]: DeviceLogUploadStatusEnum.Done,\n  [DeviceLogUploadStatus.Failed]: DeviceLogUploadStatusEnum.Failed,\n  [DeviceLogUploadStatus.Canceled]: DeviceLogUploadStatusEnum.Canceled,\n  [DeviceLogUploadStatus.Timeout]: DeviceLogUploadStatusEnum.Failed,\n}\n"
  },
  {
    "path": "src/types/device-setting.ts",
    "content": "// 夜航灯开关\nexport enum NightLightsStateEnum {\n  CLOSE = 0, // 0-关闭\n  OPEN = 1, // 1-打开\n}\n\n// 限远开关\nexport enum DistanceLimitStatusEnum {\n  UNSET = 0, // 0-未设置\n  SET = 1, // 1-已设置\n}\n\nexport interface DistanceLimitStatus {\n  state?: DistanceLimitStatusEnum;\n  distance_limit?: number; // 限远\n}\n\n// 避障\nexport enum ObstacleAvoidanceStatusEnum {\n  CLOSE = 0, // 0-关闭\n  OPEN = 1, // 1-开启\n}\n\nexport interface ObstacleAvoidance {\n  horizon?: ObstacleAvoidanceStatusEnum;// 水平避障开关\n  upside?: ObstacleAvoidanceStatusEnum;// 上行方向避障开关\n  downside?: ObstacleAvoidanceStatusEnum;// 下行方向避障开关\n}\n\n// 设备管理设置key\nexport enum DeviceSettingKeyEnum {\n  NIGHT_LIGHTS_MODE_SET = 'night_lights_state', // 夜航灯开关\n  HEIGHT_LIMIT_SET = 'height_limit', // 限高设置\n  DISTANCE_LIMIT_SET = 'distance_limit_status', // 限远开关\n  OBSTACLE_AVOIDANCE_HORIZON = 'obstacle_avoidance_horizon', // 水平避障状态\n  OBSTACLE_AVOIDANCE_UPSIDE = 'obstacle_avoidance_upside', // 上视避障状态\n  OBSTACLE_AVOIDANCE_DOWNSIDE = 'obstacle_avoidance_downside', // 下视避障状态\n}\n\nexport type DeviceSettingType = Record<DeviceSettingKeyEnum, any>\n\nexport const initDeviceSetting = {\n  [DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET]:\n  {\n    label: '飞行器夜航灯',\n    value: '',\n    trueValue: NightLightsStateEnum.CLOSE,\n    editable: false,\n    popConfirm: {\n      visible: false,\n      loading: false,\n      // content: '为保证飞行器的作业安全，建议打开夜航灯',\n      label: '飞行器夜航灯',\n    },\n    settingKey: DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET,\n  },\n  [DeviceSettingKeyEnum.HEIGHT_LIMIT_SET]:\n  {\n    label: '限高',\n    value: '',\n    trueValue: 120,\n    editable: false,\n    popConfirm: {\n      visible: false,\n      loading: false,\n      // content: '限高：20 - 1500m',\n      // info: '修改限高会影响当前机场的所有作业任务，建议确认作业情况后再进行修改',\n      label: '限高',\n    },\n    settingKey: DeviceSettingKeyEnum.HEIGHT_LIMIT_SET,\n  },\n  [DeviceSettingKeyEnum.DISTANCE_LIMIT_SET]:\n  {\n    label: '限远',\n    value: '',\n    trueValue: DistanceLimitStatusEnum.UNSET,\n    // info: '限远（15 - 8000m）是约束飞行器相对机场的最大作业距离',\n    editable: false,\n    popConfirm: {\n      visible: false,\n      loading: false,\n      // content: '限远 (15- 8000m) 是约束飞行器相对机场的最大作业距离',\n      // info: '修改限远会影响当前机场的所有作业任务，建议确认作业情况后再进行修改',\n      label: '限远',\n\n    },\n    settingKey: DeviceSettingKeyEnum.DISTANCE_LIMIT_SET,\n  },\n  [DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON]:\n  {\n    label: '水平避障',\n    value: '',\n    trueValue: ObstacleAvoidanceStatusEnum.CLOSE,\n    // info: '飞行器的避障工作状态显示，可以快速开启/关闭飞行器避障，如需进一步设置请在设备运维页面设置',\n    editable: false,\n    popConfirm: {\n      visible: false,\n      loading: false,\n      // content: '飞行器避障是保障飞行作业安全的基础功能，建议保持飞行器避障开启',\n      label: '水平避障',\n\n    },\n    settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON,\n  },\n  [DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE]:\n  {\n    label: '上视避障',\n    value: '',\n    trueValue: ObstacleAvoidanceStatusEnum.CLOSE,\n    // info: '飞行器的避障工作状态显示，可以快速开启/关闭飞行器避障，如需进一步设置请在设备运维页面设置',\n    editable: false,\n    popConfirm: {\n      visible: false,\n      loading: false,\n      // content: '飞行器避障是保障飞行作业安全的基础功能，建议保持飞行器避障开启',\n      label: '上视避障',\n\n    },\n    settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE,\n  },\n  [DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE]:\n  {\n    label: '下视避障',\n    value: '',\n    trueValue: ObstacleAvoidanceStatusEnum.CLOSE,\n    // info: '飞行器的避障工作状态显示，可以快速开启/关闭飞行器避障，如需进一步设置请在设备运维页面设置',\n    editable: false,\n    popConfirm: {\n      visible: false,\n      loading: false,\n      // content: '飞行器避障是保障飞行作业安全的基础功能，建议保持飞行器避障开启',\n      label: '下视避障',\n\n    },\n    settingKey: DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE,\n  },\n} as DeviceSettingType\n\nexport const initDeviceSettingFormModel = {\n  nightLightsState: false, // 夜航灯开关\n  heightLimit: 20, // 限高设置\n  distanceLimitStatus: { state: false, distanceLimit: 15 }, // 限远开关\n  obstacleAvoidanceHorizon: false, // 飞行器避障-水平开关设置\n  obstacleAvoidanceUpside: false, // 飞行器避障-上视开关设置\n  obstacleAvoidanceDownside: false, // 飞行器避障-下视开关设置\n}\n\nexport type DeviceSettingFormModel = typeof initDeviceSettingFormModel\n"
  },
  {
    "path": "src/types/device.ts",
    "content": "import { commonColor } from '/@/utils/color'\nimport { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from './device-setting'\nimport { AlarmModeEnum, BatteryStoreModeEnum, DroneBatteryStateEnum, FourGLinkStateEnum, SdrLinkStateEnum, LinkWorkModeEnum } from './airport-tsa'\nimport { CameraMode } from '/@/types/live-stream'\n\nexport interface DeviceValue {\n  key: string; // 'domain-type-subtype'\n  domain: string; // 表示一个领域，作为一个命名空间，暂时分 飞机类-0, 负载类-1,RC类-2,机场类-3 4种\n  type: number; // 设备类型枚举\n  sub_type: number; // 设备类型枚举 负载一般表示镜头\n}\n\n// domain\nexport enum DOMAIN {\n  DRONE = '0', // 飞行器\n  PAYLOAD = '1', // 负载\n  RC = '2', // 遥控\n  DOCK = '3', // 机场\n}\n\n// DJI飞机类型\nexport enum DRONE_TYPE {\n  M30 = 67,\n  M300 = 60,\n  Mavic3EnterpriseAdvanced= 77,\n  M350 = 89,\n  M3D = 91,\n}\n\n// DJI负载类型枚举值\nexport enum PAYLOAD_TYPE {\n  FPV = 39,\n  H20 = 42,\n  H20T = 43,\n  H20N = 61,\n  EP600 = 50,\n  EP800 = 90742,\n  M30D = 52,\n  M30T = 53,\n  XT2 = 26,\n  XTS = 41,\n  Z30 = 20,\n  DockTopCamera = 165,\n\n  M3E = 66,\n  M3T = 67,\n  M3D = 80,\n  M3TD = 81,\n  // UNKNOWN = 65535\n}\n\n// RC type\nexport enum RC_TYPE {\n  RC = 56,\n  RCPlus = 119,\n  RC144 = 144,\n}\n\n// DOCK type\nexport enum DOCK_TYPE {\n  Dock = 1,\n  Dock2 = 2,\n}\n\n// 设备sub_type 从0升序\nexport enum DEVICE_SUB_TYPE {\n  ZERO,\n  ONE,\n  TWO,\n  THREE,\n  UNKNOWN = 65535,\n}\n\nexport const DEVICE_MODEL_KEY = {\n  M30: `${DOMAIN.DRONE}-${DRONE_TYPE.M30}-${DEVICE_SUB_TYPE.ZERO}`,\n  M30T: `${DOMAIN.DRONE}-${DRONE_TYPE.M30}-${DEVICE_SUB_TYPE.ONE}`,\n\n  M3E: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ZERO}`,\n  M3T: `${DOMAIN.DRONE}-${DRONE_TYPE.Mavic3EnterpriseAdvanced}-${DEVICE_SUB_TYPE.ONE}`,\n\n  M300: `${DOMAIN.DRONE}-${DRONE_TYPE.M300}-${DEVICE_SUB_TYPE.ZERO}`,\n  M350: `${DOMAIN.DRONE}-${DRONE_TYPE.M350}-${DEVICE_SUB_TYPE.ZERO}`,\n\n  M3D: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ZERO}`,\n  M3TD: `${DOMAIN.DRONE}-${DRONE_TYPE.M3D}-${DEVICE_SUB_TYPE.ONE}`,\n\n  FPV: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.FPV}-${DEVICE_SUB_TYPE.ZERO}`,\n  H20: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20}-${DEVICE_SUB_TYPE.ZERO}`,\n  H20T: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20T}-${DEVICE_SUB_TYPE.ZERO}`,\n  H20N: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.H20N}-${DEVICE_SUB_TYPE.ZERO}`,\n  EP600: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.EP600}-${DEVICE_SUB_TYPE.UNKNOWN}`,\n  EP800: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.EP800}-${DEVICE_SUB_TYPE.ZERO}`,\n  M30Camera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M30D}-${DEVICE_SUB_TYPE.ZERO}`,\n  M30TCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M30T}-${DEVICE_SUB_TYPE.ZERO}`,\n\n  M3ECamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3E}-${DEVICE_SUB_TYPE.ZERO}`,\n  M3TCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3T}-${DEVICE_SUB_TYPE.ZERO}`,\n  M3DCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3D}-${DEVICE_SUB_TYPE.ZERO}`,\n  M3TDCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3TD}-${DEVICE_SUB_TYPE.ZERO}`,\n  // M3MCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.M3M}-${DEVICE_SUB_TYPE.ZERO}`,\n\n  XT2: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.XT2}-${DEVICE_SUB_TYPE.ZERO}`,\n  XTS: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.XTS}-${DEVICE_SUB_TYPE.ZERO}`,\n  Z30: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.Z30}-${DEVICE_SUB_TYPE.ZERO}`,\n  DockTopCamera: `${DOMAIN.PAYLOAD}-${PAYLOAD_TYPE.DockTopCamera}-${DEVICE_SUB_TYPE.ZERO}`,\n\n  RC: `${DOMAIN.RC}-${RC_TYPE.RC}-${DEVICE_SUB_TYPE.ZERO}`,\n  RCPlus: `${DOMAIN.RC}-${RC_TYPE.RCPlus}-${DEVICE_SUB_TYPE.ZERO}`,\n\n  Dock: `${DOMAIN.DOCK}-${DOCK_TYPE.Dock}-${DEVICE_SUB_TYPE.ZERO}`,\n  Dock2: `${DOMAIN.DOCK}-${DOCK_TYPE.Dock2}-${DEVICE_SUB_TYPE.ZERO}`,\n}\n\nexport const DEVICE_NAME = {\n  // drone\n  [DEVICE_MODEL_KEY.M30]: 'M30',\n  [DEVICE_MODEL_KEY.M30T]: 'M30T',\n  [DEVICE_MODEL_KEY.M3E]: 'Mavic 3E',\n  [DEVICE_MODEL_KEY.M3T]: 'Mavic 3T',\n  // [DEVICE_MODEL_KEY.M3M]: 'Mavic 3M',\n  [DEVICE_MODEL_KEY.M300]: 'M300 RTK',\n  [DEVICE_MODEL_KEY.M350]: 'M350 RTK',\n  [DEVICE_MODEL_KEY.M3D]: 'M3D',\n  [DEVICE_MODEL_KEY.M3TD]: 'M3TD',\n\n  // payload\n  [DEVICE_MODEL_KEY.FPV]: 'FPV',\n  [DEVICE_MODEL_KEY.H20]: 'H20',\n  [DEVICE_MODEL_KEY.H20T]: 'H20T',\n  [DEVICE_MODEL_KEY.H20N]: 'H20N',\n  [DEVICE_MODEL_KEY.EP600]: 'P1',\n  [DEVICE_MODEL_KEY.EP800]: 'L1',\n  [DEVICE_MODEL_KEY.M30Camera]: 'M30 Camera',\n  [DEVICE_MODEL_KEY.M30TCamera]: 'M30T Camera',\n  [DEVICE_MODEL_KEY.M3ECamera]: 'Mavic 3E',\n  [DEVICE_MODEL_KEY.M3TCamera]: 'Mavic 3T',\n  // [DEVICE_MODEL_KEY.M3MCamera]: 'Mavic 3M',\n  [DEVICE_MODEL_KEY.XT2]: 'XT2',\n  [DEVICE_MODEL_KEY.XTS]: 'XTS',\n  [DEVICE_MODEL_KEY.Z30]: 'Z30',\n  [DEVICE_MODEL_KEY.DockTopCamera]: 'Dock Camera',\n  [DEVICE_MODEL_KEY.M3DCamera]: 'M3D Camera',\n  [DEVICE_MODEL_KEY.M3TDCamera]: 'M3TD Camera',\n\n  // rc\n  [DEVICE_MODEL_KEY.RC]: 'RC',\n  [DEVICE_MODEL_KEY.RCPlus]: 'RC Plus',\n\n  // dock\n  [DEVICE_MODEL_KEY.Dock]: 'Dock',\n  [DEVICE_MODEL_KEY.Dock2]: 'Dock2',\n}\n\n// 控制权\nexport enum ControlSource {\n  A = 'A',\n  B = 'B'\n}\n\nexport interface PayloadInfo {\n  index: number,\n  model: string,\n  control_source?: ControlSource,\n  payload_sn?: string,\n  payload_index?: string,\n  payload_name?: string,\n}\n\n// 设备信息\nexport interface OnlineDevice {\n  model: string,\n  callsign: string,\n  sn: string,\n  mode: number,\n  gateway: {\n    model: string,\n    callsign: string,\n    sn: string,\n    domain: string,\n  },\n  payload: PayloadInfo[]\n}\n\n// 固件升级类型\nexport enum DeviceFirmwareTypeEnum {\n  ToUpgraded = 3, // 普通升级\n  ConsistencyUpgrade =2, // 一致性升级\n}\n\n// 固件升级状态\nexport enum DeviceFirmwareStatusEnum {\n  None = 1, // 无需升级\n  ToUpgraded = 2, // 待升级\n  ConsistencyUpgrade = 3, // 一致性升级\n  DuringUpgrade = 4, // 升级中\n}\n\nexport const DeviceFirmwareStatus = {\n  [DeviceFirmwareStatusEnum.None]: '',\n  [DeviceFirmwareStatusEnum.ToUpgraded]: '待升级',\n  [DeviceFirmwareStatusEnum.ConsistencyUpgrade]: '一致性升级',\n  [DeviceFirmwareStatusEnum.DuringUpgrade]: '升级中',\n}\n\nexport const DeviceFirmwareStatusColor = {\n  [DeviceFirmwareStatusEnum.None]: commonColor.WHITE,\n  [DeviceFirmwareStatusEnum.ToUpgraded]: commonColor.BLUE,\n  [DeviceFirmwareStatusEnum.ConsistencyUpgrade]: commonColor.WARN,\n  [DeviceFirmwareStatusEnum.DuringUpgrade]: commonColor.NORMAL,\n}\n\nexport interface Device {\n  device_name: string,\n  device_sn: string,\n  nickname: string,\n  firmware_version: string,\n  firmware_status: DeviceFirmwareStatusEnum,\n  status: string,\n  workspace_name: string,\n  bound_time: string,\n  login_time: string,\n  children?: Device[],\n  domain: number,\n  type: number,\n  firmware_progress?: string, // 升级进度\n}\n\nexport interface DeviceStatus {\n  sn: string,\n  online_status: boolean,\n  device_callsign: string,\n  user_id: string,\n  user_callsign: string\n  bound_status: boolean,\n  model: string,\n  gateway_sn: string,\n  domain: number\n}\n\nexport interface OSDVisible {\n  sn: string,\n  model: string,\n  callsign: string,\n  visible: boolean,\n  is_dock: boolean,\n  gateway_sn: string,\n  gateway_callsign: string,\n  payloads: null | PayloadInfo [],\n}\n\nexport interface GatewayOsd {\n  capacity_percent: string,\n  transmission_signal_quality: string,\n  longitude: number,\n  latitude: number,\n}\n\nexport interface OsdCameraLiveview {\n  bottom: number,\n  left: number,\n  right: number,\n  top: number,\n}\nexport interface DeviceOsdCamera {\n  camera_mode: CameraMode,\n  payload_index: string,\n  photo_state: number,\n  record_time: number,\n  recording_state: number,\n  remain_photo_num: number,\n  remain_record_duration: number,\n  liveview_world_region: OsdCameraLiveview\n}\n\nexport interface DeviceOsd {\n  longitude: number,\n  latitude: number,\n  gear: number,\n  mode_code: number,\n  height: string,\n  home_distance: string,\n  horizontal_speed: string,\n  vertical_speed: string,\n  wind_speed: string,\n  wind_direction: string,\n  elevation: string,\n  position_state: {\n    gps_number: string,\n    is_fixed: number,\n    rtk_number: string\n  },\n  battery: {\n    capacity_percent: string,\n    landing_power: string,\n    remain_flight_time: number,\n    return_home_power: string,\n  },\n  night_lights_state?: NightLightsStateEnum;// 夜航灯开关\n  height_limit?: number;// 限高设置\n  distance_limit_status?: DistanceLimitStatus;// 限远开关\n  obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置\n  cameras?: DeviceOsdCamera[]\n}\n\nexport enum NetworkStateTypeEnum {\n  FOUR_G = 1,\n  ETHERNET = 2,\n}\n\nexport enum NetworkStateQualityEnum {\n  NO_SIGNAL = 0,\n  BAD = 1,\n  POOR = 2,\n  FAIR = 3,\n  GOOD = 4,\n  EXCELLENT = 5,\n}\n\nexport enum RainfallEnum {\n  NONE = 0,\n  LIGHT_RAIN = 1,\n  MODERATE_RAIN = 2,\n  HEAVY_RAIN = 3,\n}\n\nexport enum DroneInDockEnum {\n  OUTSIDE, INSIDE\n}\n\nexport interface DockBasicOsd {\n  network_state?: {\n    type: NetworkStateTypeEnum,\n    quality: number,\n    rate: number,\n  },\n  drone_charge_state?: {\n    state: number,\n    capacity_percent: number,\n  },\n  drone_in_dock: boolean,\n  rainfall: RainfallEnum,\n  wind_speed: number,\n  environment_temperature: number,\n  temperature: number,\n  humidity: number,\n  latitude: number,\n  longitude: number,\n  height: number,\n  alternate_land_point?: {\n    latitude: number,\n    longitude: number,\n    height: number,\n    safe_land_height: number,\n    is_configured: number\n  }\n  first_power_on: number,\n  positionState?: {\n    gps_number: number,\n    is_fixed: number,\n    rtk_number: number,\n    is_calibration: number,\n    quality: number,\n  },\n  storage?: {\n    total: number,\n    used: number,\n  },\n  mode_code: number,\n  cover_state: number,\n  supplement_light_state: number,\n  emergency_stop_state: number,\n  air_conditioner?: {\n    air_conditioner_state: number,\n    switch_time: number,\n  }\n  battery_store_mode?: BatteryStoreModeEnum; // 电池保养(存储)模式\n  alarm_state?: AlarmModeEnum; // 机场声光报警状态\n  putter_state: number,\n  sub_device?: {\n    device_sn?: string,\n    device_model_key?: string,\n    device_online_status: number,\n    device_paired: number,\n  },\n  // live_capacity?: LiveCapacity; // 直播能力\n  // live_status?: Array<LiveStatus>; // 直播状态\n}\nexport enum DrcStateEnum {\n  DISCONNECT = 0,\n  CONNECTING = 1,\n  CONNECTED = 2\n}\nexport interface DockLinkOsd {\n  drc_state: DrcStateEnum,\n  flighttask_prepare_capacity: number,\n  flighttask_step_code: number,\n  media_file_detail?: {\n    remain_upload: number\n  },\n  sdr?: {\n    up_quality: string,\n    down_quality: string,\n    frequency_band: number,\n  },\n  wireless_link?:{ // 图传链路<会包括4G和sdr信息\n    dongle_number: number, // dongle 数量\n    ['4g_link_state']: FourGLinkStateEnum, // 4g_link_state\n    sdr_link_state: SdrLinkStateEnum, // sdr链路连接状态\n    link_workmode: LinkWorkModeEnum, // 图传链路模式\n    sdr_quality: number, // sdr信号质量 0-5\n    ['4g_quality']: number, // 4G信号质量 0-5\n    ['4g_freq_band']: number,\n    ['4g_gnd_quality']: number,\n    ['4g_uav_quality']: number,\n    sdr_freq_band: number,\n  }\n}\n\nexport interface MaintainStatus {\n  state: number,\n  last_maintain_type: number,\n  last_maintain_time: number,\n  last_maintain_work_sorties: number,\n}\n\nexport interface DockWorkOsd {\n  job_number: number,\n  acc_time: number,\n  activation_time: number,\n  maintain_status?: {\n    maintain_status_array: MaintainStatus[]\n  }\n  electric_supply_voltage: number,\n  working_voltage: string,\n  working_current: string,\n  backup_battery?: {\n    voltage: number,\n    temperature: number,\n    switch: number,\n  }\n  drone_battery_maintenance_info?: { // 飞行器电池保养信息\n    maintenance_state: DroneBatteryStateEnum, // 保养状态\n    maintenance_time_left: number, // 电池保养剩余时间(小时)\n  }\n}\n\nexport interface DockOsd {\n  basic_osd: DockBasicOsd,\n  link_osd: DockLinkOsd,\n  work_osd: DockWorkOsd\n}\n\nexport enum EModeCode {\n  Standby,\n  Preparing,\n  Ready,\n  Manual,\n  Automatic,\n  Waypoint,\n  Panoramic,\n  Active_Track,\n  ADS_B,\n  Return_To_Home,\n  Landing,\n  Forced_Landing,\n  Three_Blades_Landing,\n  Upgrading,\n  Disconnected,\n}\n\nexport enum EGear {\n  A,\n  P,\n  NAV,\n  FPV,\n  FARM,\n  S,\n  F,\n  M,\n  G,\n  T\n}\n\nexport enum EDockModeCode {\n  Disconnected = -1,\n  Idle,\n  Debugging,\n  Remote_Debugging,\n  Upgrading,\n  Working,\n}\n\nexport interface DeviceHms {\n  hms_id: string,\n  tid: string,\n  bid: string,\n  sn: string,\n  level: number,\n  module: number,\n  key: string,\n  message_en: string,\n  message_zh: string,\n  create_time: string,\n  update_time: string,\n  domain: number\n}\n\n// TODO: 设备拓扑管理优化\n// 设备osd信息\nexport interface DeviceInfoType {\n  gateway: GatewayOsd, // 遥控器\n  dock: DockOsd, // 机场\n  device: DeviceOsd, // 飞机\n}\n"
  },
  {
    "path": "src/types/drc.ts",
    "content": "export enum DRC_METHOD {\n  HEART_BEAT = 'heart_beat',\n  DRONE_CONTROL = 'drone_control', // 飞行控制-虚拟摇杆\n  DRONE_EMERGENCY_STOP = 'drone_emergency_stop', // 急停\n  OSD_INFO_PUSH = 'osd_info_push', // 高频osd信息上报\n  HSI_INFO_PUSH = 'hsi_info_push', // 避障信息上报\n  DELAY_TIME_INFO_PUSH = 'delay_info_push', // 图传链路延时信息上报\n}\n\n// 手动控制\nexport interface DroneControlProtocol {\n  x?: number; // 水平方向速度，正值为A指令  负值为D指令 单位：m/s\n  y?: number; // 前进后退方向速度，正值为W指令  负值为S指令 单位：m/s\n  h?: number;// 上下高度值，正值为上升指令  负值为下降指令 单位：m\n  w?: number; // 机头角速度，正值为顺时针，负值为逆时针 单位：degree/s   （web端暂无此设计）\n  seq?: number; // 从0计时\n}\n\n// 低延时osd\nexport interface DRCOsdInfo {\n  attitude_head: number;// 飞机姿态head角，单位：度\n  latitude: number;// 飞机经纬度\n  longitude: number;\n  altitude: number;\n  speed_x: number;\n  speed_y: number;\n  speed_z: number;\n  gimbal_pitch: number;// 云台pitch角\n  gimbal_roll: number;// 云台roll角\n  gimbal_yaw: number;// 云台yaw角\n}\n\n// 态势感知-HSI\nexport interface DRCHsiInfo {\n  up_distance: number;// 上方的障碍物距离，单位：mm\n  down_distance: number;// 下方的障碍物距离，单位：mm\n  around_distances: number[]; // 水平方向观察点,分布在[0,360)区间，表示障碍物与飞机距离，单位为mm。 0对应机头方向正前方，顺时针分布，例如0度为机头正前方，90度为飞机正右方\n  up_enable: boolean; // 上视避障开关状态，true：已开启 false：已关闭\n  up_work: boolean; // 上视避障工作状态，true：正常工作 false：异常或离线\n  down_enable: boolean; // 下视避障开关状态，true：已开启 false：已关闭\n  down_work: boolean; // 下视避障工作状态，true：正常工作 false：异常或离线\n  left_enable: boolean; // 左视避障开关状态，true：已开启 false：已关闭\n  left_work: boolean; // 左视避障工作状态，true：正常工作 false：异常或离线\n  right_enable: boolean; // 右视避障开关状态，true：已开启 false：已关闭\n  right_work: boolean; // 右视避障工作状态，true：正常工作 false：异常或离线\n  front_enable: boolean; // 前视避障开关状态，true：已开启 false：已关闭\n  front_work: boolean; // 前视避障工作状态，true：正常工作 false：异常或离线\n  back_enable: boolean; // 后视避障开关状态，true：已开启 false：已关闭\n  back_work: boolean; // 后视避障工作状态，true：正常工作 false：异常或离线\n  vertical_enable: boolean; // 垂直方向综合开关状态，当本协议中上、下视开关状态均为true时，输出true：已开启，否则输出false：已关闭\n  vertical_work: boolean; // 垂直方向避障工作状态，当本协议中上、下视工作均为true时，输出true：正常工作，否则输出false：异常或离线\n  horizontal_enable: boolean; // 水平方向综合开关状态，当本协议中前、后、左、右、开关状态均为true时，输出true：已开启，否则输出false：已关闭\n  horizontal_work: boolean; // 水平方向避障工作综合状态，当本协议中前、后、左、右视工作均为true时，输出true：正常工作，否则输出false：异常或离线\n}\n\nexport interface LiveViewDelayItem {\n  video_id: string;\n  liveview_delay_time: number;\n}\n\n// 链路时延信息\nexport interface DRCDelayTimeInfo {\n  sdr_cmd_delay: number; // sdr链路命令延时，单位：ms\n  liveview_delay_list: LiveViewDelayItem[];\n}\n\nexport interface DrcResponseInfo {\n  result: number;\n  output: {\n    seq: number\n  }\n}\n"
  },
  {
    "path": "src/types/drone-control.ts",
    "content": "import { ControlSource } from './device'\nimport { ECommanderModeLostAction, ERthMode, LostControlActionInCommandFLight, WaylineLostControlActionInCommandFlight } from '/@/api/drone-control/drone'\n\nexport enum ControlSourceChangeType {\n  Flight = 1,\n  Payload = 2,\n}\n\n// 控制权变化消息\nexport interface ControlSourceChangeInfo {\n  sn: string,\n  type: ControlSourceChangeType,\n  control_source: ControlSource\n}\n\n// 飞向目标点结果\nexport interface FlyToPointMessage {\n  sn: string,\n  result: number,\n  message: string,\n}\n\n// 一键起飞结果\nexport interface TakeoffToPointMessage {\n  sn: string,\n  result: number,\n  message: string,\n}\n\n// 设备端退出drc模式\nexport interface DrcModeExitNotifyMessage {\n  sn: string,\n  result: number,\n  message: string,\n}\n\n// 飞行控制模式状态\nexport interface DrcStatusNotifyMessage {\n  sn: string,\n  result: number,\n  message: string,\n}\n\nexport const WaylineLostControlActionInCommandFlightOptions = [\n  { label: 'Continue', value: WaylineLostControlActionInCommandFlight.CONTINUE },\n  { label: 'Execute Lost Action', value: WaylineLostControlActionInCommandFlight.EXEC_LOST_ACTION }\n]\n\nexport const LostControlActionInCommandFLightOptions = [\n  { label: 'Return Home', value: LostControlActionInCommandFLight.RETURN_HOME },\n  { label: 'Hover', value: LostControlActionInCommandFLight.HOVER },\n  { label: 'Landing', value: LostControlActionInCommandFLight.Land }\n]\n\nexport const RthModeInCommandFlightOptions = [\n  { label: 'Smart Height', value: ERthMode.SMART },\n  { label: 'Setting Height', value: ERthMode.SETTING }\n]\n\nexport const CommanderModeLostActionInCommandFlightOptions = [\n  { label: 'Continue', value: ECommanderModeLostAction.CONTINUE },\n  { label: 'Execute Lost Action', value: ECommanderModeLostAction.EXEC_LOST_ACTION }\n]\n\nexport const CommanderFlightModeInCommandFlightOptions = [\n  { label: 'Smart Height', value: ERthMode.SMART },\n  { label: 'Setting Height', value: ERthMode.SETTING }\n]\n\n// 云台重置模式\nexport enum GimbalResetMode {\n  Recenter = 0,\n  Down = 1,\n  RecenterGimbalPan = 2,\n  PitchDown = 3,\n}\n\nexport const GimbalResetModeOptions = [\n  { label: 'Gimbal Recenter', value: GimbalResetMode.Recenter },\n  { label: 'Gimbal down', value: GimbalResetMode.Down },\n  { label: 'Recenter Gimbal Pan', value: GimbalResetMode.RecenterGimbalPan },\n  { label: 'Gimbal Pitch Down', value: GimbalResetMode.PitchDown }\n]\n"
  },
  {
    "path": "src/types/enums.ts",
    "content": "export enum ERouterName {\n    ELEMENT = 'element',\n    PROJECT = 'project',\n    HOME = 'home',\n    TSA = 'tsa',\n    LAYER = 'layer',\n    MEDIA = 'media',\n    WAYLINE = 'wayline',\n    LIVESTREAM = 'livestream',\n    LIVING = 'living',\n    WORKSPACE = 'workspace',\n    MEMBERS = 'members',\n    DEVICES = 'devices',\n    TASK = 'task',\n    CREATE_PLAN = 'create-plan',\n    SELECT_PLAN = 'select-plan',\n    FIRMWARES = 'firmwares',\n    FLIGHT_AREA = 'flight-area',\n\n    PILOT = 'pilot-login',\n    PILOT_HOME = 'pilot-home',\n    PILOT_MEDIA = 'pilot-media',\n    PILOT_LIVESHARE = 'pilot-liveshare',\n    PILOT_BIND = 'pilot-bind'\n}\n\nexport enum EStorageKey {\n    LANG_CODE = 'DJI_CREATE_VITE_H5_APP:lang_code',\n    TEST_TOOLS_POSITION_STORAGE_KEY = 'DJI_CREATE_VITE_H5_APP:test_tools_position',\n    SESSION_ID = 'DJI_CREATE_VITE_H5_APP:sess'\n}\n\nexport enum EStatusValue {\n    CONNECTED = 'Connected',\n    DISCONNECT = 'Disconnect',\n    LIVING = 'Living'\n}\n\nexport enum ELiveStatusValue {\n    DISCONNECT,\n    CONNECTED,\n    LIVING\n}\n\nexport enum EComponentName {\n    Thing = 'thing',\n    Liveshare = 'liveshare',\n    Api = 'api',\n    Ws = 'ws',\n    Map = 'map',\n    Tsa = 'tsa',\n    Media = 'media',\n    Mission = 'mission'\n}\n\nexport enum ELocalStorageKey {\n    Username = 'username',\n    WorkspaceId = 'workspace_id',\n    Token = 'x-auth-token',\n    PlatformName = 'platform_name',\n    WorkspaceName = 'workspace_name',\n    WorkspaceDesc = 'workspace_desc',\n    Flag = 'flag',\n    UserId = 'user_id',\n    Device = 'device',\n    GatewayOnline = 'gateway_online',\n}\n\nexport enum EPhotoType {\n    Original = 0,\n    Preview = 1,\n    Unknown = -1\n}\n\nexport enum EDownloadOwner {\n    Mine = 0,\n    Others = 1,\n    Unknown = -1\n}\n\nexport enum EUserType {\n    Web = 1,\n    Pilot = 2,\n}\n\nexport enum EBizCode {\n    GatewayOsd = 'gateway_osd',\n    DeviceOsd = 'device_osd',\n    DockOsd = 'dock_osd',\n    MapElementCreate = 'map_element_create',\n    MapElementUpdate = 'map_element_update',\n    MapElementDelete = 'map_element_delete',\n    DeviceOnline = 'device_online',\n    DeviceOffline = 'device_offline',\n    DeviceHms = 'device_hms',\n\n    // 机场任务\n    FlightTaskProgress = 'flighttask_progress', // 机场任务执行进度\n    FlightTaskMediaProgress = 'file_upload_callback', // 机场任务媒体上传进度\n    FlightTaskMediaHighestPriority = 'highest_priority_upload_flighttask_media', // 机场任务媒体优先级上报\n\n    // 设备指令\n    DeviceReboot = 'device_reboot', // 机场重启\n    DroneOpen = 'drone_open', // 飞行器开机\n    DroneClose = 'drone_close', // 飞行器关机\n    DeviceFormat = 'device_format', // 机场数据格式化\n    DroneFormat = 'drone_format', // 飞行器数据格式化\n    CoverOpen = 'cover_open', // 打开舱盖\n    CoverClose = 'cover_close', // 关闭舱盖\n    PutterOpen = 'putter_open', // 推杆展开\n    PutterClose = 'putter_close', // 推杆闭合\n    ChargeOpen = 'charge_open', // 打开充电\n    ChargeClose = 'charge_close', // 关闭充电\n\n    // 设备升级\n    DeviceUpgrade = 'ota_progress', // 设备升级\n\n    // 设备日志\n    DeviceLogUploadProgress = 'fileupload_progress', // 设备日志上传\n\n    // 飞行指令消息\n    ControlSourceChange = 'control_source_change', // 控制权更新\n    FlyToPointProgress = 'fly_to_point_progress', // 飞向目标点\n    TakeoffToPointProgress = 'takeoff_to_point_progress', // 一键起飞\n    JoystickInvalidNotify = 'joystick_invalid_notify', // 设备端退出drc模式\n    DrcStatusNotify = 'drc_status_notify', // 飞行控制模式状态\n\n    // custom flight area\n    FlightAreasSyncProgress = 'flight_areas_sync_progress',\n    FlightAreasDroneLocation = 'flight_areas_drone_location',\n    FlightAreasUpdate = 'flight_areas_update',\n}\n\nexport enum EDeviceTypeName {\n    Aircraft = 0,\n    Gateway = 2,\n    Dock = 3,\n}\n\nexport enum EHmsLevel {\n    NOTICE,\n    CAUTION,\n    WARN,\n}\n"
  },
  {
    "path": "src/types/flight-area.ts",
    "content": "import { GeojsonCoordinate, GeojsonPolygon } from '../utils/genjson'\n\nexport enum EFlightAreaType {\n  NFZ = 'nfz',\n  DFENCE = 'dfence',\n}\n\nexport enum EGeometryType {\n  CIRCLE = 'Circle',\n  POLYGON = 'Polygon',\n}\n\nexport enum EFlightAreaUpdate {\n  ADD = 'add',\n  UPDATE = 'update',\n  DELETE = 'delete',\n}\n\nexport enum ESyncStatus {\n  WAIT_SYNC = 'wait_sync',\n  SWITCH_FAIL = 'switch_fail',\n  SYNCHRONIZING = 'synchronizing',\n  SYNCHRONIZED = 'synchronized',\n  FAIL = 'fail',\n}\n\nexport interface GeojsonCircle {\n  type: 'Feature'\n  properties: {\n    color: string\n    clampToGround?: boolean\n  }\n  geometry: {\n    type: EGeometryType.CIRCLE\n    coordinates: GeojsonCoordinate\n    radius: number\n  }\n}\n\nexport interface DroneLocation {\n  area_distance: number,\n  area_id: string,\n  is_in_area: boolean,\n}\n\nexport interface FlightAreasDroneLocation {\n  drone_locations: DroneLocation[]\n}\n\nexport type FlightAreaContent = GeojsonCircle | GeojsonPolygon\n\nexport interface FlightAreaUpdate {\n  operation: EFlightAreaUpdate,\n  area_id: string,\n  name: string,\n  type: EFlightAreaType,\n  content: FlightAreaContent,\n  status: boolean,\n  username: string,\n  create_time: number,\n  update_time: number,\n}\n\nexport interface FlightAreaSyncProgress {\n  sn: string,\n  result: number,\n  status: ESyncStatus,\n  message: string,\n}\n\nexport const FlightAreaTypeTitleMap = {\n  [EFlightAreaType.NFZ]: {\n    [EGeometryType.CIRCLE]: 'Circular GEO Zone',\n    [EGeometryType.POLYGON]: 'Polygonal GEO Zone',\n  },\n  [EFlightAreaType.DFENCE]: {\n    [EGeometryType.CIRCLE]: 'Circular Task Area',\n    [EGeometryType.POLYGON]: 'Polygonal Task Area',\n  },\n}\n"
  },
  {
    "path": "src/types/index.ts",
    "content": "export * from './enums'\n"
  },
  {
    "path": "src/types/live-stream.ts",
    "content": "\nexport interface LiveStreamStatus {\n    audioBitRate: number,\n    dropRate: number,\n    fps: number,\n    jitter: number,\n    quality: number,\n    rtt: number,\n    status: number,\n    type: number,\n    videoBitRate: number\n}\n\nexport interface GB28181Param {\n    serverIp: string,\n    serverPort: string,\n    serverId: string,\n    agentId: string,\n    password: string,\n    agentPort: string,\n    agentChannel: string\n}\n\nexport interface RTSPParam {\n    userName: string,\n    password: string,\n    port: string\n}\n\nexport interface LiveConfigParam {\n    params: number,\n    type: any\n}\n\nexport enum EVideoPublishType {\n    VideoOnDemand = 'video-on-demand',\n    VideoByManual = 'video-by-manual',\n    VideoDemandAuxManual = 'video-demand-aux-manual'\n}\n\nexport enum ELiveTypeValue {\n    Unknown,\n    Agora,\n    RTMP,\n    RTSP,\n    GB28181\n}\n\nexport enum ELiveTypeName {\n    Unknown = 'Unknown',\n    Agora = 'Agora',\n    RTMP = 'RTMP',\n    RTSP = 'RTSP',\n    GB28181 = 'GB28181'\n}\n\nexport enum CameraMode {\n    Photo = 0, // 拍照\n    Video = 1, // 录像\n}\n\n// 镜头类型\nexport enum VideoType {\n    NORMAL = 'normal',\n    WIDE = 'wide',\n    ZOOM = 'zoom',\n    IR = 'ir'\n}\n\n// 镜头类型\nexport enum CameraType {\n    WIDE = 'wide',\n    ZOOM = 'zoom',\n    IR = 'ir'\n}\n\nexport const CameraTypeOptions = [\n  { label: CameraType.WIDE, value: CameraType.WIDE },\n  { label: CameraType.ZOOM, value: CameraType.ZOOM },\n  { label: CameraType.IR, value: CameraType.IR },\n]\n\nexport const ZoomCameraTypeOptions = [\n  { label: CameraType.ZOOM, value: CameraType.ZOOM },\n  { label: CameraType.IR, value: CameraType.IR },\n]\n\nexport interface VideoListItem {\n    video_index: string;\n    video_type: VideoType;\n    switchable_video_types?: Array<VideoType>;\n}\n\nexport interface CameraListItem {\n    available_video_number: number;\n    camera_index: string;\n    camera_name: string;\n    coexist_video_number_max: number;\n    video_list: VideoListItem[];\n    // 自定义\n    switchCamera?: boolean;\n    content?: string;\n    // 该camera由哪个控上报的\n    camera_carrier_sns?: string[];\n}\n\nexport interface DeviceListItem {\n    sn: string;\n    available_video_number: number;\n    coexist_video_number_max: number;\n    camera_list: CameraListItem[];\n}\n\n// export interface LiveCapacity {\n//     available_video_number: number;\n//     coexist_video_number_max: number;\n//     device_list: DeviceListItem[];\n// }\n\n// export interface LiveStatus {\n//     live_time: number; // 直播时间 该路码流已推流时间 unit: s\n//     live_trendline: number; // 直播带宽的使用状态 代表直播性能趋势,0-4表示overuse，其中，数值越小，表示overuse程度越大，5表示normal状态，6~10表示underuse，其中，数值越大，表示有更多比例的带宽未能充分利用\n//     video_id: string; // 直播码流标识符 某路在推视频码流的标识符，格式为 #{uav_sn}/#{camera_id}/#{video_index}\n//     video_quality: number; // 直播码流的质量 0: 自动， 1: 流畅, 2: 高清， 3: 超清\n//     error_status?: number; // 设备端当前状态，是错误码，需要匹配到文案上\n//   }\n"
  },
  {
    "path": "src/types/map-enum.ts",
    "content": "export enum MapDoodleEnum {\n    PIN = 'pin',\n    POLYLINE = 'polyline',\n    POLYGON = 'polygon',\n    Close = 'off',\n    CIRCLE = 'circle',\n}\n"
  },
  {
    "path": "src/types/map.d.ts",
    "content": "\nexport interface MapGeographicPosition {\n longitude: number;\n latitude: number;\n height?: number;\n}\nexport enum LayerType {\n Normal,\n Default,\n Share\n}\nexport interface pinAMapPosition {\n KL: number\n className: string\n kT: number\n lng: number\n lat: number\n}\nexport enum ResourceStatus {\n NotShow,\n Show\n}\nexport type GeojsonCoordinate = [number, number, number?]\n\nexport interface GeojsonLine {\n type: 'Feature'\n properties: {\n   color: string\n   directConnected?: boolean\n }\n geometry: {\n   type: 'LineString'\n   coordinates: GeojsonCoordinate[]\n }\n}\n\nexport interface GeojsonPolygon {\n type: 'Feature'\n properties: {\n   color: string\n }\n geometry: {\n   type: 'Polygon'\n   coordinates: GeojsonCoordinate[][]\n }\n}\n\nexport interface GeojsonPoint {\n type: 'Feature'\n properties: {\n   color: string\n   clampToGround?: boolean\n }\n geometry: {\n   type: 'Point'\n   coordinates: GeojsonCoordinate\n }\n}\nexport type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint\n\ninterface ResourceObjectBasic {\n user_name: string\n user_id?: string\n type:0| 1 | 2\n content: unknown\n}\nexport interface PinResource extends ResourceObjectBasic {\n type: 0\n content: GeojsonFeature\n}\n\nexport type ResourceObject = PinResource\nexport enum LayerElevationLoadStatus {\n Unload,\n Load\n}\n\nexport interface LayerResource {\n id: string\n name: string\n order: number\n status: ResourceStatus\n resource: ResourceObject | null\n display: number\n create_time: number\n elevation_load_status?: LayerElevationLoadStatus //\n}\nexport interface Layer {\n id: string\n name: string\n order: number\n create_time: number\n type: LayerType\n is_distributed: boolean\n is_lock: boolean\n elements: null | LayerResource[],\n is_check?: boolean\n is_select?: boolean\n\n}\n"
  },
  {
    "path": "src/types/mapLayer.ts",
    "content": "import { MapElementEnum } from '/@/constants/map'\n\nexport interface mapLayerStyle {\n  background: string\n}\n\nexport interface mapLayerChildren {\n  key: string\n  style: mapLayerStyle\n  title: string\n  obj: any\n}\nexport interface mapLayerChildrenObj {\n  className: string\n  key: string\n  name: string\n  type: string\n}\n\n// 拖拽事件\nexport interface DropEvent {\n  node: {\n    eventKey: string\n    pos: string\n    $parent: any\n  }\n  dragNode: {\n    eventKey: string\n  }\n  dropPosition: number\n  dropToGap: boolean\n}\nexport interface mapLayer {\n  key?: string\n  title: string\n  id: string\n  name: string\n  style: mapLayerStyle\n  elements: any\n}\nexport interface elementGroupsReq{\n  groupId: string\n  isDistributed: boolean\n}\nexport interface PostElementsBody {\n  id: string\n  name: string\n  resource: {\n   type: MapElementEnum,\n   user_name?: string,\n   content: {\n    type:string,\n    properties:{\n      color:string,\n      clampToGround:boolean\n    },\n    geometry:{\n      type:string,\n      coordinates:unknown\n    }\n   },\n  }\n}\n\nexport interface Color {\n  id: number\n  color: string\n  selected: boolean,\n  name: string\n}\n\nexport enum GeoType {\n  LineString = 'LineString',\n  Polygon = 'Polygon',\n  Point = 'Point'\n}\nexport enum ResourceStatus {\n  NotShow,\n  Show\n}\n\nexport enum LayerElevationLoadStatus {\n  Unload,\n  Load\n}\nexport interface PutElementsBody {\n  name?: string\n  status?: ResourceStatus\n  content?: unknown\n  display?: number\n  elevation_load_status?: LayerElevationLoadStatus\n}\nexport enum LayerType {\n  Normal,\n  Default,\n  Share,\n  Reconstruction\n}\n"
  },
  {
    "path": "src/types/task.ts",
    "content": "import { commonColor } from '/@/utils/color'\n\n// 任务类型\nexport enum TaskType {\n  Immediate = 0, // 立即执行\n  Timed = 1, // 单次定时任务\n  Condition = 2,\n}\n\nexport const TaskTypeMap = {\n  [TaskType.Immediate]: 'Immediate',\n  [TaskType.Timed]: 'Timed',\n  [TaskType.Condition]: 'Continuous',\n}\n\nexport const TaskTypeOptions = [\n  { value: TaskType.Immediate, label: TaskTypeMap[TaskType.Immediate] },\n  { value: TaskType.Timed, label: TaskTypeMap[TaskType.Timed] },\n  { value: TaskType.Condition, label: TaskTypeMap[TaskType.Condition] },\n]\n\n// 失控动作\nexport enum OutOfControlAction {\n  ReturnToHome = 0,\n  Hover = 1,\n  Land = 2,\n}\n\nexport const OutOfControlActionMap = {\n  [OutOfControlAction.ReturnToHome]: 'Return to Home',\n  [OutOfControlAction.Hover]: 'Hover',\n  [OutOfControlAction.Land]: 'Land',\n}\n\nexport const OutOfControlActionOptions = [\n  { value: OutOfControlAction.ReturnToHome, label: OutOfControlActionMap[OutOfControlAction.ReturnToHome] },\n  { value: OutOfControlAction.Hover, label: OutOfControlActionMap[OutOfControlAction.Hover] },\n  { value: OutOfControlAction.Land, label: OutOfControlActionMap[OutOfControlAction.Land] },\n]\n\n// 任务状态\nexport enum TaskStatus {\n  Wait = 1, //  待执行\n  Carrying = 2, // 执行中\n  Success = 3, // 完成\n  CanCel = 4, // 取消\n  Fail = 5, // 失败\n  Paused = 6, // 暂停\n}\n\nexport const TaskStatusMap = {\n  [TaskStatus.Wait]: 'To be performed',\n  [TaskStatus.Carrying]: 'In progress',\n  [TaskStatus.Success]: 'Task completed',\n  [TaskStatus.CanCel]: 'Task canceled',\n  [TaskStatus.Fail]: 'Task failed',\n  [TaskStatus.Paused]: 'Paused',\n\n}\n\nexport const TaskStatusColor = {\n  [TaskStatus.Wait]: commonColor.BLUE,\n  [TaskStatus.Carrying]: commonColor.BLUE,\n  [TaskStatus.Success]: commonColor.NORMAL,\n  [TaskStatus.CanCel]: commonColor.FAIL,\n  [TaskStatus.Fail]: commonColor.FAIL,\n  [TaskStatus.Paused]: commonColor.BLUE,\n}\n\n// 任务执行 ws 消息状态\nexport enum TaskProgressStatus {\n  Sent = 'sent', // 已下发\n  inProgress = 'in_progress', // 执行中\n  Paused = 'paused', // 暂停\n  Rejected = 'rejected', // 拒绝\n  Canceled = 'canceled', // 取消或终止\n  Timeout = 'timeout', // 超时\n  Failed = 'failed', // 失败\n  OK = 'ok', // 上传成功\n}\n\n// 任务进度消息\nexport interface TaskProgressInfo {\n  bid: string,\n  output:{\n    ext: {\n      current_waypoint_index: number,\n      media_count: number // 媒体文件\n    },\n    progress:{\n      current_step: number,\n      percent: number\n    },\n    status: TaskProgressStatus\n  },\n  result: number,\n}\n\n// ws status => log status\nexport const TaskProgressWsStatusMap = {\n  [TaskProgressStatus.Sent]: TaskStatus.Carrying,\n  [TaskProgressStatus.inProgress]: TaskStatus.Carrying,\n  [TaskProgressStatus.Rejected]: TaskStatus.Fail,\n  [TaskProgressStatus.OK]: TaskStatus.Success,\n  [TaskProgressStatus.Failed]: TaskStatus.Fail,\n  [TaskProgressStatus.Canceled]: TaskStatus.CanCel,\n  [TaskProgressStatus.Timeout]: TaskStatus.Fail,\n  [TaskProgressStatus.Paused]: TaskStatus.Paused,\n}\n\n// 根据媒体文件上传进度信息，前端自己判断出的状态\nexport enum MediaStatus { // 媒体上传进度\n  ToUpload = 1, // 待上传\n  Uploading = 2, // 上传中\n  Empty = 3, // 无媒体文件\n  Success = 4, // 上传成功\n}\n\nexport const MediaStatusMap = {\n  [MediaStatus.ToUpload]: 'Waiting to upload',\n  [MediaStatus.Uploading]: 'Uploading…',\n  [MediaStatus.Success]: 'Uploaded',\n  [MediaStatus.Empty]: 'No media files',\n}\n\nexport const MediaStatusColorMap = {\n  [MediaStatus.ToUpload]: commonColor.BLUE,\n  [MediaStatus.Uploading]: commonColor.BLUE,\n  [MediaStatus.Success]: commonColor.NORMAL,\n  [MediaStatus.Empty]: commonColor.WARN,\n}\n\n// 媒体上传进度消息\nexport interface MediaStatusProgressInfo {\n  job_id: string,\n  media_count: number\n  uploaded_count: number,\n}\n\n// 媒体上传优先级消息\nexport interface TaskMediaHighestPriorityProgressInfo {\n  pre_job_id: string,\n  job_id: string,\n}\n"
  },
  {
    "path": "src/types/wayline.ts",
    "content": "// 航线类型\nexport enum WaylineType {\n  NormalWaypointWayline = 0, // 普通航点航线\n  AccurateReshootingWayline = 1 // 精准复拍航线\n}\n\nexport interface WaylineFile {\n  id: string,\n  name: string,\n  drone_model_key: any,\n  payload_model_keys: string[],\n  template_types: WaylineType[],\n  update_time: number,\n  user_name: string,\n}\n"
  },
  {
    "path": "src/use-common-components.ts",
    "content": "import { App, DefineComponent } from 'vue'\n\nconst components: Record<string, DefineComponent<{}, {}, any>> = {\n\n}\n\nexport const CommonComponents = {\n  install (app: App): void {\n    Object.keys(components).forEach(name => {\n      app.component(name, components[name])\n    })\n  }\n}\n"
  },
  {
    "path": "src/utils/bytes.ts",
    "content": "import { DEFAULT_PLACEHOLDER, SIZES as byteSizes, BYTE_SIZES } from './constants'\n\n/**\n * 转换字节数为单位B，KB，GB...\n * 保留一位小数\n * @param bytes 字节数\n * @param holder 0字节占位符，默认 --\n * @returns\n */\nexport function bytesToSize (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1, unit = false): string {\n  if (isNaN(bytes) || bytes === 0) {\n    return holder\n  }\n  // 兼容负数\n  let prefix = ''\n  if (bytes < 0) {\n    bytes = 0 - bytes\n    prefix = '-'\n  }\n  const k = 1024\n  const sizes = unit ? BYTE_SIZES : byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']\n  const i = Math.floor(Math.log(bytes) / Math.log(k))\n  return prefix + (bytes / Math.pow(k, i)).toFixed(fix) + '' + sizes[i]\n}\n\n//  获取转化后数据及单位\nexport function getBytesObject (bytes: number, holder = DEFAULT_PLACEHOLDER, fix = 1): {\n  value: string,\n  size: string\n  index: number\n} {\n  if (isNaN(bytes) || bytes === 0) {\n    return {\n      value: holder,\n      size: '',\n      index: -1,\n    }\n  }\n  // 兼容负数\n  let prefix = ''\n  if (bytes < 0) {\n    bytes = 0 - bytes\n    prefix = '-'\n  }\n  const k = 1024\n  const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']\n  const i = Math.floor(Math.log(bytes) / Math.log(k))\n\n  return {\n    value: prefix + (bytes / Math.pow(k, i)).toFixed(fix),\n    size: sizes[i],\n    index: i,\n  }\n}\n\n/**\n * 根据最小单位返回文件大小\n * @param bytes\n * @param minUnit\n * @param fix\n * @returns\n */\nexport function bytesToSizeWithMinUnit (bytes: number, minUnit = 'B', fix = 1): string {\n  const holder = `0${minUnit}`\n  const sizes = byteSizes// ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']\n  const k = 1024\n  const findIndex = sizes.findIndex(item => item === minUnit)\n\n  const { value, size, index } = getBytesObject(bytes, holder, fix)\n  // 0\n  if (index === -1) {\n    return holder\n  }\n  // 转换后单位小于传入的最小单位\n  if (index < findIndex) {\n    const sizeToMinUint = parseFloat(value) / (Math.pow(k, findIndex - index))\n    return sizeToMinUint.toFixed(fix) + minUnit\n  }\n  // 其他\n  return value + size\n}\n// console.log('size', bytesToSizeWithMinUnit(0))\n// console.log('size', bytesToSizeWithMinUnit(1023))\n// console.log('size', bytesToSizeWithMinUnit(1024))\n// console.log('size', bytesToSizeWithMinUnit(1000 * 1024, 'MB', 2))\n// console.log('size', bytesToSizeWithMinUnit(1024 * 1024, 'MB', 2))\n"
  },
  {
    "path": "src/utils/color.ts",
    "content": "export const commonColor = {\n  WARN: '#FF9900', // 黄色\n  FAIL: '#E02020', // 红色\n  WHITE: '#FFFFFF', // 白色\n  NORMAL: '#19BE6B', // 绿色\n  BLUE: '#2B85E4', // 蓝色\n  PINK: '#F7C0BA', // 粉\n}\n"
  },
  {
    "path": "src/utils/common.ts",
    "content": "/**\n * 下载文件\n * @param data\n * @param fileName\n */\nexport function downloadFile (data: Blob, fileName: string) {\n  const lable = document.createElement('a')\n  lable.href = window.URL.createObjectURL(data)\n  lable.download = fileName\n  lable.click()\n  URL.revokeObjectURL(lable.href)\n}\n"
  },
  {
    "path": "src/utils/constants.ts",
    "content": "\nexport const DEFAULT_PLACEHOLDER = '--' // 默认占位符\n\n// 全局日期格式\nexport const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'\nexport const DATE_FORMAT_MINUTE = 'YYYY-MM-DD HH:mm'\nexport const DATE_FORMAT_DAY = 'YYYY-MM-DD'\nexport const TIME_FORMAT = 'HH:mm:ss'\nexport const TIME_FORMAT_MINUTE = 'HH:mm'\nexport const DATE_FORMAT_MM = 'MM-DD HH:mm'\n\nexport const SIZES = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']\nexport const BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']\nexport const PAGE_SIZE_OPTIONS = ['20', '50', '100']\nexport const PAGE_SIZE = 50\n"
  },
  {
    "path": "src/utils/data-process.ts",
    "content": "export function formatPhoneNum (phoneNum: string | number) {\n  const str = String(phoneNum)\n  return str.substring(0, 3) + '****' + str.slice(-4)\n}\n"
  },
  {
    "path": "src/utils/device-cmd.ts",
    "content": "import { DroneBatteryModeEnum, DroneBatteryStateEnum, LinkWorkModeEnum } from './../types/airport-tsa'\nimport { DeviceInfoType } from '/@/types/device'\nimport { DeviceCmd, DeviceCmdItem, DeviceCmdExecuteInfo, DeviceCmdStatusText, DeviceCmdExecuteStatus } from '/@/types/device-cmd'\nimport { AirportStorage, CoverStateEnum, PutterStateEnum, ChargeStateEnum, SupplementLightStateEnum, AlarmModeEnum, BatteryStoreModeEnum } from '/@/types/airport-tsa'\nimport { getBytesObject } from './bytes'\nimport { DEFAULT_PLACEHOLDER } from './constants'\n\n/**\n * 根据osd 更新信息\n * @param cmdList\n * @param deviceInfo\n * @returns\n */\nexport function updateDeviceCmdInfoByOsd (cmdList: DeviceCmdItem[], deviceInfo: DeviceInfoType) {\n  const { device, dock, gateway } = deviceInfo || {}\n  if (!cmdList || cmdList.length < 1) {\n    return\n  }\n  cmdList.forEach(cmdItem => {\n    if (cmdItem.loading) {\n      return\n    }\n    if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启\n      // console.log('DeviceReboot')\n    } else if (cmdItem.cmdKey === DeviceCmd.DroneOpen || cmdItem.cmdKey === DeviceCmd.DroneClose) { // 飞行器开关机\n      getDroneState(cmdItem, device)\n    } else if (cmdItem.cmdKey === DeviceCmd.CoverOpen || cmdItem.cmdKey === DeviceCmd.CoverClose) { // 舱盖开关\n      getCoverState(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.PutterOpen || cmdItem.cmdKey === DeviceCmd.PutterClose) { // 推杆闭合展开\n      getPutterState(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen || cmdItem.cmdKey === DeviceCmd.ChargeClose) { // 充电状态\n      getChargeState(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储\n      deviceFormat(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储\n      droneFormat(cmdItem, device)\n    } else if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen || cmdItem.cmdKey === DeviceCmd.SupplementLightClose) { // 补光灯开关\n      getSupplementLightState(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 声光报警\n      getAlarmState(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养\n      getBatteryStoreMode(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养\n      getDroneBatteryMode(cmdItem, dock)\n    } else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传开关\n      getSdrWorkNode(cmdItem, dock)\n    }\n  })\n}\n\n// 飞行器开关机\nfunction getDroneState (cmdItem: DeviceCmdItem, droneProperties: any) {\n  if (!droneProperties) {\n    cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.DroneOpen) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  } else {\n    cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.DroneClose) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  }\n}\n\n// 舱盖开关\nfunction getCoverState (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const coverState = airportProperties?.basic_osd?.cover_state as CoverStateEnum\n\n  if (coverState === CoverStateEnum.Close || coverState === CoverStateEnum.Failed) {\n    cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.CoverOpen) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  } else if (coverState === CoverStateEnum.Open || coverState === CoverStateEnum.HalfOpen) {\n    cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.CoverClose) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  }\n}\n\n// 推杆状态\nfunction getPutterState (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const putterState = airportProperties?.basic_osd?.putter_state as PutterStateEnum\n  if (putterState === PutterStateEnum.Close || putterState === PutterStateEnum.Failed) {\n    cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.PutterOpen) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  } else if (putterState === PutterStateEnum.Open || putterState === PutterStateEnum.HalfOpen) {\n    cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.PutterClose) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  }\n}\n\n// 充电状态\nfunction getChargeState (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const chargeState = airportProperties?.basic_osd?.drone_charge_state\n  const state = chargeState?.state as ChargeStateEnum\n  if (!state) return\n  if (state === ChargeStateEnum.Charge) {\n    cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.ChargeClose) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  } else if (state === ChargeStateEnum.NotCharge) {\n    cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText\n    cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText\n    if (cmdItem.cmdKey !== DeviceCmd.ChargeOpen) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  }\n}\n\n// 机场存储格式化\nfunction deviceFormat (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const airportStorage = airportProperties?.basic_osd?.storage\n  const value = getAirportStorage(airportStorage)\n  cmdItem.status = value\n}\n\n// 机场存储格式化\nfunction droneFormat (cmdItem: DeviceCmdItem, droneProperties: any) {\n  const droneStorage = droneProperties?.storage\n  const value = getAirportStorage(droneStorage)\n  cmdItem.status = value\n}\n\n// 获取机场存储容量\n// {\n// \"total\": 10000, // 单位：KB\n// \"used\": 500\n// }\nexport function getAirportStorage (storage: AirportStorage) {\n  if (!storage) {\n    return DEFAULT_PLACEHOLDER\n  }\n  const total = storage.total\n  const used = storage.used\n  const byteObj = getBytesObject(total * 1024)\n  const _total = byteObj.value\n  const _used = getBytes(used * 1024, byteObj.index)\n  return `${_used}/${_total} ${byteObj.size}`\n}\n\nfunction getBytes (bytes: number, index: number, fixed = 1) {\n  return (bytes / Math.pow(1024, index)).toFixed(fixed)\n}\n\n// 补光灯状态\nfunction getSupplementLightState (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const supplementLightState = airportProperties?.basic_osd?.supplement_light_state\n  if (supplementLightState === SupplementLightStateEnum.Close) {\n    cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText\n    cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText\n    if (cmdItem.cmdKey !== DeviceCmd.SupplementLightOpen) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  } else if (supplementLightState === SupplementLightStateEnum.Open) {\n    cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText\n    cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText\n    if (cmdItem.cmdKey !== DeviceCmd.SupplementLightClose) {\n      exchangeDeviceCmd(cmdItem)\n    }\n  }\n}\n\n// 声光报警\nfunction getAlarmState (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const alarmState = airportProperties?.basic_osd?.alarm_state\n  if (alarmState === AlarmModeEnum.CLOSE) {\n    cmdItem.operateText = DeviceCmdStatusText.AlarmStateCloseBtnText\n    cmdItem.status = DeviceCmdStatusText.AlarmStateCloseNormalText\n    cmdItem.action = AlarmModeEnum.OPEN\n  } else if (alarmState === AlarmModeEnum.OPEN) {\n    cmdItem.operateText = DeviceCmdStatusText.AlarmStateOpenBtnText\n    cmdItem.status = DeviceCmdStatusText.AlarmStateOpenNormalText\n    cmdItem.action = AlarmModeEnum.CLOSE\n  }\n}\n\n// 机场电池模式\nfunction getBatteryStoreMode (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const batteryStoreMode = airportProperties?.basic_osd?.battery_store_mode\n  if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {\n    cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModePlanBtnText\n    cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanNormalText\n    cmdItem.action = BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE\n  } else if (batteryStoreMode === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {\n    cmdItem.operateText = DeviceCmdStatusText.BatteryStoreModeEmergencyBtnText\n    cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyNormalText\n    cmdItem.action = BatteryStoreModeEnum.BATTERY_PLAN_STORE\n  }\n}\n\n// 飞行器电池保养\nfunction getDroneBatteryMode (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const maintenanceState = airportProperties?.work_osd?.drone_battery_maintenance_info?.maintenance_state\n  if (maintenanceState === DroneBatteryStateEnum.MaintenanceInProgress) {\n    cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeCloseBtnText\n    cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText\n    cmdItem.action = DroneBatteryModeEnum.CLOSE\n    cmdItem.disabled = false\n  } else if (maintenanceState === DroneBatteryStateEnum.NoMaintenanceRequired) {\n    cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText\n    cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText\n    cmdItem.action = DroneBatteryModeEnum.OPEN\n    cmdItem.disabled = true\n  } else if (maintenanceState === DroneBatteryStateEnum.MaintenanceRequired) {\n    cmdItem.operateText = DeviceCmdStatusText.DroneBatteryModeOpenBtnText\n    cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText\n    cmdItem.action = DroneBatteryModeEnum.OPEN\n    cmdItem.disabled = false\n  }\n}\n\n// 增强图传开关\nfunction getSdrWorkNode (cmdItem: DeviceCmdItem, airportProperties: any) {\n  const linkWorkMode = airportProperties?.link_osd?.wireless_link?.link_workmode\n  if (linkWorkMode === LinkWorkModeEnum.SDR) {\n    cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourCloseBtnText\n    cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText\n    cmdItem.action = LinkWorkModeEnum.FourG_FUSION_MODE\n  } else if (linkWorkMode === LinkWorkModeEnum.FourG_FUSION_MODE) {\n    cmdItem.operateText = DeviceCmdStatusText.SdrWorkModeFourGOpenBtnText\n    cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText\n    cmdItem.action = LinkWorkModeEnum.SDR\n  }\n}\n\n/**\n * 交换指令\n * @param cmd\n */\nfunction exchangeDeviceCmd (cmdItem: DeviceCmdItem) {\n  if (cmdItem.oppositeCmdKey) {\n    const oppositeCmdKey = cmdItem.oppositeCmdKey\n    cmdItem.oppositeCmdKey = cmdItem.cmdKey\n    cmdItem.cmdKey = oppositeCmdKey\n  }\n}\n\n// /**\n//  * 更新简单指令发送情况更新信息\n//  * @param cmd\n//  */\n// export function updateDeviceSingleCmdInfo (cmdItem: DeviceCmdItem) {\n//   // 补光灯\n//   if (cmdItem.cmdKey === DeviceCmd.SupplementLightOpen) {\n//     cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightOpenNormalText\n//     cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightOpenBtnText\n//     exchangeDeviceCmd(cmdItem)\n//   } else if (cmdItem.cmdKey === DeviceCmd.SupplementLightClose) {\n//     cmdItem.status = DeviceCmdStatusText.DeviceSupplementLightCloseNormalText\n//     cmdItem.operateText = DeviceCmdStatusText.DeviceSupplementLightCloseBtnText\n//     exchangeDeviceCmd(cmdItem)\n//   }\n// }\n\n/**\n * 根据指令执行消息更新信息\n * @param cmd\n * @param deviceCmdExecuteInfo\n * @returns\n */\nexport function updateDeviceCmdInfoByExecuteInfo (cmdList: DeviceCmdItem[], deviceCmdExecuteInfos?: DeviceCmdExecuteInfo[]) {\n  if (!deviceCmdExecuteInfos || !cmdList) {\n    return\n  }\n  cmdList.forEach(cmdItem => {\n    // 获取当前设备相应指令信息\n    const deviceCmdExecuteInfo = deviceCmdExecuteInfos.find(cmdExecuteInfo => cmdExecuteInfo.biz_code === cmdItem.cmdKey)\n    if (deviceCmdExecuteInfo) {\n      if (cmdItem.cmdKey === DeviceCmd.DeviceReboot) { // 重启\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DeviceRebootInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DeviceRebootFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DeviceRebootNormalText\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.DroneOpen) { // 飞行器开关机\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DroneStatusOpenInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DroneStatusOpenFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DroneStatusOpenNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DroneStatusOpenBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.DroneClose) {\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DroneStatusCloseInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DroneStatusCloseFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DroneStatusCloseNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DroneStatusCloseBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.CoverOpen) { // 舱盖开关\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DeviceCoverOpenNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DeviceCoverOpenBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.CoverClose) {\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DeviceCoverCloseNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DeviceCoverCloseBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.PutterOpen) { // 推杆闭合展开\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DevicePutterOpenInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DevicePutterOpenFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DevicePutterOpenNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DevicePutterOpenBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.PutterClose) {\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DevicePutterCloseInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DevicePutterCloseFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DevicePutterCloseNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DevicePutterCloseBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.ChargeOpen) { // 充电状态\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DeviceChargeOpenNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DeviceChargeOpenBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.ChargeClose) {\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.status = DeviceCmdStatusText.DeviceChargeCloseNormalText\n          cmdItem.operateText = DeviceCmdStatusText.DeviceChargeCloseBtnText\n          exchangeDeviceCmd(cmdItem)\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.DeviceFormat) { // 机场存储\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DeviceFormatInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DeviceFormatFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.DroneFormat) { // 飞行器存储\n        if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n          cmdItem.status = DeviceCmdStatusText.DroneFormatInProgressText\n          cmdItem.loading = true\n        } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n          cmdItem.status = DeviceCmdStatusText.DroneFormatFailedText\n          cmdItem.loading = false\n        } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n          cmdItem.loading = false\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.AlarmStateSwitch) { // 机场声光报警\n        if (cmdItem.action === AlarmModeEnum.CLOSE) {\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.AlarmStateCloseText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.AlarmStateCloseFailedText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.loading = false\n          }\n        } else if (cmdItem.action === AlarmModeEnum.OPEN) {\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.AlarmStateOpenText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.AlarmStateOpenFailedText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.loading = false\n          }\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.BatteryStoreModeSwitch) { // 电池保养\n        if (cmdItem.action === BatteryStoreModeEnum.BATTERY_PLAN_STORE) {\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.BatteryStoreModePlanFailedText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.loading = false\n          }\n        } else if (cmdItem.action === BatteryStoreModeEnum.BATTERY_EMERGENCY_STORE) {\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.BatteryStoreModeEmergencyFailedText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.loading = false\n          }\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.DroneBatteryModeSwitch) { // 飞行器电池保养\n        if (cmdItem.action === DroneBatteryModeEnum.OPEN) {\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNotNeedText\n            cmdItem.loading = false\n          }\n        } else if (cmdItem.action === DroneBatteryModeEnum.CLOSE) {\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceInProgressText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.status = DeviceCmdStatusText.DroneBatteryModeMaintenanceNeedText\n            cmdItem.loading = false\n          }\n        }\n      } else if (cmdItem.cmdKey === DeviceCmd.SdrWorkModeSwitch) { // 增强图传\n        if (cmdItem.action === LinkWorkModeEnum.SDR) { // 关闭\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseFailedText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGCloseNormalText\n            cmdItem.loading = false\n          }\n        } else if (cmdItem.action === LinkWorkModeEnum.FourG_FUSION_MODE) { // 开启\n          if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.InProgress) {\n            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenText\n            cmdItem.loading = true\n          } else if (isExecuteFailed(deviceCmdExecuteInfo.output.status)) {\n            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenFailedText\n            cmdItem.loading = false\n          } else if (deviceCmdExecuteInfo.output.status === DeviceCmdExecuteStatus.OK) {\n            cmdItem.status = DeviceCmdStatusText.SdrWorkModeFourGOpenNormalText\n            cmdItem.loading = false\n          }\n        }\n      }\n    }\n  })\n}\n\n/**\n * 判断是否执行失败\n * @param status\n * @returns\n */\nfunction isExecuteFailed (status: DeviceCmdExecuteStatus) {\n  return [DeviceCmdExecuteStatus.Canceled, DeviceCmdExecuteStatus.Failed, DeviceCmdExecuteStatus.Timeout].includes(status)\n}\n"
  },
  {
    "path": "src/utils/device-setting.ts",
    "content": "import { DeviceInfoType } from '/@/types/device'\nimport { DeviceSettingType, DeviceSettingKeyEnum, DistanceLimitStatusEnum, ObstacleAvoidanceStatusEnum, DeviceSettingFormModel, NightLightsStateEnum } from '/@/types/device-setting'\nimport { DEFAULT_PLACEHOLDER } from './constants'\nimport { isNil } from 'lodash'\n\nconst Unit_M = ' m'\n\n/**\n * 根据osd 更新信息\n * @param deviceSetting\n * @param deviceInfo\n * @returns\n */\nexport function updateDeviceSettingInfoByOsd (deviceSetting: DeviceSettingType, deviceInfo: DeviceInfoType) {\n  const { device, dock, gateway } = deviceInfo || {}\n  if (!deviceSetting) {\n    return\n  }\n  // 夜航灯\n  let nightLightsState = '' as any\n  if (isNil(device?.night_lights_state)) {\n    deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = false\n    deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER\n    nightLightsState = DEFAULT_PLACEHOLDER\n  } else {\n    deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].editable = true\n    nightLightsState = device?.night_lights_state\n    if (nightLightsState === NightLightsStateEnum.CLOSE) {\n      deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '关闭'\n    } else if (nightLightsState === NightLightsStateEnum.OPEN) {\n      deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = '开启'\n    } else {\n      deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].value = DEFAULT_PLACEHOLDER\n    }\n  }\n  deviceSetting[DeviceSettingKeyEnum.NIGHT_LIGHTS_MODE_SET].trueValue = nightLightsState\n\n  // 限高\n  let heightLimit = device?.height_limit as any\n  if (isNil(heightLimit) || heightLimit === 0) {\n    heightLimit = DEFAULT_PLACEHOLDER\n    deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = false\n  } else {\n    deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].editable = true\n  }\n  deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].trueValue = heightLimit\n  deviceSetting[DeviceSettingKeyEnum.HEIGHT_LIMIT_SET].value = heightLimit + Unit_M\n\n  // 限远\n  let distanceLimitStatus = '' as any\n  if (isNil(device?.distance_limit_status?.state)) {\n    distanceLimitStatus = DEFAULT_PLACEHOLDER\n    deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = false\n    deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER\n  } else {\n    deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].editable = true\n    distanceLimitStatus = device?.distance_limit_status?.state\n    if (distanceLimitStatus === DistanceLimitStatusEnum.UNSET) {\n      deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = '关闭'\n    } else if (distanceLimitStatus === DistanceLimitStatusEnum.SET) {\n      const distanceLimit = device?.distance_limit_status?.distance_limit\n      if (distanceLimit) {\n        deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = distanceLimit + Unit_M\n      } else {\n        deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER\n      }\n    } else {\n      deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].value = DEFAULT_PLACEHOLDER\n    }\n  }\n  deviceSetting[DeviceSettingKeyEnum.DISTANCE_LIMIT_SET].trueValue = distanceLimitStatus\n\n  // 避障\n  if (isNil(device?.obstacle_avoidance)) {\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER\n    deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER\n  } else {\n    const { horizon, upside, downside } = device.obstacle_avoidance || {}\n    if (isNil(horizon)) {\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = DEFAULT_PLACEHOLDER\n    } else {\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].editable = false\n      if (horizon === ObstacleAvoidanceStatusEnum.CLOSE) {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '关闭'\n      } else if (horizon === ObstacleAvoidanceStatusEnum.OPEN) {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = '开启'\n      } else {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].value = DEFAULT_PLACEHOLDER\n      }\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_HORIZON].trueValue = horizon\n    }\n\n    if (isNil(upside)) {\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = DEFAULT_PLACEHOLDER\n    } else {\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].editable = false\n      if (upside === ObstacleAvoidanceStatusEnum.CLOSE) {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '关闭'\n      } else if (upside === ObstacleAvoidanceStatusEnum.OPEN) {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = '开启'\n      } else {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].value = DEFAULT_PLACEHOLDER\n      }\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_UPSIDE].trueValue = upside\n    }\n\n    if (isNil(downside)) {\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = DEFAULT_PLACEHOLDER\n    } else {\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].editable = false\n      if (downside === ObstacleAvoidanceStatusEnum.CLOSE) {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '关闭'\n      } else if (downside === ObstacleAvoidanceStatusEnum.OPEN) {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = '开启'\n      } else {\n        deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].value = DEFAULT_PLACEHOLDER\n      }\n      deviceSetting[DeviceSettingKeyEnum.OBSTACLE_AVOIDANCE_DOWNSIDE].trueValue = downside\n    }\n  }\n  return deviceSetting\n}\n\n// 更新formModel\nexport function updateDeviceSettingFormModelByOsd (deviceSettingFormModelFromOsd: DeviceSettingFormModel, deviceInfo: DeviceInfoType) {\n  const { device, dock, gateway } = deviceInfo || {}\n  if (!deviceSettingFormModelFromOsd) {\n    return\n  }\n  // 夜航灯\n  const nightLightsState = device?.night_lights_state as any\n  if (!isNil(nightLightsState) && nightLightsState === NightLightsStateEnum.OPEN) {\n    deviceSettingFormModelFromOsd.nightLightsState = true\n  } else {\n    deviceSettingFormModelFromOsd.nightLightsState = false\n  }\n\n  // 限高\n  const heightLimit = device?.height_limit as any\n  if (isNil(heightLimit) || heightLimit === 0) {\n    deviceSettingFormModelFromOsd.heightLimit = 20\n  } else {\n    deviceSettingFormModelFromOsd.heightLimit = heightLimit\n  }\n\n  // 限远\n  const distanceLimitStatus = device?.distance_limit_status?.state as any\n  if (!isNil(distanceLimitStatus) && distanceLimitStatus === DistanceLimitStatusEnum.SET) {\n    deviceSettingFormModelFromOsd.distanceLimitStatus.state = true\n    deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = device?.distance_limit_status?.distance_limit || 15\n  } else {\n    deviceSettingFormModelFromOsd.distanceLimitStatus.state = false\n    deviceSettingFormModelFromOsd.distanceLimitStatus.distanceLimit = 15\n  }\n\n  // 避障\n  if (isNil(device?.obstacle_avoidance)) {\n    deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false\n    deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false\n    deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false\n  } else {\n    const { horizon, upside, downside } = device.obstacle_avoidance || {}\n    if (!isNil(horizon) && horizon === ObstacleAvoidanceStatusEnum.OPEN) {\n      deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = true\n    } else {\n      deviceSettingFormModelFromOsd.obstacleAvoidanceHorizon = false\n    }\n    if (!isNil(upside) && upside === ObstacleAvoidanceStatusEnum.OPEN) {\n      deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = true\n    } else {\n      deviceSettingFormModelFromOsd.obstacleAvoidanceUpside = false\n    }\n    if (!isNil(downside) && downside === ObstacleAvoidanceStatusEnum.OPEN) {\n      deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = true\n    } else {\n      deviceSettingFormModelFromOsd.obstacleAvoidanceDownside = false\n    }\n  }\n  return deviceSettingFormModelFromOsd\n}\n"
  },
  {
    "path": "src/utils/download.ts",
    "content": "/**\n * 加载图片\n * @param url\n * @returns\n */\nexport function urlToImage (url: string) {\n  return new Promise<HTMLImageElement>((resolve, reject) => {\n    const image = new Image()\n    image.src = url\n    image.onload = () => { resolve(image) }\n    image.onerror = () => { reject(new Error('image load error')) }\n  })\n}\n\nexport interface CompressImageData {\n  blob: Blob | null;\n  imageData: ImageData;\n}\nexport function compressImage (imgToCompress: HTMLImageElement, targetWidth: number, targetHeight: number): Promise<CompressImageData> | undefined {\n  // resizing the image\n  const canvas = document.createElement('canvas')\n  const context = canvas.getContext('2d')\n  if (context) {\n    const iWidth = imgToCompress.width\n    const iHeight = imgToCompress.height\n    const iRatio = iWidth / iHeight // 图像宽高比\n    const tRatio = targetWidth / targetHeight // 目标宽高比\n    let dw = targetWidth\n    let dh = targetHeight\n    let dx = 0\n    let dy = 0\n    if (iRatio > tRatio) {\n      // 如果图像宽高比比目标宽高比要大，说明图像比目标尺寸更宽，这时候我们应该按照高度缩放比来进行缩放宽度\n      dw = (targetHeight / iHeight) * iWidth\n      // 宽度溢出，应该放在中间\n      dx = -(dw - targetWidth) / 2\n    } else {\n      // 否则说明图像比目标尺寸更高，按照宽度缩放比来缩放高度\n      dh = (targetWidth / iWidth) * iHeight\n      // 高度溢出，应该放在中间\n      dy = -(dh - targetHeight) / 2\n    }\n\n    canvas.width = targetWidth\n    canvas.height = targetHeight\n\n    context.drawImage(\n      imgToCompress,\n      dx,\n      dy,\n      dw,\n      dh,\n    )\n\n    return new Promise<CompressImageData>((resolve) => {\n      const imageData = context.getImageData(0, 0, canvas.width, canvas.height)\n\n      canvas.toBlob(blob => resolve({\n        blob,\n        imageData,\n      }))\n    })\n  }\n}\n\n/**\n * 根据资源url下载文件\n * @param url\n * @param fileName\n */\nexport function download (url: string, fileName = ''): void {\n  const aLink = document.createElement('a')\n  aLink.style.display = 'none'\n  aLink.download = fileName\n  aLink.href = url\n  document.body.appendChild(aLink)\n  // 避免新开页面，闪烁\n  // aLink.target = '_blank'\n  aLink.click()\n  document.body.removeChild(aLink)\n  // aLink.remove()\n}\n"
  },
  {
    "path": "src/utils/error-code/index.ts",
    "content": "export interface ErrorCode {\n  code: number;\n  msg: string;\n}\n\n/**\n * 根据错误码翻译错误信息\n * @param code\n * @param errorMsg\n * @returns\n */\nexport function getErrorMessage (code: number, errorMsg?: string): string {\n  const errorInfo = ERROR_CODE.find((item: ErrorCode) => item.code === code)\n  return errorInfo ? errorInfo.msg : errorMsg || 'Server error'\n}\n\n// 暂时只添加航线错误\nexport const ERROR_CODE = [\n  {\n    code: 314001,\n    msg: 'The issued route task url is empty',\n  },\n  {\n    code: 314002,\n    msg: 'The issued route task md5 is empty',\n  },\n  {\n    code: 314003,\n    msg: 'MissionID is invalid',\n  },\n  {\n    code: 314004,\n    msg: 'Failed to send flight route task from cloud',\n  },\n  {\n    code: 314005,\n    msg: 'Route md5 check failed',\n  },\n  {\n    code: 314006,\n    msg: 'Timeout waiting for aircraft to upload route (waiting for gs_state)',\n  },\n  {\n    code: 314007,\n    msg: 'Failed to upload route to aircraft',\n  },\n  {\n    code: 314008,\n    msg: 'Timeout waiting for the aircraft to enter the route executable state',\n  },\n  {\n    code: 314009,\n    msg: 'Failed to open route mission',\n  },\n  {\n    code: 314010,\n    msg: 'Route execution failed',\n  },\n  {\n    code: 316001,\n    msg: 'Failed to set alternate point',\n  },\n  {\n    code: 316002,\n    msg: 'Alternate safety transfer altitude equipment failed',\n  },\n  {\n    code: 316003,\n    msg: 'Failed to set takeoff altitude. Remarks: The default safe takeoff height of the aircraft set by the current DJI Dock is: 1.8',\n  },\n  {\n    code: 316004,\n    msg: 'Failed to set runaway behavior',\n  },\n  {\n    code: 316005,\n    msg: 'Aircraft RTK convergence failed',\n  },\n  {\n    code: 316013,\n    msg: 'DJI Dock Moved',\n  },\n  {\n    code: 316015,\n    msg: 'The aircraft RTK convergence position is too far from the DJI Dock',\n  },\n  {\n    code: 316007,\n    msg: 'Set parameter timeout while waiting for aircraft to be ready',\n  },\n  {\n    code: 316008,\n    msg: 'Failed to gain control of aircraft',\n  },\n  {\n    code: 316009,\n    msg: 'Aircraft power is low',\n  },\n  {\n    code: 316010,\n    msg: 'After power on, the aircraft is not connected for more than 2 minutes (flight control OSD reception timeout)',\n  },\n  {\n    code: 316011,\n    msg: 'Landing Position Offset',\n  },\n\n  {\n    code: 317001,\n    msg: 'Failed to get the number of media files',\n  },\n\n  {\n    code: 319001,\n    msg: 'The task center is not currently idle',\n  },\n  {\n    code: 319002,\n    msg: 'dronenest communication timeout',\n  },\n  {\n    code: 319999,\n    msg: 'Unknown error, e.g. restart after crash',\n  },\n  {\n    code: 321000,\n    msg: 'Route execution failed, unknown error',\n  },\n  {\n    code: 321257,\n    msg: 'The route has already started and cannot be started again',\n  },\n  {\n    code: 321258,\n    msg: 'The route cannot be interrupted in this state',\n  },\n  {\n    code: 321259,\n    msg: 'The route has not started and cannot end the route',\n  },\n  {\n    code: 321513,\n    msg: 'Reach the height limit',\n  },\n  {\n    code: 321514,\n    msg: 'Reach the limit',\n  },\n  {\n    code: 321515,\n    msg: 'Crossing the restricted flight zone',\n  },\n  {\n    code: 321516,\n    msg: 'Low limit',\n  },\n\n  {\n    code: 321517,\n    msg: 'Obstacle Avoidance',\n  },\n  {\n    code: 321769,\n    msg: 'Weak GPS signal',\n  },\n  {\n    code: 321770,\n    msg: 'The current gear state cannot be executed, B control seizes the control, and the gear is switched',\n  },\n  {\n    code: 321771,\n    msg: 'The home point is not refreshed',\n  },\n  {\n    code: 321772,\n    msg: 'The current battery is too low to start the task',\n  },\n  {\n    code: 321773,\n    msg: 'Low battery return',\n  },\n  {\n    code: 321776,\n    msg: 'RTK not ready',\n  },\n  {\n    code: 321778,\n    msg: 'The aircraft is idling on the ground and is not allowed to start the route, thinking that the user is not ready.',\n  },\n  {\n    code: 322282,\n    msg: 'User interrupt (B control takeover)',\n  },\n  {\n    code: 514100,\n    msg: 'Command not supported',\n  },\n  {\n    code: 514101,\n    msg: 'Failed to close putter',\n  },\n  {\n    code: 514102,\n    msg: 'Failed to release putter',\n  },\n  {\n    code: 514103,\n    msg: 'Aircraft battery is low',\n  },\n  {\n    code: 514104,\n    msg: 'Failed to start charging',\n  },\n  {\n    code: 514105,\n    msg: 'Failed to stop charging',\n  },\n  {\n    code: 514106,\n    msg: 'Failed to restart the aircraft',\n  },\n  {\n    code: 514107,\n    msg: 'Failed to open hatch',\n  },\n  {\n    code: 514108,\n    msg: 'Failed to close hatch',\n  },\n  {\n    code: 514109,\n    msg: 'Failed to open the plane',\n  },\n  {\n    code: 514110,\n    msg: 'Failed to close the plane',\n  },\n  {\n    code: 514111,\n    msg: 'The aircraft failed to turn on the slow-rotating propeller in the cabin',\n  },\n  {\n    code: 514112,\n    msg: 'The aircraft failed to stop the slow-rotating propeller in the cabin',\n  },\n  {\n    code: 514113,\n    msg: 'Failed to establish wired connection with aircraft',\n  },\n  {\n    code: 514114,\n    msg: 'Get aircraft power status, command timed out, or return code is not 0',\n  },\n  {\n    code: 514116,\n    msg: 'The DJI Dock is busy and other control orders are being executed at the DJI Dock',\n  },\n  {\n    code: 514117,\n    msg: 'Check hatch status failed',\n  },\n  {\n    code: 514118,\n    msg: 'Check putter status failed',\n  },\n  {\n    code: 514120,\n    msg: 'DJI Dock and aircraft SDR connection failed',\n  },\n  {\n    code: 514121,\n    msg: 'Emergency stop state',\n  },\n  {\n    code: 514122,\n    msg: 'Failed to get the charging status of the aircraft (Failed to get the charging status, the flight mission can be executed, affecting charging and remote troubleshooting)',\n  },\n  {\n    code: 514123,\n    msg: 'Unable to power on due to low battery',\n  },\n  {\n    code: 514124,\n    msg: 'Failed to get battery information',\n  },\n  {\n    code: 514125,\n    msg: 'The battery is fully charged and cannot be charged',\n  },\n  {\n    code: 514145,\n    msg: 'Can not work while debugging on site',\n  },\n  {\n    code: 514146,\n    msg: 'Unable to work in remote debugging',\n  },\n  {\n    code: 514147,\n    msg: 'Unable to work in upgrade state',\n  },\n  {\n    code: 514148,\n    msg: 'Unable to execute new tasks in job state',\n  },\n  {\n    code: 514150,\n    msg: 'DJI Dock is automatically restarting',\n  },\n]\n"
  },
  {
    "path": "src/utils/genjson.ts",
    "content": "import {\n  MapGeographicPosition,\n} from '/@/types/map'\n\nexport type GeojsonCoordinate = [number, number, number?]\n\nexport interface GeojsonLine {\n type: 'Feature'\n properties: {\n   color: string\n   directConnected?: boolean\n }\n geometry: {\n   type: 'LineString'\n   coordinates: GeojsonCoordinate[]\n }\n}\n\nexport interface GeojsonPolygon {\n type: 'Feature'\n properties: {\n   color: string\n }\n geometry: {\n   type: 'Polygon'\n   coordinates: GeojsonCoordinate[][]\n }\n}\n\nexport interface GeojsonPoint {\n type: 'Feature'\n properties: {\n   color: string\n   clampToGround?: boolean\n }\n geometry: {\n   type: 'Point'\n   coordinates: GeojsonCoordinate\n }\n}\n\nexport interface GeojsonCircle {\n type: 'Feature'\n properties: {\n   color: string\n   clampToGround?: boolean\n }\n geometry: {\n   type: 'Circle'\n   coordinates: GeojsonCoordinate\n   radius: number\n }\n}\n\nexport type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint | GeojsonCircle\n\nexport function geographic2Coordinate (position: MapGeographicPosition): GeojsonCoordinate {\n  const coordinates: GeojsonCoordinate = [position.longitude, position.latitude]\n  if (position.height !== undefined) coordinates.push(position.height)\n  return coordinates\n}\n\nexport function generateLine (coordinates: MapGeographicPosition[], properties: GeojsonLine['properties']): GeojsonFeature {\n  return {\n    type: 'Feature',\n    properties,\n    geometry: {\n      type: 'LineString',\n      coordinates: coordinates.map(geographic2Coordinate),\n    },\n  }\n}\n\nexport function generatePolygon (coordinates: MapGeographicPosition[], properties: GeojsonPolygon['properties']): GeojsonFeature {\n  return {\n    type: 'Feature',\n    properties,\n    geometry: {\n      type: 'Polygon',\n      coordinates: [coordinates.map(geographic2Coordinate)],\n    },\n  }\n}\n\nexport function generatePoint (position: MapGeographicPosition, properties: GeojsonPoint['properties']): GeojsonFeature {\n  return {\n    type: 'Feature',\n    properties,\n    geometry: {\n      type: 'Point',\n      coordinates: geographic2Coordinate(position),\n    },\n  }\n}\n\nexport function generateCircle (position: MapGeographicPosition, properties: GeojsonCircle['properties'], radius: number): GeojsonFeature {\n  return {\n    type: 'Feature',\n    properties,\n    geometry: {\n      type: 'Circle',\n      coordinates: geographic2Coordinate(position),\n      radius: radius,\n    },\n  }\n}\n"
  },
  {
    "path": "src/utils/layer-tree.ts",
    "content": "const layerTreeTypes = ['layer', 'resource'] as const\ntype LayerTreeType = (typeof layerTreeTypes)[number]\nconst Spliter = '__'\n\nexport function getLayerTreeKey (type: LayerTreeType, id: number | string) {\n  return `${type}${Spliter}${id}`\n}\n\nexport function isLayerTreeKey (key: string, type?: LayerTreeType) {\n  if (type) {\n    return key.startsWith(`${type}${Spliter}`)\n  } else {\n    return layerTreeTypes.some(t => key.startsWith(`${t}${Spliter}`))\n  }\n}\n\nexport function getIdFromLayerTreeKey (key: string) {\n  return key.split(Spliter)[1]\n}\n"
  },
  {
    "path": "src/utils/logger.ts",
    "content": "\n/**\n * Used for log printing in a non-production environment\n * @param args\n */\nexport function consoleLog (...args: Parameters<typeof console.log>) {\n  if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {\n    window.console.log.apply(null, args) // eslint-disable-line no-console\n  }\n}\n\nexport function consoleWarn (...args: Parameters<typeof console.warn>) {\n  if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {\n    console.warn.apply(null, args) // eslint-disable-line no-console\n  }\n}\n\nexport function consoleError (...args: Parameters<typeof console.error>) {\n  if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {\n    console.error.apply(null, args) // eslint-disable-line no-console\n  }\n}\n\nexport function testEnvLog (...args: Parameters<typeof console.log>) {\n  if (import.meta.env.VITE_APP_ENVIRONMENT !== 'PROD') {\n    console.log.apply(null, args) // eslint-disable-line no-console\n  }\n}\n"
  },
  {
    "path": "src/utils/map-layer-utils.ts",
    "content": "import { pinAMapPosition, MapGeographicPosition, Layer, LayerType, LayerElevationLoadStatus } from '/@/types/map'\nimport { generatePoint, generateLine, generatePolygon, generateCircle } from '/@/utils/genjson'\nimport { MapDoodleColor, MapElementEnum } from '/@/constants/map'\nfunction getPinPosition (pinAMapPosition: pinAMapPosition):MapGeographicPosition {\n  return { height: 0, latitude: pinAMapPosition.lat, longitude: pinAMapPosition.lng }\n}\n\nexport function generatePointContent (pinAMapPosition: pinAMapPosition) {\n  const position = getPinPosition(pinAMapPosition)\n  return {\n    type: MapElementEnum.PIN,\n    content: generatePoint(position, {\n      color: MapDoodleColor.PinColor,\n      clampToGround: true,\n    })\n  }\n}\nfunction getLieOrPolyPosition (mapPosition: pinAMapPosition[]):MapGeographicPosition[] {\n  const position = [] as MapGeographicPosition[]\n  mapPosition.forEach(item => {\n    position.push({ height: 0, latitude: item.lat, longitude: item.lng })\n  })\n  return position\n}\nexport function generateLineContent (mapPosition: pinAMapPosition[]) {\n  const position = getLieOrPolyPosition(mapPosition)\n  return {\n    type: MapElementEnum.LINE,\n    content: generateLine(position, {\n      color: MapDoodleColor.PolylineColor,\n      directConnected: false,\n    })\n  }\n}\n\nexport function generatePolyContent (mapPosition: pinAMapPosition[]) {\n  const position = getLieOrPolyPosition(mapPosition)\n  return {\n    type: MapElementEnum.POLY,\n    content: generatePolygon(position, {\n      color: MapDoodleColor.PolygonColor,\n    })\n  }\n}\n\nexport function generateCircleContent (pinAMapPosition: pinAMapPosition, radius: number) {\n  const position = getPinPosition(pinAMapPosition)\n  return generateCircle(position, { color: MapDoodleColor.PolygonColor }, radius)\n}\n"
  },
  {
    "path": "src/utils/storage.ts",
    "content": "import { EStorageKey } from '/@/types/enums'\nimport { consoleWarn } from './logger'\n\nfunction getStorageData (key: EStorageKey, parse?: boolean): string | null\nfunction getStorageData<T> (key: EStorageKey, parse?: boolean): T | null\nfunction getStorageData (key: EStorageKey, parse?: boolean): any {\n  const value = window.localStorage.getItem(key)\n  if (parse && value) {\n    try {\n      const result = JSON.parse(value)\n      return result\n    } catch (e) {\n      consoleWarn('appStorage.get failed, err:', e)\n      return null\n    }\n  } else {\n    return value\n  }\n}\n\nfunction clearStorageData (key: EStorageKey | EStorageKey[]) {\n  let keyList: EStorageKey[] = []\n  if (Array.isArray(key)) {\n    keyList = key\n  } else {\n    keyList = [key]\n  }\n  keyList.forEach(item => {\n    window.localStorage.removeItem(item)\n  })\n}\n\nconst appStorage = {\n  save (key: EStorageKey, value: string) {\n    window.localStorage.setItem(key, value)\n  },\n  get: getStorageData,\n\n  clear: clearStorageData,\n}\n\nexport default appStorage\n"
  },
  {
    "path": "src/utils/time.ts",
    "content": "import {\n  DATE_FORMAT,\n  DEFAULT_PLACEHOLDER\n} from '/@/utils/constants'\nimport moment, { Moment } from 'moment'\n\n// 时间字符串 或者 Unix 时间戳（毫秒数）\nexport function formatDateTime (time: string | number, format = DATE_FORMAT) {\n  return time ? moment(time).format(format) : DEFAULT_PLACEHOLDER\n}\n\n// Unix 时间戳 (秒)\nexport function formatUnixTime (time: number, format = DATE_FORMAT): string {\n  return time ? moment.unix(time).format(format) : DEFAULT_PLACEHOLDER\n}\n"
  },
  {
    "path": "src/utils/uuid.ts",
    "content": "export function uuidv4 () {\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n    const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8)\n    return v.toString(16)\n  })\n}\n"
  },
  {
    "path": "src/vendors/coordtransform.js",
    "content": "/**\n * Conversion between the coordinates of the National Bureau of Survey and Measurement (Mars coordinates, GCJ02) and the WGS84 coordinate system\n */\nconst x_PI = 3.14159265358979324 * 3000.0 / 180.0;\nconst PI = 3.1415926535897932384626;\nconst a = 6378245.0;\nconst ee = 0.00669342162296594323;\n\n\n/**\n * WGS84 to Mars coordinate system GCj02\n * @param lng\n * @param lat\n * @returns {*[]}\n */\nexport function wgs84togcj02(lng, lat) {\n    if (out_of_china(lng, lat)) {\n        return [lng, lat]\n    }\n    else {\n        var dlat = transformlat(lng - 105.0, lat - 35.0);\n        var dlng = transformlng(lng - 105.0, lat - 35.0);\n        var radlat = lat / 180.0 * PI;\n        var magic = Math.sin(radlat);\n        magic = 1 - ee * magic * magic;\n        var sqrtmagic = Math.sqrt(magic);\n        dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n        dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n        var mglat = lat + dlat;\n        var mglng = lng + dlng;\n        return [mglng, mglat]\n    }\n}\n\n/**\n * GCJ02 transform WGS84\n * @param lng\n * @param lat\n * @returns {*[]}\n */\n export function gcj02towgs84(lng, lat) {\n    var lat = +lat;\n    var lng = +lng;\n    if (out_of_china(lng, lat)) {\n      return [lng, lat]\n    } else {\n      var dlat = transformlat(lng - 105.0, lat - 35.0);\n      var dlng = transformlng(lng - 105.0, lat - 35.0);\n      var radlat = lat / 180.0 * PI;\n      var magic = Math.sin(radlat);\n      magic = 1 - ee * magic * magic;\n      var sqrtmagic = Math.sqrt(magic);\n      dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n      dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n      var mglat = lat + dlat;\n      var mglng = lng + dlng;\n      return [lng * 2 - mglng, lat * 2 - mglat]\n    }\n}\n\nfunction transformlat(lng, lat) {\n    var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));\n    ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n    ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;\n    ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;\n    return ret\n}\n\nexport function transformlng(lng, lat) {\n    var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));\n    ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n    ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;\n    ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;\n    return ret\n}\n\n/**\n * Judge whether you are in the country or not if you are not in the country\n * @param lng\n * @param lat\n * @returns {boolean}\n */\nfunction out_of_china(lng, lat) {\n    return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false);\n}"
  },
  {
    "path": "src/vendors/srs.sdk.js",
    "content": "\n//\n// Copyright (c) 2013-2021 Winlin\n//\n// SPDX-License-Identifier: MIT\n//\n\n'use strict';\n\nfunction SrsError(name, message) {\n  this.name = name;\n  this.message = message;\n  this.stack = (new Error()).stack;\n}\nSrsError.prototype = Object.create(Error.prototype);\nSrsError.prototype.constructor = SrsError;\n\n// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter\n// Async-awat-prmise based SRS RTC Publisher.\nfunction SrsRtcPublisherAsync() {\n  var self = {};\n\n  // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\n  self.constraints = {\n    audio: true,\n    video: {\n      width: { ideal: 320, max: 576 }\n    }\n  };\n\n  // @see https://github.com/rtcdn/rtcdn-draft\n  // @url The WebRTC url to play with, for example:\n  //      webrtc://r.ossrs.net/live/livestream\n  // or specifies the API port:\n  //      webrtc://r.ossrs.net:11985/live/livestream\n  // or autostart the publish:\n  //      webrtc://r.ossrs.net/live/livestream?autostart=true\n  // or change the app from live to myapp:\n  //      webrtc://r.ossrs.net:11985/myapp/livestream\n  // or change the stream from livestream to mystream:\n  //      webrtc://r.ossrs.net:11985/live/mystream\n  // or set the api server to myapi.domain.com:\n  //      webrtc://myapi.domain.com/live/livestream\n  // or set the candidate(eip) of answer:\n  //      webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185\n  // or force to access https API:\n  //      webrtc://r.ossrs.net/live/livestream?schema=https\n  // or use plaintext, without SRTP:\n  //      webrtc://r.ossrs.net/live/livestream?encrypt=false\n  // or any other information, will pass-by in the query:\n  //      webrtc://r.ossrs.net/live/livestream?vhost=xxx\n  //      webrtc://r.ossrs.net/live/livestream?token=xxx\n  self.publish = async function (url) {\n    var conf = self.__internal.prepareUrl(url);\n    self.pc.addTransceiver(\"audio\", { direction: \"sendonly\" });\n    self.pc.addTransceiver(\"video\", { direction: \"sendonly\" });\n    //self.pc.addTransceiver(\"video\", {direction: \"sendonly\"});\n    //self.pc.addTransceiver(\"audio\", {direction: \"sendonly\"});\n\n    if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {\n      throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);\n    }\n    var stream = await navigator.mediaDevices.getUserMedia(self.constraints);\n\n    // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack\n    stream.getTracks().forEach(function (track) {\n      self.pc.addTrack(track);\n\n      // Notify about local track when stream is ok.\n      self.ontrack && self.ontrack({ track: track });\n    });\n\n    var offer = await self.pc.createOffer();\n    await self.pc.setLocalDescription(offer);\n    var session = await new Promise(function (resolve, reject) {\n      // @see https://github.com/rtcdn/rtcdn-draft\n      var data = {\n        api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,\n        clientip: null, sdp: offer.sdp\n      };\n      console.log(\"Generated offer: \", data);\n\n      const xhr = new XMLHttpRequest();\n      xhr.onload = function () {\n        if (xhr.readyState !== xhr.DONE) return;\n        if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);\n        const data = JSON.parse(xhr.responseText);\n        console.log(\"Got answer: \", data);\n        return data.code ? reject(xhr) : resolve(data);\n      }\n      xhr.open('POST', conf.apiUrl, true);\n      xhr.setRequestHeader('Content-type', 'application/json');\n      xhr.send(JSON.stringify(data));\n    });\n    await self.pc.setRemoteDescription(\n      new RTCSessionDescription({ type: 'answer', sdp: session.sdp })\n    );\n    session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';\n\n    return session;\n  };\n\n  // Close the publisher.\n  self.close = function () {\n    self.pc && self.pc.close();\n    self.pc = null;\n  };\n\n  // The callback when got local stream.\n  // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack\n  self.ontrack = function (event) {\n    // Add track to stream of SDK.\n    self.stream.addTrack(event.track);\n  };\n\n  // Internal APIs.\n  self.__internal = {\n    defaultPath: '/rtc/v1/publish/',\n    prepareUrl: function (webrtcUrl) {\n      var urlObject = self.__internal.parse(webrtcUrl);\n\n      // If user specifies the schema, use it as API schema.\n      var schema = urlObject.user_query.schema;\n      schema = schema ? schema + ':' : window.location.protocol;\n\n      var port = urlObject.port || 1985;\n      if (schema === 'https:') {\n        port = urlObject.port || 443;\n      }\n\n      // @see https://github.com/rtcdn/rtcdn-draft\n      var api = urlObject.user_query.play || self.__internal.defaultPath;\n      if (api.lastIndexOf('/') !== api.length - 1) {\n        api += '/';\n      }\n\n      var apiUrl = schema + '//' + urlObject.server + ':' + port + api;\n      for (var key in urlObject.user_query) {\n        if (key !== 'api' && key !== 'play') {\n          apiUrl += '&' + key + '=' + urlObject.user_query[key];\n        }\n      }\n      // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v\n      apiUrl = apiUrl.replace(api + '&', api + '?');\n\n      var streamUrl = urlObject.url;\n\n      return {\n        apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,\n        tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)\n      };\n    },\n    parse: function (url) {\n      // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri\n      var a = document.createElement(\"a\");\n      a.href = url.replace(\"rtmp://\", \"http://\")\n        .replace(\"webrtc://\", \"http://\")\n        .replace(\"rtc://\", \"http://\");\n\n      var vhost = a.hostname;\n      var app = a.pathname.substring(1, a.pathname.lastIndexOf(\"/\"));\n      var stream = a.pathname.slice(a.pathname.lastIndexOf(\"/\") + 1);\n\n      // parse the vhost in the params of app, that srs supports.\n      app = app.replace(\"...vhost...\", \"?vhost=\");\n      if (app.indexOf(\"?\") >= 0) {\n        var params = app.slice(app.indexOf(\"?\"));\n        app = app.slice(0, app.indexOf(\"?\"));\n\n        if (params.indexOf(\"vhost=\") > 0) {\n          vhost = params.slice(params.indexOf(\"vhost=\") + \"vhost=\".length);\n          if (vhost.indexOf(\"&\") > 0) {\n            vhost = vhost.slice(0, vhost.indexOf(\"&\"));\n          }\n        }\n      }\n\n      // when vhost equals to server, and server is ip,\n      // the vhost is __defaultVhost__\n      if (a.hostname === vhost) {\n        var re = /^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$/;\n        if (re.test(a.hostname)) {\n          vhost = \"__defaultVhost__\";\n        }\n      }\n\n      // parse the schema\n      var schema = \"rtmp\";\n      if (url.indexOf(\"://\") > 0) {\n        schema = url.slice(0, url.indexOf(\"://\"));\n      }\n\n      var port = a.port;\n      if (!port) {\n        // Finger out by webrtc url, if contains http or https port, to overwrite default 1985.\n        if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {\n          port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;\n        }\n\n        // Guess by schema.\n        if (schema === 'http') {\n          port = 80;\n        } else if (schema === 'https') {\n          port = 443;\n        } else if (schema === 'rtmp') {\n          port = 1935;\n        }\n      }\n\n      var ret = {\n        url: url,\n        schema: schema,\n        server: a.hostname, port: port,\n        vhost: vhost, app: app, stream: stream\n      };\n      self.__internal.fill_query(a.search, ret);\n\n      // For webrtc API, we use 443 if page is https, or schema specified it.\n      if (!ret.port) {\n        if (schema === 'webrtc' || schema === 'rtc') {\n          if (ret.user_query.schema === 'https') {\n            ret.port = 443;\n          } else if (window.location.href.indexOf('https://') === 0) {\n            ret.port = 443;\n          } else {\n            // For WebRTC, SRS use 1985 as default API port.\n            ret.port = 1985;\n          }\n        }\n      }\n\n      return ret;\n    },\n    fill_query: function (query_string, obj) {\n      // pure user query object.\n      obj.user_query = {};\n\n      if (query_string.length === 0) {\n        return;\n      }\n\n      // split again for angularjs.\n      if (query_string.indexOf(\"?\") >= 0) {\n        query_string = query_string.split(\"?\")[1];\n      }\n\n      var queries = query_string.split(\"&\");\n      for (var i = 0; i < queries.length; i++) {\n        var elem = queries[i];\n\n        var query = elem.split(\"=\");\n        obj[query[0]] = query[1];\n        obj.user_query[query[0]] = query[1];\n      }\n\n      // alias domain for vhost.\n      if (obj.domain) {\n        obj.vhost = obj.domain;\n      }\n    }\n  };\n\n  self.pc = new RTCPeerConnection(null);\n\n  // To keep api consistent between player and publisher.\n  // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack\n  // @see https://webrtc.org/getting-started/media-devices\n  self.stream = new MediaStream();\n\n  return self;\n}\n\n// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter\n// Async-await-promise based SRS RTC Player.\nfunction SrsRtcPlayerAsync() {\n  var self = {};\n\n  // @see https://github.com/rtcdn/rtcdn-draft\n  // @url The WebRTC url to play with, for example:\n  //      webrtc://r.ossrs.net/live/livestream\n  // or specifies the API port:\n  //      webrtc://r.ossrs.net:11985/live/livestream\n  //      webrtc://r.ossrs.net:80/live/livestream\n  // or autostart the play:\n  //      webrtc://r.ossrs.net/live/livestream?autostart=true\n  // or change the app from live to myapp:\n  //      webrtc://r.ossrs.net:11985/myapp/livestream\n  // or change the stream from livestream to mystream:\n  //      webrtc://r.ossrs.net:11985/live/mystream\n  // or set the api server to myapi.domain.com:\n  //      webrtc://myapi.domain.com/live/livestream\n  // or set the candidate(eip) of answer:\n  //      webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185\n  // or force to access https API:\n  //      webrtc://r.ossrs.net/live/livestream?schema=https\n  // or use plaintext, without SRTP:\n  //      webrtc://r.ossrs.net/live/livestream?encrypt=false\n  // or any other information, will pass-by in the query:\n  //      webrtc://r.ossrs.net/live/livestream?vhost=xxx\n  //      webrtc://r.ossrs.net/live/livestream?token=xxx\n  self.play = async function (url) {\n    var conf = self.__internal.prepareUrl(url);\n    self.pc.addTransceiver(\"audio\", { direction: \"recvonly\" });\n    self.pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n    //self.pc.addTransceiver(\"video\", {direction: \"recvonly\"});\n    //self.pc.addTransceiver(\"audio\", {direction: \"recvonly\"});\n\n    var offer = await self.pc.createOffer();\n    await self.pc.setLocalDescription(offer);\n    var session = await new Promise(function (resolve, reject) {\n      // @see https://github.com/rtcdn/rtcdn-draft\n      var data = {\n        api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,\n        clientip: null, sdp: offer.sdp\n      };\n      console.log(\"Generated offer: \", data);\n\n      const xhr = new XMLHttpRequest();\n      xhr.onload = function () {\n        if (xhr.readyState !== xhr.DONE) return;\n        if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);\n        const data = JSON.parse(xhr.responseText);\n        console.log(\"Got answer: \", data);\n        return data.code ? reject(xhr) : resolve(data);\n      }\n      xhr.open('POST', conf.apiUrl, true);\n      xhr.setRequestHeader('Content-type', 'application/json');\n      xhr.send(JSON.stringify(data));\n    });\n    await self.pc.setRemoteDescription(\n      new RTCSessionDescription({ type: 'answer', sdp: session.sdp })\n    );\n    session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';\n\n    return session;\n  };\n\n  // Close the player.\n  self.close = function () {\n    self.pc && self.pc.close();\n    self.pc = null;\n  };\n\n  // The callback when got remote track.\n  // Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream\n  self.ontrack = function (event) {\n    // https://webrtc.org/getting-started/remote-streams\n    self.stream.addTrack(event.track);\n  };\n\n  // Internal APIs.\n  self.__internal = {\n    defaultPath: '/rtc/v1/play/',\n    prepareUrl: function (webrtcUrl) {\n      var urlObject = self.__internal.parse(webrtcUrl);\n\n      // If user specifies the schema, use it as API schema.\n      var schema = urlObject.user_query.schema;\n      schema = schema ? schema + ':' : window.location.protocol;\n\n      var port = urlObject.port || 1985;\n      if (schema === 'https:') {\n        port = urlObject.port || 443;\n      }\n\n      // @see https://github.com/rtcdn/rtcdn-draft\n      var api = urlObject.user_query.play || self.__internal.defaultPath;\n      if (api.lastIndexOf('/') !== api.length - 1) {\n        api += '/';\n      }\n\n      var apiUrl = schema + '//' + urlObject.server + ':' + port + api;\n      for (var key in urlObject.user_query) {\n        if (key !== 'api' && key !== 'play') {\n          apiUrl += '&' + key + '=' + urlObject.user_query[key];\n        }\n      }\n      // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v\n      apiUrl = apiUrl.replace(api + '&', api + '?');\n\n      var streamUrl = urlObject.url;\n\n      return {\n        apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,\n        tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)\n      };\n    },\n    parse: function (url) {\n      // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri\n      var a = document.createElement(\"a\");\n      a.href = url.replace(\"rtmp://\", \"http://\")\n        .replace(\"webrtc://\", \"http://\")\n        .replace(\"rtc://\", \"http://\");\n\n      var vhost = a.hostname;\n      var app = a.pathname.substring(1, a.pathname.lastIndexOf(\"/\"));\n      var stream = a.pathname.slice(a.pathname.lastIndexOf(\"/\") + 1);\n\n      // parse the vhost in the params of app, that srs supports.\n      app = app.replace(\"...vhost...\", \"?vhost=\");\n      if (app.indexOf(\"?\") >= 0) {\n        var params = app.slice(app.indexOf(\"?\"));\n        app = app.slice(0, app.indexOf(\"?\"));\n\n        if (params.indexOf(\"vhost=\") > 0) {\n          vhost = params.slice(params.indexOf(\"vhost=\") + \"vhost=\".length);\n          if (vhost.indexOf(\"&\") > 0) {\n            vhost = vhost.slice(0, vhost.indexOf(\"&\"));\n          }\n        }\n      }\n\n      // when vhost equals to server, and server is ip,\n      // the vhost is __defaultVhost__\n      if (a.hostname === vhost) {\n        var re = /^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$/;\n        if (re.test(a.hostname)) {\n          vhost = \"__defaultVhost__\";\n        }\n      }\n\n      // parse the schema\n      var schema = \"rtmp\";\n      if (url.indexOf(\"://\") > 0) {\n        schema = url.slice(0, url.indexOf(\"://\"));\n      }\n\n      var port = a.port;\n      if (!port) {\n        // Finger out by webrtc url, if contains http or https port, to overwrite default 1985.\n        if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {\n          port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;\n        }\n\n        // Guess by schema.\n        if (schema === 'http') {\n          port = 80;\n        } else if (schema === 'https') {\n          port = 443;\n        } else if (schema === 'rtmp') {\n          port = 1935;\n        }\n      }\n\n      var ret = {\n        url: url,\n        schema: schema,\n        server: a.hostname, port: port,\n        vhost: vhost, app: app, stream: stream\n      };\n      self.__internal.fill_query(a.search, ret);\n\n      // For webrtc API, we use 443 if page is https, or schema specified it.\n      if (!ret.port) {\n        if (schema === 'webrtc' || schema === 'rtc') {\n          if (ret.user_query.schema === 'https') {\n            ret.port = 443;\n          } else if (window.location.href.indexOf('https://') === 0) {\n            ret.port = 443;\n          } else {\n            // For WebRTC, SRS use 1985 as default API port.\n            ret.port = 1985;\n          }\n        }\n      }\n\n      return ret;\n    },\n    fill_query: function (query_string, obj) {\n      // pure user query object.\n      obj.user_query = {};\n\n      if (query_string.length === 0) {\n        return;\n      }\n\n      // split again for angularjs.\n      if (query_string.indexOf(\"?\") >= 0) {\n        query_string = query_string.split(\"?\")[1];\n      }\n\n      var queries = query_string.split(\"&\");\n      for (var i = 0; i < queries.length; i++) {\n        var elem = queries[i];\n\n        var query = elem.split(\"=\");\n        obj[query[0]] = query[1];\n        obj.user_query[query[0]] = query[1];\n      }\n\n      // alias domain for vhost.\n      if (obj.domain) {\n        obj.vhost = obj.domain;\n      }\n    }\n  };\n\n  self.pc = new RTCPeerConnection(null);\n\n  // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams\n  self.stream = new MediaStream();\n\n  // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack\n  self.pc.ontrack = function (event) {\n    if (self.ontrack) {\n      self.ontrack(event);\n    }\n  };\n\n  return self;\n}\n\n// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter\n// Async-awat-prmise based SRS RTC Publisher by WHIP.\nfunction SrsRtcWhipWhepAsync() {\n  var self = {};\n\n  // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\n  self.constraints = {\n    audio: true,\n    video: {\n      width: { ideal: 320, max: 576 }\n    }\n  };\n\n  // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/\n  // @url The WebRTC url to publish with, for example:\n  //      http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream\n  self.publish = async function (url) {\n    if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);\n\n    self.pc.addTransceiver(\"audio\", { direction: \"sendonly\" });\n    self.pc.addTransceiver(\"video\", { direction: \"sendonly\" });\n\n    if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {\n      throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);\n    }\n    var stream = await navigator.mediaDevices.getUserMedia(self.constraints);\n\n    // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack\n    stream.getTracks().forEach(function (track) {\n      self.pc.addTrack(track);\n\n      // Notify about local track when stream is ok.\n      self.ontrack && self.ontrack({ track: track });\n    });\n\n    var offer = await self.pc.createOffer();\n    await self.pc.setLocalDescription(offer);\n    const answer = await new Promise(function (resolve, reject) {\n      console.log(\"Generated offer: \", offer);\n\n      const xhr = new XMLHttpRequest();\n      xhr.onload = function () {\n        if (xhr.readyState !== xhr.DONE) return;\n        if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);\n        const data = xhr.responseText;\n        console.log(\"Got answer: \", data);\n        return data.code ? reject(xhr) : resolve(data);\n      }\n      xhr.open('POST', url, true);\n      xhr.setRequestHeader('Content-type', 'application/sdp');\n      xhr.send(offer.sdp);\n    });\n    await self.pc.setRemoteDescription(\n      new RTCSessionDescription({ type: 'answer', sdp: answer })\n    );\n\n    return self.__internal.parseId(url, offer.sdp, answer);\n  };\n\n  // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/\n  // @url The WebRTC url to play with, for example:\n  //      http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream\n  self.play = async function (url) {\n    if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);\n\n    self.pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n\n    var offer = await self.pc.createOffer();\n    await self.pc.setLocalDescription(offer);\n    const answer = await new Promise(function (resolve, reject) {\n      console.log(\"Generated offer: \", offer);\n\n      const xhr = new XMLHttpRequest();\n      xhr.onload = function () {\n        if (xhr.readyState !== xhr.DONE) return;\n        if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);\n        const data = xhr.responseText;\n        console.log(\"Got answer: \", data);\n        return data.code ? reject(xhr) : resolve(data);\n      }\n      xhr.open('POST', url, true);\n      xhr.setRequestHeader('Content-type', 'application/sdp');\n      xhr.send(offer.sdp);\n    });\n    await self.pc.setRemoteDescription(\n      new RTCSessionDescription({ type: 'answer', sdp: answer })\n    );\n\n    return self.__internal.parseId(url, offer.sdp, answer);\n  };\n\n  // Close the publisher.\n  self.close = function () {\n    self.pc && self.pc.close();\n    self.pc = null;\n  };\n\n  // The callback when got local stream.\n  // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack\n  self.ontrack = function (event) {\n    // Add track to stream of SDK.\n    self.stream.addTrack(event.track);\n  };\n\n  self.pc = new RTCPeerConnection(null);\n\n  // To keep api consistent between player and publisher.\n  // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack\n  // @see https://webrtc.org/getting-started/media-devices\n  self.stream = new MediaStream();\n\n  // Internal APIs.\n  self.__internal = {\n    parseId: (url, offer, answer) => {\n      let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);\n      sessionid = sessionid.substr(0, sessionid.indexOf('\\n') - 1) + ':';\n      sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);\n      sessionid = sessionid.substr(0, sessionid.indexOf('\\n'));\n\n      const a = document.createElement(\"a\");\n      a.href = url;\n      return {\n        sessionid: sessionid, // Should be ice-ufrag of answer:offer.\n        simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',\n      };\n    },\n  };\n\n  // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack\n  self.pc.ontrack = function (event) {\n    if (self.ontrack) {\n      self.ontrack(event);\n    }\n  };\n\n  return self;\n}\n\n// Format the codec of RTCRtpSender, kind(audio/video) is optional filter.\n// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs\nfunction SrsRtcFormatSenders(senders, kind) {\n  var codecs = [];\n  senders.forEach(function (sender) {\n    var params = sender.getParameters();\n    params && params.codecs && params.codecs.forEach(function (c) {\n      if (kind && sender.track.kind !== kind) {\n        return;\n      }\n\n      if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {\n        return;\n      }\n\n      var s = '';\n\n      s += c.mimeType.replace('audio/', '').replace('video/', '');\n      s += ', ' + c.clockRate + 'HZ';\n      if (sender.track.kind === \"audio\") {\n        s += ', channels: ' + c.channels;\n      }\n      s += ', pt: ' + c.payloadType;\n\n      codecs.push(s);\n    });\n  });\n  return codecs.join(\", \");\n}\n\nexport default {\n  SrsError,\n  SrsRtcPublisherAsync,\n  SrsRtcPlayerAsync,\n  SrsRtcWhipWhepAsync,\n  SrsRtcFormatSenders,\n}"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "src/websocket/index.ts",
    "content": "import { message } from 'ant-design-vue'\nimport ReconnectingWebSocket from 'reconnecting-websocket'\nimport { EBizCode } from '../types'\n\ninterface WebSocketOptions {\n  data: any\n  cache?: boolean | string\n  destroyCache?: string\n}\n\nexport interface MessageHandler {\n  (data : {[key: string]: any}): void\n}\n\nexport interface CommonHostWs<T> {\n  sn: string\n  host: T\n}\n\n/**\n * ConnectWebSocket 类\n * TODO: 优化messageHandler: EventEmitter。暂时传入回调函数\n */\nclass ConnectWebSocket {\n  _url: string\n  _socket: ReconnectingWebSocket | null\n  _hasInit: boolean\n  _messageHandler: MessageHandler | null\n\n  constructor (url: string) {\n    this._url = url\n    this._socket = null\n    this._hasInit = false\n    this._messageHandler = null\n  }\n\n  initSocket () {\n    if (this._hasInit) {\n      return\n    }\n    if (!this._url) {\n      return\n    }\n\n    // 会自动重连，无需处理重连逻辑\n    this._socket = new ReconnectingWebSocket(this._url, [], {\n      maxReconnectionDelay: 20000, // 断开后最大的重连时间： 20s，每多一次重连，会增加 1.3 倍，5 * 1.3 * 1.3 * 1.3...\n      minReconnectionDelay: 5000, // 断开后最短的重连时间： 5s\n      maxRetries: 5\n    })\n\n    this._hasInit = true\n\n    this._socket.addEventListener('open', this._onOpen.bind(this))\n    this._socket.addEventListener('close', this._onClose.bind(this))\n    this._socket.addEventListener('error', this._onError.bind(this))\n    this._socket.addEventListener('message', this._onMessage.bind(this))\n  }\n\n  _onOpen () {\n    console.log('连接成功')\n  }\n\n  _onClose () {\n    console.log('连接已断开')\n  }\n\n  _onError () {\n    console.log('连接 error')\n  }\n\n  registerMessageHandler (messageHandler: MessageHandler) {\n    this._messageHandler = messageHandler\n  }\n\n  _onMessage (msg: MessageEvent) {\n    const data = JSON.parse(msg.data)\n    this._messageHandler && this._messageHandler(data)\n    // console.log('接受消息', message)\n  }\n\n  sendMessage = (message: WebSocketOptions): void => {\n    this._socket?.send(JSON.stringify(message.data))\n  }\n\n  close () {\n    this._socket?.close()\n  }\n}\n\nexport default ConnectWebSocket\n"
  },
  {
    "path": "src/websocket/util/config.ts",
    "content": "import { ELocalStorageKey } from '/@/types/enums'\nimport { CURRENT_CONFIG } from '/@/api/http/config'\n\nexport function getWebsocketUrl () {\n  const token: string = localStorage.getItem(ELocalStorageKey.Token) || '' as string\n  const url = CURRENT_CONFIG.websocketURL + '?x-auth-token=' + encodeURI(token)\n  return url\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"baseUrl\": \".\",\n    \"types\": [\"vite/client\"],\n    \"lib\": [\n      \"esnext\",\n      \"dom\"\n    ],\n    \"paths\": {\n      \"/@/*\": [\n        \"src/*\"\n      ],\n    }\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.d.ts\",\n    \"src/**/*.tsx\",\n    \"src/**/*.vue\", \n    \"src/vendors/coordtransform.js\"  \n  ]\n}"
  },
  {
    "path": "vite.config.ts",
    "content": "import vue from '@vitejs/plugin-vue'\n// config alias\nimport path from 'path'\nimport { ConfigEnv, defineConfig, UserConfigExport } from 'vite'\nimport ViteComponents, { AntDesignVueResolver } from 'vite-plugin-components'\n// Introduce eslint plugin\nimport eslintPlugin from 'vite-plugin-eslint'\nimport OptimizationPersist from 'vite-plugin-optimize-persist'\nimport PkgConfig from 'vite-plugin-package-config'\nimport viteSvgIcons from 'vite-plugin-svg-icons'\nimport { viteVConsole } from 'vite-plugin-vconsole'\n\n// https://vitejs.dev/config/\nexport default ({ command, mode }: ConfigEnv): UserConfigExport => defineConfig({\n  plugins: [\n    vue(),\n    eslintPlugin({\n      fix: true\n    }),\n    ViteComponents({\n      customComponentResolvers: [AntDesignVueResolver()],\n    }),\n    viteSvgIcons({\n      // 指定需要缓存的图标文件夹\n      iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],\n      // 指定symbolId格式\n      symbolId: 'icon-[dir]-[name]',\n    }),\n    viteVConsole({\n      entry: path.resolve(__dirname, './src/main.ts'), // 入口文件\n      localEnabled: command === 'serve', // serve开发环境下\n      // enabled: command !== 'serve' || mode === 'test', // 打包环境下/发布测试包,\n      config: { // vconsole 配置项\n        maxLogNumber: 1000,\n        theme: 'light'\n      }\n    }),\n    PkgConfig(),\n    OptimizationPersist()\n    // [svgBuilder('./src/assets/icons/')] // All svg under src/icons/svg/ have been imported here, no need to import separately\n  ],\n  server: {\n    open: true,\n    host: '0.0.0.0',\n    port: 8080\n  },\n  envDir: './env',\n  resolve: {\n    alias: [{\n      // https://github.com/vitejs/vite/issues/279#issuecomment-635646269\n      find: '/@',\n      replacement: path.resolve(__dirname, './src'),\n    }\n    ]\n  },\n  css: {\n    preprocessorOptions: {\n      scss: {\n        // example : additionalData: `@import \"./src/design/styles/variables\";`\n        // dont need include file extend .scss\n        additionalData: '@import \"./src/styles/variables\";'\n      },\n    }\n  },\n  base: '/',\n  build: {\n    target: ['es2015'], // 最低支持 es2015\n    sourcemap: true\n  }\n})\n"
  }
]