Browse Source

设备管理

李富豪 1 year ago
parent
commit
2713ceaddc

+ 15 - 8
Web/src/App.vue

@@ -1,24 +1,31 @@
 <template>
-  <div class="demo-app">
-    <router-view />
-    <!-- <div class="map-wrapper">
+  <a-config-provider :locale="locale">
+    <div class="demo-app">
+      <router-view />
+      <!-- <div class="map-wrapper">
       <GMap/>
     </div> -->
-  </div>
+    </div>
+  </a-config-provider>
+
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, ref } from 'vue'
+import { defineComponent } from 'vue'
 import { useMyStore } from './store'
 import GMap from '/@/components/GMap.vue'
+import zhCN from 'ant-design-vue/es/locale/zh_CN';
 
 export default defineComponent({
   name: 'App',
   components: { GMap },
 
-  setup () {
-    const store = useMyStore()
-    return {}
+  setup() {
+    const store = useMyStore();
+
+    return {
+      locale: zhCN,
+    }
   }
 })
 </script>

+ 2 - 2
Web/src/api/http/request.ts

@@ -7,7 +7,7 @@ import { ELocalStorageKey, ERouterName, EUserType } from '/@/types/enums'
 export * from './type'
 
 const REQUEST_ID = 'X-Request-Id'
-function getAuthToken () {
+function getAuthToken() {
   return localStorage.getItem(ELocalStorageKey.Token)
 }
 
@@ -55,7 +55,7 @@ instance.interceptors.response.use(
     }
     // @See: https://github.com/axios/axios/issues/383
     if (!err.response || !err.response.status) {
-      message.error('The network is abnormal, please check the backend service and try again')
+      message.error('服务异常')
       return
     }
     if (err.response?.status !== 200) {

+ 75 - 0
Web/src/components/common/nav.vue

@@ -0,0 +1,75 @@
+<template>
+  <a-layout-sider collapsible>
+    <a-menu theme="dark" mode="inline" v-model:selectedKeys="state.selectedKeys" @select="handleSelect">
+      <a-menu-item :key="'/' + ERouterName.WORKSPACE">
+        <MacCommandOutlined />
+        <span>
+          项目工作空间
+        </span>
+      </a-menu-item>
+      <a-menu-item :key="'/' + ERouterName.DEVICES">
+        <HddOutlined />
+        <span>
+          设备管理
+        </span>
+      </a-menu-item>
+      <a-menu-item :key="'/' + ERouterName.TASK">
+        <ContainerOutlined />
+        <span>
+          远程计划任务
+        </span>
+      </a-menu-item>
+      <a-menu-item :key="'/' + ERouterName.MEDIA">
+        <PictureOutlined />
+        <span>
+          照片管理
+        </span>
+      </a-menu-item>
+      <a-menu-item :key="'/' + ERouterName.REPLAY">
+        <VideoCameraOutlined />
+        <span>
+          视频回放
+        </span>
+      </a-menu-item>
+      <a-menu-item :key="'/' + ERouterName.TRAJECTORY">
+        <GatewayOutlined />
+        <span>
+          轨迹回放
+        </span>
+      </a-menu-item>
+    </a-menu>
+  </a-layout-sider>
+</template>
+<script lang="ts" setup>
+import { reactive, watchEffect } from 'vue';
+import { GatewayOutlined, ContainerOutlined, HddOutlined, PictureOutlined, VideoCameraOutlined, MacCommandOutlined } from '@ant-design/icons-vue';
+import { ERouterName } from '/@/types/enums';
+import router from '/@/router';
+
+interface State {
+  selectedKeys: string[],
+}
+
+const state: State = reactive({
+  selectedKeys: [],
+})
+
+watchEffect(() => {
+  const path = router.currentRoute.value.fullPath;
+  const firstKey = path.split('/')[1];
+  state.selectedKeys = ['/' + firstKey];
+});
+
+// 选择菜单
+const handleSelect = (item: any) => {
+  router.push({ path: item.key })
+  // state.selectedKeys = [item.key];
+
+
+  // router
+  // const menuLevel = item.keyPath.length > 1 ? 2 : 1;
+  // // props.onChangeSelectedKey(item.key, menuLevel);
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 31 - 29
Web/src/components/common/sidebar.vue

@@ -1,23 +1,20 @@
 <template>
   <div class="demo-project-sidebar-wrapper flex-justify-between">
     <div>
-    <router-link
-      v-for="item in options"
-      :key="item.key"
-      :to="item.path"
-      :class="{
+      <router-link v-for="item in options" :key="item.key" :to="item.path" :class="{
         'menu-item': true,
         selected: selectedRoute(item),
-      }"
-    >
-      <a-tooltip :title="item.label" placement="right">
-        <Icon class="fz20" style="width: 50px;" :icon="item.icon"/>
-      </a-tooltip>
-    </router-link>
+      }">
+        <a-tooltip :title="item.label" placement="right">
+          <Icon class="fz20" style="width: 50px;" :icon="item.icon" />
+        </a-tooltip>
+      </router-link>
     </div>
     <div class="mb20 flex-display flex-column flex-align-center flex-justify-between">
       <a-tooltip title="Back to home" placement="right">
-        <a @click="goHome"> <Icon icon="ImportOutlined" style="font-size: 22px; color: white"/></a>
+        <a @click="goHome">
+          <Icon icon="ImportOutlined" style="font-size: 22px; color: white" />
+        </a>
       </a-tooltip>
     </div>
   </div>
@@ -33,15 +30,15 @@ interface IOptions {
   key: number
   label: string
   path:
-    | string
-    | {
-        path: string
-        query?: any
-      }
+  | string
+  | {
+    path: string
+    query?: any
+  }
   icon: string
 }
 
-const Icon = (props: {icon: string}) => {
+const Icon = (props: { icon: string }) => {
   return createVNode((icons as any)[props.icon])
 }
 
@@ -50,25 +47,25 @@ export default defineComponent({
     Icon,
   },
   name: 'Sidebar',
-  setup () {
+  setup() {
     const root = getRoot()
     const options = [
       { key: 0, label: 'Tsa', path: '/' + ERouterName.TSA, icon: 'TeamOutlined' },
-      { key: 1, label: 'Livestream', path: '/' + ERouterName.LIVESTREAM, icon: 'VideoCameraOutlined' },
-      { key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
-      { key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
-      { key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
-      { key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },
-      { key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },
+      // { key: 1, label: 'Livestream', path: '/' + ERouterName.LIVESTREAM, icon: 'VideoCameraOutlined' },
+      // { key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
+      // { key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
+      // { key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
+      // { key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },
+      // { key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },
     ]
 
-    function selectedRoute (item: IOptions) {
+    function selectedRoute(item: IOptions) {
       const path = typeof item.path === 'string' ? item.path : item.path.path
       return root.$route.path?.indexOf(path) === 0
     }
 
-    function goHome () {
-      root.$router.push('/' + ERouterName.MEMBERS)
+    function goHome() {
+      root.$router.push('/' + ERouterName.DEVICES)
     }
 
     return {
@@ -90,6 +87,7 @@ export default defineComponent({
   color: $text-white-basic;
   // flex: 1;
   overflow: hidden;
+
   .menu-item {
     width: 100%;
     padding: 16px 0px;
@@ -98,15 +96,18 @@ export default defineComponent({
     align-items: center;
     color: $text-white-basic;
     cursor: pointer;
+
     &.selected {
       background-color: #101010;
       color: $primary;
     }
+
     &.disabled {
       pointer-events: none;
       opacity: 0.45;
     }
   }
+
   .filling {
     flex: 1;
   }
@@ -118,7 +119,8 @@ export default defineComponent({
   }
 
 }
+
 .ant-tooltip-open {
   border: 0;
 }
-</style>
+</style>

+ 10 - 18
Web/src/components/common/topbar.vue

@@ -1,20 +1,13 @@
 <template>
-  <div class="width-100 flex-row flex-justify-between flex-align-center" style="height: 60px;">
-    <div class="height-100">
+  <div class="width-100 flex-row flex-justify-between flex-align-center"
+    style="height: 60px;border-bottom: 1px solid #F0F2F5;">
+    <div>
       <a-avatar :size="40" shape="square" :src="cloudapi" />
-      <span class="ml10 fontBold">{{ workspaceName }}</span>
+      <span class="ml10">
+        无人机管理系统
+      </span>
     </div>
-
-    <a-space class="fz16 height-100" size="large">
-      <router-link v-for="item in options" :key="item.key" :to="item.path" :class="{
-        'menu-item': true,
-      }">
-        <span @click="selectedRoute(item.path)" :style="selected === item.path ? 'color: #2d8cf0;' : 'color: white'">{{
-          item.label }}</span>
-      </router-link>
-    </a-space>
-
-    <div class="height-100 fz16 flex-row flex-justify-between flex-align-center">
+    <div style="cursor: pointer;" class="height-100 fz16 flex-row flex-justify-between flex-align-center">
       <a-dropdown>
         <div class="height-100">
           <span class="fz20 mt20" style="border: 2px solid white; border-radius: 50%; display: inline-flex;">
@@ -23,7 +16,7 @@
           <span class="ml10 mr10" style="float: right;">{{ username }}</span>
         </div>
         <template #overlay>
-          <a-menu theme="dark" class="flex-column flex-justify-between flex-align-center">
+          <a-menu class="flex-column flex-justify-between flex-align-center">
             <a-menu-item>
               <span class="mr10" style="font-size: 16px;">
                 <PoweroffOutlined />
@@ -38,8 +31,7 @@
 </template>
 
 <script lang="ts" setup>
-import { message } from 'ant-design-vue'
-import { defineComponent, onMounted, ref } from 'vue'
+import { onMounted, ref } from 'vue'
 import { getRoot } from '/@/root'
 import { getPlatformInfo } from '/@/api/manage'
 import { ELocalStorageKey, ERouterName } from '/@/types'
@@ -93,4 +85,4 @@ const logout = () => {
   font-weight: 500;
   font-size: 18px;
 }
-</style>
+</style>

+ 16 - 12
Web/src/pages/page-web/home.vue

@@ -1,18 +1,21 @@
 <template>
   <a-layout class="width-100 flex-display" style="height: 100vh">
-    <a-layout-header class="header">
-      <Topbar />
-    </a-layout-header>
-    <a-layout-content>
-      <router-view />
-    </a-layout-content>
-
+    <Nav></Nav>
+    <a-layout>
+      <a-layout-header class="header">
+        <Topbar />
+      </a-layout-header>
+      <a-layout-content>
+        <router-view />
+      </a-layout-content>
+    </a-layout>
   </a-layout>
 </template>
 
 <script lang="ts" setup>
-import Topbar from '/@/components/common/topbar.vue'
-import { onMounted, reactive, ref, UnwrapRef, watch } from 'vue'
+import { onMounted, ref } from 'vue'
+import Nav from '/@/components/common/nav.vue';
+import Topbar from '/@/components/common/topbar.vue';
 import { getRoot } from '/@/root'
 import { EBizCode, ELocalStorageKey, ERouterName } from '/@/types'
 import { useConnectWebSocket } from '/@/hooks/use-connect-websocket'
@@ -23,6 +26,8 @@ interface FormState {
   password: string
 }
 
+const collapsed = ref<boolean>(false);
+
 const root = getRoot()
 
 const messageHandler = async (payload: any) => {
@@ -62,10 +67,9 @@ onMounted(() => {
 }
 
 .header {
-  background-color: black;
-  color: white;
+  background-color: #FFFFFF;
   height: 60px;
   font-size: 15px;
   padding: 0 20px;
 }
-</style>
+</style>

+ 17 - 37
Web/src/pages/page-web/index.vue

@@ -1,48 +1,29 @@
 <template>
-  <div
-    class="login flex-column flex-justify-center flex-align-center m0 b0">
-    <a-image
-      style="width: 17vw; height: 10vw; margin-bottom: 50px"
-      :src="djiLogo"
-    />
-    <p class="fz35 pb50" style="color: #2d8cf0">Cloud API Demo</p>
-    <a-form
-      layout="inline"
-      :model="formState"
-      class="flex-row flex-justify-center flex-align-center"
-    >
+  <div class="login flex-column flex-justify-center flex-align-center m0 b0">
+    <a-image :preview="false" style="width: 17vw; height: 10vw; margin-bottom: 50px" :src="djiLogo" />
+    <p class="fz35 pb50" style="color: #2d8cf0">无人机管理系统</p>
+    <a-form layout="inline" :model="formState" class="flex-row flex-justify-center flex-align-center">
       <a-form-item>
-        <a-input v-model:value="formState.username" placeholder="Username">
-          <template #prefix
-            ><UserOutlined style="color: rgba(0, 0, 0, 0.25)"
-          /></template>
+        <a-input v-model:value="formState.username" placeholder="账号">
+          <template #prefix>
+            <UserOutlined style="color: rgba(0, 0, 0, 0.25)" />
+          </template>
         </a-input>
       </a-form-item>
       <a-form-item>
-        <a-input
-          v-model:value="formState.password"
-          type="password"
-          placeholder="Password"
-        >
-          <template #prefix
-            ><LockOutlined style="color: rgba(0, 0, 0, 0.25)"
-          /></template>
+        <a-input v-model:value="formState.password" type="password" placeholder="密码">
+          <template #prefix>
+            <LockOutlined style="color: rgba(0, 0, 0, 0.25)" />
+          </template>
         </a-input>
       </a-form-item>
       <a-form-item>
-        <a-button
-          class="m0"
-          type="primary"
-          html-type="submit"
-          :disabled="loginBtnDisabled"
-          @click="onSubmit"
-        >
-          Login
+        <a-button class="m0" type="primary" html-type="submit" :disabled="loginBtnDisabled" @click="onSubmit">
+          登录
         </a-button>
       </a-form-item>
     </a-form>
   </div>
-
 </template>
 
 <script lang="ts" setup>
@@ -53,7 +34,6 @@ import { reactive, computed, UnwrapRef } from 'vue'
 import { login, LoginBody } from '/@/api/manage'
 import { getRoot } from '/@/root'
 import { ELocalStorageKey, ERouterName, EUserType } from '/@/types'
-import router from '/@/router'
 
 const root = getRoot()
 
@@ -75,18 +55,18 @@ const onSubmit = async (e: any) => {
     localStorage.setItem(ELocalStorageKey.Username, result.data.username)
     localStorage.setItem(ELocalStorageKey.UserId, result.data.user_id)
     localStorage.setItem(ELocalStorageKey.Flag, EUserType.Web.toString())
-    root.$router.push(ERouterName.MEMBERS)
+    root.$router.push(ERouterName.DEVICES)
   } else {
     message.error(result.message)
   }
 }
-
 </script>
 
 <style lang="scss" scoped>
 @import '/@/styles/index.scss';
+
 .login {
   background-color: $dark-highlight;
   height: 100vh;
 }
-</style>
+</style>

+ 103 - 104
Web/src/pages/page-web/projects/devices.vue

@@ -1,24 +1,23 @@
-
 <template>
   <a-menu v-model:selectedKeys="current" mode="horizontal" @select="select">
-    <a-menu-item :key="EDeviceTypeName.Aircraft" class="ml20">
-      Aircraft
+    <a-menu-item :key="3">
+      设备列表
+    </a-menu-item>
+    <a-menu-item :key="0">
+      机场异常反馈记录
     </a-menu-item>
-    <a-menu-item :key="EDeviceTypeName.Dock">
-      Dock
+    <a-menu-item :key="2">
+      所有设备变化记录
     </a-menu-item>
   </a-menu>
   <div class="device-table-wrap table flex-display flex-column">
-    <a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData" row-key="device_sn" :expandedRowKeys="expandRows"
-    :row-selection="rowSelection" :rowClassName="rowClassName" :scroll="{ x: '100%', y: 600 }"
-      :expandIcon="expandIcon" :loading="loading">
+    <a-table :columns="columns" :data-source="data.device" :pagination="paginationProp" @change="refreshData"
+      row-key="device_sn" :expandedRowKeys="expandRows" :row-selection="rowSelection" :rowClassName="rowClassName"
+      :scroll="{ x: '100%', y: 600 }" :expandIcon="expandIcon" :loading="loading">
       <template v-for="col in ['nickname']" #[col]="{ text, record }" :key="col">
         <div>
-          <a-input
-            v-if="editableData[record.device_sn]"
-            v-model:value="editableData[record.device_sn][col]"
-            style="margin: -5px 0"
-          />
+          <a-input v-if="editableData[record.device_sn]" v-model:value="editableData[record.device_sn][col]"
+            style="margin: -5px 0" />
           <template v-else>
             <a-tooltip :title="text">
               {{ text }}
@@ -28,30 +27,36 @@
       </template>
       <template v-for="col in ['sn', 'workspace']" #[col]="{ text }" :key="col">
         <a-tooltip :title="text">
-            <span>{{ text }}</span>
+          <span>{{ text }}</span>
         </a-tooltip>
       </template>
       <!-- 固件版本 -->
       <template #firmware_version="{ record }">
         <span v-if="judgeCurrentType(EDeviceTypeName.Dock)">
-          <DeviceFirmwareUpgrade :device="record"
-                                  class="table-flex-col"
-                                  @device-upgrade="onDeviceUpgrade"
-                                 />
+          <DeviceFirmwareUpgrade :device="record" class="table-flex-col" @device-upgrade="onDeviceUpgrade" />
         </span>
         <span v-else>
           {{ record.firmware_version }}
         </span>
       </template>
+      <!-- 固件升级 -->
+      <template #firmware_status="{ text }">
+        <div v-if="text === -1">
+          不支持
+        </div>
+        <div v-else>
+          {{ DeviceFirmwareStatus[text] }}
+        </div>
+      </template>
       <!-- 状态 -->
       <template #status="{ text }">
         <span v-if="text" class="flex-row flex-align-center">
-            <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" />
-            <span>Online</span>
+          <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: green;" />
+          <span>在线</span>
         </span>
         <span class="flex-row flex-align-center" v-else>
-            <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" />
-            <span>Offline</span>
+          <span class="mr5" style="width: 12px; height: 12px; border-radius: 50%; background-color: red;" />
+          <span>离线</span>
         </span>
       </template>
       <!-- 操作 -->
@@ -60,68 +65,64 @@
           <!-- 编辑态操作 -->
           <div v-if="editableData[record.device_sn]">
             <a-tooltip title="Confirm changes">
-              <span @click="save(record)" style="color: #28d445;"><CheckOutlined /></span>
+              <span @click="save(record)" style="color: #28d445;">
+                <CheckOutlined />
+              </span>
             </a-tooltip>
             <a-tooltip title="Modification canceled">
-              <span @click="() => delete editableData[record.device_sn]" style="color: #e70102;"><CloseOutlined /></span>
+              <span @click="() => delete editableData[record.device_sn]" style="color: #e70102;">
+                <CloseOutlined />
+              </span>
             </a-tooltip>
           </div>
           <!-- 非编辑态操作 -->
           <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
             <a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="设备日志">
-              <CloudServerOutlined @click="showDeviceLogUploadRecord(record)"/>
+              <CloudServerOutlined @click="showDeviceLogUploadRecord(record)" />
             </a-tooltip>
             <a-tooltip v-if="current.indexOf(EDeviceTypeName.Dock) !== -1" title="Hms Info">
-              <FileSearchOutlined @click="showHms(record)"/>
+              <FileSearchOutlined @click="showHms(record)" />
             </a-tooltip>
             <a-tooltip title="Edit">
-              <EditOutlined @click="edit(record)"/>
+              <EditOutlined @click="edit(record)" />
             </a-tooltip>
             <a-tooltip title="Delete">
-              <DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }"/>
+              <DeleteOutlined @click="() => { deleteTip = true, deleteSn = record.device_sn }" />
             </a-tooltip>
           </div>
         </div>
       </template>
 
     </a-table>
-    <a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }" @ok="unbind">
-        <p class="pt10 pl20" style="height: 50px;">Delete device from workspace?</p>
-        <template #title>
-            <div class="flex-row flex-justify-center">
-                <span>Delete devices</span>
-            </div>
-        </template>
+    <a-modal v-model:visible="deleteTip" width="450px" :closable="false" centered :okButtonProps="{ danger: true }"
+      @ok="unbind">
+      <p class="pt10 pl20" style="height: 50px;">Delete device from workspace?</p>
+      <template #title>
+        <div class="flex-row flex-justify-center">
+          <span>Delete devices</span>
+        </div>
+      </template>
     </a-modal>
-
     <!-- 设备升级 -->
-    <DeviceFirmwareUpgradeModal title="设备升级"
-      v-model:visible="deviceFirmwareUpgradeModalVisible"
-      :device="selectedDevice"
-      @ok="onUpgradeDeviceOk"
-    ></DeviceFirmwareUpgradeModal>
-
+    <DeviceFirmwareUpgradeModal title="设备升级" v-model:visible="deviceFirmwareUpgradeModalVisible"
+      :device="selectedDevice" @ok="onUpgradeDeviceOk"></DeviceFirmwareUpgradeModal>
     <!-- 设备日志上传记录 -->
-    <DeviceLogUploadRecordDrawer
-      v-model:visible="deviceLogUploadRecordVisible"
-      :device="currentDevice"
-    ></DeviceLogUploadRecordDrawer>
-
+    <DeviceLogUploadRecordDrawer v-model:visible="deviceLogUploadRecordVisible" :device="currentDevice">
+    </DeviceLogUploadRecordDrawer>
     <!-- hms 信息 -->
-    <DeviceHmsDrawer
-       v-model:visible="hmsVisible"
-      :device="currentDevice">
+    <DeviceHmsDrawer v-model:visible="hmsVisible" :device="currentDevice">
     </DeviceHmsDrawer>
   </div>
 </template>
+
 <script lang="ts" setup>
-import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
 import { h, onMounted, reactive, ref, UnwrapRef } from 'vue'
-import { IPage } from '/@/api/http/type'
-import { BindBody, bindDevice, getBindingDevices, unbindDevice, updateDevice } from '/@/api/manage'
+import { notification } from 'ant-design-vue'
+import { ColumnProps, TableState } from 'ant-design-vue/lib/table/interface'
+import { getBindingDevices, unbindDevice, updateDevice } from '/@/api/manage'
 import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
 import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue'
-import { Device, DeviceFirmwareStatusEnum } from '/@/types/device'
+import { Device, DeviceFirmwareStatus, DeviceFirmwareStatusEnum } from '/@/types/device'
 import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue'
 import DeviceFirmwareUpgradeModal from '/@/components/devices/device-upgrade/DeviceFirmwareUpgradeModal.vue'
 import { useDeviceFirmwareUpgrade } from '/@/components/devices/device-upgrade/use-device-upgrade'
@@ -129,7 +130,7 @@ import { useDeviceUpgradeEvent } from '/@/components/devices/device-upgrade/use-
 import { DeviceCmdExecuteInfo, DeviceCmdExecuteStatus } from '/@/types/device-cmd'
 import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue'
 import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue'
-import { message, notification } from 'ant-design-vue'
+import { IPage } from '/@/api/http/type'
 
 interface DeviceData {
   device: Device[]
@@ -138,45 +139,32 @@ interface DeviceData {
 const loading = ref(true)
 const deleteTip = ref<boolean>(false)
 const deleteSn = ref<string>()
+
 const columns: ColumnProps[] = [
-  { title: 'Model', dataIndex: 'device_name', width: 100, className: 'titleStyle' },
-  { title: 'SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } },
   {
-    title: 'Name',
-    dataIndex: 'nickname',
-    width: 100,
+    title: '设备型号', dataIndex: 'device_name', width: 150,
     sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),
-    className: 'titleStyle',
-    ellipsis: true,
-    slots: { customRender: 'nickname' }
+    className: 'titleStyle'
   },
-  { title: 'Firmware Version', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } },
-  { title: 'Status', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } },
+  { title: '设备SN', dataIndex: 'device_sn', width: 100, className: 'titleStyle', ellipsis: true, slots: { customRender: 'sn' } },
   {
-    title: 'Workspace',
-    dataIndex: 'workspace_name',
-    width: 100,
+    title: '设备名称',
+    dataIndex: 'nickname',
+    width: 150,
+    sorter: (a: Device, b: Device) => a.nickname.localeCompare(b.nickname),
     className: 'titleStyle',
     ellipsis: true,
-    slots: { customRender: 'workspace' },
-    customRender: ({ text, record, index }) => {
-      const obj = {
-        children: text,
-        props: {} as any,
-      }
-      if (current.value.indexOf(EDeviceTypeName.Dock) !== -1) {
-        if (record.domain === EDeviceTypeName.Aircraft) {
-          obj.children = ''
-        }
-      }
-      return obj
-    }
+    slots: { customRender: 'nickname' }
   },
-  { title: 'Joined', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' },
-  { title: 'Last Online', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' },
+  { title: '固件版本', dataIndex: 'firmware_version', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_version' } },
+  { title: '固件升级', dataIndex: 'firmware_status', width: 150, className: 'titleStyle', slots: { customRender: 'firmware_status' } },
+  { title: '当前状态', dataIndex: 'status', width: 100, className: 'titleStyle', slots: { customRender: 'status' } },
+  { title: '加入项目时间', dataIndex: 'bound_time', width: 150, sorter: (a: Device, b: Device) => a.bound_time.localeCompare(b.bound_time), className: 'titleStyle' },
+  { title: '最后在线时间', dataIndex: 'login_time', width: 150, sorter: (a: Device, b: Device) => a.login_time.localeCompare(b.login_time), className: 'titleStyle' },
   {
-    title: 'Actions',
+    title: '操作',
     dataIndex: 'actions',
+    fixed: 'right',
     width: 100,
     className: 'titleStyle',
     slots: { customRender: 'action' }
@@ -219,7 +207,7 @@ const paginationProp = reactive({
 })
 
 // 获取分页信息
-function getPaginationBody () {
+function getPaginationBody() {
   return {
     page: paginationProp.current,
     page_size: paginationProp.pageSize
@@ -245,9 +233,9 @@ type Pagination = TableState['pagination']
 
 const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
 const editableData: UnwrapRef<Record<string, Device>> = reactive({})
-const current = ref([EDeviceTypeName.Aircraft])
+const current = ref([EDeviceTypeName.Dock])
 
-function judgeCurrentType (type: EDeviceTypeName): boolean {
+function judgeCurrentType(type: EDeviceTypeName): boolean {
   return current.value.indexOf(type) !== -1
 }
 
@@ -259,11 +247,11 @@ const {
   onUpgradeDeviceOk
 } = useDeviceFirmwareUpgrade(workspaceId)
 
-function onDeviceUpgradeWs (payload: DeviceCmdExecuteInfo) {
+function onDeviceUpgradeWs(payload: DeviceCmdExecuteInfo) {
   updateDevicesByWs(data.device, payload)
 }
 
-function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) {
+function updateDevicesByWs(devices: Device[], payload: DeviceCmdExecuteInfo) {
   if (!devices || devices.length <= 0) {
     return
   }
@@ -297,7 +285,7 @@ function updateDevicesByWs (devices: Device[], payload: DeviceCmdExecuteInfo) {
 useDeviceUpgradeEvent(onDeviceUpgradeWs)
 
 // 获取设备列表信息
-function getDevices (domain: number, closeLoading?: boolean) {
+function getDevices(domain: number, closeLoading?: boolean) {
   if (!closeLoading) {
     loading.value = true
   }
@@ -323,30 +311,30 @@ function getDevices (domain: number, closeLoading?: boolean) {
   })
 }
 
-function refreshData (page: Pagination) {
+function refreshData(page: Pagination) {
   paginationProp.current = page?.current!
   paginationProp.pageSize = page?.pageSize!
   getDevices(current.value[0])
 }
 
 // 编辑
-function edit (record: Device) {
+function edit(record: Device) {
   editableData[record.device_sn] = record
 }
 
 // 保存
-function save (record: Device) {
+function save(record: Device) {
   delete editableData[record.device_sn]
   updateDevice({ nickname: record.nickname }, workspaceId, record.device_sn)
 }
 
 // 删除
-function showDeleteTip (sn: any) {
+function showDeleteTip(sn: any) {
   deleteTip.value = true
 }
 
 // 解绑
-function unbind () {
+function unbind() {
   deleteTip.value = false
   unbindDevice(deleteSn.value?.toString()!).then(res => {
     if (res.code !== 0) {
@@ -357,14 +345,14 @@ function unbind () {
 }
 
 // 选择设备
-function select (item: any) {
+function select(item: any) {
   getDevices(item.key)
 }
 
 const currentDevice = ref({} as Device)
 // 设备日志
 const deviceLogUploadRecordVisible = ref(false)
-function showDeviceLogUploadRecord (dock: Device) {
+function showDeviceLogUploadRecord(dock: Device) {
   deviceLogUploadRecordVisible.value = true
   currentDevice.value = dock
 }
@@ -372,7 +360,7 @@ function showDeviceLogUploadRecord (dock: Device) {
 // 健康状态
 const hmsVisible = ref<boolean>(false)
 
-function showHms (dock: Device) {
+function showHms(dock: Device) {
   hmsVisible.value = true
   currentDevice.value = dock
 }
@@ -383,9 +371,9 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
-.device-table-wrap{
-  .editable-row-operations{
-    div > span {
+.device-table-wrap {
+  .editable-row-operations {
+    div>span {
       margin-right: 10px;
     }
   }
@@ -399,45 +387,56 @@ onMounted(() => {
   padding: 20px;
   height: 88vh;
 }
+
 .table-striped {
   background-color: #f7f9fa;
 }
+
 .ant-table {
-  border-top: 1px solid rgb(0,0,0,0.06);
-  border-bottom: 1px solid rgb(0,0,0,0.06);
+  border-top: 1px solid rgb(0, 0, 0, 0.06);
+  border-bottom: 1px solid rgb(0, 0, 0, 0.06);
 }
+
 .ant-table-tbody tr td {
   border: 0;
 }
+
 .ant-table td {
   white-space: nowrap;
 }
+
 .ant-table-thead tr th {
   background: white !important;
   border: 0;
 }
+
 th.ant-table-selection-column {
   background-color: white !important;
 }
+
 .ant-table-header {
   background-color: white !important;
 }
+
 .child-row {
   height: 70px;
 }
+
 .notice {
   background: $success;
   overflow: hidden;
   cursor: pointer;
 }
+
 .caution {
   background: orange;
   cursor: pointer;
   overflow: hidden;
 }
+
 .warn {
   background: red;
   cursor: pointer;
   overflow: hidden;
 }
-</style>
+</style>

+ 36 - 30
Web/src/pages/page-web/projects/members.vue

@@ -1,15 +1,12 @@
-
 <template>
   <div class="table flex-display flex-column">
-    <a-table :columns="columns" :data-source="data.member" :pagination="paginationProp" @change="refreshData" row-key="user_id"
-    :row-selection="rowSelection" :rowClassName="(record, index) => ((index % 2) === 0 ? 'table-striped' : null)" :scroll="{ x: '100%', y: 600 }">
+    <a-table :columns="columns" :data-source="data.member" :pagination="paginationProp" @change="refreshData"
+      row-key="user_id" :row-selection="rowSelection"
+      :rowClassName="(record, index) => ((index % 2) === 0 ? 'table-striped' : null)" :scroll="{ x: '100%', y: 600 }">
       <template v-for="col in ['mqtt_username', 'mqtt_password']" #[col]="{ text, record }" :key="col">
         <div>
-          <a-input
-            v-if="editableData[record.user_id]"
-            v-model:value="editableData[record.user_id][col]"
-            style="margin: -5px 0"
-          />
+          <a-input v-if="editableData[record.user_id]" v-model:value="editableData[record.user_id][col]"
+            style="margin: -5px 0" />
           <template v-else>
             {{ text }}
           </template>
@@ -19,10 +16,14 @@
         <div class="editable-row-operations">
           <span v-if="editableData[record.user_id]">
             <a-tooltip title="Confirm changes">
-              <span @click="save(record)" style="color: #28d445;"><CheckOutlined /></span>
+              <span @click="save(record)" style="color: #28d445;">
+                <CheckOutlined />
+              </span>
             </a-tooltip>
             <a-tooltip title="Modification canceled">
-              <span @click="() => delete editableData[record.user_id]" class="ml15" style="color: #e70102;"><CloseOutlined /></span>
+              <span @click="() => delete editableData[record.user_id]" class="ml15" style="color: #e70102;">
+                <CloseOutlined />
+              </span>
             </a-tooltip>
           </span>
           <span v-else class="fz18 flex-align-center flex-row" style="color: #2d8cf0">
@@ -30,12 +31,11 @@
           </span>
         </div>
       </template>
-
     </a-table>
   </div>
 </template>
 <script lang="ts" setup>
-import { message, PaginationProps } from 'ant-design-vue'
+import { message } from 'ant-design-vue'
 import { TableState } from 'ant-design-vue/lib/table/interface'
 import { onMounted, reactive, Ref, ref, UnwrapRef } from 'vue'
 import { IPage } from '/@/api/http/type'
@@ -44,13 +44,13 @@ import { ELocalStorageKey } from '/@/types'
 import { EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue'
 
 export interface Member {
-    user_id: string
-    username: string
-    user_type: string
-    workspace_name: string
-    create_time: string
-    mqtt_username: string
-    mqtt_password: string
+  user_id: string
+  username: string
+  user_type: string
+  workspace_name: string
+  create_time: string
+  mqtt_username: string
+  mqtt_password: string
 }
 
 interface MemberData {
@@ -105,13 +105,13 @@ onMounted(() => {
   getAllUsers(workspaceId, body)
 })
 
-function refreshData (page: Pagination) {
+function refreshData(page: Pagination) {
   body.page = page?.current!
   body.page_size = page?.pageSize!
   getAllUsers(workspaceId, body)
 }
 
-function getAllUsers (workspaceId: string, page: IPage) {
+function getAllUsers(workspaceId: string, page: IPage) {
   getAllUsersInfo(workspaceId, page).then(res => {
     const userList: Member[] = res.data.list
     data.member = userList
@@ -120,11 +120,11 @@ function getAllUsers (workspaceId: string, page: IPage) {
   })
 }
 
-function edit (record: Member) {
+function edit(record: Member) {
   editableData[record.user_id] = record
 }
 
-function save (record: Member) {
+function save(record: Member) {
   delete editableData[record.user_id]
   updateUserInfo(workspaceId, record.user_id, record).then(res => {
     if (res.code !== 0) {
@@ -135,33 +135,39 @@ function save (record: Member) {
 
 </script>
 <style>
-
 .table {
-    background-color: white;
-    margin: 20px;
-    padding: 20px;
-    height: 88vh;
+  background-color: white;
+  margin: 20px;
+  padding: 20px;
+  height: 88vh;
 }
+
 .table-striped {
   background-color: #f7f9fa;
 }
+
 .ant-table {
-  border-top: 1px solid rgb(0,0,0,0.06);
-  border-bottom: 1px solid rgb(0,0,0,0.06);
+  border-top: 1px solid rgb(0, 0, 0, 0.06);
+  border-bottom: 1px solid rgb(0, 0, 0, 0.06);
 }
+
 .ant-table-tbody tr td {
   border: 0;
 }
+
 .ant-table td {
   white-space: nowrap;
 }
+
 .ant-table-thead tr th {
   background: white !important;
   border: 0;
 }
+
 th.ant-table-selection-column {
   background-color: white !important;
 }
+
 .ant-table-header {
   background-color: white !important;
 }

+ 1 - 4
Web/src/router/index.ts

@@ -17,7 +17,6 @@ const routes: Array<RouteRecordRaw> = [
     name: ERouterName.PROJECT,
     component: () => import('/@/pages/page-web/index.vue')
   },
-  // members, devices
   {
     path: '/' + ERouterName.HOME,
     name: ERouterName.HOME,
@@ -40,7 +39,6 @@ const routes: Array<RouteRecordRaw> = [
       }
     ]
   },
-  // workspace
   {
     path: '/' + ERouterName.WORKSPACE,
     name: ERouterName.WORKSPACE,
@@ -111,7 +109,6 @@ const routes: Array<RouteRecordRaw> = [
       },
     ]
   },
-  // pilot
   {
     path: '/' + ERouterName.PILOT,
     name: ERouterName.PILOT,
@@ -140,4 +137,4 @@ const router = createRouter({
   routes
 })
 
-export default router
+export default router

+ 2 - 2
Web/src/types/device-cmd.ts

@@ -29,7 +29,7 @@ export enum DeviceCmd {
 
 export type DeviceCmdItemAction = AlarmModeEnum | BatteryStoreModeEnum | DroneBatteryModeEnum | LinkWorkModeEnum
 
-export interface DeviceCmdItem{
+export interface DeviceCmdItem {
   label: string, // 标题
   status: string, // 当前状态
   operateText: string, // 按钮文字
@@ -286,7 +286,7 @@ export interface DeviceCmdExecuteInfo {
   timestamp: number,
   sn: string,
   bid: string,
-  output:{
+  output: {
     status: DeviceCmdExecuteStatus,
     progress?: {
       percent: number,

+ 8 - 5
Web/src/types/enums.ts

@@ -1,17 +1,20 @@
 export enum ERouterName {
+    WORKSPACE = 'workspace',
+    DEVICES = 'devices',
+    TASK = 'task',
+    MEDIA = 'media',
+    REPLAY = 'replay',
+    TRAJECTORY = 'trajectory',
+
+    MEMBERS = 'members',
     ELEMENT = 'element',
     PROJECT = 'project',
     HOME = 'home',
     TSA = 'tsa',
     LAYER = 'layer',
-    MEDIA = 'media',
     WAYLINE = 'wayline',
     LIVESTREAM = 'livestream',
     LIVING = 'living',
-    WORKSPACE = 'workspace',
-    MEMBERS = 'members',
-    DEVICES = 'devices',
-    TASK = 'task',
     CREATE_PLAN = 'create-plan',
     SELECT_PLAN = 'select-plan',
     FIRMWARES = 'firmwares',

+ 1 - 1
Web/src/utils/color.ts

@@ -4,5 +4,5 @@ export const commonColor = {
   WHITE: '#FFFFFF', // 白色
   NORMAL: '#19BE6B', // 绿色
   BLUE: '#2B85E4', // 蓝色
-  PINK: '#F7C0BA', // 粉
+  PINK: '#F7C0BA', // 粉
 }